diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 3b2f2d49..46e40dec 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -59,7 +59,6 @@ src_libbitcoin_node_la_SOURCES = \ ${srcdir}/../../src/settings.cpp \ ${srcdir}/../../src/channels/channel_peer.cpp \ ${srcdir}/../../src/chasers/chaser.cpp \ - ${srcdir}/../../src/chasers/chaser_block.cpp \ ${srcdir}/../../src/chasers/chaser_check.cpp \ ${srcdir}/../../src/chasers/chaser_confirm.cpp \ ${srcdir}/../../src/chasers/chaser_estimate.cpp \ @@ -73,9 +72,7 @@ src_libbitcoin_node_la_SOURCES = \ ${srcdir}/../../src/chasers/chaser_validate_capture.cpp \ ${srcdir}/../../src/chasers/chaser_validate_parallel.cpp \ ${srcdir}/../../src/protocols/protocol.cpp \ - ${srcdir}/../../src/protocols/protocol_block_in_106.cpp \ ${srcdir}/../../src/protocols/protocol_block_in_31800.cpp \ - ${srcdir}/../../src/protocols/protocol_block_in_70001.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 \ @@ -133,12 +130,10 @@ include_bitcoin_node_chasersdir = \ include_bitcoin_node_chasers_HEADERS = \ ${srcdir}/../../include/bitcoin/node/chasers/chaser.hpp \ - ${srcdir}/../../include/bitcoin/node/chasers/chaser_block.hpp \ ${srcdir}/../../include/bitcoin/node/chasers/chaser_check.hpp \ ${srcdir}/../../include/bitcoin/node/chasers/chaser_confirm.hpp \ ${srcdir}/../../include/bitcoin/node/chasers/chaser_estimate.hpp \ ${srcdir}/../../include/bitcoin/node/chasers/chaser_header.hpp \ - ${srcdir}/../../include/bitcoin/node/chasers/chaser_organize.hpp \ ${srcdir}/../../include/bitcoin/node/chasers/chaser_snapshot.hpp \ ${srcdir}/../../include/bitcoin/node/chasers/chaser_storage.hpp \ ${srcdir}/../../include/bitcoin/node/chasers/chaser_template.hpp \ @@ -146,12 +141,6 @@ include_bitcoin_node_chasers_HEADERS = \ ${srcdir}/../../include/bitcoin/node/chasers/chaser_validate.hpp \ ${srcdir}/../../include/bitcoin/node/chasers/chasers.hpp -include_bitcoin_node_impl_chasersdir = \ - ${includedir}/bitcoin/node/impl/chasers - -include_bitcoin_node_impl_chasers_HEADERS = \ - ${srcdir}/../../include/bitcoin/node/impl/chasers/chaser_organize.ipp - include_bitcoin_node_impl_sessionsdir = \ ${includedir}/bitcoin/node/impl/sessions @@ -163,9 +152,7 @@ include_bitcoin_node_protocolsdir = \ include_bitcoin_node_protocols_HEADERS = \ ${srcdir}/../../include/bitcoin/node/protocols/protocol.hpp \ - ${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_in_70001.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 \ @@ -232,7 +219,6 @@ test_libbitcoin_node_test_SOURCES = \ ${srcdir}/../../test/settings.cpp \ ${srcdir}/../../test/test.cpp \ ${srcdir}/../../test/chasers/chaser.cpp \ - ${srcdir}/../../test/chasers/chaser_block.cpp \ ${srcdir}/../../test/chasers/chaser_check.cpp \ ${srcdir}/../../test/chasers/chaser_confirm.cpp \ ${srcdir}/../../test/chasers/chaser_estimate.cpp \ diff --git a/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj b/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj index d52609c3..b7cc80e3 100644 --- a/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj @@ -122,7 +122,6 @@ - diff --git a/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj.filters index d1511e0f..b3597357 100644 --- a/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-node-test/libbitcoin-node-test.vcxproj.filters @@ -36,9 +36,6 @@ src\chasers - - src\chasers - src\chasers diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj index 4f9c9564..c4141838 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj @@ -125,7 +125,6 @@ - @@ -143,9 +142,7 @@ - - @@ -179,12 +176,10 @@ - - @@ -198,9 +193,7 @@ - - @@ -229,7 +222,6 @@ - diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters index c07c8793..ab9882c8 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -25,35 +25,32 @@ {5FFB5F52-0772-4404-0000-000000000005} - - {5FFB5F52-0772-4404-0000-000000000006} - - {5FFB5F52-0772-4404-0000-000000000007} + {5FFB5F52-0772-4404-0000-000000000006} - {5FFB5F52-0772-4404-0000-000000000008} + {5FFB5F52-0772-4404-0000-000000000007} - {5FFB5F52-0772-4404-0000-000000000009} + {5FFB5F52-0772-4404-0000-000000000008} - {5FFB5F52-0772-4404-0000-00000000000A} + {5FFB5F52-0772-4404-0000-000000000009} - {5FFB5F52-0772-4404-0000-00000000000B} + {5FFB5F52-0772-4404-0000-00000000000A} - {5FFB5F52-0772-4404-0000-00000000000C} + {5FFB5F52-0772-4404-0000-00000000000B} - {5FFB5F52-0772-4404-0000-00000000000D} + {5FFB5F52-0772-4404-0000-00000000000C} - {5FFB5F52-0772-4404-0000-00000000000E} + {5FFB5F52-0772-4404-0000-00000000000D} - {5FFB5F52-0772-4404-0000-00000000000F} + {5FFB5F52-0772-4404-0000-00000000000E} @@ -69,9 +66,6 @@ src\chasers - - src\chasers - src\chasers @@ -123,15 +117,9 @@ src\protocols - - src\protocols - src\protocols - - src\protocols - src\protocols @@ -227,9 +215,6 @@ include\bitcoin\node\chasers - - include\bitcoin\node\chasers - include\bitcoin\node\chasers @@ -242,9 +227,6 @@ include\bitcoin\node\chasers - - include\bitcoin\node\chasers - include\bitcoin\node\chasers @@ -284,15 +266,9 @@ include\bitcoin\node\protocols - - include\bitcoin\node\protocols - include\bitcoin\node\protocols - - include\bitcoin\node\protocols - include\bitcoin\node\protocols @@ -373,9 +349,6 @@ - - include\bitcoin\node\impl\chasers - include\bitcoin\node\impl\sessions diff --git a/include/bitcoin/node.hpp b/include/bitcoin/node.hpp index 3f9b44b4..a566f049 100644 --- a/include/bitcoin/node.hpp +++ b/include/bitcoin/node.hpp @@ -31,12 +31,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include #include @@ -44,9 +42,7 @@ #include #include #include -#include #include -#include #include #include #include diff --git a/include/bitcoin/node/chase.hpp b/include/bitcoin/node/chase.hpp index faa11d8c..105d87f2 100644 --- a/include/bitcoin/node/chase.hpp +++ b/include/bitcoin/node/chase.hpp @@ -92,10 +92,6 @@ enum class chase /// Candidate Chain. /// ----------------------------------------------------------------------- - /// A new candidate branch exists from given branch point. - /// Issued by 'block' and handled by none. - blocks, - /// A new candidate branch exists from given branch point. /// Issued by 'header' and handled by 'check'. headers, @@ -257,12 +253,6 @@ struct report count_t sequence; }; -struct blocks -{ - static constexpr chase id{ chase::blocks }; - height_t branch_point; -}; - struct headers { static constexpr chase id{ chase::headers }; @@ -386,7 +376,6 @@ using event_value = std::variant chases::stall, chases::purge, chases::report, - chases::blocks, chases::headers, chases::download, chases::regressed, diff --git a/include/bitcoin/node/chasers/chaser_block.hpp b/include/bitcoin/node/chasers/chaser_block.hpp deleted file mode 100644 index 91e34dff..00000000 --- a/include/bitcoin/node/chasers/chaser_block.hpp +++ /dev/null @@ -1,81 +0,0 @@ -/** - * 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_CHASERS_CHASER_BLOCK_HPP -#define LIBBITCOIN_NODE_CHASERS_CHASER_BLOCK_HPP - -#include -#include - -namespace libbitcoin { -namespace node { - -class full_node; - -/// Chase down stronger block branches for the confirmed chain. -/// Weak branches are retained in a hash table if not store populated. -/// Strong branches reorganize the candidate chain and fire the 'connect' event. -class BCN_API chaser_block - : public chaser_organize -{ -public: - DELETE_COPY_MOVE_DESTRUCT(chaser_block); - - chaser_block(full_node& node) NOEXCEPT; - -protected: - /// Get header from Block instance. - const system::chain::header& get_header( - const system::chain::block& block) const NOEXCEPT override; - - /// Query store for const pointer to Block instance by candidate height. - bool get_block(system::chain::block::cptr& out, - const header_link& link) const NOEXCEPT override; - - /// Determine if Block is a duplicate (success for not duplicate). - code duplicate(size_t& height, - const system::hash_digest& hash) const NOEXCEPT override; - - /// Determine if Block is valid. - code validate(const system::chain::block& block, - const chain_state& state) const NOEXCEPT override; - - /// Determine if state is top of a storable branch (always true). - bool is_storable(const chain_state& state) const NOEXCEPT override; - - /// True if Block is on a milestone-covered branch. - bool is_under_milestone(size_t height) const NOEXCEPT override; - - /// Milestone tracking, true if updated. - bool update_milestone(const system::chain::header& header, - size_t height, size_t branch_point) NOEXCEPT override; - -private: - void set_prevout(const system::chain::input& input) const NOEXCEPT; - bool populate(const system::chain::block& block, - const system::chain::context& ctx) const NOEXCEPT; - -private: - // These are thread safe. - const bool node_witness_; -}; - -} // namespace node -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/node/chasers/chaser_header.hpp b/include/bitcoin/node/chasers/chaser_header.hpp index 378d63ce..ae0735f1 100644 --- a/include/bitcoin/node/chasers/chaser_header.hpp +++ b/include/bitcoin/node/chasers/chaser_header.hpp @@ -19,7 +19,8 @@ #ifndef LIBBITCOIN_NODE_CHASERS_CHASER_HEADER_HPP #define LIBBITCOIN_NODE_CHASERS_CHASER_HEADER_HPP -#include +#include +#include #include namespace libbitcoin { @@ -30,56 +31,117 @@ class full_node; /// Chase down stronger header branches for the candidate chain. /// Weak branches are retained in a hash table if not store populated. /// Strong branches reorganize the candidate chain and fire the 'header' event. +/// Headers are validated and proven by the protocol, so all are storable. class BCN_API chaser_header - : public chaser_organize + : public chaser { public: DELETE_COPY_MOVE_DESTRUCT(chaser_header); chaser_header(full_node& node) NOEXCEPT; + /// Validate a header against its chain state. + static code validate(const system::chain::header& header, + const system::chain::chain_state& state, + const system::settings& settings) NOEXCEPT; + /// Initialize chaser state. code start() NOEXCEPT override; + /// Validate and organize an unproven header. + virtual void organize(const system::chain::header::cptr& header, + organize_handler&& handler) NOEXCEPT; + + /// Organize a proven header, milestone set if in milestone branch. + virtual void organize(const system::chain::header::cptr& header, + bool milestone, organize_handler&& handler) NOEXCEPT; + + /// Reorganize to the branch of an archived block of at least equal work. + virtual void prioritize(const system::hash_digest& hash, + organize_handler&& handler) NOEXCEPT; + + /// Work a proven header branch must reach (candidate or configured). + virtual void get_minimum_work(work_handler&& handler) NOEXCEPT; + protected: - /// Get header from Block instance. - const system::chain::header& get_header( - const system::chain::header& header) const NOEXCEPT override; + using header_link = database::header_link; + using chain_state = system::chain::chain_state; - /// Query store for const pointer to Block instance by candidate height. - bool get_block(system::chain::header::cptr& out, - const header_link& link) const NOEXCEPT override; + using header_tree = std::unordered_map; - /// Determine if Block is a duplicate (success for not duplicate). - code duplicate(size_t& height, - const system::hash_digest& hash) const NOEXCEPT override; + /// Handle chaser events. + virtual bool handle_chase(const code&, event_value value) NOEXCEPT; - /// Determine if Block is valid. - code validate(const system::chain::header& header, - const chain_state& state) const NOEXCEPT override; + /// Organize a discovered header, prioritized accepts a tied branch. + virtual void do_organize(const system::chain::header::cptr& header, + bool prioritized, bool milestone, bool proven, + const organize_handler& handler) NOEXCEPT; - /// Determine if state is top of a storable branch. - bool is_storable(const chain_state& state) const NOEXCEPT override; + /// Reorganize following block unconfirmability. + virtual void do_disorganize(header_t header) NOEXCEPT; - /// True if Block is on a milestone-covered branch. - bool is_under_milestone(size_t height) const NOEXCEPT override; + /// Reorganize to the branch of the given block. + virtual void do_prioritize(const system::hash_digest& hash, + const organize_handler& handler) NOEXCEPT; - /// Milestone tracking, true if updated. - bool update_milestone(const system::chain::header& header, - size_t height, size_t branch_point) NOEXCEPT override; + /// Obtain the work a proven header branch must reach. + virtual void do_get_minimum_work(const work_handler& handler) NOEXCEPT; private: - bool is_checkpoint(const chain_state& state) const NOEXCEPT; - bool is_milestone(const chain_state& state) const NOEXCEPT; - bool is_current(const chain_state& state) const NOEXCEPT; - bool is_hard(const chain_state& state) const NOEXCEPT; - bool initialize_milestone() NOEXCEPT; + using header_links = database::header_links; + using header_states = database::header_states; - // This is thread safe. - const system::chain::checkpoint& milestone_; - - // This is protected by strand. - size_t active_milestone_height_{}; + // Validation. + code duplicate(size_t& height, + const system::hash_digest& hash) const NOEXCEPT; + + // Setters. + bool set_reorganized(height_t candidate_height) NOEXCEPT; + bool set_organized(const header_link& link, + height_t candidate_height) NOEXCEPT; + code push_header(const system::hash_digest& key) NOEXCEPT; + code push_header(const system::chain::header& header, + const system::chain::context& ctx, bool milestone) NOEXCEPT; + void cache(const system::chain::header::cptr& header, + const chain_state::cptr& state) NOEXCEPT; + + // Checkpoint gate. + bool is_under_active_checkpoint( + const system::hash_digest& previous) const NOEXCEPT; + void update_checkpoint(height_t top) NOEXCEPT; + + // Tree control. + void shrink_tree(bool current) NOEXCEPT; + void prune_tree(const uint256_t& threshold) NOEXCEPT; + void prune_tree(height_t top) NOEXCEPT; + void prune_tree() NOEXCEPT; + + // Getters. + size_t get_window() const NOEXCEPT; + uint256_t get_window_work() const NOEXCEPT; + chain_state::cptr get_chain_state( + const system::hash_digest& previous_hash) const NOEXCEPT; + bool get_branch_work(uint256_t& branch_work, + system::hashes& tree_branch, header_states& store_branch, + const system::chain::header& header) const NOEXCEPT; + + // Logging. + void log_state_change(const chain_state& from, + const chain_state& to) const NOEXCEPT; + + // These are thread safe. + const system::settings& settings_; + const system::chain::checkpoints& checkpoints_; + + // These are protected by strand. + bool bumped_{}; + bool shrunk_{}; + size_t next_window_{}; + size_t next_checkpoint_{}; + size_t active_checkpoint_{}; + chain_state::cptr state_{}; + header_tree tree_{}; }; } // namespace node diff --git a/include/bitcoin/node/chasers/chaser_organize.hpp b/include/bitcoin/node/chasers/chaser_organize.hpp deleted file mode 100644 index 869807a5..00000000 --- a/include/bitcoin/node/chasers/chaser_organize.hpp +++ /dev/null @@ -1,244 +0,0 @@ -/** - * 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_CHASERS_CHASER_ORGANIZE_HPP -#define LIBBITCOIN_NODE_CHASERS_CHASER_ORGANIZE_HPP - -#include -#include -#include - -namespace libbitcoin { -namespace node { - -class full_node; - -/// Abstract intermediate base class to hold complex and consensus critical -/// common code for blocks-first and headers-first chain organizations. -template -class chaser_organize - : public chaser -{ -public: - DELETE_COPY_MOVE_DESTRUCT(chaser_organize); - - /// Initialize chaser state. - virtual code start() NOEXCEPT; - - /// Validate and organize next Block in sequence relative to calling peer. - virtual void organize(const typename Block::cptr& block, - organize_handler&& handler) NOEXCEPT; - - /// Reorganize to the branch of an archived block of at least equal work. - virtual void prioritize(const system::hash_digest& hash, - organize_handler&& handler) NOEXCEPT; - -protected: - using header_link = database::header_link; - using chain_state = system::chain::chain_state; - using block_tree = std::unordered_map; - - /// Protected constructor for abstract base. - chaser_organize(full_node& node) NOEXCEPT; - - /// Pure Virtual - /// ----------------------------------------------------------------------- - - /// Get header from Block instance. - virtual const system::chain::header& get_header( - const Block& block) const NOEXCEPT = 0; - - /// Query store for const pointer to Block instance by link. - virtual bool get_block(typename Block::cptr& out, - const header_link& link) const NOEXCEPT = 0; - - /// Determine if Block is a duplicate (success for not duplicate). - virtual code duplicate(size_t& height, - const system::hash_digest& hash) const NOEXCEPT = 0; - - /// Determine if Block is valid. - virtual code validate(const Block& block, - const chain_state& state) const NOEXCEPT = 0; - - /// Determine if state is top of a storable branch. - virtual bool is_storable(const chain_state& state) const NOEXCEPT = 0; - - /// True if Block is on a milestone-covered branch. - virtual bool is_under_milestone(size_t height) const NOEXCEPT = 0; - - /// Milestone tracking, true if updated. - virtual bool update_milestone(const system::chain::header& header, - size_t height, size_t branch_point) NOEXCEPT = 0; - - /// Methods - /// ----------------------------------------------------------------------- - - /// Handle chaser events. - virtual bool handle_chase(const code&, event_value value) NOEXCEPT; - - /// Organize a discovered Block, prioritized accepts a tied branch. - virtual void do_organize(typename Block::cptr block, bool prioritized, - const organize_handler& handler) NOEXCEPT; - - /// Reorganize following Block unconfirmability. - virtual void do_disorganize(header_t header) NOEXCEPT; - - /// Reorganize to the branch of the given block. - virtual void do_prioritize(const system::hash_digest& hash, - const organize_handler& handler) NOEXCEPT; - - /// Properties - /// ----------------------------------------------------------------------- - - /// Constant access to Block tree. - virtual const block_tree& tree() const NOEXCEPT; - - /// System configuration settings. - virtual const system::settings& settings() const NOEXCEPT; - -private: - using header_links = database::header_links; - using header_states = database::header_states; - - // Template differentiators. - // ------------------------------------------------------------------------ - - static constexpr bool is_block_ - { - is_same_type - }; - - using chase_object = iif; - - static constexpr bool is_block() NOEXCEPT - { - return is_block_; - } - - static constexpr auto error_duplicate() NOEXCEPT - { - return is_block() ? error::duplicate_block : error::duplicate_header; - } - - static constexpr auto error_orphan() NOEXCEPT - { - return is_block() ? error::orphan_block : error::orphan_header; - } - - static constexpr auto events_object_archived() NOEXCEPT - { - return is_block() ? events::block_archived : events::header_archived; - } - - static constexpr auto events_object_organized() NOEXCEPT - { - // Using header because block organization is in confirmation chaser. - ////return is_block() ? events::block_organized : events::header_organized; - return events::header_organized; - } - - static constexpr auto events_object_reorganized() NOEXCEPT - { - // Using header because block reorganized is in confirmation chaser. - ////return is_block() ? events::block_reorganized : events::header_reorganized; - return events::header_reorganized; - } - - // Setters - // ---------------------------------------------------------------------------- - - bool set_reorganized(height_t candidate_height) NOEXCEPT; - bool set_organized(const database::header_link& link, - height_t candidate_height) NOEXCEPT; - - // Move tree Block to database and push to top of candidate chain. - code push_block(const system::hash_digest& key) NOEXCEPT; - - /// Store Block to database and push to top of candidate chain. - code push_block(const Block& block, - const system::chain::context& ctx) NOEXCEPT; - - // Set Block state and store into tree cache. - void cache(const typename Block::cptr& block, - const chain_state::cptr& state) NOEXCEPT; - - // Checkpoint gate. - // ------------------------------------------------------------------------ - - // True if the parent of a new block is below the top reached checkpoint. - bool is_under_active_checkpoint( - const system::hash_digest& previous) const NOEXCEPT; - - // Advance top reached checkpoint and purge the tree at/below it. - void update_checkpoint(height_t top) NOEXCEPT; - - // Remove tree blocks at/below the top reached checkpoint (conflicted). - void purge_under_checkpoint() NOEXCEPT; - - // Release tree buckets retained from accumulation (once, when current). - void shrink_tree(bool current) NOEXCEPT; - - // Getters. - // ------------------------------------------------------------------------ - - // Obtain chain state for given previous hash, nullptr if not found. - chain_state::cptr get_chain_state( - const system::hash_digest& previous_hash) const NOEXCEPT; - - // Sum of work from header to branch point (excluded). - bool get_branch_work(uint256_t& branch_work, - system::hashes& tree_branch, header_states& store_branch, - const system::chain::header& header) const NOEXCEPT; - - // Logging. - // ------------------------------------------------------------------------ - - // Log changes to flags and/or minimum block version in candidate chain. - void log_state_change(const chain_state& from, - const chain_state& to) const NOEXCEPT; - - // These are thread safe. - const system::settings& settings_; - const system::chain::checkpoints& checkpoints_; - - // These are protected by strand. - bool bumped_{}; - bool shrunk_{}; - size_t next_checkpoint_{}; - size_t active_checkpoint_{}; - chain_state::cptr state_{}; - block_tree tree_{}; -}; - -} // namespace node -} // namespace libbitcoin - -#define TEMPLATE template -#define CLASS chaser_organize - -BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - -#include - -BC_POP_WARNING() - -#undef CLASS -#undef TEMPLATE - -#endif diff --git a/include/bitcoin/node/chasers/chasers.hpp b/include/bitcoin/node/chasers/chasers.hpp index ee6c6220..224d0bb1 100644 --- a/include/bitcoin/node/chasers/chasers.hpp +++ b/include/bitcoin/node/chasers/chasers.hpp @@ -20,12 +20,10 @@ #define LIBBITCOIN_NODE_CHASERS_CHASERS_HPP #include -#include #include #include #include #include -#include #include #include #include diff --git a/include/bitcoin/node/define.hpp b/include/bitcoin/node/define.hpp index cdd799db..7402895a 100644 --- a/include/bitcoin/node/define.hpp +++ b/include/bitcoin/node/define.hpp @@ -52,6 +52,7 @@ typedef std::error_code code; typedef std::function estimate_handler; /// Organization types. +typedef std::function work_handler; typedef std::function organize_handler; typedef std::function submit_handler; typedef database::store store; diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index 49b22f55..59e4328d 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -58,6 +58,7 @@ enum error_t : uint8_t orphan_header, duplicate_block, duplicate_header, + unexpected_header, /// transaction pool pooling_disabled, diff --git a/include/bitcoin/node/full_node.hpp b/include/bitcoin/node/full_node.hpp index 7a01dada..ad134d6d 100644 --- a/include/bitcoin/node/full_node.hpp +++ b/include/bitcoin/node/full_node.hpp @@ -61,18 +61,21 @@ class BCN_API full_node /// Organizers. /// ----------------------------------------------------------------------- - /// Organize a validated header. + /// Validate and organize an unproven header. virtual void organize(const system::chain::header::cptr& header, organize_handler&& handler) NOEXCEPT; - /// Organize a validated block. - virtual void organize(const system::chain::block::cptr& block, - organize_handler&& handler) NOEXCEPT; + /// Organize a proven header, milestone set if in milestone branch. + virtual void organize(const system::chain::header::cptr& header, + bool milestone, organize_handler&& handler) NOEXCEPT; /// Reorganize to the branch of an archived block of at least equal work. virtual void prioritize(const system::hash_digest& hash, organize_handler&& handler) NOEXCEPT; + /// Work a proven header branch must reach (candidate or configured). + virtual void get_minimum_work(work_handler&& handler) NOEXCEPT; + /// Validate and archive a submitted package, accepted as a whole. /// The package is only validated when test, so nothing is archived. virtual void submit(const system::chain::transactions_cptr& txs, @@ -195,7 +198,6 @@ class BCN_API full_node query& query_; // These are protected by strand. - chaser_block chaser_block_; chaser_header chaser_header_; chaser_check chaser_check_; chaser_validate chaser_validate_; diff --git a/include/bitcoin/node/impl/chasers/chaser_organize.ipp b/include/bitcoin/node/impl/chasers/chaser_organize.ipp deleted file mode 100644 index 86593a1b..00000000 --- a/include/bitcoin/node/impl/chasers/chaser_organize.ipp +++ /dev/null @@ -1,804 +0,0 @@ -/** - * 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_CHASERS_CHASER_ORGANIZE_IPP -#define LIBBITCOIN_NODE_CHASERS_CHASER_ORGANIZE_IPP - -#include -#include -#include - -namespace libbitcoin { -namespace node { - -// Public -// ---------------------------------------------------------------------------- - -TEMPLATE -CLASS::chaser_organize(full_node& node) NOEXCEPT - : chaser(node), - settings_(system_settings()), - checkpoints_(system_settings().checkpoints) -{ -} - -TEMPLATE -code CLASS::start() NOEXCEPT -{ - using namespace std::placeholders; - - // 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. - const auto& query = archive(); - const auto top = query.get_top_candidate(); - state_ = query.get_candidate_chain_state(settings_, top); - - if (!state_) - { - fault(error::organize1); - return error::organize1; - } - - LOGN("Candidate top [" << system::encode_hash(state_->hash()) << ":" - << state_->height() << "]."); - - update_checkpoint(top); - SUBSCRIBE_CHASE(handle_chase, _1, _2); - return error::success; -} - -TEMPLATE -void CLASS::organize(const typename Block::cptr& block, - organize_handler&& handler) NOEXCEPT -{ - if (closed()) - return; - - POST(do_organize, block, false, std::move(handler)); -} - -TEMPLATE -void CLASS::prioritize(const system::hash_digest& hash, - organize_handler&& handler) NOEXCEPT -{ - if (closed()) - return; - - POST(do_prioritize, hash, std::move(handler)); -} - -// Methods -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::handle_chase(const code&, event_value value) NOEXCEPT -{ - if (closed()) - return false; - - switch (to_chase(value)) - { - // Roll back the candidate chain to confirmed top (via fork point). - case chase::unchecked: - { - if (database_settings().mark_unconfirmable) - { - POST(do_disorganize, to_payload(value).link); - } - - break; - } - case chase::unvalid: - { - if (database_settings().mark_unconfirmable) - { - POST(do_disorganize, to_payload(value).link); - } - - break; - } - case chase::unconfirmable: - { - if (database_settings().mark_unconfirmable) - { - POST(do_disorganize, - to_payload(value).link); - } - - break; - } - case chase::stop: - { - return false; - } - default: - { - break; - } - } - - return true; -} - -TEMPLATE -void CLASS::do_organize(typename Block::cptr block, bool prioritized, - const organize_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - - using namespace system; - const auto& query = archive(); - const auto& hash = block->get_hash(); - const auto& header = get_header(*block); - - // Skip existing/orphan, get state. - // ........................................................................ - - if (closed()) - { - handler(network::error::service_stopped, {}); - return; - } - - const auto it = tree_.find(hash); - if (it != tree_.cend()) - { - handler(error_duplicate(), it->second->get_state()->height()); - return; - } - - size_t height{}; - if (const auto ec = duplicate(height, hash)) - { - handler(ec, height); - return; - } - - // Validate parent and obtain header chain state. - // ........................................................................ - - // Shortcircuit parent unconfirmable (looping over failed block). - const auto& previous = header.previous_block_hash(); - if (query.is_unconfirmable(query.to_header(previous))) - { - handler(database::error::block_unconfirmable, {}); - return; - } - - // Shortcircuit fork at/under the top reached checkpoint. - if (is_under_active_checkpoint(previous)) - { - handler(system::error::checkpoint_conflict, {}); - return; - } - - // Obtain parent state from state_, tree, or store as applicable. - const auto parent = get_chain_state(previous); - if (!parent) - { - handler(error_orphan(), {}); - return; - } - - // Roll chain state forward from archived parent to new header. - const auto state = std::make_shared(*parent, header, settings_); - height = state->height(); - - // Validation and currency. - // ........................................................................ - - if (chain::checkpoint::is_conflict(checkpoints_, hash, height)) - { - handler(system::error::checkpoint_conflict, height); - return; - } - - // Blocks of headers are validated later, malleations ignored until then. - // Blocks are fully validated (not confirmed), so malleation is non-issue. - if (const auto ec = validate(*block, *state)) - { - handler(ec, height); - return; - } - - // Cache headers until the branch is sufficiently guaranteed. - if (!is_storable(*state)) - { - log_state_change(*parent, *state); - cache(block, state); - handler(error::success, height); - return; - } - - // Compute relative work. - // ........................................................................ - - uint256_t work{}; - hashes tree_branch{}; - header_states store_branch{}; - if (!get_branch_work(work, tree_branch, store_branch, header)) - { - handler(fault(error::organize2), height); - return; - } - - bool strong{}; - const auto branch_size = tree_branch.size() + store_branch.size(); - const auto branch_point = height - add1(branch_size); - if (!query.get_strong_branch(strong, work, branch_point, prioritized)) - { - handler(fault(error::organize3), height); - return; - } - - // New top of a weak branch. - if (!strong) - { - log_state_change(*parent, *state); - cache(block, state); - handler(error::success, height); - return; - } - - // Reorganize candidate chain. - // ........................................................................ - - // The milestone flag will be archived in the header record. - // Here it must be computed from the header tree because it trickles down. - if (update_milestone(header, height, branch_point)) - { - LOGN("Found milestone [" << encode_hash(hash) << ":" << height << "]"); - } - - // Cannot be branching above top. - auto top = state_->height(); - if (branch_point > top) - { - handler(fault(error::organize4), height); - return; - } - - // Pop top down to the branch point. - const auto regress = branch_point < top; - while (branch_point < top) - { - if (!set_reorganized(top--)) - { - handler(fault(error::organize5), height); - return; - } - } - - // Reset chasers to the branch point. - if (regress) - { - notify(error::success, chases::regressed{ branch_point }); - } - - // Push stored strong headers to candidate chain. - for (const auto& stored: std::views::reverse(store_branch)) - { - if (!set_organized(stored.link, ++top)) - { - handler(fault(error::organize6), height); - return; - } - } - - // Archive strong tree headers and push to candidate chain. - for (const auto& key: std::views::reverse(tree_branch)) - { - if (const auto ec = push_block(key)) - { - handler(fault(ec), height); - return; - } - - top++; - } - - // Push new header as top of candidate chain. - if (const auto ec = push_block(*block, state->context())) - { - handler(fault(ec), height); - return; - } - - // Reset top chain state and notify. - // ........................................................................ - - // Evaluated independently of the block short-circuit below. - const auto current = is_current_time(header.timestamp()); - - // Delay so headers can get current before block download starts. - // Checking currency before notify also avoids excessive work backlog. - if (is_block() || current) - { - if (!bumped_ || regress) - { - // If at start the fork point is top of both chains, and next candidate - // is already downloaded, then new header will arrive and download will - // be skipped, resulting in stall until restart at which time the start - // event will advance through all downloaded candidates and progress on - // arrivals. This bumps validation once for current strong headers, and - // again on regression, as the candidate above the branch point may - // already be downloaded when reorganizing back to a stored branch. - notify(error::success, chases::bump{ add1(branch_point) }); - bumped_ = true; - } - - // chase::headers | chase::blocks - // This prevents download stall, the check chaser races ahead. - // Start block downloads, which upon completion bumps validation. - notify(error::success, chase_object{ branch_point }); - } - - // Logs from candidate block parent to the candidate (forward sequential). - log_state_change(*parent, *state); - state_ = state; - - // Advance top reached checkpoint and purge the tree at/below it. - update_checkpoint(height); - shrink_tree(current); - handler(error::success, height); -} - -// bitcoind's preciousblock, a manual tie break between equal work branches. -// The preference is not retained, as the reorganized branch then wins ties. -TEMPLATE -void CLASS::do_prioritize(const system::hash_digest& hash, - const organize_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - - if (closed()) - return; - - // Only the top of a cached branch can tie the candidate top. - if (std::any_of(tree_.cbegin(), tree_.cend(), [&](const auto& item) NOEXCEPT - { - return get_header(*item.second).previous_block_hash() == hash; - })) - { - handler(error::success, {}); - return; - } - - // A tied branch is cached, extract it for reevaluation as prioritized. - auto handle = tree_.extract(hash); - if (!handle) - { - handler(database::error::not_found, {}); - return; - } - - do_organize(handle.mapped(), true, handler); -} - -TEMPLATE -void CLASS::do_disorganize(header_t link) NOEXCEPT -{ - BC_ASSERT(stranded()); - using namespace system; - auto& query = archive(); - - if (closed()) - return; - - // May have been reorganized already by previous unconfirmable. - if (!query.is_candidate_header(link)) - return; - - // Get list of links to pop (weak branch), may be empty (previous disorg). - // ........................................................................ - - // Guarded by confirmed interlock, ensures a consistent branch only. - size_t fork_point{}; - auto candidates = query.get_candidate_fork(fork_point); - - // Move candidates above the invalid link into an independent list. - header_links invalids{}; - if (!part(candidates, invalids, link)) - return; - - // Copy valid portion of branch (below link) into header tree with state. - // ........................................................................ - - auto state = query.get_candidate_chain_state(settings_, fork_point); - if (!state) - { - fault(error::organize7); - return; - } - - for (const auto& candidate: candidates) - { - typename Block::cptr block{}; - if (!get_block(block, candidate)) - { - fault(error::organize8); - return; - } - - const auto& header = get_header(*block); - state = to_shared(*state, header, settings_); - cache(block, state); - } - - // Pop invalids (top to link), set unconfirmable (stops validation). - // ........................................................................ - - for (const auto& invalid: std::views::reverse(invalids)) - { - if (!query.set_block_unconfirmable(invalid)) - { - fault(error::organize9); - return; - } - - if (!set_reorganized(invalid)) - { - fault(error::organize10); - return; - } - } - - // Pop weak candidates (below link to fork point). - // ........................................................................ - - for (const auto& candidate: std::views::reverse(candidates)) - { - if (!set_reorganized(candidate)) - { - fault(error::organize11); - return; - } - } - - // Push all confirmeds above fork point onto candidate chain. - // ........................................................................ - - // Candidate fork link used to ensure consistency with confirmed chain. - const auto fork = query.to_candidate(fork_point); - - // Guarded by confirmed interlock, ensures fork point consistency. - for (const auto& confirmed: query.get_confirmed_fork(fork)) - { - if (!set_organized(confirmed, ++fork_point)) - { - fault(error::organize12); - return; - } - } - - // Reset top candidate state to match confirmed, log and notify. - // ........................................................................ - - // fork_point reflects the new candidate top. - state = query.get_candidate_chain_state(settings_, fork_point); - if (!state) - { - fault(error::organize13); - return; - } - - // Logs from previous top candidate to previous fork point (jumps back). - log_state_change(*state_, *state); - state_ = state; - - // Candidate is same as confirmed, reset chasers to new top. - notify(error::success, chases::disorganized{ fork_point }); - - // Reset all connections to ensure that new connections exist. - notify(error::success, chases::suspend{}); -} - -// Private setters -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::set_reorganized(height_t candidate_height) NOEXCEPT -{ - BC_ASSERT(stranded()); - BC_ASSERT(!is_under_checkpoint(candidate_height)); - if (!archive().pop_candidate()) - return false; - - // events::header_reorganized - fire(events_object_reorganized(), candidate_height); - LOGV("Header reorganized: " << candidate_height); - return true; -} - -TEMPLATE -bool CLASS::set_organized(const database::header_link& link, - height_t candidate_height) NOEXCEPT -{ - BC_ASSERT(stranded()); - auto& query = archive(); - -#if !defined(NDEBUG) - const auto previous_height = query.get_top_candidate(); - if (candidate_height != add1(previous_height)) - { - fault(error::stalled_channel); - return false; - } - - const auto parent = query.to_parent(link); - const auto top = query.to_candidate(previous_height); - if (parent != top) - { - fault(error::suspended_channel); - return false; - } -#endif // !NDEBUG - - if (!query.push_candidate(link)) - return false; - - // events::header_organized - fire(events_object_organized(), candidate_height); - LOGV("Header organized: " << candidate_height); - return true; -} - -TEMPLATE -code CLASS::push_block(const Block& block, - const system::chain::context& ctx) NOEXCEPT -{ - // set_code invokes set_strong when checked, but only for a whole block. - // Headers cannot be set strong, that is only when the block is archived. - // Milestone is archived in the header and like checkpoint cannot change. - // But unlike checkpointed, milestoned blocks may not be strong chain. - const auto checked = is_block() && is_under_checkpoint(ctx.height); - const auto milestone = is_under_milestone(ctx.height); - - auto& query = archive(); - database::header_link link{}; - const auto ec = query.set_code(link, block, ctx, milestone, checked); - if (ec) - return ec; - - // events::header_archived | events::block_archived - fire(events_object_archived(), ctx.height); - LOGV("Header archived: " << ctx.height); - return set_organized(link, ctx.height) ? error::success : error::organize14; -} - -TEMPLATE -code CLASS::push_block(const system::hash_digest& key) NOEXCEPT -{ - const auto handle = tree_.extract(key); - if (!handle) - return error::organize15; - - const auto& block = handle.mapped(); - return push_block(*block, block->get_state()->context()); -} - -TEMPLATE -void CLASS::cache(const typename Block::cptr& block, - const chain_state::cptr& state) NOEXCEPT -{ - // Any block obtained from the tree must have state cached. - block->set_state(state); - - tree_.emplace(block->get_hash(), block); -} - -TEMPLATE -bool CLASS::is_under_active_checkpoint( - const system::hash_digest& previous) const NOEXCEPT -{ - BC_ASSERT(stranded()); - const auto& query = archive(); - - if (is_zero(active_checkpoint_)) - return false; - - // Extending the candidate top (the common case, necessarily above). - if (state_->hash() == previous) - return false; - - // Tree blocks are necessarily above (purged as checkpoints are reached). - if (tree_.find(previous) != tree_.end()) - return false; - - // Unstored parent is the orphan case (handled downstream). - const auto link = query.to_header(previous); - if (link.is_terminal()) - return false; - - // The new block is a child, so at/under when its parent is under. - return query.get_height(link) < active_checkpoint_; -} - -// Set the highest checkpoint reached in the candidate chain. -TEMPLATE -void CLASS::update_checkpoint(height_t top) NOEXCEPT -{ - if (top < next_checkpoint_) - return; - - next_checkpoint_ = max_size_t; - const auto previous = active_checkpoint_; - for (const auto& item: checkpoints_) - { - if (item.height() <= top) - active_checkpoint_ = std::max(active_checkpoint_, item.height()); - else - next_checkpoint_ = std::min(next_checkpoint_, item.height()); - } - - if (active_checkpoint_ != previous) - { - LOGV("Checkpoint [" << active_checkpoint_ << "] reached."); - purge_under_checkpoint(); - } -} - -TEMPLATE -void CLASS::purge_under_checkpoint() NOEXCEPT -{ - // Purged blocks conflict with the reached checkpoint (dead branches). - const auto count = std::erase_if(tree_, [this](const auto& entry) NOEXCEPT - { - return entry.second->get_state()->height() <= active_checkpoint_; - }); - - if (!is_zero(count)) - { - LOGN("Purged (" << count << ") blocks under checkpoint [" - << active_checkpoint_ << "]."); - } -} - -TEMPLATE -void CLASS::shrink_tree(bool current) NOEXCEPT -{ - BC_ASSERT(stranded()); - if (shrunk_ || !current) - return; - - shrunk_ = true; - tree_ = { tree_.cbegin(), tree_.cend() }; - LOGV("Tree buckets reduced to (" << tree_.bucket_count() << ")."); -} - -// Private getters -// ---------------------------------------------------------------------------- - -TEMPLATE -CLASS::chain_state::cptr CLASS::get_chain_state( - const system::hash_digest& previous_hash) const NOEXCEPT -{ - using namespace system; - if (!state_) - return {}; - - // Top state is cached because it is by far the most commonly retrieved. - if (state_->hash() == previous_hash) - return state_; - - // Previous block may be cached because it is not yet strong. - const auto it = tree_.find(previous_hash); - if (it != tree_.end()) - return it->second->get_state(); - - // previous_hash may or not exist and/or be a candidate. - return archive().get_confirmed_chain_state(settings_, previous_hash); -} - -// Also obtains branch point for work summation termination. -// Also obtains ordered branch identifiers for subsequent reorg. -TEMPLATE -bool CLASS::get_branch_work(uint256_t& work, - system::hashes& tree_branch, header_states& store_branch, - const system::chain::header& header) const NOEXCEPT -{ - using namespace system; - const auto& query = archive(); - hash_cref previous{ header.previous_block_hash() }; - work = header.proof(); - - // Get portion of branch from tree and sum its work. - auto it = tree_.find(previous); - while (it != tree_.end()) - { - // Accumulate. - const auto& head = get_header(*it->second); - tree_branch.push_back(head.hash()); - work += head.proof(); - - // Iterate. - previous = { head.previous_block_hash() }; - it = tree_.find(previous); - } - - // Get portion of branch that is already stored. - if (!query.get_branch(store_branch, previous)) - return false; - - // If store_branch is empty then previous is candidate/branch_point. - if (!store_branch.empty()) - { - uint256_t store_work{}; - if (!query.get_work(store_work, store_branch)) - return false; - - work += store_work; - } - - return true; -} - -// Properties -// ---------------------------------------------------------------------------- - -TEMPLATE -const system::settings& CLASS::settings() const NOEXCEPT -{ - return settings_; -} - -TEMPLATE -const typename CLASS::block_tree& CLASS::tree() const NOEXCEPT -{ - return tree_; -} - -// Logging -// ---------------------------------------------------------------------------- - -TEMPLATE -void CLASS::log_state_change(const chain_state& from, - const chain_state& to) const NOEXCEPT -{ - using namespace system; - if constexpr (network::levels::news_defined) - { - if (from.flags() != to.flags()) - { - constexpr auto flag_bits = to_bits(sizeof(chain::flags)); - const binary prev{ flag_bits, to_big_endian(from.flags()) }; - const binary next{ flag_bits, to_big_endian(to.flags()) }; - - LOGN("Fork flags changed from [" - << prev << "] to [" - << next << "] at [" - << to.height() << ":" << encode_hash(to.hash()) << "]."); - } - - if (from.minimum_block_version() != to.minimum_block_version()) - { - LOGN("Minimum block version changed from [" - << from.minimum_block_version() << "] to [" - << to.minimum_block_version() << "] at [" - << to.height() << ":" << encode_hash(to.hash()) << "]."); - } - } -} - -} // namespace node -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/node/impl/sessions/session_peer.ipp b/include/bitcoin/node/impl/sessions/session_peer.ipp index 16ef7727..90d6686f 100644 --- a/include/bitcoin/node/impl/sessions/session_peer.ipp +++ b/include/bitcoin/node/impl/sessions/session_peer.ipp @@ -75,7 +75,6 @@ inline void CLASS::attach_protocols(const channel_ptr& channel) NOEXCEPT const auto self = this->template shared_from_base(); const auto relay = this->network_settings().enable_relay; const auto delay = this->node_settings().delay_inbound; - const auto headers = this->node_settings().headers_first; const auto node_network = this->node_settings().provide_blocks; const auto node_client_filters = this->node_settings().provide_filters; @@ -109,42 +108,32 @@ inline void CLASS::attach_protocols(const channel_ptr& channel) NOEXCEPT // Configured to relay transactions (currency is signalled by fee_filter). const auto txs_in_out = relay; - // Peer advertises chain (blocks in). + // Peer advertises chain (blocks in), headers-first requires 31800. if (peer->is_peer_service(service::node_network)) { - if (headers && peer->is_negotiated(level::bip130)) + if (peer->is_negotiated(level::bip130)) { channel->attach(self)->start(); channel->attach(self)->start(); - } - else if (headers && peer->is_negotiated(level::headers_protocol)) + else if (peer->is_negotiated(level::headers_protocol)) { channel->attach(self)->start(); channel->attach(self)->start(); } - else - { - // Very hard to find < 31800 peer to connect with. - // Blocks-first synchronization (not base of block_in_31800). - if (peer->is_negotiated(level::bip37)) - channel->attach(self)->start(); - else - channel->attach(self)->start(); - } } // Blocks are ready (blocks out). if (blocks_out) { - if (headers && peer->is_negotiated(level::bip130)) + if (peer->is_negotiated(level::bip130)) { channel->attach(self)->start(); channel->attach(self)->start(); } else { - if (headers && peer->is_negotiated(level::headers_protocol)) + if (peer->is_negotiated(level::headers_protocol)) channel->attach(self)->start(); if (peer->is_negotiated(level::bip37)) diff --git a/include/bitcoin/node/protocols/protocol.hpp b/include/bitcoin/node/protocols/protocol.hpp index 88878475..b9076c19 100644 --- a/include/bitcoin/node/protocols/protocol.hpp +++ b/include/bitcoin/node/protocols/protocol.hpp @@ -67,6 +67,9 @@ class BCN_API protocol /// The candidate|confirmed chain is current. virtual bool is_current_chain(bool confirmed) const NOEXCEPT; + /// Header timestamp is within configured span from current time. + virtual bool is_current_time(uint32_t timestamp) const NOEXCEPT; + /// The minimum fee rate (satoshis/kvB) to relay, max_money if not current. virtual uint64_t minimum_fee_rate() const NOEXCEPT; @@ -107,18 +110,21 @@ class BCN_API protocol /// Organizers. /// ----------------------------------------------------------------------- - /// Organize a validated header. + /// Validate and organize an unproven header. virtual void organize(const system::chain::header::cptr& header, organize_handler&& handler) NOEXCEPT; - /// Organize a checked block. - virtual void organize(const system::chain::block::cptr& block, - organize_handler&& handler) NOEXCEPT; + /// Organize a proven header, milestone set if in milestone branch. + virtual void organize(const system::chain::header::cptr& header, + bool milestone, organize_handler&& handler) NOEXCEPT; /// Reorganize to the branch of an archived block of at least equal work. virtual void prioritize(const system::hash_digest& hash, organize_handler&& handler) NOEXCEPT; + /// Work a proven header branch must reach (candidate or configured). + virtual void get_minimum_work(work_handler&& handler) NOEXCEPT; + /// Validate and archive a submitted package, accepted as a whole. /// The package is only validated when test, so nothing is archived. virtual void submit(const system::chain::transactions_cptr& txs, diff --git a/include/bitcoin/node/protocols/protocol_block_in_106.hpp b/include/bitcoin/node/protocols/protocol_block_in_106.hpp deleted file mode 100644 index 9826ac4d..00000000 --- a/include/bitcoin/node/protocols/protocol_block_in_106.hpp +++ /dev/null @@ -1,97 +0,0 @@ -/** - * 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_IN_106_HPP -#define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_BLOCK_IN_106_HPP - -#include -#include -#include - -namespace libbitcoin { -namespace node { - -class BCN_API protocol_block_in_106 - : public node::protocol_peer, - protected network::tracker -{ -public: - typedef std::shared_ptr ptr; - - protocol_block_in_106(const auto& session, - const network::channel::ptr& channel) NOEXCEPT - : node::protocol_peer(session, channel), - block_type_(session->node_settings().require_witness ? - type_id::witness_block : type_id::block), - network::tracker(session->log) - { - } - - /// Start/stop protocol (strand required). - void start() NOEXCEPT override; - -protected: - /// Clear the request record, false if the block was not requested. - bool erase_requested(const system::hash_digest& hash) NOEXCEPT; - - /// Accept incoming inventory message. - virtual bool handle_receive_inventory(const code& ec, - const network::messages::peer::inventory::cptr& message) NOEXCEPT; - - /// Accept incoming block message. - virtual bool handle_receive_block(const code& ec, - const network::messages::peer::block::cptr& message) NOEXCEPT; - virtual void handle_organize(const code& ec, size_t height, - const system::chain::block::cptr& block_ptr) NOEXCEPT; - virtual void do_handle_organize(const code& ec, size_t height, - const system::chain::block::cptr& block_ptr) NOEXCEPT; - -private: - /// Squash duplicates and provide constant time retrieval. - using hashmap = std::unordered_set; - - struct track - { - hashmap ids{}; - size_t announced{}; - system::hash_digest last{}; - }; - - static hashmap to_hashes(size_t reserve, - const network::messages::peer::get_data& getter) NOEXCEPT; - - network::messages::peer::get_blocks create_get_inventory() const NOEXCEPT; - network::messages::peer::get_blocks create_get_inventory( - const system::hash_digest& last) const NOEXCEPT; - network::messages::peer::get_blocks create_get_inventory( - system::hashes&& start_hashes) const NOEXCEPT; - - network::messages::peer::get_data create_get_data( - const network::messages::peer::inventory& message) const NOEXCEPT; - - // This is thread safe. - const type_id block_type_; - - // This is protected by strand. - track tracker_{}; -}; - -} // namespace node -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/node/protocols/protocol_block_in_31800.hpp b/include/bitcoin/node/protocols/protocol_block_in_31800.hpp index 7dd789bd..9d33d737 100644 --- a/include/bitcoin/node/protocols/protocol_block_in_31800.hpp +++ b/include/bitcoin/node/protocols/protocol_block_in_31800.hpp @@ -26,7 +26,7 @@ namespace libbitcoin { namespace node { -/// This class does NOT inherit from protocol_block_in_106. +/// Headers-first block download (bip130 announcements via headers). class BCN_API protocol_block_in_31800 : public protocol_performer, protected network::tracker diff --git a/include/bitcoin/node/protocols/protocol_block_in_70001.hpp b/include/bitcoin/node/protocols/protocol_block_in_70001.hpp deleted file mode 100644 index ade66970..00000000 --- a/include/bitcoin/node/protocols/protocol_block_in_70001.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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_IN_70001_HPP -#define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_BLOCK_IN_70001_HPP - -#include -#include - -namespace libbitcoin { -namespace node { - -class BCN_API protocol_block_in_70001 - : public protocol_block_in_106, - protected network::tracker -{ -public: - typedef std::shared_ptr ptr; - - protocol_block_in_70001(const auto& session, - const network::channel::ptr& channel) NOEXCEPT - : protocol_block_in_106(session, channel), - network::tracker(session->log) - { - } - - /// Start protocol (strand required). - void start() NOEXCEPT override; - -protected: - /// Accept incoming not_found message. - virtual bool handle_receive_not_found(const code& ec, - const network::messages::peer::not_found::cptr& message) NOEXCEPT; -}; - -} // namespace node -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/node/protocols/protocol_header_in_31800.hpp b/include/bitcoin/node/protocols/protocol_header_in_31800.hpp index 5e25ea79..6b6280aa 100644 --- a/include/bitcoin/node/protocols/protocol_header_in_31800.hpp +++ b/include/bitcoin/node/protocols/protocol_header_in_31800.hpp @@ -24,7 +24,10 @@ namespace libbitcoin { namespace node { - + +/// Synchronize the peer's header branch, validating and discarding headers +/// until proven (a checkpoint, or exhausted, current and at minimum work). +/// Then get it again and archive as verified against sampled hashes. class BCN_API protocol_header_in_31800 : public node::protocol_peer, protected network::tracker @@ -43,23 +46,53 @@ class BCN_API protocol_header_in_31800 void start() NOEXCEPT override; protected: + using chain_state = system::chain::chain_state; + using headers = network::messages::peer::headers; + using inventory = network::messages::peer::inventory; + using get_headers = network::messages::peer::get_headers; + virtual bool handle_receive_inventory(const code& ec, - const network::messages::peer::inventory::cptr& message) NOEXCEPT; + const inventory::cptr& message) NOEXCEPT; virtual bool handle_receive_headers(const code& ec, - const network::messages::peer::headers::cptr& message) NOEXCEPT; + const headers::cptr& message) NOEXCEPT; virtual void handle_organize(const code& ec, size_t height, const system::chain::header::cptr& header_ptr) NOEXCEPT; + virtual void handle_minimum_work(const code& ec, const uint256_t& work, + bool initial) NOEXCEPT; + virtual void do_minimum_work(const code& ec, const uint256_t& work, + bool initial) NOEXCEPT; virtual void complete() NOEXCEPT; // This is protected by strand. bool subscribed{}; private: - network::messages::peer::get_headers create_get_headers() const NOEXCEPT; - network::messages::peer::get_headers create_get_headers( + + void synchronize(const headers& message, bool full) NOEXCEPT; + void collect(const headers& message, bool full) NOEXCEPT; + bool restart(const system::hash_digest& previous) NOEXCEPT; + void sample(const system::hash_digest& hash) NOEXCEPT; + void prove() NOEXCEPT; + void finish() NOEXCEPT; + + get_headers create_get_headers() const NOEXCEPT; + get_headers create_get_headers( const system::hash_digest& last) const NOEXCEPT; - network::messages::peer::get_headers create_get_headers( + get_headers create_get_headers( system::hashes&& start_hashes) const NOEXCEPT; + + // These are protected by strand. + bool archiving_{}; + size_t top_{}; + size_t index_{}; + size_t height_{}; + size_t milestone_{}; + system::hashes samples_{}; + system::hash_digest previous_{}; + system::chain::header_cptrs buffer_{}; + size_t interval_{ network::messages::peer::max_get_headers }; + uint256_t minimum_work_{}; + chain_state::cptr state_{}; }; } // namespace node diff --git a/include/bitcoin/node/protocols/protocols.hpp b/include/bitcoin/node/protocols/protocols.hpp index 6dffb253..f1c233d5 100644 --- a/include/bitcoin/node/protocols/protocols.hpp +++ b/include/bitcoin/node/protocols/protocols.hpp @@ -20,9 +20,7 @@ #define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOLS_HPP #include -#include #include -#include #include #include #include diff --git a/include/bitcoin/node/sessions/session.hpp b/include/bitcoin/node/sessions/session.hpp index 0dff8aa2..259940e2 100644 --- a/include/bitcoin/node/sessions/session.hpp +++ b/include/bitcoin/node/sessions/session.hpp @@ -39,13 +39,13 @@ class BCN_API session /// Organizers. /// ----------------------------------------------------------------------- - /// Organize a validated header. + /// Validate and organize an unproven header. virtual void organize(const system::chain::header::cptr& header, organize_handler&& handler) NOEXCEPT; - /// Organize a validated block. - virtual void organize(const system::chain::block::cptr& block, - organize_handler&& handler) NOEXCEPT; + /// Organize a proven header, milestone set if in milestone branch. + virtual void organize(const system::chain::header::cptr& header, + bool milestone, organize_handler&& handler) NOEXCEPT; /// Reorganize to the branch of an archived block of at least equal work. virtual void prioritize(const system::hash_digest& hash, @@ -56,6 +56,9 @@ class BCN_API session virtual void submit(const system::chain::transactions_cptr& txs, bool test, submit_handler&& handler) NOEXCEPT; + /// Work a proven header branch must reach (candidate or configured). + virtual void get_minimum_work(work_handler&& handler) NOEXCEPT; + /// Manage download queue. virtual void get_hashes(map_handler&& handler) NOEXCEPT; virtual void put_hashes(const map_ptr& map, @@ -125,6 +128,7 @@ class BCN_API session /// The candidate|confirmed chain is current. virtual bool is_current_chain(bool confirmed) const NOEXCEPT; + virtual bool is_current_time(uint32_t timestamp) const NOEXCEPT; /// The confirmed chain is confirmed to maximum height or is current. virtual bool is_recent() const NOEXCEPT; diff --git a/include/bitcoin/node/settings.hpp b/include/bitcoin/node/settings.hpp index 91643aac..6938321c 100644 --- a/include/bitcoin/node/settings.hpp +++ b/include/bitcoin/node/settings.hpp @@ -36,7 +36,6 @@ class BCN_API settings /// Properties. uint32_t threads; bool delay_inbound; - bool headers_first; bool thread_priority; bool memory_priority; bool provide_blocks; diff --git a/src/chasers/chaser_block.cpp b/src/chasers/chaser_block.cpp deleted file mode 100644 index c1751ca2..00000000 --- a/src/chasers/chaser_block.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/** - * 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 -#include -#include - -namespace libbitcoin { -namespace node { - -using namespace system; -using namespace system::chain; - -chaser_block::chaser_block(full_node& node) NOEXCEPT - : chaser_organize(node), - node_witness_(node.node_settings().require_witness) -{ -} - -const header& chaser_block::get_header(const block& block) const NOEXCEPT -{ - return block.header(); -} - -bool chaser_block::get_block(block::cptr& out, - const header_link& link) const NOEXCEPT -{ - const auto& query = archive(); - out = query.get_block(link, node_witness_); - return !is_null(out); -} - -code chaser_block::duplicate(size_t& height, - const system::hash_digest& hash) const NOEXCEPT -{ - height = max_size_t; - const auto& query = archive(); - const auto id = query.to_header(hash); - if (!id.is_terminal()) - { - // database::error::unassociated - // database::error::block_unconfirmable - // database::error::block_confirmable - // database::error::block_valid - // database::error::unknown_state - // database::error::unvalidated - const auto ec = query.get_block_state(id); - - // Most header states are duplicates, one implies fail. - if (ec == database::error::block_unconfirmable) - { - height = query.get_height(id); - return ec; - } - - // unassociated is only non-duplicate. - if (ec != database::error::unassociated) - { - height = query.get_height(id); - return error::duplicate_block; - } - } - - return error::success; -} - -code chaser_block::validate(const block& block, - const chain_state& state) const NOEXCEPT -{ - code ec{}; - const auto& header = block.header(); - const auto& setting = settings(); - const auto ctx = state.context(); - - // header.check is never bypassed. - // block.check does not invoke header.check. - if ((ec = header.check( - setting.timestamp_limit_seconds, - setting.proof_of_work_limit, - setting.forks.ltc_scrypt_proof_of_work))) - return ec; - - // header.accept is never bypassed. - // block.accept does not invoke header.accept. - if ((ec = header.accept(ctx, - setting.retargeting_interval()))) - return ec; - - if (is_under_checkpoint(state.height())) - { - // Only identity is required under checkpoint. - if (((ec = block.identify())) || ((ec = block.identify(ctx)))) - return ec; - } - else - { - // Identity is not assured if invalid (but is not required). - if (((ec = block.check())) || ((ec = block.check(ctx)))) - return ec; - - if (!populate(block, ctx)) - return system::error::missing_previous_output; - - if ((ec = block.accept(ctx, - setting.subsidy_interval_blocks, - setting.initial_subsidy()))) - return ec; - - if ((ec = block.connect(ctx))) - return ec; - } - - /////////////////////////////////////////////////////////////////////////// - // TODO: chaser_validate bypasses set filter body, prevouts, block_valid. - // These and notify(chase::valid) could be set here in chaser_block but - // blocks are populated from the block tree and therefore there is not yet - // tx metadata for all prevouts. So presently blocks first does not proceed - // to confirmation following this block validation and header organization. - /////////////////////////////////////////////////////////////////////////// - //// None of this can be done because block is not archived. - //// - ////auto& query = archive(); - ////if (!query.set_prevouts(link, block)) - //// return error::validate6; - //// - ////if (!query.set_filter_body(link, block)) - //// return error::validate7; - //// - ////// After set_prevouts and set_filter_body. - ////if (!bypass && !query.set_block_valid(link, block.fees())) - //// return error::validate8; - //// - ////notify(error::success, chase::valid, state.height()); - /////////////////////////////////////////////////////////////////////////// - - return system::error::block_success; -} - -bool chaser_block::is_storable(const chain_state&) const NOEXCEPT -{ - return true; -} - -// Milestone methods. -// ---------------------------------------------------------------------------- - -bool chaser_block::is_under_milestone(size_t) const NOEXCEPT -{ - return false; -} - -bool chaser_block::update_milestone(const header&, size_t, size_t) NOEXCEPT -{ - return false; -} - -// Populate methods (private). -// ---------------------------------------------------------------------------- - -void chaser_block::set_prevout(const input& input) const NOEXCEPT -{ - const auto& point = input.point(); - if (input.prevout || point.is_null()) - return; - - // Scan all tree blocks for matching tx (linear :/ but legacy scenario) - std::ranges::for_each(tree(), [&](const auto& item) NOEXCEPT - { - const auto& txs = item.second->transactions_ptr(); - const auto it = std::ranges::find_if(*txs, [&](const auto& tx) NOEXCEPT - { - return tx->hash(false) == point.hash(); - }); - - if (it != txs->end()) - { - const auto& tx = **it; - const auto& outs = *tx.outputs_ptr(); - if (point.index() < outs.size()) - { - // prevout is mutable so can be set on a const object. - input.prevout = outs.at(point.index()); - return; - } - } - }); -} - -// Populate prevouts from self/tree/store (without metadata). -bool chaser_block::populate(const block& block, - const chain::context& ctx) const NOEXCEPT -{ - block.populate(ctx); - - const auto& ins = *block.inputs_ptr(); - std::ranges::for_each(ins, [&](const auto& in) NOEXCEPT - { - set_prevout(*in); - }); - - return archive().populate_without_metadata(block); -} - -} // namespace node -} // namespace libbitcoin diff --git a/src/chasers/chaser_header.cpp b/src/chasers/chaser_header.cpp index 913f5f8e..c29be2fd 100644 --- a/src/chasers/chaser_header.cpp +++ b/src/chasers/chaser_header.cpp @@ -18,44 +18,531 @@ */ #include -#include +#include +#include #include #include namespace libbitcoin { namespace node { - + +#define CLASS chaser_header + +using namespace system; using namespace system::chain; +using namespace std::placeholders; + +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +// Public +// ---------------------------------------------------------------------------- + +// static +code chaser_header::validate(const header& header, const chain_state& state, + const system::settings& settings) NOEXCEPT +{ + if (const auto ec = header.check( + settings.timestamp_limit_seconds, + settings.proof_of_work_limit, + settings.forks.ltc_scrypt_proof_of_work)) + return ec; + + if (const auto ec = header.accept(state.context(), + settings.retargeting_interval())) + return ec; + + const auto height = state.height(); + const auto& checkpoints = settings.checkpoints; + if (checkpoint::is_conflict(checkpoints, header.get_hash(), height)) + return system::error::checkpoint_conflict; + + return error::success; +} chaser_header::chaser_header(full_node& node) NOEXCEPT - : chaser_organize
(node), - milestone_(system_settings().milestone) + : chaser(node), + settings_(system_settings()), + checkpoints_(system_settings().checkpoints) { } code chaser_header::start() NOEXCEPT { - if (!initialize_milestone()) - return fault(error::header1); + // 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. + const auto& query = archive(); + const auto top = query.get_top_candidate(); + state_ = query.get_candidate_chain_state(settings_, top); + + if (!state_) + { + fault(error::organize1); + return error::organize1; + } + + LOGN("Candidate top [" << encode_hash(state_->hash()) << ":" + << state_->height() << "]."); + + update_checkpoint(top); + prune_tree(top); + SUBSCRIBE_CHASE(handle_chase, _1, _2); + return error::success; +} + +void chaser_header::organize(const header::cptr& header, + organize_handler&& handler) NOEXCEPT +{ + if (closed()) + return; + + POST(do_organize, header, false, false, false, std::move(handler)); +} + +void chaser_header::organize(const header::cptr& header, bool milestone, + organize_handler&& handler) NOEXCEPT +{ + if (closed()) + return; + + POST(do_organize, header, false, milestone, true, std::move(handler)); +} + +void chaser_header::prioritize(const hash_digest& hash, + organize_handler&& handler) NOEXCEPT +{ + if (closed()) + return; + + POST(do_prioritize, hash, std::move(handler)); +} + +void chaser_header::get_minimum_work(work_handler&& handler) NOEXCEPT +{ + if (closed()) + return; - return chaser_organize
::start(); + POST(do_get_minimum_work, std::move(handler)); } -const header& chaser_header::get_header(const header& header) const NOEXCEPT +// Methods +// ---------------------------------------------------------------------------- + +bool chaser_header::handle_chase(const code&, event_value value) NOEXCEPT { - return header; + if (closed()) + return false; + + switch (to_chase(value)) + { + // Roll back the candidate chain to confirmed top (via fork point). + case chase::unchecked: + { + if (database_settings().mark_unconfirmable) + { + POST(do_disorganize, to_payload(value).link); + } + + break; + } + case chase::unvalid: + { + if (database_settings().mark_unconfirmable) + { + POST(do_disorganize, to_payload(value).link); + } + + break; + } + case chase::unconfirmable: + { + if (database_settings().mark_unconfirmable) + { + POST(do_disorganize, + to_payload(value).link); + } + + break; + } + case chase::stop: + { + return false; + } + default: + { + break; + } + } + + return true; } -bool chaser_header::get_block(header::cptr& out, - const header_link& link) const NOEXCEPT +void chaser_header::do_organize(const header::cptr& header_ptr, + bool prioritized, bool milestone, bool proven, + const organize_handler& handler) NOEXCEPT { + BC_ASSERT(stranded()); + const auto& query = archive(); - out = query.get_header(link); - return !is_null(out); + const auto& header = *header_ptr; + const auto& hash = header.get_hash(); + + // Skip existing/orphan, get state. + // ........................................................................ + + if (closed()) + { + handler(network::error::service_stopped, {}); + return; + } + + const auto it = tree_.find(hash); + if (it != tree_.cend()) + { + const auto& state = it->second->get_state(); + handler(error::duplicate_header, state->height()); + return; + } + + size_t height{}; + if (const auto ec = duplicate(height, hash)) + { + handler(ec, height); + return; + } + + // Validate parent and obtain header chain state. + // ........................................................................ + + // Shortcircuit parent unconfirmable (looping over failed block). + const auto& previous = header.previous_block_hash(); + if (query.is_unconfirmable(query.to_header(previous))) + { + handler(database::error::block_unconfirmable, {}); + return; + } + + // Shortcircuit fork at/under the top reached checkpoint. + if (is_under_active_checkpoint(previous)) + { + handler(system::error::checkpoint_conflict, {}); + return; + } + + // Obtain parent state from state_, tree, or store as applicable. + const auto parent = get_chain_state(previous); + if (!parent) + { + handler(error::orphan_header, {}); + return; + } + + // Roll chain state forward from archived parent to new header. + const auto state = emplace_shared(*parent, header, settings_); + height = state->height(); + + if (checkpoint::is_conflict(checkpoints_, hash, height)) + { + handler(system::error::checkpoint_conflict, height); + return; + } + + // A proven header was validated by the protocol, all are storable. + if (!proven) + { + if (const auto ec = validate(header, *state, settings_)) + { + handler(ec, height); + return; + } + } + + // Compute relative work. + // ........................................................................ + + uint256_t work{}; + hashes tree_branch{}; + header_states store_branch{}; + if (!get_branch_work(work, tree_branch, store_branch, header)) + { + handler(fault(error::organize2), height); + return; + } + + bool strong{}; + const auto branch_size = tree_branch.size() + store_branch.size(); + const auto branch_point = height - add1(branch_size); + if (!query.get_strong_branch(strong, work, branch_point, prioritized)) + { + handler(fault(error::organize3), height); + return; + } + + // New top of a weak branch. + if (!strong) + { + log_state_change(*parent, *state); + cache(header_ptr, state); + handler(error::success, height); + return; + } + + // Reorganize candidate chain. + // ........................................................................ + + // Cannot be branching above top. + auto top = state_->height(); + if (branch_point > top) + { + handler(fault(error::organize4), height); + return; + } + + // Pop top down to the branch point. + const auto regress = branch_point < top; + while (branch_point < top) + { + if (!set_reorganized(top--)) + { + handler(fault(error::organize5), height); + return; + } + } + + // Reset chasers to the branch point. + if (regress) + { + notify(error::success, chases::regressed{ branch_point }); + } + + // Push stored strong headers to candidate chain. + for (const auto& stored: std::views::reverse(store_branch)) + { + if (!set_organized(stored.link, ++top)) + { + handler(fault(error::organize6), height); + return; + } + } + + // Archive strong tree headers and push to candidate chain. + for (const auto& key: std::views::reverse(tree_branch)) + { + if (const auto ec = push_header(key)) + { + handler(fault(ec), height); + return; + } + + top++; + } + + // Push new header as top of candidate chain. + if (const auto ec = push_header(header, state->context(), milestone)) + { + handler(fault(ec), height); + return; + } + + // Reset top chain state and notify. + // ........................................................................ + + // Delay so headers can get current before block download starts. + // Checking currency before notify also avoids excessive work backlog. + const auto current = is_current_time(header.timestamp()); + if (current) + { + if (!bumped_ || regress) + { + // If at start the fork point is top of both chains, and next + // candidate is already downloaded, then new header will arrive and + // download will be skipped, resulting in stall until restart at + // which time the start event will advance through all downloaded + // candidates and progress on arrivals. This bumps validation once + // for current strong headers, and again on regression, as the + // candidate above the branch point may already be downloaded when + // reorganizing back to a stored branch. + notify(error::success, chases::bump{ add1(branch_point) }); + bumped_ = true; + } + + // This prevents download stall, the check chaser races ahead. + // Start block downloads, which upon completion bumps validation. + notify(error::success, chases::headers{ branch_point }); + } + + // Logs from candidate block parent to the candidate (forward sequential). + log_state_change(*parent, *state); + state_ = state; + + // Advance top reached checkpoint and prune the tree. + update_checkpoint(height); + prune_tree(height); + shrink_tree(current); + handler(error::success, height); +} + +// bitcoind's preciousblock, a manual tie break between equal work branches. +// The preference is not retained, as the reorganized branch then wins ties. +void chaser_header::do_prioritize(const hash_digest& hash, + const organize_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (closed()) + return; + + // Only the top of a cached branch can tie the candidate top. + if (std::any_of(tree_.cbegin(), tree_.cend(), [&](const auto& item) NOEXCEPT + { + return item.second->previous_block_hash() == hash; + })) + { + handler(error::success, {}); + return; + } + + // A tied branch is cached, extract it for reevaluation as prioritized. + auto handle = tree_.extract(hash); + if (!handle) + { + handler(database::error::not_found, {}); + return; + } + + do_organize(handle.mapped(), true, false, true, handler); +} + +// The greater of configured minimum work and candidate work less a window. +void chaser_header::do_get_minimum_work(const work_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + const uint256_t minimum_work = settings_.minimum_work; + handler(error::success, std::max(minimum_work, get_window_work())); +} + +void chaser_header::do_disorganize(header_t link) NOEXCEPT +{ + BC_ASSERT(stranded()); + auto& query = archive(); + + if (closed()) + return; + + // May have been reorganized already by previous unconfirmable. + if (!query.is_candidate_header(link)) + return; + + // Get list of links to pop (weak branch), may be empty (previous disorg). + // ........................................................................ + + // Guarded by confirmed interlock, ensures a consistent branch only. + size_t fork_point{}; + auto candidates = query.get_candidate_fork(fork_point); + + // Move candidates above the invalid link into an independent list. + header_links invalids{}; + if (!part(candidates, invalids, link)) + return; + + // Copy valid portion of branch (below link) into header tree with state. + // ........................................................................ + + auto state = query.get_candidate_chain_state(settings_, fork_point); + if (!state) + { + fault(error::organize7); + return; + } + + for (const auto& candidate: candidates) + { + const auto header_ptr = query.get_header(candidate); + if (is_null(header_ptr)) + { + fault(error::organize8); + return; + } + + state = to_shared(*state, *header_ptr, settings_); + cache(header_ptr, state); + } + + // Pop invalids (top to link), set unconfirmable (stops validation). + // ........................................................................ + + for (const auto& invalid: std::views::reverse(invalids)) + { + if (!query.set_block_unconfirmable(invalid)) + { + fault(error::organize9); + return; + } + + if (!set_reorganized(invalid)) + { + fault(error::organize10); + return; + } + } + + // Pop weak candidates (below link to fork point). + // ........................................................................ + + for (const auto& candidate: std::views::reverse(candidates)) + { + if (!set_reorganized(candidate)) + { + fault(error::organize11); + return; + } + } + + // Push all confirmeds above fork point onto candidate chain. + // ........................................................................ + + // Candidate fork link used to ensure consistency with confirmed chain. + const auto fork = query.to_candidate(fork_point); + + // Guarded by confirmed interlock, ensures fork point consistency. + for (const auto& confirmed: query.get_confirmed_fork(fork)) + { + if (!set_organized(confirmed, ++fork_point)) + { + fault(error::organize12); + return; + } + } + + // Reset top candidate state to match confirmed, log and notify. + // ........................................................................ + + // fork_point reflects the new candidate top. + state = query.get_candidate_chain_state(settings_, fork_point); + if (!state) + { + fault(error::organize13); + return; + } + + // Logs from previous top candidate to previous fork point (jumps back). + log_state_change(*state_, *state); + state_ = state; + + // Candidate is same as confirmed, reset chasers to new top. + notify(error::success, chases::disorganized{ fork_point }); + + // Reset all connections to ensure that new connections exist. + notify(error::success, chases::suspend{}); } +// Validation (private). +// ---------------------------------------------------------------------------- + code chaser_header::duplicate(size_t& height, - const system::hash_digest& hash) const NOEXCEPT + const hash_digest& hash) const NOEXCEPT { // With a candidate reorg that drops strong below a valid header chain, // this will cause a sequence of headers to be bypassed, such that a @@ -89,159 +576,344 @@ code chaser_header::duplicate(size_t& height, return error::success; } -code chaser_header::validate(const header& header, - const chain_state& state) const NOEXCEPT +// Setters (private). +// ---------------------------------------------------------------------------- + +bool chaser_header::set_reorganized(height_t candidate_height) NOEXCEPT { - // header.check is never bypassed. - if (const auto ec = header.check( - settings().timestamp_limit_seconds, - settings().proof_of_work_limit, - settings().forks.ltc_scrypt_proof_of_work)) - return ec; + BC_ASSERT(stranded()); + BC_ASSERT(!is_under_checkpoint(candidate_height)); + if (!archive().pop_candidate()) + return false; - // header.accept is never bypassed. - if (const auto ec = header.accept(state.context(), - settings().retargeting_interval())) - return ec; + fire(events::header_reorganized, candidate_height); + LOGV("Header reorganized: " << candidate_height); + return true; +} - // This prevents a long unconfirmable header chain with an early - // unconfirmable from reinitiating a long validation chain before hitting - // the invalidation again. This is more likely the case of a bug than IRL. - ////const auto& query = archive(); - ////const auto ec = query.get_header_state(query.to_header(header.hash())); - ////if (ec == database::error::block_unconfirmable) - //// return ec; +bool chaser_header::set_organized(const header_link& link, + height_t candidate_height) NOEXCEPT +{ + BC_ASSERT(stranded()); + auto& query = archive(); - return system::error::block_success; -} +#if !defined(NDEBUG) + const auto previous_height = query.get_top_candidate(); + if (candidate_height != add1(previous_height)) + { + fault(error::stalled_channel); + return false; + } -// Storable methods (private). -// ---------------------------------------------------------------------------- + const auto parent = query.to_parent(link); + const auto top = query.to_candidate(previous_height); + if (parent != top) + { + fault(error::suspended_channel); + return false; + } +#endif // !NDEBUG -bool chaser_header::is_storable(const chain_state& state) const NOEXCEPT + if (!query.push_candidate(link)) + return false; + + fire(events::header_organized, candidate_height); + LOGV("Header organized: " << candidate_height); + return true; +} + +// Headers cannot be set strong, that is only when the block is archived. +// Milestone is archived in the header and like checkpoint cannot change. +// But unlike checkpointed, milestoned blocks may not be strong chain. +code chaser_header::push_header(const header& header, const context& ctx, + bool milestone) NOEXCEPT { - return is_checkpoint(state) || is_milestone(state) - || (is_current(state) && is_hard(state)); + auto& query = archive(); + header_link link{}; + const auto ec = query.set_code(link, header, ctx, milestone, false); + if (ec) + return ec; + + fire(events::header_archived, ctx.height); + LOGV("Header archived: " << ctx.height); + return set_organized(link, ctx.height) ? error::success : error::organize14; } -bool chaser_header::is_checkpoint(const chain_state& state) const NOEXCEPT +code chaser_header::push_header(const hash_digest& key) NOEXCEPT { - return checkpoint::is_at(settings().checkpoints, state.height()); + const auto handle = tree_.extract(key); + if (!handle) + return error::organize15; + + const auto& header_ptr = handle.mapped(); + const auto& state = header_ptr->get_state(); + return push_header(*header_ptr, state->context(), false); } -bool chaser_header::is_milestone(const chain_state& state) const NOEXCEPT +void chaser_header::cache(const header::cptr& header, + const chain_state::cptr& state) NOEXCEPT { - return milestone_.equals(state.hash(), state.height()); + // Any header obtained from the tree must have state cached. + header->set_state(state); + + tree_.emplace(header->get_hash(), header); } -bool chaser_header::is_current(const chain_state& state) const NOEXCEPT +// Checkpoint gate (private). +// ---------------------------------------------------------------------------- + +bool chaser_header::is_under_active_checkpoint( + const hash_digest& previous) const NOEXCEPT { - return chaser::is_current_time(state.timestamp()); + BC_ASSERT(stranded()); + const auto& query = archive(); + + if (is_zero(active_checkpoint_)) + return false; + + // Extending the candidate top (the common case, necessarily above). + if (state_->hash() == previous) + return false; + + // Tree headers are necessarily above (purged as checkpoints are reached). + if (tree_.find(previous) != tree_.end()) + return false; + + // Unstored parent is the orphan case (handled downstream). + const auto link = query.to_header(previous); + if (link.is_terminal()) + return false; + + // The new header is a child, so at/under when its parent is under. + return query.get_height(link) < active_checkpoint_; } -bool chaser_header::is_hard(const chain_state& state) const NOEXCEPT +// Set the highest checkpoint reached in the candidate chain. +void chaser_header::update_checkpoint(height_t top) NOEXCEPT { - // TODO: use minimum_work as a threshold but once chain is organized and - // TODO: this is exceed, the comparison should be against the cumulative - // TODO: work of the current top block. This value is already stored in - // TODO: the top block chain state, so strong can be reduced to compare. - return state.cumulative_work() >= settings().minimum_work; + if (top < next_checkpoint_) + return; + + next_checkpoint_ = max_size_t; + const auto previous = active_checkpoint_; + for (const auto& item: checkpoints_) + { + if (item.height() <= top) + active_checkpoint_ = std::max(active_checkpoint_, item.height()); + else + next_checkpoint_ = std::min(next_checkpoint_, item.height()); + } + + if (active_checkpoint_ != previous) + { + LOGV("Checkpoint [" << active_checkpoint_ << "] reached."); + prune_tree(); + } } -// Milestone methods. +// Tree control (private). // ---------------------------------------------------------------------------- -// private -bool chaser_header::initialize_milestone() NOEXCEPT +void chaser_header::shrink_tree(bool current) NOEXCEPT { - active_milestone_height_ = zero; - if (is_zero(milestone_.height()) || - milestone_.hash() == system::null_hash) - return true; + BC_ASSERT(stranded()); + if (shrunk_ || !current) + return; - const auto& query = archive(); - const auto link = query.to_candidate(milestone_.height()); - if (link.is_terminal()) - return true; + shrunk_ = true; + tree_ = { tree_.cbegin(), tree_.cend() }; + LOGV("Tree buckets reduced to (" << tree_.bucket_count() << ")."); +} - const auto hash = query.get_header_key(link); - if (hash == system::null_hash) - return false; +// Purged branches have top work under the window (dead branches). +void chaser_header::prune_tree(const uint256_t& threshold) NOEXCEPT +{ + std::unordered_map children{}; + for (const auto& item: tree_) + ++children[item.second->previous_block_hash()]; - if (hash == milestone_.hash()) - active_milestone_height_ = milestone_.height(); + hashes tops{}; + for (const auto& item: tree_) + if (children.find(item.first) == children.end()) + tops.push_back(item.first); - return true; + size_t count{}; + for (const auto& top: tops) + { + auto it = tree_.find(top); + if (it == tree_.end()) + continue; + + const auto& state = it->second->get_state(); + if (state->cumulative_work() >= threshold) + continue; + + // Descend while each parent has no other child (copy before erase). + while (true) + { + const auto& head = *it->second; + const hash_digest previous{ head.previous_block_hash() }; + tree_.erase(it); + ++count; + + const auto child = children.find(previous); + if (child == children.end() || !is_zero(--child->second)) + break; + + it = tree_.find(previous); + if (it == tree_.end()) + break; + } + } + + if (!is_zero(count)) + { + LOGN("Purged (" << count << ") headers under window at [" + << state_->height() << "]."); + } } -bool chaser_header::is_under_milestone(size_t height) const NOEXCEPT +// Purge once per window of candidate progress, never on arrival. +void chaser_header::prune_tree(height_t top) NOEXCEPT { - return height <= active_milestone_height_; -} + if (top < next_window_) + return; -BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + const auto window = get_window(); + if (is_zero(window)) + return; + + next_window_ = top + window; + prune_tree(get_window_work()); +} -bool chaser_header::update_milestone(const system::chain::header& header, - size_t height, size_t branch_point) NOEXCEPT +// Purged headers conflict with the reached checkpoint (dead branches). +void chaser_header::prune_tree() NOEXCEPT { - if (milestone_.equals(header.get_hash(), height)) + const auto count = std::erase_if(tree_, [this](const auto& item) NOEXCEPT { - active_milestone_height_ = height; - return true; + const auto& state = item.second->get_state(); + return state->height() <= active_checkpoint_; + }); + + if (!is_zero(count)) + { + LOGN("Purged (" << count << ") headers under checkpoint [" + << active_checkpoint_ << "]."); } +} + +// Getters (private). +// ---------------------------------------------------------------------------- + +// The currency window in blocks, zero disables. +size_t chaser_header::get_window() const NOEXCEPT +{ + const auto minutes = node_settings().currency_window_minutes; + const auto spacing = settings_.block_spacing_seconds; + return (minutes * 60u) / spacing; +} + +// Candidate top work less a window of its proof, zero if none. +uint256_t chaser_header::get_window_work() const NOEXCEPT +{ + const auto window = get_window(); + const auto& work = state_->cumulative_work(); + const auto proof = chain::header::proof(state_->work_required()); + const auto span = uint256_t{ window } * proof; + return (is_zero(window) || (work <= span)) ? uint256_t{} : work - span; +} - using namespace system; +chaser_header::chain_state::cptr chaser_header::get_chain_state( + const hash_digest& previous_hash) const NOEXCEPT +{ + if (!state_) + return {}; + + // Top state is cached because it is by far the most commonly retrieved. + if (state_->hash() == previous_hash) + return state_; + + // Previous header may be cached because it is not yet strong. + const auto it = tree_.find(previous_hash); + if (it != tree_.end()) + return it->second->get_state(); + + // previous_hash may or not exist and/or be a candidate. + return archive().get_confirmed_chain_state(settings_, previous_hash); +} + +// Also obtains branch point for work summation termination. +// Also obtains ordered branch identifiers for subsequent reorg. +bool chaser_header::get_branch_work(uint256_t& work, hashes& tree_branch, + header_states& store_branch, const header& header) const NOEXCEPT +{ + const auto& query = archive(); hash_cref previous{ header.previous_block_hash() }; + work = header.proof(); - // Scan branch for milestone match. - for (auto it = tree().find(previous); it != tree().end(); - it = tree().find(previous)) + // Get portion of branch from tree and sum its work. + auto it = tree_.find(previous); + while (it != tree_.end()) { - const auto& state = *(it->second->get_state()); - const auto index = state.height(); - if (milestone_.equals(state.hash(), index)) - { - active_milestone_height_ = index; - return true; - } + // Accumulate. + const auto& head = *it->second; + tree_branch.push_back(head.hash()); + work += head.proof(); // Iterate. - const auto& next = get_header(*it->second); - previous = { next.previous_block_hash() }; + previous = { head.previous_block_hash() }; + it = tree_.find(previous); } - // The current active milestone is necessarily on the candidate branch. - // New branch doesn't have milestone and reorganizes the branch with it. - // Can retain a milestone at the branch point (below its definition). - if (active_milestone_height_ > branch_point) + // Get portion of branch that is already stored. + if (!query.get_branch(store_branch, previous)) + return false; + + // If store_branch is empty then previous is candidate/branch_point. + if (!store_branch.empty()) { - active_milestone_height_ = branch_point; - return true; + uint256_t store_work{}; + if (!query.get_work(store_work, store_branch)) + return false; + + work += store_work; } - return false; + return true; +} + +// Logging +// ---------------------------------------------------------------------------- + +void chaser_header::log_state_change(const chain_state& from, + const chain_state& to) const NOEXCEPT +{ + if constexpr (network::levels::news_defined) + { + if (from.flags() != to.flags()) + { + constexpr auto flag_bits = to_bits(sizeof(chain::flags)); + const binary prev{ flag_bits, to_big_endian(from.flags()) }; + const binary next{ flag_bits, to_big_endian(to.flags()) }; + + LOGN("Fork flags changed from [" + << prev << "] to [" + << next << "] at [" + << to.height() << ":" << encode_hash(to.hash()) << "]."); + } + + if (from.minimum_block_version() != to.minimum_block_version()) + { + LOGN("Minimum block version changed from [" + << from.minimum_block_version() << "] to [" + << to.minimum_block_version() << "] at [" + << to.height() << ":" << encode_hash(to.hash()) << "]."); + } + } } BC_POP_WARNING() } // namespace node } // namespace libbitcoin - -// bip90 prevents bip34/65/66 activation oscillations - -// Forks (default configuration). -// Forked from [00000000000000100000000010001011] to [00000000000000100000000010000011] at [ 91842:00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec] -// Forked from [00000000000000100000000010000011] to [00000000000000100000000010001011] at [ 91843:0000000000016ca756e810d44aee6be7eabad75d2209d7f4542d1fd53bafc984] -// Forked from [00000000000000100000000010001011] to [00000000000000100000000010000011] at [ 91880:00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721] -// Forked from [00000000000000100000000010000011] to [00000000000000100000000010001011] at [ 91881:00000000000cf6204ce82deed75b050014dc57d7bb462d7214fcc4e59c48d66d] -// Forked from [00000000000000100000000010001011] to [00000000000000100000000010001111] at [173805:00000000000000ce80a7e057163a4db1d5ad7b20fb6f598c9597b9665c8fb0d4] -// Forked from [00000000000000100000000010001111] to [00000000000000100000000010010111] at [227931:000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8] -// Forked from [00000000000000100000000010010111] to [00000000000000100000000010110111] at [363725:00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931] -// Forked from [00000000000000100000000010110111] to [00000000000000100000000011110111] at [388381:000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0] -// Forked from [00000000000000100000000011110111] to [00000000000000100000011111110111] at [419328:000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5] -// Forked from [00000000000000100000011111110111] to [00000000000000100011111111110111] at [481824:0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893] - -// Minimum block versions (bip90 disabled). -// Minimum block version [1] changed to [2] at [227931:000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8] -// Minimum block version [2] changed to [3] at [363725:00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931] -// Minimum block version [3] changed to [4] at [388381:000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0] diff --git a/src/chasers/chaser_snapshot.cpp b/src/chasers/chaser_snapshot.cpp index 04df0e3f..bff0c955 100644 --- a/src/chasers/chaser_snapshot.cpp +++ b/src/chasers/chaser_snapshot.cpp @@ -85,7 +85,6 @@ bool chaser_snapshot::handle_chase(const code&, event_value value) NOEXCEPT switch (to_chase(value)) { // blocks first and headers first (checked) messages - ////case chase::blocks: ////case chase::checked: ////{ //// if (!enabled_bytes_ || ec) diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 06786311..7602b4a2 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -54,9 +54,6 @@ chaser_validate::chaser_validate(full_node& node) NOEXCEPT code chaser_validate::start() NOEXCEPT { - if (!node_settings().headers_first) - return error::success; - if (node_settings().batch_signatures_enabled() && !batch_enabled_) LOGN("Signature batching disabled (" << (system::batched::compiled() ? "no device" : "not compiled") diff --git a/src/error.cpp b/src/error.cpp index 408318a1..67eae43d 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -48,6 +48,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { orphan_header, "orphan header" }, { duplicate_block, "duplicate block" }, { duplicate_header, "duplicate header" }, + { unexpected_header, "unexpected header" }, // transaction pool { pooling_disabled, "pooling disabled" }, diff --git a/src/full_node.cpp b/src/full_node.cpp index e031e657..dab8764a 100644 --- a/src/full_node.cpp +++ b/src/full_node.cpp @@ -41,7 +41,6 @@ full_node::full_node(query& query, const configuration& configuration, config_(configuration), start_time_(zulu_time()), query_(query), - chaser_block_(*this), chaser_header_(*this), chaser_check_(*this), chaser_validate_(*this), @@ -78,9 +77,7 @@ void full_node::do_start(const result_handler& handler) NOEXCEPT BC_ASSERT(stranded()); code ec{}; - if (((ec = (config_.node.headers_first ? - chaser_header_.start() : - chaser_block_.start()))) || + if (((ec = chaser_header_.start())) || ((ec = chaser_check_.start())) || ((ec = chaser_validate_.start())) || ((ec = chaser_confirm_.start())) || @@ -128,7 +125,6 @@ void full_node::close() NOEXCEPT // Block on chaser stop (including dedicated threadpool joins). chaser_header_.stop(); - chaser_block_.stop(); chaser_check_.stop(); chaser_validate_.stop(); chaser_confirm_.stop(); @@ -146,7 +142,6 @@ void full_node::do_close() NOEXCEPT // Initiate chaser stopping (including dedicated threadpools). chaser_header_.stopping(network::error::service_stopped); - chaser_block_.stopping(network::error::service_stopped); chaser_check_.stopping(network::error::service_stopped); chaser_validate_.stopping(network::error::service_stopped); chaser_confirm_.stopping(network::error::service_stopped); @@ -169,19 +164,21 @@ void full_node::organize(const system::chain::header::cptr& header, chaser_header_.organize(header, std::move(handler)); } -void full_node::organize(const system::chain::block::cptr& block, - organize_handler&& handler) NOEXCEPT +void full_node::organize(const system::chain::header::cptr& header, + bool milestone, organize_handler&& handler) NOEXCEPT { - chaser_block_.organize(block, std::move(handler)); + chaser_header_.organize(header, milestone, std::move(handler)); } void full_node::prioritize(const system::hash_digest& hash, organize_handler&& handler) NOEXCEPT { - if (config_.node.headers_first) - chaser_header_.prioritize(hash, std::move(handler)); - else - chaser_block_.prioritize(hash, std::move(handler)); + chaser_header_.prioritize(hash, std::move(handler)); +} + +void full_node::get_minimum_work(work_handler&& handler) NOEXCEPT +{ + chaser_header_.get_minimum_work(std::move(handler)); } void full_node::submit(const system::chain::transactions_cptr& txs, bool test, diff --git a/src/protocols/protocol.cpp b/src/protocols/protocol.cpp index 5b2e64ae..14ac3160 100644 --- a/src/protocols/protocol.cpp +++ b/src/protocols/protocol.cpp @@ -66,6 +66,11 @@ bool protocol::is_current_chain(bool confirmed) const NOEXCEPT return session_->is_current_chain(confirmed); } +bool protocol::is_current_time(uint32_t timestamp) const NOEXCEPT +{ + return session_->is_current_time(timestamp); +} + uint64_t protocol::minimum_fee_rate() const NOEXCEPT { return is_current_chain(true) ? node_settings().minimum_fee_rate_() : @@ -139,10 +144,10 @@ void protocol::organize(const system::chain::header::cptr& header, session_->organize(header, std::move(handler)); } -void protocol::organize(const system::chain::block::cptr& block, - organize_handler&& handler) NOEXCEPT +void protocol::organize(const system::chain::header::cptr& header, + bool milestone, organize_handler&& handler) NOEXCEPT { - session_->organize(block, std::move(handler)); + session_->organize(header, milestone, std::move(handler)); } void protocol::prioritize(const system::hash_digest& hash, @@ -151,6 +156,11 @@ void protocol::prioritize(const system::hash_digest& hash, session_->prioritize(hash, std::move(handler)); } +void protocol::get_minimum_work(work_handler&& handler) NOEXCEPT +{ + session_->get_minimum_work(std::move(handler)); +} + void protocol::submit(const system::chain::transactions_cptr& txs, bool test, submit_handler&& handler) NOEXCEPT { diff --git a/src/protocols/protocol_block_in_106.cpp b/src/protocols/protocol_block_in_106.cpp deleted file mode 100644 index e1eeec6d..00000000 --- a/src/protocols/protocol_block_in_106.cpp +++ /dev/null @@ -1,296 +0,0 @@ -/** - * 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 - -// The block protocol is partially obsoleted by the headers protocol. -// Both block and header protocols conflate iterative requests and unsolicited -// announcements, which introduces several ambiguities. Furthermore inventory -// messages can contain a mix of types, further increasing complexity. Unlike -// header protocol, block protocol cannot leave announcement disabled until -// current and in both cases nodes announce to peers that are not current. - -namespace libbitcoin { -namespace node { - -#define CLASS protocol_block_in_106 - -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(NO_THROW_IN_NOEXCEPT) -BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) -BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) - -// start/stop -// ---------------------------------------------------------------------------- - -void protocol_block_in_106::start() NOEXCEPT -{ - BC_ASSERT(stranded()); - - if (started()) - return; - - SUBSCRIBE_CHANNEL(block, handle_receive_block, _1, _2); - SUBSCRIBE_CHANNEL(inventory, handle_receive_inventory, _1, _2); - SEND(create_get_inventory(), handle_send, _1); - protocol_peer::start(); -} - -// accept inventory -// ---------------------------------------------------------------------------- - -// Receive inventory and send get_data for all blocks that are not found. -bool protocol_block_in_106::handle_receive_inventory(const code& ec, - const inventory::cptr& message) NOEXCEPT -{ - BC_ASSERT(stranded()); - - if (stopped(ec)) - return false; - - // Ignore non-block inventory. - // bip144: get_data uses witness type_id but inv does not. - const auto block_count = message->count(type_id::block); - if (is_zero(block_count)) - return true; - - // Work on only one block inventory at a time. - if (!tracker_.ids.empty()) - { - LOGP("Unrequested (" << block_count << ") block inventory from [" - << opposite() << "] with (" << tracker_.ids.size() - << ") pending."); - return true; - } - - const auto getter = create_get_data(*message); - LOGP("Received (" << block_count << ") block inventory from [" - << opposite() << "] with (" << getter.items.size() - << ") new blocks."); - - // If getter is empty it may be because we have them all. - if (getter.items.empty()) - { - // The inventory response to get_blocks is limited to max_get_blocks. - if (block_count == max_get_blocks) - { - 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("Completed block inventory from [" << opposite() << "]."); - return true; - } - - LOGP("Requested (" << getter.items.size() << ") blocks from [" - << opposite() << "]."); - - // Track inventory and request blocks (to_hashes order is reversed). - tracker_.announced = block_count; - tracker_.last = getter.items.back().hash; - tracker_.ids = to_hashes(block_count, getter); - SEND(getter, handle_send, _1); - return true; -} - -// accept block -// ---------------------------------------------------------------------------- - -// protected -bool protocol_block_in_106::erase_requested( - const hash_digest& hash) NOEXCEPT -{ - BC_ASSERT(stranded()); - - return !is_zero(tracker_.ids.erase(hash)); -} - -// Process block responses in order as dictated by tracker. -bool protocol_block_in_106::handle_receive_block(const code& ec, - const block::cptr& ) NOEXCEPT -{ - BC_ASSERT(stranded()); - - if (stopped(ec)) - return false; - - // TODO: add `chain::block get_block() const` method to message. - ////const auto& block_ptr = message->block_ptr; - //// - ////// Unrequested block, may not have been announced via inventory. - ////if (tracker_.ids.find(block_ptr->get_hash()) == tracker_.ids.end()) - ////{ - //// LOGP("Unrequested block [" << encode_hash(block_ptr->get_hash()) - //// << "] from [" << opposite() << "]."); - //// return true; - ////} - //// - ////// Inventory backlog is limited to 500 per channel. - ////organize(block_ptr, BIND(handle_organize, _1, _2, block_ptr)); - return true; -} - -// return to strand to guard tracker_. -void protocol_block_in_106::handle_organize(const code& ec, size_t height, - const chain::block::cptr& block_ptr) NOEXCEPT -{ - POST(do_handle_organize, ec, height, block_ptr); -} - -void protocol_block_in_106::do_handle_organize(const code& ec, size_t height, - const chain::block::cptr& block_ptr) NOEXCEPT -{ - BC_ASSERT(stranded()); - - // Chaser may be stopped before protocol. - if (stopped() || ec == network::error::service_stopped) - return; - - // Order is enforced by organize. - tracker_.ids.erase(block_ptr->get_hash()); - - // Must erase duplicates, handled above. - if (ec == error::duplicate_block) - return; - - // Assuming no store failure this is an orphan or consensus failure. - if (ec) - { - if (is_zero(height)) - { - // Many peers blindly broadcast blocks even at/above v31800, ugh. - // If we are not caught up on headers this is useless information. - LOGP("Block [" << encode_hash(block_ptr->get_hash()) << "] from [" - << opposite() << "] " << ec.message()); - } - else - { - LOGR("Block [" << encode_hash(block_ptr->hash()) << ":" << height - << "] from [" << opposite() << "] " << ec.message()); - } - - stop(ec); - return; - } - - LOGP("Block [" << encode_hash(block_ptr->get_hash()) << ":" << height - << "] from [" << opposite() << "]."); - - // Completion of tracked inventory. - if (tracker_.ids.empty()) - { - // Protocol presumes max_get_blocks unless complete. - if (tracker_.announced == max_get_blocks) - { - 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("Completed blocks from [" << opposite() << "] with (" - << tracker_.announced << ") announced."); - } - } -} - -// utilities -// ---------------------------------------------------------------------------- - -get_blocks protocol_block_in_106::create_get_inventory() const NOEXCEPT -{ - // Block-first sync is from the archived (strong) candidate chain. - // All strong block branches are archived, so this will reflect latest. - // 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(); - const auto index = get_blocks::heights(query.get_top_candidate()); - return create_get_inventory(query.get_candidate_hashes(index)); -} - -get_blocks protocol_block_in_106::create_get_inventory( - const hash_digest& last) const NOEXCEPT -{ - return create_get_inventory(hashes{ last }); -} - -get_blocks protocol_block_in_106::create_get_inventory( - hashes&& hashes) const NOEXCEPT -{ - if (hashes.empty()) - return {}; - - if (hashes.size() == one) - { - LOGP("Request block inventory after [" << encode_hash(hashes.front()) - << "] from [" << opposite() << "]."); - } - else - { - LOGP("Request block inventory (" << hashes.size() << ") after [" - << encode_hash(hashes.front()) << "] from [" - << opposite() << "]."); - } - - return { std::move(hashes) }; -} - -// This will prevent most duplicate block requests despite each channel -// synchronizing its own inventory branch from startup to complete. -get_data protocol_block_in_106::create_get_data( - const inventory& message) const NOEXCEPT -{ - // bip144: get_data uses witness type_id but inv does not. - - get_data getter{}; - getter.items.reserve(message.count(type_id::block)); - for (const auto& item: message.view(type_id::block)) - if (!archive().is_block(item.hash)) - getter.items.emplace_back(block_type_, item.hash); - - getter.items.shrink_to_fit(); - return getter; -} - -// static -protocol_block_in_106::hashmap protocol_block_in_106::to_hashes(size_t reserve, - const get_data& getter) NOEXCEPT -{ - hashmap out{}; - out.reserve(reserve); - for (const auto& item: getter.view(type_id::block)) - out.insert(item.hash); - - return out; -} - -BC_POP_WARNING() -BC_POP_WARNING() -BC_POP_WARNING() - -} // namespace node -} // namespace libbitcoin diff --git a/src/protocols/protocol_block_in_31800.cpp b/src/protocols/protocol_block_in_31800.cpp index 27994db6..a4553828 100644 --- a/src/protocols/protocol_block_in_31800.cpp +++ b/src/protocols/protocol_block_in_31800.cpp @@ -24,7 +24,7 @@ namespace libbitcoin { namespace node { -// This class does NOT inherit from protocol_block_in_106. +// Headers-first block download (bip130 announcements via headers). #define CLASS protocol_block_in_31800 using namespace system; @@ -364,7 +364,7 @@ bool protocol_block_in_31800::handle_receive_block(const code& ec, return true; } -// Header is checked by organize, Check/Accept/Connect are called by validate. +// Header is checked by header_in, Check/Accept/Connect are called by validate. // While check could be called here, it's more optimal to defer to validate, as // requiring only identity here allows the use of the simplified block_view. code protocol_block_in_31800::identify(const chain::block_view& block, diff --git a/src/protocols/protocol_block_in_70001.cpp b/src/protocols/protocol_block_in_70001.cpp deleted file mode 100644 index efa8dddb..00000000 --- a/src/protocols/protocol_block_in_70001.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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_in_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) - -// Start. -// ---------------------------------------------------------------------------- - -void protocol_block_in_70001::start() NOEXCEPT -{ - BC_ASSERT(stranded()); - - if (started()) - return; - - SUBSCRIBE_CHANNEL(not_found, handle_receive_not_found, _1, _2); - protocol_block_in_106::start(); -} - -// Inbound (not_found). -// ---------------------------------------------------------------------------- - -bool protocol_block_in_70001::handle_receive_not_found(const code& ec, - const not_found::cptr& message) NOEXCEPT -{ - BC_ASSERT(stranded()); - - if (stopped(ec)) - return false; - - // bip144: get_data uses witness type_id but inv does not. - for (const auto& item: message->items) - { - if (!item.is_block_type()) - continue; - - // A reply to a request that was not made is not accepted. - if (!erase_requested(item.hash)) - { - LOGR("Unrequested block not_found [" << encode_hash(item.hash) - << "] from [" << opposite() << "]."); - stop(network::error::protocol_violation); - return false; - } - } - - return true; -} - -BC_POP_WARNING() -BC_POP_WARNING() - -} // namespace node -} // namespace libbitcoin diff --git a/src/protocols/protocol_header_in_31800.cpp b/src/protocols/protocol_header_in_31800.cpp index 81927c01..d58bab12 100644 --- a/src/protocols/protocol_header_in_31800.cpp +++ b/src/protocols/protocol_header_in_31800.cpp @@ -18,6 +18,7 @@ */ #include +#include #include namespace libbitcoin { @@ -34,6 +35,9 @@ using namespace std::placeholders; BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) +// Sample budget when header rows are not configured (expected is zero). +constexpr size_t default_samples = 1024; + // Start. // ---------------------------------------------------------------------------- @@ -45,10 +49,43 @@ void protocol_header_in_31800::start() NOEXCEPT return; SUBSCRIBE_CHANNEL(headers, handle_receive_headers, _1, _2); - SEND(create_get_headers(), handle_send, _1); + get_minimum_work(BIND(handle_minimum_work, _1, _2, true)); protocol_peer::start(); } +// not stranded +void protocol_header_in_31800::handle_minimum_work(const code& ec, + const uint256_t& work, bool initial) NOEXCEPT +{ + // Chaser may be stopped before protocol. + if (stopped() || ec == network::error::service_stopped) + return; + + POST(do_minimum_work, ec, work, initial); +} + +// A proven branch must reach this work, sync begins once it is first set. +void protocol_header_in_31800::do_minimum_work(const code& ec, + const uint256_t& work, bool initial) NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (stopped()) + return; + + if (ec) + { + stop(ec); + return; + } + + minimum_work_ = work; + if (initial) + { + SEND(create_get_headers(), handle_send, _1); + } +} + // Inbound (headers). // ---------------------------------------------------------------------------- @@ -62,46 +99,258 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, if (stopped(ec)) return false; - LOGP("Headers (" << message->header_ptrs.size() << ") from [" - << opposite() << "]."); + const auto& ptrs = message->header_ptrs; + LOGP("Headers (" << ptrs.size() << ") from [" << opposite() << "]."); + + if (subscribed) + for (const auto& ptr: ptrs) + set_announced(ptr->get_hash()); + + // Protocol presumes max_get_headers unless complete. + // Completeness assumes empty response from peer if caught up at 2000. + const auto full = (ptrs.size() == max_get_headers); + if (archiving_) + collect(*message, full); + else + synchronize(*message, full); + + return true; +} - // Store each header, drop channel if invalid. - for (const auto& ptr: message->header_ptrs) +// Validate and discard each header, sampling hashes, until proven. +void protocol_header_in_31800::synchronize(const headers& message, + bool full) NOEXCEPT +{ + BC_ASSERT(stranded()); + const auto& ptrs = message.header_ptrs; + if (ptrs.empty()) { - if (stopped()) - return false; + finish(); + return; + } - if (subscribed) - set_announced(ptr->get_hash()); + // A message not extending the branch restarts from its stored parent. + // An unstored parent (announcement) is requested from the candidate. + const auto& first = ptrs.front()->previous_block_hash(); + if ((!state_ || first != state_->hash()) && !restart(first)) + { + LOGP("Header [" << encode_hash(ptrs.front()->get_hash()) << "] from [" + << opposite() << "] " << code{ error::orphan_header }.message()); + SEND(create_get_headers(), handle_send, _1); + return; + } - // A job backlog will occur when organize is slower than download. - // This is not likely with headers-first even for high channel count. - organize(ptr, BIND(handle_organize, _1, _2, ptr)); + const auto& settings = system_settings(); + for (const auto& ptr: ptrs) + { + const auto& header = *ptr; + const auto& hash = header.get_hash(); + if (header.previous_block_hash() != state_->hash()) + { + const code ec{ error::orphan_header }; + LOGR("Header [" << encode_hash(hash) << "] from [" << opposite() + << "] " << ec.message()); + stop(ec); + return; + } + + const auto state = to_shared(*state_, header, settings); + if (const auto ec = chaser_header::validate(header, *state, settings)) + { + LOGR("Header [" << encode_hash(hash) << ":" << state->height() + << "] from [" << opposite() << "] " << ec.message()); + stop(ec); + return; + } + + state_ = state; + const auto height = state->height(); + if (is_zero(height % interval_)) + sample(hash); + + if (settings.milestone.equals(hash, height)) + milestone_ = height; + + // A checkpoint proves the branch to itself, the rest is resynced. + if (chain::checkpoint::is_at(settings.checkpoints, height)) + { + prove(); + return; + } } - // The headers response to get_headers is limited to max_get_headers. - if (message->header_ptrs.size() == max_get_headers) + if (full) { - const auto& last = message->header_ptrs.back()->get_hash(); - SEND(create_get_headers(last), handle_send, _1); + SEND(create_get_headers(state_->hash()), handle_send, _1); + return; } - else + + finish(); +} + +// Verify each header against the sampled hashes and organize as proven. +void protocol_header_in_31800::collect(const headers& message, + bool full) NOEXCEPT +{ + BC_ASSERT(stranded()); + + for (const auto& ptr: message.header_ptrs) { - // Protocol presumes max_get_headers unless complete. - // Completeness assumes empty response from peer if caught up at 2000. - LOGP("Completed headers from [" << opposite() << "]."); - complete(); + const auto& header = *ptr; + if (header.previous_block_hash() != previous_) + { + const code ec{ error::orphan_header }; + LOGR("Header [" << encode_hash(header.get_hash()) << "] from [" + << opposite() << "] " << ec.message()); + stop(ec); + return; + } + + previous_ = header.get_hash(); + buffer_.push_back(ptr); + ++height_; + + // Buffer until the next sample (at each interval and the top). + if (!is_zero(height_ % interval_) && height_ != top_) + continue; + + if (index_ >= samples_.size() || previous_ != samples_.at(index_)) + { + const code ec{ error::unexpected_header }; + LOGR("Header [" << encode_hash(previous_) << ":" << height_ + << "] from [" << opposite() << "] " << ec.message()); + stop(ec); + return; + } + + // Organize verified segment, milestone at/under milestone. + ++index_; + auto height = height_ - buffer_.size(); + for (const auto& proven: buffer_) + { + const auto milestone = (++height <= milestone_); + organize(proven, milestone, BIND(handle_organize, _1, _2, proven)); + } + + buffer_.clear(); + if (height_ == top_) + { + LOGP("Archived headers to [" << encode_hash(previous_) << ":" + << height_ << "] from [" << opposite() << "]."); + + // Resume synchronization above the proven top (state_ retained), + // which is independent of when the organizer archives it. + samples_.clear(); + milestone_ = zero; + archiving_ = false; + interval_ = max_get_headers; + SEND(create_get_headers(previous_), handle_send, _1); + return; + } + } + + if (full) + { + SEND(create_get_headers(previous_), handle_send, _1); + return; } + // The peer no longer presents the branch (reorganized). + LOGP("Discarded headers from [" << opposite() << "]."); + + archiving_ = false; + state_.reset(); + buffer_.clear(); + complete(); +} + +// Start a branch from a stored parent (locator hit or announcement). +bool protocol_header_in_31800::restart(const hash_digest& previous) NOEXCEPT +{ + BC_ASSERT(stranded()); + state_ = archive().get_confirmed_chain_state(system_settings(), previous); + if (!state_) + return false; + + samples_.clear(); + milestone_ = zero; + previous_ = previous; + height_ = state_->height(); + interval_ = max_get_headers; + + // Refresh the proof threshold as the candidate advances. + get_minimum_work(BIND(handle_minimum_work, _1, _2, false)); return true; } +// At the sample budget the interval doubles, retaining its multiples. +void protocol_header_in_31800::sample(const hash_digest& hash) NOEXCEPT +{ + BC_ASSERT(stranded()); + samples_.push_back(hash); + + // Budget retains the expected header rows at the base interval. + const auto expected = database_settings().header.expected; + const auto budget = is_zero(expected) ? default_samples : + ceilinged_divide(expected, max_get_headers); + + if (samples_.size() < budget) + return; + + hashes retained{}; + retained.reserve(to_half(samples_.size())); + const auto first = add1(height_ / interval_); + + // Sample heights are multiples of interval_ above height_ (branch start). + for (size_t index{}; index < samples_.size(); ++index) + if (is_zero((first + index) % two)) + retained.push_back(samples_.at(index)); + + samples_ = std::move(retained); + interval_ *= two; +} + +// The branch is proven, request it again from its parent for archival. +void protocol_header_in_31800::prove() NOEXCEPT +{ + BC_ASSERT(stranded()); + top_ = state_->height(); + if (!is_zero(top_ % interval_)) + samples_.push_back(state_->hash()); + + LOGP("Proven headers to [" << encode_hash(state_->hash()) << ":" << top_ + << "] from [" << opposite() << "]."); + + index_ = zero; + buffer_.clear(); + archiving_ = true; + SEND(create_get_headers(previous_), handle_send, _1); +} + +// The peer is exhausted, the branch is proven if current at minimum work. +void protocol_header_in_31800::finish() NOEXCEPT +{ + BC_ASSERT(stranded()); + if (state_ && (state_->height() > height_) && + is_current_time(state_->timestamp()) && + (state_->cumulative_work() >= minimum_work_)) + { + prove(); + return; + } + + LOGP("Completed headers from [" << opposite() << "]."); + state_.reset(); + complete(); +} + // not stranded void protocol_header_in_31800::handle_organize(const code& ec, size_t height, const chain::header::cptr& LOG_ONLY(header_ptr)) NOEXCEPT { // Chaser may be stopped before protocol. - if (stopped() || ec == network::error::service_stopped || + if (stopped() || + ec == network::error::service_stopped || ec == error::duplicate_header) return; @@ -110,8 +359,8 @@ void protocol_header_in_31800::handle_organize(const code& ec, { if (is_zero(height)) { - LOGP("Header [" << encode_hash(header_ptr->get_hash()) << "] from [" - << opposite() << "] " << ec.message()); + LOGP("Header [" << encode_hash(header_ptr->get_hash()) + << "] from [" << opposite() << "] " << ec.message()); } else { diff --git a/src/sessions/session.cpp b/src/sessions/session.cpp index 74c92c3f..3c855889 100644 --- a/src/sessions/session.cpp +++ b/src/sessions/session.cpp @@ -45,10 +45,10 @@ void session::organize(const header::cptr& header, node_.organize(header, std::move(handler)); } -void session::organize(const block::cptr& block, +void session::organize(const header::cptr& header, bool milestone, organize_handler&& handler) NOEXCEPT { - node_.organize(block, std::move(handler)); + node_.organize(header, milestone, std::move(handler)); } void session::prioritize(const hash_digest& hash, @@ -63,6 +63,11 @@ void session::submit(const transactions_cptr& txs, bool test, node_.submit(txs, test, std::move(handler)); } +void session::get_minimum_work(work_handler&& handler) NOEXCEPT +{ + node_.get_minimum_work(std::move(handler)); +} + void session::get_hashes(map_handler&& handler) NOEXCEPT { node_.get_hashes(std::move(handler)); @@ -165,6 +170,11 @@ bool session::is_current_chain(bool confirmed) const NOEXCEPT return node_.is_current_chain(confirmed); } +bool session::is_current_time(uint32_t timestamp) const NOEXCEPT +{ + return node_.is_current_time(timestamp); +} + bool session::is_recent() const NOEXCEPT { return node_.is_recent(); diff --git a/src/settings.cpp b/src/settings.cpp index 90876f76..8f996931 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -31,7 +31,6 @@ namespace node { settings::settings() NOEXCEPT : threads{ 0 }, delay_inbound{ true }, - headers_first{ true }, memory_priority{ true }, thread_priority{ true }, provide_blocks{ true }, diff --git a/test/chasers/chaser_block.cpp b/test/chasers/chaser_block.cpp deleted file mode 100644 index 971a9c07..00000000 --- a/test/chasers/chaser_block.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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 "../test.hpp" - -BOOST_AUTO_TEST_SUITE(chaser_blocks_tests) - -BOOST_AUTO_TEST_CASE(chaser_blocks_test) -{ - BOOST_REQUIRE(true); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/test/configuration.cpp b/test/configuration.cpp index 0d31658e..aa0c7ba2 100644 --- a/test/configuration.cpp +++ b/test/configuration.cpp @@ -28,7 +28,6 @@ BOOST_AUTO_TEST_CASE(configuration__construct1__none_context__expected) const node::configuration instance(chain::selection::none); // Just a sample of settings. - BOOST_REQUIRE(instance.node.headers_first); BOOST_REQUIRE_EQUAL(instance.network.threads, 0u); BOOST_REQUIRE_EQUAL(instance.bitcoin.first_version, 1u); } diff --git a/test/error.cpp b/test/error.cpp index 1cb99afb..04916225 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -175,6 +175,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__estimate_disabled__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "estimate_disabled"); } +BOOST_AUTO_TEST_CASE(error_t__code__unexpected_header__true_expected_message) +{ + constexpr auto value = error::unexpected_header; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "unexpected header"); +} + BOOST_AUTO_TEST_CASE(error_t__code__estimate_premature__true_expected_message) { constexpr auto value = error::estimate_premature; diff --git a/test/settings.cpp b/test/settings.cpp index 5d89ea17..142b970a 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -32,7 +32,6 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected) const node::settings node{}; BOOST_REQUIRE_EQUAL(node.threads, 0_u32); BOOST_REQUIRE_EQUAL(node.delay_inbound, true); - BOOST_REQUIRE_EQUAL(node.headers_first, true); BOOST_REQUIRE_EQUAL(node.memory_priority, true); BOOST_REQUIRE_EQUAL(node.thread_priority, true); BOOST_REQUIRE_EQUAL(node.provide_blocks, true);