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