From 2975cb0dfe499af766966d1a5cf46b5f2a821802 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 18 Feb 2024 13:57:51 -0500 Subject: [PATCH 1/9] Style. --- include/bitcoin/node/protocols/protocol_block_in.hpp | 2 +- src/chasers/chaser_header.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/node/protocols/protocol_block_in.hpp b/include/bitcoin/node/protocols/protocol_block_in.hpp index 838f286a..a6edce7b 100644 --- a/include/bitcoin/node/protocols/protocol_block_in.hpp +++ b/include/bitcoin/node/protocols/protocol_block_in.hpp @@ -41,7 +41,7 @@ class BCN_API protocol_block_in : node::protocol(session, channel), network::tracker(session.log), report_performance_(report_performance && - !is_zero(session.config().node.sample_period_seconds)), + to_bool(session.config().node.sample_period_seconds)), block_type_(session.config().network.witness_node() ? type_id::witness_block : type_id::block), performance_timer_(std::make_shared(session.log, diff --git a/src/chasers/chaser_header.cpp b/src/chasers/chaser_header.cpp index 2bcd9bba..15d63e20 100644 --- a/src/chasers/chaser_header.cpp +++ b/src/chasers/chaser_header.cpp @@ -37,7 +37,7 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) chaser_header::chaser_header(full_node& node) NOEXCEPT : chaser(node), currency_window_(node.node_settings().currency_window()), - use_currency_window_(currency_window_ != wall_clock::duration::zero()) + use_currency_window_(to_bool(node.node_settings().currency_window_minutes)) { } From 18480b2b58908cc05677d780ca378cad2e4636eb Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 18 Feb 2024 14:46:57 -0500 Subject: [PATCH 2/9] Comments, remove dead code. --- src/protocols/protocol_block_in.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/protocols/protocol_block_in.cpp b/src/protocols/protocol_block_in.cpp index bdb33db3..a17edb12 100644 --- a/src/protocols/protocol_block_in.cpp +++ b/src/protocols/protocol_block_in.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include #include @@ -116,12 +116,7 @@ void protocol_block_in::start() NOEXCEPT return; state_ = archive().get_confirmed_chain_state(config().bitcoin); - - if (!state_) - { - LOGF("protocol_block_in, state not initialized."); - return; - } + BC_ASSERT_MSG(state_, "Store not initialized."); if (report_performance_) { @@ -145,6 +140,15 @@ void protocol_block_in::stopping(const code& ec) NOEXCEPT // Inbound (blocks). // ---------------------------------------------------------------------------- +// Validation is limited to block.check() and block.check(ctx). +// Context is obtained from stored header state as blocks are out of order. +// Tx check could be short-circuited against the database but since the checks +// are fast, it is optimal to wait until block/tx accept to hit the store. +// So header.state is read and when contextual checks are complete, block is +// stored. The set of blocks is obtained from the check chaser, and reported +// against it. Stopping channels return the set. May require height and/or +// header.fk to be stored with block hash set. + // local inline hashes to_hashes(const get_data& getter) NOEXCEPT { From 6471d5f2e9aa0a2df9023cb4ae9293dd5c169409 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 18 Feb 2024 19:34:15 -0500 Subject: [PATCH 3/9] Refine block_in protocol validation, comments. --- src/protocols/protocol_block_in.cpp | 149 +++++++++++++++------------- 1 file changed, 79 insertions(+), 70 deletions(-) diff --git a/src/protocols/protocol_block_in.cpp b/src/protocols/protocol_block_in.cpp index a17edb12..408e2879 100644 --- a/src/protocols/protocol_block_in.cpp +++ b/src/protocols/protocol_block_in.cpp @@ -97,7 +97,7 @@ void protocol_block_in::do_handle_performance(const code& ec) NOEXCEPT // stalled_channel or slow_channel if (ec) { - LOGF("Performance error, " << ec.message()); + LOGF("Performance action, " << ec.message()); stop(ec); return; }; @@ -257,7 +257,7 @@ bool protocol_block_in::handle_receive_block(const code& ec, } } - const auto error = block.check(); + auto error = block.check(); if (error) { LOGR("Invalid block (check) [" << encode_hash(hash) @@ -270,77 +270,88 @@ bool protocol_block_in::handle_receive_block(const code& ec, BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) state_.reset(new chain::chain_state(*state_, block.header(), coin)); BC_POP_WARNING() + + const auto context = state_->context(); + error = block.check(context); + if (error) + { + LOGR("Invalid block (check(context)) [" << encode_hash(hash) + << "] from [" << authority() << "] " << error.message()); + stop(network::error::protocol_violation); + return false; + } + + // Populate prevouts only, internal to block. + block.populate(); - ////auto& query = archive(); - ////const auto context = state_->context(); - ////const auto link = query.set_link(block, context); - ////if (link.is_terminal()) + // Populate stored missing prevouts only, not input metadata. + auto& query = archive(); + if (!query.populate(block)) + { + LOGR("Invalid block (populate) [" << encode_hash(hash) + << "] from [" << authority() << "]."); + stop(network::error::protocol_violation); + return false; + } + + ////// TODO: also requires input metadata population. + ////error = block.accept(context, coin.subsidy_interval_blocks, + //// coin.initial_subsidy()); + ////if (error) ////{ - //// // Should only be from missing parent, and that's guarded above. - //// LOGF("Store block error [" << encode_hash(hash) - //// << "] from [" << authority() << "]."); - //// stop(network::error::unknown); + //// LOGR("Invalid block (accept) [" << encode_hash(hash) + //// << "] from [" << authority() << "] " << error.message()); + //// stop(network::error::protocol_violation); //// return false; ////} - //// - ////////// Block must be archived for populate. - ////////if (!query.populate(block)) - ////////{ - //////// // Invalid block is archived. - //////// LOGR("Invalid block (populate) [" << encode_hash(hash) - //////// << "] from [" << authority() << "]."); - //////// stop(network::error::protocol_violation); - //////// return false; - ////////} - //// - ////////error = block.accept(context, coin.subsidy_interval_blocks, - //////// coin.initial_subsidy()); - ////////if (error) - ////////{ - //////// // Invalid block is archived. - //////// LOGR("Invalid block (accept) [" << encode_hash(hash) - //////// << "] from [" << authority() << "] " << error.message()); - //////// stop(network::error::protocol_violation); - //////// return false; - ////////} - //// - ////////error = block.connect(context); - ////////if (error) - ////////{ - //////// // Invalid block is archived. - //////// LOGR("Invalid block (connect) [" << encode_hash(hash) - //////// << "] from [" << authority() << "] " << error.message()); - //////// stop(network::error::protocol_violation); - //////// return false; - ////////} - //// - ////////// If populate, accept, or connect fail this is bypassed and a restart will - ////////// initialize state_ at the prior block as top. But this block exists, so - ////////// it will be skipped for download. This results in the next being orphaned - ////////// following the channel stop/start or any subsequent runs on the store. - ////////// This is the job of the confirmation chaser (todo). - ////////if (!query.push_confirmed(link)) - ////////{ - //////// // Invalid block is archived. - //////// LOGF("Push confirmed error [" << encode_hash(hash) - //////// << "] from [" << authority() << "]."); - //////// stop(network::error::unknown); - //////// return false; - ////////} - //// - ////// Size will be incorrect with multiple peers or headers protocol. - ////if (is_zero(context.height % 1'000)) + + ////// TODO: also requires input metadata population. + ////error = block.confirm(context); + ////if (error) ////{ - //// ////reporter::fire(event_block, context.height); - //// ////reporter::fire(event_archive, query.archive_size()); - //// LOGN("BLOCK: " << context.height - //// << " secs: " << (unix_time() - start_) - //// << " txs: " << query.tx_records() - //// << " archive: " << query.archive_size()); + //// LOGR("Invalid block (accept) [" << encode_hash(hash) + //// << "] from [" << authority() << "] " << error.message()); + //// stop(network::error::protocol_violation); + //// return false; ////} - //// - ////LOGP("Block [" << encode_hash(message->block_ptr->hash()) << "] from [" - //// << authority() << "]."); + + // Requires only prevout population. + error = block.connect(context); + if (error) + { + LOGR("Invalid block (connect) [" << encode_hash(hash) + << "] from [" << authority() << "] " << error.message()); + stop(network::error::protocol_violation); + return false; + } + + const auto link = query.set_link(block, context); + if (link.is_terminal()) + { + LOGF("Store block error [" << encode_hash(hash) + << "] from [" << authority() << "]."); + stop(network::error::unknown); + return false; + } + + if (!query.push_candidate(link)) + { + LOGF("Push candidate error [" << encode_hash(hash) + << "] from [" << authority() << "]."); + stop(network::error::unknown); + return false; + } + + if (!query.push_confirmed(link)) + { + LOGF("Push confirmed error [" << encode_hash(hash) + << "] from [" << authority() << "]."); + stop(network::error::unknown); + return false; + } + + LOGP("Block [" << encode_hash(message->block_ptr->hash()) << "] at (" + << context.height << ") from [" << authority() << "]."); // Accumulate byte count. bytes_ += message->cached_size; @@ -366,8 +377,7 @@ bool protocol_block_in::handle_receive_block(const code& ec, } // Release subscription if exhausted. - // This will terminate block iteration if send_headers has been sent. - // Otherwise handle_receive_inventory will restart inventory iteration. + // handle_receive_inventory will restart inventory iteration. return !tracker->hashes.empty(); } @@ -375,7 +385,6 @@ bool protocol_block_in::handle_receive_block(const code& ec, // The distinction is ultimately arbitrary, but this signals initial currency. void protocol_block_in::current() NOEXCEPT { - ////reporter::fire(event_current_blocks, state_->height()); LOGN("Blocks from [" << authority() << "] complete at (" << state_->height() << ")."); } From 644ca4084edb2c1ad02a07fb2578b271fc55a853 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 18 Feb 2024 20:09:33 -0500 Subject: [PATCH 4/9] Comments. --- src/protocols/protocol_block_in.cpp | 9 ++++++++- src/protocols/protocol_header_in_31800.cpp | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/protocols/protocol_block_in.cpp b/src/protocols/protocol_block_in.cpp index 408e2879..8a9f3540 100644 --- a/src/protocols/protocol_block_in.cpp +++ b/src/protocols/protocol_block_in.cpp @@ -304,7 +304,7 @@ bool protocol_block_in::handle_receive_block(const code& ec, //// stop(network::error::protocol_violation); //// return false; ////} - + //// ////// TODO: also requires input metadata population. ////error = block.confirm(context); ////if (error) @@ -325,6 +325,11 @@ bool protocol_block_in::handle_receive_block(const code& ec, return false; } + // ------------------------------------------------------------------------ + // NOTE: this is a naive implementation intended for only one peer and + // lacking reorganization support. It provides a fair performance baseline + // for a sequential blocks-first design given a trusted peer. + // const auto link = query.set_link(block, context); if (link.is_terminal()) { @@ -349,6 +354,8 @@ bool protocol_block_in::handle_receive_block(const code& ec, stop(network::error::unknown); return false; } + // + // ------------------------------------------------------------------------ LOGP("Block [" << encode_hash(message->block_ptr->hash()) << "] at (" << context.height << ") from [" << authority() << "]."); diff --git a/src/protocols/protocol_header_in_31800.cpp b/src/protocols/protocol_header_in_31800.cpp index e3e71c3a..df256476 100644 --- a/src/protocols/protocol_header_in_31800.cpp +++ b/src/protocols/protocol_header_in_31800.cpp @@ -152,7 +152,6 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, // The distinction is ultimately arbitrary, but this signals peer completeness. void protocol_header_in_31800::complete() NOEXCEPT { - ////reporter::fire(event_current_headers, state_->height()); LOGN("Headers from [" << authority() << "] complete at (" << state_->height() << ")."); } From fb2a220124ff71536ba8813cc43fa85736bb779d Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 19 Feb 2024 19:49:26 -0500 Subject: [PATCH 5/9] Add block chaser and update block protocol for legacy sync. --- Makefile.am | 3 + builds/cmake/CMakeLists.txt | 2 + .../libbitcoin-node-test.vcxproj | 1 + .../libbitcoin-node-test.vcxproj.filters | 3 + .../libbitcoin-node/libbitcoin-node.vcxproj | 2 + .../libbitcoin-node.vcxproj.filters | 6 + include/bitcoin/node.hpp | 1 + include/bitcoin/node/chasers/chaser.hpp | 7 + include/bitcoin/node/chasers/chaser_block.hpp | 111 ++++++ .../bitcoin/node/chasers/chaser_header.hpp | 6 +- include/bitcoin/node/chasers/chasers.hpp | 1 + include/bitcoin/node/error.hpp | 1 + include/bitcoin/node/full_node.hpp | 5 + include/bitcoin/node/protocols/protocol.hpp | 4 + .../node/protocols/protocol_block_in.hpp | 6 +- .../protocols/protocol_header_in_31800.hpp | 6 +- include/bitcoin/node/sessions/attach.hpp | 28 +- include/bitcoin/node/sessions/session.hpp | 4 + src/chasers/chaser_block.cpp | 345 ++++++++++++++++++ src/chasers/chaser_header.cpp | 20 +- src/error.cpp | 1 + src/full_node.cpp | 10 +- src/protocols/protocol.cpp | 6 + src/protocols/protocol_block_in.cpp | 213 +++++------ src/protocols/protocol_header_in_31800.cpp | 55 +-- src/sessions/session.cpp | 6 + src/sessions/session_outbound.cpp | 30 +- test/chasers/chaser_block.cpp | 28 ++ test/chasers/chaser_header.cpp | 14 +- test/error.cpp | 9 + 30 files changed, 744 insertions(+), 190 deletions(-) create mode 100644 include/bitcoin/node/chasers/chaser_block.hpp create mode 100644 src/chasers/chaser_block.cpp create mode 100644 test/chasers/chaser_block.cpp diff --git a/Makefile.am b/Makefile.am index 4313a3ce..dc5c675c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -41,6 +41,7 @@ src_libbitcoin_node_la_SOURCES = \ src/parser.cpp \ src/settings.cpp \ src/chasers/chaser.cpp \ + src/chasers/chaser_block.cpp \ src/chasers/chaser_candidate.cpp \ src/chasers/chaser_check.cpp \ src/chasers/chaser_confirm.cpp \ @@ -79,6 +80,7 @@ test_libbitcoin_node_test_SOURCES = \ test/test.cpp \ test/test.hpp \ test/chasers/chaser.cpp \ + test/chasers/chaser_block.cpp \ test/chasers/chaser_candidate.cpp \ test/chasers/chaser_check.cpp \ test/chasers/chaser_confirm.cpp \ @@ -126,6 +128,7 @@ include_bitcoin_node_HEADERS = \ include_bitcoin_node_chasersdir = ${includedir}/bitcoin/node/chasers include_bitcoin_node_chasers_HEADERS = \ include/bitcoin/node/chasers/chaser.hpp \ + include/bitcoin/node/chasers/chaser_block.hpp \ include/bitcoin/node/chasers/chaser_candidate.hpp \ include/bitcoin/node/chasers/chaser_check.hpp \ include/bitcoin/node/chasers/chaser_confirm.hpp \ diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index 5ef8261f..5915549e 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -274,6 +274,7 @@ add_library( ${CANONICAL_LIB_NAME} "../../src/parser.cpp" "../../src/settings.cpp" "../../src/chasers/chaser.cpp" + "../../src/chasers/chaser_block.cpp" "../../src/chasers/chaser_candidate.cpp" "../../src/chasers/chaser_check.cpp" "../../src/chasers/chaser_confirm.cpp" @@ -348,6 +349,7 @@ if (with-tests) "../../test/test.cpp" "../../test/test.hpp" "../../test/chasers/chaser.cpp" + "../../test/chasers/chaser_block.cpp" "../../test/chasers/chaser_candidate.cpp" "../../test/chasers/chaser_check.cpp" "../../test/chasers/chaser_confirm.cpp" diff --git a/builds/msvc/vs2022/libbitcoin-node-test/libbitcoin-node-test.vcxproj b/builds/msvc/vs2022/libbitcoin-node-test/libbitcoin-node-test.vcxproj index b7680fdc..441ffa7f 100644 --- a/builds/msvc/vs2022/libbitcoin-node-test/libbitcoin-node-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-node-test/libbitcoin-node-test.vcxproj @@ -71,6 +71,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-node-test/libbitcoin-node-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-node-test/libbitcoin-node-test.vcxproj.filters index 3f1647d1..91781e25 100644 --- a/builds/msvc/vs2022/libbitcoin-node-test/libbitcoin-node-test.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-node-test/libbitcoin-node-test.vcxproj.filters @@ -24,6 +24,9 @@ src\chasers + + src\chasers + src\chasers diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj index 5a1e5bbb..5278b9ae 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj @@ -74,6 +74,7 @@ + @@ -102,6 +103,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters index 31a4637e..e24246be 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -45,6 +45,9 @@ src\chasers + + src\chasers + src\chasers @@ -125,6 +128,9 @@ include\bitcoin\node\chasers + + include\bitcoin\node\chasers + include\bitcoin\node\chasers diff --git a/include/bitcoin/node.hpp b/include/bitcoin/node.hpp index 4c8d3490..d2dbcaae 100644 --- a/include/bitcoin/node.hpp +++ b/include/bitcoin/node.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/node/chasers/chaser.hpp b/include/bitcoin/node/chasers/chaser.hpp index e9ec3ed9..8ee51834 100644 --- a/include/bitcoin/node/chasers/chaser.hpp +++ b/include/bitcoin/node/chasers/chaser.hpp @@ -43,8 +43,15 @@ class BCN_API chaser public: enum class chase { + /// A new strong branch exists (strong height_t). + /// Issued by 'block' and handled by 'confirm'. + /// The block chaser works with the blocks-first protocol. + /// Bocks first performs header/checked/connected stages. + block, + /// A new strong branch exists (strong height_t). /// Issued by 'header' and handled by 'check'. + /// The block chaser works with the header-first protocol. header, /// A block has been downloaded, checked and stored (header_t). diff --git a/include/bitcoin/node/chasers/chaser_block.hpp b/include/bitcoin/node/chasers/chaser_block.hpp new file mode 100644 index 00000000..607ee0a3 --- /dev/null +++ b/include/bitcoin/node/chasers/chaser_block.hpp @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * 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 +#include +#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 +{ +public: + chaser_block(full_node& node) NOEXCEPT; + + virtual code start() NOEXCEPT; + + /// Organize the next block in sequence, relative to caller's peer. + /// Causes a fault/stop if preceding blocks have not been stored. + /// Caller must validate the block and provide context. + virtual void organize(const system::chain::block::cptr& block, + system::chain::context&& context) NOEXCEPT; + +protected: + struct validated_block + { + database::context context; + system::chain::block::cptr item; + }; + typedef std::vector header_links; + + // This is protected by strand. + std::unordered_map tree_{}; + + /// Handlers. + virtual void handle_event(const code& ec, chase event_, + link value) NOEXCEPT; + + /// Sum of work from header to fork point (excluded). + virtual bool get_branch_work(uint256_t& work, size_t& point, + system::hashes& tree_branch, header_links& store_branch, + const system::chain::header& header) const NOEXCEPT; + + /// Strong if new branch work exceeds confirmed work. + /// Also obtains fork point for work summation termination. + /// Also obtains ordered branch identifiers for subsequent reorg. + virtual bool get_is_strong(bool& strong, const uint256_t& work, + size_t point) const NOEXCEPT; + + /// Header timestamp is within configured span from current time. + virtual bool is_current(const system::chain::header& header, + size_t height) const NOEXCEPT; + + /// Save block to tree with validation context. + virtual void save(const system::chain::block::cptr& block, + const system::chain::context& context) NOEXCEPT; + + /// Store block to database and push to top of candidate chain. + virtual database::header_link push( + const system::chain::block::cptr& block, + const system::chain::context& context) const NOEXCEPT; + + /// Move tree header to database and push to top of candidate chain. + virtual bool push(const system::hash_digest& key) NOEXCEPT; + + /// Properties. + /// Given non-current blocks cached in memory, should always be zero/false. + virtual const network::wall_clock::duration& currency_window() const NOEXCEPT; + virtual bool use_currency_window() const NOEXCEPT; + +private: + void do_handle_event(const code& ec, chase event_, link value) NOEXCEPT; + void do_organize(const system::chain::block::cptr& block, + const system::chain::context& context) NOEXCEPT; + + // These are thread safe. + const system::chain::checkpoints& checkpoints_; + const network::wall_clock::duration currency_window_; + const bool use_currency_window_; +}; + +} // namespace node +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/node/chasers/chaser_header.hpp b/include/bitcoin/node/chasers/chaser_header.hpp index a6924f39..95aa1c81 100644 --- a/include/bitcoin/node/chasers/chaser_header.hpp +++ b/include/bitcoin/node/chasers/chaser_header.hpp @@ -51,7 +51,7 @@ class BCN_API chaser_header struct proposed_header { database::context context; - system::chain::header::cptr header; + system::chain::header::cptr item; }; typedef std::vector header_links; @@ -74,7 +74,8 @@ class BCN_API chaser_header size_t point) const NOEXCEPT; /// Header timestamp is within configured span from current time. - virtual bool is_current(const system::chain::header& header) const NOEXCEPT; + virtual bool is_current(const system::chain::header& header, + size_t height) const NOEXCEPT; /// Save header to tree with validation context. virtual void save(const system::chain::header::cptr& header, @@ -98,6 +99,7 @@ class BCN_API chaser_header const system::chain::context& context) NOEXCEPT; // These are thread safe. + const system::chain::checkpoints& checkpoints_; const network::wall_clock::duration currency_window_; const bool use_currency_window_; }; diff --git a/include/bitcoin/node/chasers/chasers.hpp b/include/bitcoin/node/chasers/chasers.hpp index 4fa00223..a511e05e 100644 --- a/include/bitcoin/node/chasers/chasers.hpp +++ b/include/bitcoin/node/chasers/chasers.hpp @@ -20,6 +20,7 @@ #define LIBBITCOIN_NODE_CHASERS_CHASERS_HPP #include +#include #include #include #include diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index 201e001c..5bea182c 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -49,6 +49,7 @@ enum error_t : uint8_t stalled_channel, // blockchain + orphan_block, orphan_header, insufficient_work, duplicate_block diff --git a/include/bitcoin/node/full_node.hpp b/include/bitcoin/node/full_node.hpp index 224981d4..1b1341e3 100644 --- a/include/bitcoin/node/full_node.hpp +++ b/include/bitcoin/node/full_node.hpp @@ -65,6 +65,10 @@ class BCN_API full_node virtual void organize(const system::chain::header::cptr& header, system::chain::context&& context) NOEXCEPT; + /// Organize a validated block, failures stop the node. + virtual void organize(const system::chain::block::cptr& block, + system::chain::context&& context) NOEXCEPT; + /// Properties. /// ----------------------------------------------------------------------- @@ -100,6 +104,7 @@ class BCN_API full_node // These are protected by strand. chaser::event_subscriber event_subscriber_; + chaser_block chaser_block_; chaser_header chaser_header_; chaser_check chaser_check_; chaser_connect chaser_connect_; diff --git a/include/bitcoin/node/protocols/protocol.hpp b/include/bitcoin/node/protocols/protocol.hpp index 03daf854..9bf8dbc9 100644 --- a/include/bitcoin/node/protocols/protocol.hpp +++ b/include/bitcoin/node/protocols/protocol.hpp @@ -57,6 +57,10 @@ class BCN_API protocol virtual void organize(const system::chain::header::cptr& header, system::chain::context&& context) NOEXCEPT; + /// Organize a validated block, failures stop the node. + virtual void organize(const system::chain::block::cptr& block, + system::chain::context&& context) NOEXCEPT; + /// Configuration settings for all libraries. const configuration& config() const NOEXCEPT; diff --git a/include/bitcoin/node/protocols/protocol_block_in.hpp b/include/bitcoin/node/protocols/protocol_block_in.hpp index a6edce7b..22199629 100644 --- a/include/bitcoin/node/protocols/protocol_block_in.hpp +++ b/include/bitcoin/node/protocols/protocol_block_in.hpp @@ -79,10 +79,12 @@ class BCN_API protocol_block_in /// Handle result of performance reporting. virtual void handle_performance(const code& ec) NOEXCEPT; - /// Invoked when initial blocks sync is current. - virtual void current() NOEXCEPT; + /// Invoked when initial blocks sync is complete. + virtual void complete() NOEXCEPT; private: + static system::hashes to_hashes(const get_data& getter) NOEXCEPT; + network::messages::get_blocks create_get_inventory() const NOEXCEPT; network::messages::get_blocks create_get_inventory( const system::hash_digest& last) const NOEXCEPT; diff --git a/include/bitcoin/node/protocols/protocol_header_in_31800.hpp b/include/bitcoin/node/protocols/protocol_header_in_31800.hpp index ad5dcc33..54aa1bfe 100644 --- a/include/bitcoin/node/protocols/protocol_header_in_31800.hpp +++ b/include/bitcoin/node/protocols/protocol_header_in_31800.hpp @@ -45,7 +45,7 @@ class BCN_API protocol_header_in_31800 void start() NOEXCEPT override; protected: - /// Invoked when initial headers sync is current. + /// Invoked when initial headers sync is complete. virtual void complete() NOEXCEPT; /// Recieved incoming headers message. @@ -55,7 +55,9 @@ class BCN_API protocol_header_in_31800 private: network::messages::get_headers create_get_headers() NOEXCEPT; network::messages::get_headers create_get_headers( - system::hashes&& start_hashes) NOEXCEPT; + const system::hash_digest& last) const NOEXCEPT; + network::messages::get_headers create_get_headers( + system::hashes&& start_hashes) const NOEXCEPT; // Protected by strand. system::chain::chain_state::ptr state_{}; diff --git a/include/bitcoin/node/sessions/attach.hpp b/include/bitcoin/node/sessions/attach.hpp index 709e081f..78b83363 100644 --- a/include/bitcoin/node/sessions/attach.hpp +++ b/include/bitcoin/node/sessions/attach.hpp @@ -61,21 +61,21 @@ class attach Session::attach_protocols(channel); auto& self = *this; - const auto version = channel->negotiated_version(); - - if (version >= network::messages::level::bip130) - { - channel->attach(self)->start(); - channel->attach(self)->start(); - } - else if (version >= network::messages::level::headers_protocol) - { - channel->attach(self)->start(); - channel->attach(self)->start(); - } + ////const auto version = channel->negotiated_version(); + //// + ////if (version >= network::messages::level::bip130) + ////{ + //// channel->attach(self)->start(); + //// channel->attach(self)->start(); + ////} + ////else if (version >= network::messages::level::headers_protocol) + ////{ + //// channel->attach(self)->start(); + //// channel->attach(self)->start(); + ////} - ////constexpr auto performance = false; - ////channel->attach(self, performance)->start(); + constexpr auto performance = false; + channel->attach(self, performance)->start(); ////channel->attach(self)->start(); ////channel->attach(self)->start(); ////channel->attach(self)->start(); diff --git a/include/bitcoin/node/sessions/session.hpp b/include/bitcoin/node/sessions/session.hpp index ff3fffc2..36839c21 100644 --- a/include/bitcoin/node/sessions/session.hpp +++ b/include/bitcoin/node/sessions/session.hpp @@ -40,6 +40,10 @@ class BCN_API session virtual void organize(const system::chain::header::cptr& header, system::chain::context&& context) NOEXCEPT; + /// Organize a validated block, failures stop the node. + virtual void organize(const system::chain::block::cptr& block, + system::chain::context&& context) NOEXCEPT; + /// Configuration settings for all libraries. const configuration& config() const NOEXCEPT; diff --git a/src/chasers/chaser_block.cpp b/src/chasers/chaser_block.cpp new file mode 100644 index 00000000..3fddc13f --- /dev/null +++ b/src/chasers/chaser_block.cpp @@ -0,0 +1,345 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * 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 +#include +#include +#include + +namespace libbitcoin { +namespace node { + +using namespace network; +using namespace system; +using namespace std::placeholders; + +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +chaser_block::chaser_block(full_node& node) NOEXCEPT + : chaser(node), + checkpoints_(node.config().bitcoin.checkpoints), + currency_window_(node.node_settings().currency_window()), + use_currency_window_(to_bool(node.node_settings().currency_window_minutes)) +{ +} + +// protected +const network::wall_clock::duration& +chaser_block::currency_window() const NOEXCEPT +{ + return currency_window_; +} + +// protected +bool chaser_block::use_currency_window() const NOEXCEPT +{ + return use_currency_window_; +} + +// protected +code chaser_block::start() NOEXCEPT +{ + BC_ASSERT_MSG(node_stranded(), "chaser_block"); + return subscribe(std::bind(&chaser_block::handle_event, + this, _1, _2, _3)); +} + +// protected +void chaser_block::handle_event(const code& ec, chase event_, + link value) NOEXCEPT +{ + boost::asio::post(strand(), + std::bind(&chaser_block::do_handle_event, + this, ec, event_, value)); +} + +// private +void chaser_block::do_handle_event(const code&, chase, link) NOEXCEPT +{ + BC_ASSERT_MSG(stranded(), "chaser_block"); +} + +void chaser_block::organize(const chain::block::cptr& block, + chain::context&& context) NOEXCEPT +{ + boost::asio::post(strand(), + std::bind(&chaser_block::do_organize, + this, block, std::move(context))); +} + +// private +void chaser_block::do_organize(const chain::block::cptr& block, + const chain::context& context) NOEXCEPT +{ + BC_ASSERT_MSG(stranded(), "chaser_block"); + + // Determine if work should be computed. + // ------------------------------------------------------------------------ + // This presumes a header is never archived independent of its valid block. + // This presumes the block is valid (check/accept/connect) and therefore + // all of its prevouts exist, but it is not yet confirmed. + + auto& query = archive(); + const auto hash = block->header().hash(); + if (tree_.contains(hash) || query.is_block(hash)) + { + // Block (header and txs) already exists. + return; + } + + auto& previous = block->header().previous_block_hash(); + if (!tree_.contains(previous) && !query.is_block(previous)) + { + // Peer processing should have precluded orphan submission. + stop(error::orphan_block); + return; + } + + if (!is_current(block->header(), context.height)) + { + // Block is new top of stale branch (strength not computed). + save(block, context); + return; + } + + // Compute relative work. + // ------------------------------------------------------------------------ + + size_t point{}; + uint256_t work{}; + hashes tree_branch{}; + header_links store_branch{}; + if (!get_branch_work(work, point, tree_branch, store_branch, block->header())) + { + stop(error::store_integrity); + return; + } + + bool strong{}; + if (!get_is_strong(strong, work, point)) + { + stop(error::store_integrity); + return; + } + + // If a long candidate chain is first created using headers-first and then + // blocks-first is executed (after a restart/config) it can result in up to + // the entire blockchain being cached into memory before becoming strong, + // which means stronger than the candidate chain. While switching config + // between modes by varying network protocol is supported, blocks-first is + // inherently inefficient and weak on this aspect of DoS protection. This + // is acceptable for its purpose and consistent with early implementations. + if (!strong) + { + // Block is new top of current weak branch. + save(block, context); + return; + } + + // Reorganize candidate chain. + // ------------------------------------------------------------------------ + + // Obtain the top height. + auto top = query.get_top_candidate(); + if (top < point) + { + stop(error::store_integrity); + return; + } + + // Pop down to the branch point, underflow guarded above. + while (top-- > point) + { + if (!query.pop_candidate()) + { + stop(error::store_integrity); + return; + } + } + + // Push stored strong block headers to candidate chain. + for (const auto& link: views_reverse(store_branch)) + { + if (!query.push_candidate(link)) + { + stop(error::store_integrity); + return; + } + } + + // Store strong tree blocks and push headers to candidate chain. + for (const auto& key: views_reverse(tree_branch)) + { + if (!push(key)) + { + stop(error::store_integrity); + return; + } + } + + // Push new block as top of candidate chain. + const auto link = push(block, context); + if (link.is_terminal()) + { + stop(error::store_integrity); + return; + } + + // Notify candidate reorganization with branch point. + // ------------------------------------------------------------------------ + + notify(error::success, chase::block, + { possible_narrow_cast(point) }); +} + +// protected +bool chaser_block::is_current(const chain::header& header, + size_t height) const NOEXCEPT +{ + if (!use_currency_window()) + return true; + + // Checkpoints are already validated. Current if at a checkpoint height. + if (chain::checkpoint::is_at(checkpoints_, height)) + return true; + + // en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2106 + const auto time = wall_clock::from_time_t(header.timestamp()); + const auto current = wall_clock::now() - currency_window(); + return time >= current; +} + +// protected +bool chaser_block::get_branch_work(uint256_t& work, size_t& point, + system::hashes& tree_branch, header_links& store_branch, + const chain::header& header) const NOEXCEPT +{ + // Use pointer to avoid const/copy. + auto previous = &header.previous_block_hash(); + tree_branch.clear(); + store_branch.clear(); + work = header.proof(); + const auto& query = archive(); + + // Sum all branch work from tree. + for (auto it = tree_.find(*previous); it != tree_.end(); + it = tree_.find(*previous)) + { + previous = &it->second.item->header().previous_block_hash(); + tree_branch.push_back(it->second.item->header().hash()); + work += it->second.item->header().proof(); + } + + // Sum branch work from store. + database::height_link link{}; + for (link = query.to_header(*previous); !query.is_candidate_block(link); + link = query.to_parent(link)) + { + uint32_t bits{}; + if (link.is_terminal() || !query.get_bits(bits, link)) + return false; + + store_branch.push_back(link); + work += system::chain::header::proof(bits); + } + + // Height of the highest candidate header is the branch point. + return query.get_height(point, link); +} + +// protected +// **************************************************************************** +// CONSENSUS: branch with greater work causes candidate reorganization. +// Chasers eventually reorganize candidate branch into confirmed if valid. +// **************************************************************************** +bool chaser_block::get_is_strong(bool& strong, const uint256_t& work, + size_t point) const NOEXCEPT +{ + strong = false; + uint256_t candidate_work{}; + const auto& query = archive(); + for (auto height = query.get_top_candidate(); height > point; --height) + { + uint32_t bits{}; + if (!query.get_bits(bits, query.to_candidate(height))) + return false; + + candidate_work += chain::header::proof(bits); + if (!((strong = work > candidate_work))) + return true; + } + + strong = true; + return true; +} + +// protected +void chaser_block::save(const chain::block::cptr& block, + const chain::context& context) NOEXCEPT +{ + tree_.insert( + { + block->hash(), + { + { + possible_narrow_cast(context.forks), + possible_narrow_cast(context.height), + context.median_time_past, + }, + block + } + }); +} + +// protected +database::header_link chaser_block::push(const chain::block::cptr& block, + const chain::context& context) const NOEXCEPT +{ + auto& query = archive(); + const auto link = query.set_link(*block, database::context + { + possible_narrow_cast(context.forks), + possible_narrow_cast(context.height), + context.median_time_past, + }); + + if (!query.push_candidate(link)) + return {}; + + return link; +} + +// protected +bool chaser_block::push(const system::hash_digest& key) NOEXCEPT +{ + const auto value = tree_.extract(key); + BC_ASSERT_MSG(!value.empty(), "missing tree value"); + + auto& query = archive(); + const auto& node = value.mapped(); + return query.push_candidate(query.set_link(*node.item, node.context)); +} + +BC_POP_WARNING() + +} // namespace database +} // namespace libbitcoin diff --git a/src/chasers/chaser_header.cpp b/src/chasers/chaser_header.cpp index 15d63e20..e43667b3 100644 --- a/src/chasers/chaser_header.cpp +++ b/src/chasers/chaser_header.cpp @@ -36,6 +36,7 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) chaser_header::chaser_header(full_node& node) NOEXCEPT : chaser(node), + checkpoints_(node.config().bitcoin.checkpoints), currency_window_(node.node_settings().currency_window()), use_currency_window_(to_bool(node.node_settings().currency_window_minutes)) { @@ -110,7 +111,7 @@ void chaser_header::do_organize(const chain::header::cptr& header, return; } - if (!is_current(*header)) + if (!is_current(*header, context.height)) { // Header is new top of stale branch (strength not computed). save(header, context); @@ -193,7 +194,7 @@ void chaser_header::do_organize(const chain::header::cptr& header, return; } - // Notify reorganization with branch point. + // Notify candidate reorganization with branch point. // ------------------------------------------------------------------------ notify(error::success, chase::header, @@ -201,11 +202,16 @@ void chaser_header::do_organize(const chain::header::cptr& header, } // protected -bool chaser_header::is_current(const chain::header& header) const NOEXCEPT +bool chaser_header::is_current(const chain::header& header, + size_t height) const NOEXCEPT { if (!use_currency_window()) return true; + // Checkpoints are already validated. Current if at a checkpoint height. + if (chain::checkpoint::is_at(checkpoints_, height)) + return true; + // en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2106 const auto time = wall_clock::from_time_t(header.timestamp()); const auto current = wall_clock::now() - currency_window(); @@ -228,9 +234,9 @@ bool chaser_header::get_branch_work(uint256_t& work, size_t& point, for (auto it = tree_.find(*previous); it != tree_.end(); it = tree_.find(*previous)) { - previous = &it->second.header->previous_block_hash(); - tree_branch.push_back(it->second.header->hash()); - work += it->second.header->proof(); + previous = &it->second.item->previous_block_hash(); + tree_branch.push_back(it->second.item->hash()); + work += it->second.item->proof(); } // Sum branch work from store. @@ -320,7 +326,7 @@ bool chaser_header::push(const system::hash_digest& key) NOEXCEPT auto& query = archive(); const auto& node = value.mapped(); - return query.push_candidate(query.set_link(*node.header, node.context)); + return query.push_candidate(query.set_link(*node.item, node.context)); } BC_POP_WARNING() diff --git a/src/error.cpp b/src/error.cpp index 6afc1a98..b7898be3 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -40,6 +40,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { stalled_channel, "stalled channel" }, // blockchain + { orphan_block, "orphan block" }, { orphan_header, "orphan header" }, { insufficient_work, "insufficient work" }, { duplicate_block, "duplicate block" } diff --git a/src/full_node.cpp b/src/full_node.cpp index ed644f88..004aba19 100644 --- a/src/full_node.cpp +++ b/src/full_node.cpp @@ -41,6 +41,7 @@ full_node::full_node(query& query, const configuration& configuration, config_(configuration), query_(query), event_subscriber_(strand()), + chaser_block_(*this), chaser_header_(*this), chaser_check_(*this), chaser_connect_(*this), @@ -74,7 +75,8 @@ void full_node::do_start(const result_handler& handler) NOEXCEPT BC_ASSERT_MSG(stranded(), "full_node"); code ec; - if (((ec = chaser_header_.start())) || + if (((ec = chaser_block_.start())) || + ((ec = chaser_header_.start())) || ((ec = chaser_check_.start())) || ((ec = chaser_connect_.start())) || ((ec = chaser_confirm_.start())) || @@ -134,6 +136,12 @@ void full_node::organize(const system::chain::header::cptr& header, chaser_header_.organize(header, std::move(context)); } +void full_node::organize(const system::chain::block::cptr& block, + system::chain::context&& context) NOEXCEPT +{ + chaser_block_.organize(block, std::move(context)); +} + // Properties. // ---------------------------------------------------------------------------- diff --git a/src/protocols/protocol.cpp b/src/protocols/protocol.cpp index 8d757ece..8340d3c5 100644 --- a/src/protocols/protocol.cpp +++ b/src/protocols/protocol.cpp @@ -44,6 +44,12 @@ void protocol::organize(const system::chain::header::cptr& header, session_.organize(header, std::move(context)); } +void protocol::organize(const system::chain::block::cptr& block, + system::chain::context&& context) NOEXCEPT +{ + session_.organize(block, std::move(context)); +} + const configuration& protocol::config() const NOEXCEPT { return session_.config(); diff --git a/src/protocols/protocol_block_in.cpp b/src/protocols/protocol_block_in.cpp index 8a9f3540..e4bff214 100644 --- a/src/protocols/protocol_block_in.cpp +++ b/src/protocols/protocol_block_in.cpp @@ -115,7 +115,7 @@ void protocol_block_in::start() NOEXCEPT if (started()) return; - state_ = archive().get_confirmed_chain_state(config().bitcoin); + state_ = archive().get_candidate_chain_state(config().bitcoin); BC_ASSERT_MSG(state_, "Store not initialized."); if (report_performance_) @@ -140,31 +140,6 @@ void protocol_block_in::stopping(const code& ec) NOEXCEPT // Inbound (blocks). // ---------------------------------------------------------------------------- -// Validation is limited to block.check() and block.check(ctx). -// Context is obtained from stored header state as blocks are out of order. -// Tx check could be short-circuited against the database but since the checks -// are fast, it is optimal to wait until block/tx accept to hit the store. -// So header.state is read and when contextual checks are complete, block is -// stored. The set of blocks is obtained from the check chaser, and reported -// against it. Stopping channels return the set. May require height and/or -// header.fk to be stored with block hash set. - -// local -inline hashes to_hashes(const get_data& getter) NOEXCEPT -{ - hashes out{}; - out.resize(getter.items.size()); - - // Order reversed for individual erase performance (using pop_back). - std::transform(getter.items.rbegin(), getter.items.rend(), out.begin(), - [](const auto& item) NOEXCEPT - { - return item.hash; - }); - - return out; -} - // Receive inventory and send get_data for all blocks that are not found. bool protocol_block_in::handle_receive_inventory(const code& ec, const inventory::cptr& message) NOEXCEPT @@ -212,6 +187,7 @@ bool protocol_block_in::handle_receive_inventory(const code& ec, return true; } +// Process block responses in order as dictated by tracker. bool protocol_block_in::handle_receive_block(const code& ec, const block::cptr& message, const track_ptr& tracker) NOEXCEPT { @@ -242,7 +218,7 @@ bool protocol_block_in::handle_receive_block(const code& ec, { // Treat as invalid inventory. LOGR("Orphan block inventory [" - << encode_hash(message->block_ptr->hash()) << "] from [" + << encode_hash(hash) << "] from [" << authority() << "]."); stop(network::error::protocol_violation); return false; @@ -251,115 +227,99 @@ bool protocol_block_in::handle_receive_block(const code& ec, { // Block announcements may come before caught-up. LOGP("Orphan block announcement [" - << encode_hash(message->block_ptr->hash()) + << encode_hash(hash) << "] from [" << authority() << "]."); return false; } } - auto error = block.check(); - if (error) - { - LOGR("Invalid block (check) [" << encode_hash(hash) - << "] from [" << authority() << "] " << error.message()); - stop(network::error::protocol_violation); - return false; - } - // Rolling forward chain_state eliminates database cost. BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) state_.reset(new chain::chain_state(*state_, block.header(), coin)); BC_POP_WARNING() - - const auto context = state_->context(); - error = block.check(context); - if (error) + + // Checkpoints are considered chain not block/header validation. + if (chain::checkpoint::is_conflict(coin.checkpoints, hash, + state_->height())) { - LOGR("Invalid block (check(context)) [" << encode_hash(hash) - << "] from [" << authority() << "] " << error.message()); + LOGR("Invalid block (checkpoint) [" << encode_hash(hash) + << "] from [" << authority() << "]."); stop(network::error::protocol_violation); return false; } - // Populate prevouts only, internal to block. - block.populate(); + auto context = state_->context(); - // Populate stored missing prevouts only, not input metadata. - auto& query = archive(); - if (!query.populate(block)) + // Block validations are bypassed when under checkpoint/milestone. + if (!chain::checkpoint::is_under(coin.checkpoints, state_->height())) { - LOGR("Invalid block (populate) [" << encode_hash(hash) - << "] from [" << authority() << "]."); - stop(network::error::protocol_violation); - return false; - } + auto error = block.check(); + if (error) + { + LOGR("Invalid block (check) [" << encode_hash(hash) + << "] from [" << authority() << "] " << error.message()); + stop(network::error::protocol_violation); + return false; + } + + error = block.check(context); + if (error) + { + LOGR("Invalid block (check(context)) [" << encode_hash(hash) + << "] from [" << authority() << "] " << error.message()); + stop(network::error::protocol_violation); + return false; + } + + // Move populate(block) to chaser. + /////////////////////////////////////////////////////////////////////// + ////// Populate prevouts only, internal to block. + ////block.populate(); + + ////// TODO: populate from block tree via chaser. + + ////// Populate stored missing prevouts only, not input metadata. + ////auto& query = archive(); + ////if (!query.populate(block)) + ////{ + //// LOGR("Invalid block (populate) [" << encode_hash(hash) + //// << "] from [" << authority() << "]."); + //// stop(network::error::protocol_violation); + //// return false; + ////} + /////////////////////////////////////////////////////////////////////// - ////// TODO: also requires input metadata population. - ////error = block.accept(context, coin.subsidy_interval_blocks, - //// coin.initial_subsidy()); - ////if (error) - ////{ - //// LOGR("Invalid block (accept) [" << encode_hash(hash) - //// << "] from [" << authority() << "] " << error.message()); - //// stop(network::error::protocol_violation); - //// return false; - ////} - //// - ////// TODO: also requires input metadata population. - ////error = block.confirm(context); - ////if (error) - ////{ - //// LOGR("Invalid block (accept) [" << encode_hash(hash) - //// << "] from [" << authority() << "] " << error.message()); - //// stop(network::error::protocol_violation); - //// return false; - ////} + ////// TODO: also requires input metadata population. + ////error = block.accept(context, coin.subsidy_interval_blocks, + //// coin.initial_subsidy()); + ////if (error) + ////{ + //// LOGR("Invalid block (accept) [" << encode_hash(hash) + //// << "] from [" << authority() << "] " << error.message()); + //// stop(network::error::protocol_violation); + //// return false; + ////} - // Requires only prevout population. - error = block.connect(context); - if (error) - { - LOGR("Invalid block (connect) [" << encode_hash(hash) - << "] from [" << authority() << "] " << error.message()); - stop(network::error::protocol_violation); - return false; + // Requires only prevout population. + ////error = block.connect(context); + ////if (error) + ////{ + //// LOGR("Invalid block (connect) [" << encode_hash(hash) + //// << "] from [" << authority() << "] " << error.message()); + //// stop(network::error::protocol_violation); + //// return false; + ////} } // ------------------------------------------------------------------------ - // NOTE: this is a naive implementation intended for only one peer and - // lacking reorganization support. It provides a fair performance baseline - // for a sequential blocks-first design given a trusted peer. - // - const auto link = query.set_link(block, context); - if (link.is_terminal()) - { - LOGF("Store block error [" << encode_hash(hash) - << "] from [" << authority() << "]."); - stop(network::error::unknown); - return false; - } - if (!query.push_candidate(link)) - { - LOGF("Push candidate error [" << encode_hash(hash) - << "] from [" << authority() << "]."); - stop(network::error::unknown); - return false; - } + organize(message->block_ptr, std::move(context)); - if (!query.push_confirmed(link)) - { - LOGF("Push confirmed error [" << encode_hash(hash) - << "] from [" << authority() << "]."); - stop(network::error::unknown); - return false; - } - // - // ------------------------------------------------------------------------ - - LOGP("Block [" << encode_hash(message->block_ptr->hash()) << "] at (" + LOGP("Block [" << encode_hash(hash) << "] at (" << context.height << ") from [" << authority() << "]."); + // ------------------------------------------------------------------------ + // Accumulate byte count. bytes_ += message->cached_size; @@ -369,7 +329,7 @@ bool protocol_block_in::handle_receive_block(const code& ec, // Handle completion of the inventory block subset. if (tracker->hashes.empty()) { - // Implementation presumes max_get_blocks unless complete. + // Protocol presumes max_get_blocks unless complete. if (tracker->announced == max_get_blocks) { LOGP("Get inventory [" << authority() << "] (exhausted maximal)."); @@ -377,9 +337,9 @@ bool protocol_block_in::handle_receive_block(const code& ec, } else { - // Currency stalls if current on 500 as empty message is ambiguous. - // This is ok, since currency is not used for anything essential. - current(); + // Completeness stalls if on 500 as empty message is ambiguous. + // This is ok, since complete is not used for anything essential. + complete(); } } @@ -390,7 +350,7 @@ bool protocol_block_in::handle_receive_block(const code& ec, // This could be the end of a catch-up sequence, or a singleton announcement. // The distinction is ultimately arbitrary, but this signals initial currency. -void protocol_block_in::current() NOEXCEPT +void protocol_block_in::complete() NOEXCEPT { LOGN("Blocks from [" << authority() << "] complete at (" << state_->height() << ")."); @@ -401,9 +361,8 @@ void protocol_block_in::current() NOEXCEPT get_blocks protocol_block_in::create_get_inventory() const NOEXCEPT { - // block sync is always CONFIRMEDs. - return create_get_inventory(archive().get_confirmed_hashes( - get_blocks::heights(archive().get_top_confirmed()))); + return create_get_inventory(archive().get_candidate_hashes( + get_blocks::heights(archive().get_top_candidate()))); } get_blocks protocol_block_in::create_get_inventory( @@ -440,6 +399,22 @@ get_data protocol_block_in::create_get_data( return getter; } +// static +hashes protocol_block_in::to_hashes(const get_data& getter) NOEXCEPT +{ + hashes out{}; + out.resize(getter.items.size()); + + // Order reversed for individual erase performance (using pop_back). + std::transform(getter.items.rbegin(), getter.items.rend(), out.begin(), + [](const auto& item) NOEXCEPT + { + return item.hash; + }); + + return out; +} + BC_POP_WARNING() BC_POP_WARNING() BC_POP_WARNING() diff --git a/src/protocols/protocol_header_in_31800.cpp b/src/protocols/protocol_header_in_31800.cpp index df256476..e66278ce 100644 --- a/src/protocols/protocol_header_in_31800.cpp +++ b/src/protocols/protocol_header_in_31800.cpp @@ -61,6 +61,7 @@ void protocol_header_in_31800::start() NOEXCEPT // Inbound (headers). // ---------------------------------------------------------------------------- +// Send get_headers and process responses in order until peer is exhausted. bool protocol_header_in_31800::handle_receive_headers(const code& ec, const headers::cptr& message) NOEXCEPT { @@ -91,19 +92,14 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, return false; } - auto error = header.check(coin.timestamp_limit_seconds, - coin.proof_of_work_limit, coin.scrypt_proof_of_work); - if (error) - { - LOGR("Invalid header (check) [" << encode_hash(hash) - << "] from [" << authority() << "] " << error.message()); - stop(network::error::protocol_violation); - return false; - } + // Rolling forward chain_state eliminates database cost. + BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + state_.reset(new chain::chain_state(*state_, header, coin)); + BC_POP_WARNING() - // Checkpoints are considered chain not header validation. + // Checkpoints are considered chain not block/header validation. if (chain::checkpoint::is_conflict(coin.checkpoints, hash, - add1(state_->height()))) + state_->height())) { LOGR("Invalid header (checkpoint) [" << encode_hash(hash) << "] from [" << authority() << "]."); @@ -111,10 +107,17 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, return false; } - // Rolling forward chain_state eliminates database cost. - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - state_.reset(new chain::chain_state(*state_, header, coin)); - BC_POP_WARNING() + // Header validations are not bypassed when under checkpoint. + + auto error = header.check(coin.timestamp_limit_seconds, + coin.proof_of_work_limit, coin.scrypt_proof_of_work); + if (error) + { + LOGR("Invalid header (check) [" << encode_hash(hash) + << "] from [" << authority() << "] " << error.message()); + stop(network::error::protocol_violation); + return false; + } auto context = state_->context(); error = header.accept(context); @@ -126,17 +129,20 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, return false; } - // context is moved, so use here first. - if (is_zero(context.height % 1'000)) - reporter::fire(event_header, context.height); + // -------------------------------------------------------------------- organize(header_ptr, std::move(context)); + + LOGP("Header [" << encode_hash(hash) << "] at (" + << context.height << ") from [" << authority() << "]."); + + // -------------------------------------------------------------------- } // Protocol presumes max_get_headers unless complete. if (message->header_ptrs.size() == max_get_headers) { - SEND1(create_get_headers({ message->header_ptrs.back()->hash() }), + SEND1(create_get_headers(message->header_ptrs.back()->hash()), handle_send, _1); } else @@ -157,6 +163,8 @@ void protocol_header_in_31800::complete() NOEXCEPT } // private +// ---------------------------------------------------------------------------- + get_headers protocol_header_in_31800::create_get_headers() NOEXCEPT { // Header sync is from the archived (strong) candidate chain. @@ -166,9 +174,14 @@ get_headers protocol_header_in_31800::create_get_headers() NOEXCEPT get_headers::heights(archive().get_top_candidate()))); } -// private get_headers protocol_header_in_31800::create_get_headers( - hashes&& hashes) NOEXCEPT + const hash_digest& last) const NOEXCEPT +{ + return create_get_headers(hashes{ last }); +} + +get_headers protocol_header_in_31800::create_get_headers( + hashes&& hashes) const NOEXCEPT { if (!hashes.empty()) { diff --git a/src/sessions/session.cpp b/src/sessions/session.cpp index 0002be22..f4c61636 100644 --- a/src/sessions/session.cpp +++ b/src/sessions/session.cpp @@ -50,6 +50,12 @@ void session::organize(const system::chain::header::cptr& header, node_.organize(header, std::move(context)); } +void session::organize(const system::chain::block::cptr& block, + system::chain::context&& context) NOEXCEPT +{ + node_.organize(block, std::move(context)); +} + const configuration& session::config() const NOEXCEPT { return node_.config(); diff --git a/src/sessions/session_outbound.cpp b/src/sessions/session_outbound.cpp index c1cb8b7c..cdc242c8 100644 --- a/src/sessions/session_outbound.cpp +++ b/src/sessions/session_outbound.cpp @@ -99,21 +99,21 @@ void session_outbound::attach_protocols( network::session_outbound::attach_protocols(channel); auto& self = *this; - const auto version = channel->negotiated_version(); - - if (version >= network::messages::level::bip130) - { - channel->attach(self)->start(); - channel->attach(self)->start(); - } - else if (version >= network::messages::level::headers_protocol) - { - channel->attach(self)->start(); - channel->attach(self)->start(); - } - - ////constexpr auto performance = true; - ////channel->attach(self, performance)->start(); + ////const auto version = channel->negotiated_version(); + + ////if (version >= network::messages::level::bip130) + ////{ + //// channel->attach(self)->start(); + //// channel->attach(self)->start(); + ////} + ////else if (version >= network::messages::level::headers_protocol) + ////{ + //// channel->attach(self)->start(); + //// channel->attach(self)->start(); + ////} + + constexpr auto performance = true; + channel->attach(self, performance)->start(); ////channel->attach(self)->start(); ////channel->attach(self)->start(); ////channel->attach(self)->start(); diff --git a/test/chasers/chaser_block.cpp b/test/chasers/chaser_block.cpp new file mode 100644 index 00000000..e57951b6 --- /dev/null +++ b/test/chasers/chaser_block.cpp @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * + * 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/chasers/chaser_header.cpp b/test/chasers/chaser_header.cpp index 7e16057b..96b28bbe 100644 --- a/test/chasers/chaser_header.cpp +++ b/test/chasers/chaser_header.cpp @@ -66,10 +66,10 @@ class mock_chaser_header return chaser_header::get_is_strong(strong, work, point); } - bool is_current( - const system::chain::header& header) const NOEXCEPT override + bool is_current(const system::chain::header& header, + size_t height) const NOEXCEPT override { - return chaser_header::is_current(header); + return chaser_header::is_current(header, height); } void save(const system::chain::header::cptr& header, @@ -127,8 +127,8 @@ BOOST_AUTO_TEST_CASE(chaser_header_test__is_current__zero_currency_window__true) full_node node(query, config, log); mock_chaser_header instance(node); - BOOST_REQUIRE(instance.is_current(system::chain::header{ {}, {}, {}, 0, {}, {} })); - BOOST_REQUIRE(instance.is_current(system::chain::header{ {}, {}, {}, max_uint32, {}, {} })); + BOOST_REQUIRE(instance.is_current(system::chain::header{ {}, {}, {}, 0, {}, {} }, 0)); + BOOST_REQUIRE(instance.is_current(system::chain::header{ {}, {}, {}, max_uint32, {}, {} }, 0)); } BOOST_AUTO_TEST_CASE(chaser_header_test__is_current__one_minute_currency_window__expected) @@ -142,8 +142,8 @@ BOOST_AUTO_TEST_CASE(chaser_header_test__is_current__one_minute_currency_window_ full_node node(query, config, log); mock_chaser_header instance(node); - BOOST_REQUIRE(!instance.is_current(system::chain::header{ {}, {}, {}, 0, {}, {} })); - BOOST_REQUIRE(instance.is_current(system::chain::header{ {}, {}, {}, max_uint32, {}, {} })); + BOOST_REQUIRE(!instance.is_current(system::chain::header{ {}, {}, {}, 0, {}, {} }, 0)); + BOOST_REQUIRE(instance.is_current(system::chain::header{ {}, {}, {}, max_uint32, {}, {} }, 0)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/error.cpp b/test/error.cpp index f68be24a..f4772ebe 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -86,6 +86,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__stalled_channel__true_exected_message) BOOST_REQUIRE_EQUAL(ec.message(), "stalled channel"); } +BOOST_AUTO_TEST_CASE(error_t__code__orphan_block__true_exected_message) +{ + constexpr auto value = error::orphan_block; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "orphan block"); +} + BOOST_AUTO_TEST_CASE(error_t__code__orphan_header__true_exected_message) { constexpr auto value = error::orphan_header; From 09dee337e239caea591298b525c44dec6aba4939 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 20 Feb 2024 14:35:29 -0500 Subject: [PATCH 6/9] Move sequential block/header validation to chasers. --- include/bitcoin/node/chasers/chaser.hpp | 5 + include/bitcoin/node/chasers/chaser_block.hpp | 13 +- .../bitcoin/node/chasers/chaser_candidate.hpp | 3 + include/bitcoin/node/chasers/chaser_check.hpp | 3 + .../bitcoin/node/chasers/chaser_confirm.hpp | 3 + .../bitcoin/node/chasers/chaser_connect.hpp | 3 + .../bitcoin/node/chasers/chaser_header.hpp | 16 +- .../node/chasers/chaser_transaction.hpp | 3 + include/bitcoin/node/full_node.hpp | 6 +- include/bitcoin/node/protocols/protocol.hpp | 6 +- .../node/protocols/protocol_block_in.hpp | 5 +- .../protocols/protocol_header_in_31800.hpp | 2 +- include/bitcoin/node/sessions/session.hpp | 6 +- src/chasers/chaser.cpp | 7 + src/chasers/chaser_block.cpp | 152 +++++++++++++++--- src/chasers/chaser_candidate.cpp | 4 + src/chasers/chaser_check.cpp | 4 + src/chasers/chaser_confirm.cpp | 4 + src/chasers/chaser_connect.cpp | 4 + src/chasers/chaser_header.cpp | 95 ++++++++--- src/chasers/chaser_transaction.cpp | 4 + src/full_node.cpp | 10 +- src/protocols/protocol.cpp | 10 +- src/protocols/protocol_block_in.cpp | 127 ++------------- src/protocols/protocol_header_in_31800.cpp | 68 ++------ src/sessions/session.cpp | 12 +- 26 files changed, 312 insertions(+), 263 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser.hpp b/include/bitcoin/node/chasers/chaser.hpp index 8ee51834..03a23f6e 100644 --- a/include/bitcoin/node/chasers/chaser.hpp +++ b/include/bitcoin/node/chasers/chaser.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace libbitcoin { @@ -98,6 +99,9 @@ class BCN_API chaser chaser(full_node& node) NOEXCEPT; ~chaser() NOEXCEPT; + /// Node configuration settings. + const node::configuration& config() const NOEXCEPT; + /// Thread safe synchronous archival interface. query& archive() const NOEXCEPT; @@ -124,6 +128,7 @@ class BCN_API chaser // These are thread safe (mostly). full_node& node_; + const node::configuration& config_; network::asio::strand strand_; // This is protected by the network strand. diff --git a/include/bitcoin/node/chasers/chaser_block.hpp b/include/bitcoin/node/chasers/chaser_block.hpp index 607ee0a3..e1a28e55 100644 --- a/include/bitcoin/node/chasers/chaser_block.hpp +++ b/include/bitcoin/node/chasers/chaser_block.hpp @@ -37,15 +37,16 @@ class BCN_API chaser_block : public chaser { public: + DELETE_COPY_MOVE(chaser_block); + chaser_block(full_node& node) NOEXCEPT; + virtual ~chaser_block() NOEXCEPT; virtual code start() NOEXCEPT; - /// Organize the next block in sequence, relative to caller's peer. + /// Validate and organize next block in sequence relative to caller's peer. /// Causes a fault/stop if preceding blocks have not been stored. - /// Caller must validate the block and provide context. - virtual void organize(const system::chain::block::cptr& block, - system::chain::context&& context) NOEXCEPT; + virtual void organize(const system::chain::block::cptr& block) NOEXCEPT; protected: struct validated_block @@ -56,6 +57,7 @@ class BCN_API chaser_block typedef std::vector header_links; // This is protected by strand. + system::chain::chain_state::ptr state_{}; std::unordered_map tree_{}; /// Handlers. @@ -96,8 +98,7 @@ class BCN_API chaser_block private: void do_handle_event(const code& ec, chase event_, link value) NOEXCEPT; - void do_organize(const system::chain::block::cptr& block, - const system::chain::context& context) NOEXCEPT; + void do_organize(const system::chain::block::cptr& block) NOEXCEPT; // These are thread safe. const system::chain::checkpoints& checkpoints_; diff --git a/include/bitcoin/node/chasers/chaser_candidate.hpp b/include/bitcoin/node/chasers/chaser_candidate.hpp index bbfc00cc..3fa63590 100644 --- a/include/bitcoin/node/chasers/chaser_candidate.hpp +++ b/include/bitcoin/node/chasers/chaser_candidate.hpp @@ -34,7 +34,10 @@ class BCN_API chaser_candidate : public chaser { public: + DELETE_COPY_MOVE(chaser_candidate); + chaser_candidate(full_node& node) NOEXCEPT; + virtual ~chaser_candidate() NOEXCEPT; virtual code start() NOEXCEPT; diff --git a/include/bitcoin/node/chasers/chaser_check.hpp b/include/bitcoin/node/chasers/chaser_check.hpp index cb141298..e6c24b6d 100644 --- a/include/bitcoin/node/chasers/chaser_check.hpp +++ b/include/bitcoin/node/chasers/chaser_check.hpp @@ -34,7 +34,10 @@ class BCN_API chaser_check : public chaser { public: + DELETE_COPY_MOVE(chaser_check); + chaser_check(full_node& node) NOEXCEPT; + virtual ~chaser_check() NOEXCEPT; virtual code start() NOEXCEPT; virtual void checked(const system::chain::block::cptr& block) NOEXCEPT; diff --git a/include/bitcoin/node/chasers/chaser_confirm.hpp b/include/bitcoin/node/chasers/chaser_confirm.hpp index d6ca4c3b..295db1b5 100644 --- a/include/bitcoin/node/chasers/chaser_confirm.hpp +++ b/include/bitcoin/node/chasers/chaser_confirm.hpp @@ -34,7 +34,10 @@ class BCN_API chaser_confirm : public chaser { public: + DELETE_COPY_MOVE(chaser_confirm); + chaser_confirm(full_node& node) NOEXCEPT; + virtual ~chaser_confirm() NOEXCEPT; virtual code start() NOEXCEPT; diff --git a/include/bitcoin/node/chasers/chaser_connect.hpp b/include/bitcoin/node/chasers/chaser_connect.hpp index dd8c3aab..d2c01fa8 100644 --- a/include/bitcoin/node/chasers/chaser_connect.hpp +++ b/include/bitcoin/node/chasers/chaser_connect.hpp @@ -34,7 +34,10 @@ class BCN_API chaser_connect : public chaser { public: + DELETE_COPY_MOVE(chaser_connect); + chaser_connect(full_node& node) NOEXCEPT; + virtual ~chaser_connect() NOEXCEPT; virtual code start() NOEXCEPT; diff --git a/include/bitcoin/node/chasers/chaser_header.hpp b/include/bitcoin/node/chasers/chaser_header.hpp index 95aa1c81..da510c83 100644 --- a/include/bitcoin/node/chasers/chaser_header.hpp +++ b/include/bitcoin/node/chasers/chaser_header.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -37,15 +38,16 @@ class BCN_API chaser_header : public chaser { public: + DELETE_COPY_MOVE(chaser_header); + chaser_header(full_node& node) NOEXCEPT; + virtual ~chaser_header() NOEXCEPT; virtual code start() NOEXCEPT; - /// Organize the next header in sequence, relative to caller's peer. + /// Validate and organize next header in sequence relative to caller's peer. /// Causes a fault/stop if preceding headers have not been stored. - /// Caller must validate the header and provide context. - virtual void organize(const system::chain::header::cptr& header, - system::chain::context&& context) NOEXCEPT; + virtual void organize(const system::chain::header::cptr& header) NOEXCEPT; protected: struct proposed_header @@ -55,7 +57,8 @@ class BCN_API chaser_header }; typedef std::vector header_links; - // This is protected by strand. + // These are protected by strand. + system::chain::chain_state::ptr state_{}; std::unordered_map tree_{}; /// Handlers. @@ -95,8 +98,7 @@ class BCN_API chaser_header private: void do_handle_event(const code& ec, chase event_, link value) NOEXCEPT; - void do_organize(const system::chain::header::cptr& header, - const system::chain::context& context) NOEXCEPT; + void do_organize(const system::chain::header::cptr& header) NOEXCEPT; // These are thread safe. const system::chain::checkpoints& checkpoints_; diff --git a/include/bitcoin/node/chasers/chaser_transaction.hpp b/include/bitcoin/node/chasers/chaser_transaction.hpp index 6f982e81..bdc595a8 100644 --- a/include/bitcoin/node/chasers/chaser_transaction.hpp +++ b/include/bitcoin/node/chasers/chaser_transaction.hpp @@ -34,7 +34,10 @@ class BCN_API chaser_transaction : public chaser { public: + DELETE_COPY_MOVE(chaser_transaction); + chaser_transaction(full_node& node) NOEXCEPT; + virtual ~chaser_transaction() NOEXCEPT; code start() NOEXCEPT override; virtual void store(const system::chain::transaction::cptr& block) NOEXCEPT; diff --git a/include/bitcoin/node/full_node.hpp b/include/bitcoin/node/full_node.hpp index 1b1341e3..69f3ac59 100644 --- a/include/bitcoin/node/full_node.hpp +++ b/include/bitcoin/node/full_node.hpp @@ -62,12 +62,10 @@ class BCN_API full_node /// ----------------------------------------------------------------------- /// Organize a validated header, failures stop the node. - virtual void organize(const system::chain::header::cptr& header, - system::chain::context&& context) NOEXCEPT; + virtual void organize(const system::chain::header::cptr& header) NOEXCEPT; /// Organize a validated block, failures stop the node. - virtual void organize(const system::chain::block::cptr& block, - system::chain::context&& context) NOEXCEPT; + virtual void organize(const system::chain::block::cptr& block) NOEXCEPT; /// Properties. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/node/protocols/protocol.hpp b/include/bitcoin/node/protocols/protocol.hpp index 9bf8dbc9..ce7cf4af 100644 --- a/include/bitcoin/node/protocols/protocol.hpp +++ b/include/bitcoin/node/protocols/protocol.hpp @@ -54,12 +54,10 @@ class BCN_API protocol network::result_handler&& handler) const NOEXCEPT; /// Organize a validated header, failures stop the node. - virtual void organize(const system::chain::header::cptr& header, - system::chain::context&& context) NOEXCEPT; + virtual void organize(const system::chain::header::cptr& header) NOEXCEPT; /// Organize a validated block, failures stop the node. - virtual void organize(const system::chain::block::cptr& block, - system::chain::context&& context) NOEXCEPT; + virtual void organize(const system::chain::block::cptr& block) NOEXCEPT; /// Configuration settings for all libraries. const configuration& config() const NOEXCEPT; diff --git a/include/bitcoin/node/protocols/protocol_block_in.hpp b/include/bitcoin/node/protocols/protocol_block_in.hpp index 22199629..5464cf09 100644 --- a/include/bitcoin/node/protocols/protocol_block_in.hpp +++ b/include/bitcoin/node/protocols/protocol_block_in.hpp @@ -83,7 +83,8 @@ class BCN_API protocol_block_in virtual void complete() NOEXCEPT; private: - static system::hashes to_hashes(const get_data& getter) NOEXCEPT; + static system::hashes to_hashes( + const network::messages::get_data& getter) NOEXCEPT; network::messages::get_blocks create_get_inventory() const NOEXCEPT; network::messages::get_blocks create_get_inventory( @@ -103,8 +104,8 @@ class BCN_API protocol_block_in // Protected by strand. uint64_t bytes_{ zero }; + system::chain::checkpoint top_{}; network::steady_clock::time_point start_{}; - system::chain::chain_state::ptr state_{}; network::deadline::ptr performance_timer_; }; diff --git a/include/bitcoin/node/protocols/protocol_header_in_31800.hpp b/include/bitcoin/node/protocols/protocol_header_in_31800.hpp index 54aa1bfe..1fd37995 100644 --- a/include/bitcoin/node/protocols/protocol_header_in_31800.hpp +++ b/include/bitcoin/node/protocols/protocol_header_in_31800.hpp @@ -60,7 +60,7 @@ class BCN_API protocol_header_in_31800 system::hashes&& start_hashes) const NOEXCEPT; // Protected by strand. - system::chain::chain_state::ptr state_{}; + system::chain::checkpoint top_{}; }; } // namespace node diff --git a/include/bitcoin/node/sessions/session.hpp b/include/bitcoin/node/sessions/session.hpp index 36839c21..b0029817 100644 --- a/include/bitcoin/node/sessions/session.hpp +++ b/include/bitcoin/node/sessions/session.hpp @@ -37,12 +37,10 @@ class BCN_API session network::result_handler&& handler) NOEXCEPT; /// Organize a validated header, failures stop the node. - virtual void organize(const system::chain::header::cptr& header, - system::chain::context&& context) NOEXCEPT; + virtual void organize(const system::chain::header::cptr& header) NOEXCEPT; /// Organize a validated block, failures stop the node. - virtual void organize(const system::chain::block::cptr& block, - system::chain::context&& context) NOEXCEPT; + virtual void organize(const system::chain::block::cptr& block) NOEXCEPT; /// Configuration settings for all libraries. const configuration& config() const NOEXCEPT; diff --git a/src/chasers/chaser.cpp b/src/chasers/chaser.cpp index 332c6790..761e205b 100644 --- a/src/chasers/chaser.cpp +++ b/src/chasers/chaser.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,7 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) chaser::chaser(full_node& node) NOEXCEPT : node_(node), + config_(node.config()), strand_(node.service().get_executor()), subscriber_(node.event_subscriber()), reporter(node.log) @@ -43,6 +45,11 @@ chaser::~chaser() NOEXCEPT { } +const node::configuration& chaser::config() const NOEXCEPT +{ + return config_; +} + chaser::query& chaser::archive() const NOEXCEPT { return node_.archive(); diff --git a/src/chasers/chaser_block.cpp b/src/chasers/chaser_block.cpp index 3fddc13f..681c8e8f 100644 --- a/src/chasers/chaser_block.cpp +++ b/src/chasers/chaser_block.cpp @@ -32,6 +32,7 @@ using namespace network; using namespace system; using namespace std::placeholders; +BC_PUSH_WARNING(NO_NEW_OR_DELETE) BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) chaser_block::chaser_block(full_node& node) NOEXCEPT @@ -42,6 +43,10 @@ chaser_block::chaser_block(full_node& node) NOEXCEPT { } +chaser_block::~chaser_block() NOEXCEPT +{ +} + // protected const network::wall_clock::duration& chaser_block::currency_window() const NOEXCEPT @@ -59,6 +64,10 @@ bool chaser_block::use_currency_window() const NOEXCEPT code chaser_block::start() NOEXCEPT { BC_ASSERT_MSG(node_stranded(), "chaser_block"); + + state_ = archive().get_candidate_chain_state(config().bitcoin); + BC_ASSERT_MSG(state_, "Store not initialized."); + return subscribe(std::bind(&chaser_block::handle_event, this, _1, _2, _3)); } @@ -78,57 +87,142 @@ void chaser_block::do_handle_event(const code&, chase, link) NOEXCEPT BC_ASSERT_MSG(stranded(), "chaser_block"); } -void chaser_block::organize(const chain::block::cptr& block, - chain::context&& context) NOEXCEPT +void chaser_block::organize(const chain::block::cptr& block) NOEXCEPT { boost::asio::post(strand(), std::bind(&chaser_block::do_organize, - this, block, std::move(context))); + this, block)); } // private -void chaser_block::do_organize(const chain::block::cptr& block, - const chain::context& context) NOEXCEPT +void chaser_block::do_organize(const chain::block::cptr& block_ptr) NOEXCEPT { BC_ASSERT_MSG(stranded(), "chaser_block"); - // Determine if work should be computed. + auto& query = archive(); + const auto& block = *block_ptr; + const auto& header = block.header(); + const auto& previous = header.previous_block_hash(); + const auto& coin = config().bitcoin; + const auto hash = header.hash(); + + // Skip existing, fail orphan. // ------------------------------------------------------------------------ - // This presumes a header is never archived independent of its valid block. - // This presumes the block is valid (check/accept/connect) and therefore - // all of its prevouts exist, but it is not yet confirmed. - auto& query = archive(); - const auto hash = block->header().hash(); + // Block (header and txs) already exists. if (tree_.contains(hash) || query.is_block(hash)) - { - // Block (header and txs) already exists. return; - } - auto& previous = block->header().previous_block_hash(); + // Peer processing should have precluded orphan submission. if (!tree_.contains(previous) && !query.is_block(previous)) { - // Peer processing should have precluded orphan submission. stop(error::orphan_block); return; } - if (!is_current(block->header(), context.height)) + // Validate block. + // ------------------------------------------------------------------------ + + // Rolling forward chain_state eliminates requery cost. + // Do not use block ref here as the block override is for tx pool. + state_.reset(new chain::chain_state(*state_, header, coin)); + const auto context = state_->context(); + + // Checkpoints are considered chain not block/header validation. + if (chain::checkpoint::is_conflict(coin.checkpoints, hash, + state_->height())) { - // Block is new top of stale branch (strength not computed). - save(block, context); - return; + ////LOGR("Invalid block (checkpoint) [" << encode_hash(hash) + //// << "] from [" << authority() << "]."); + ////stop(network::error::protocol_violation); + ////return false; + }; + + // Block validations are bypassed when under checkpoint/milestone. + if (!chain::checkpoint::is_under(coin.checkpoints, state_->height())) + { + auto error = block.check(); + if (error) + { + ////LOGR("Invalid block (check) [" << encode_hash(hash) + //// << "] from [" << authority() << "] " << error.message()); + ////stop(network::error::protocol_violation); + ////return false; + } + + error = block.check(context); + if (error) + { + ////LOGR("Invalid block (check(context)) [" << encode_hash(hash) + //// << "] from [" << authority() << "] " << error.message()); + ////stop(network::error::protocol_violation); + ////return false; + } + + // Populate prevouts only, internal to block. + // ******************************************************************** + // TODO: populate input metadata for block internal. + // ******************************************************************** + ////block.populate(); + + // ******************************************************************** + // TODO: populate prevouts and input metadata for block tree. + // ******************************************************************** + + // Populate stored missing prevouts only, not input metadata. + // ******************************************************************** + // TODO: populate input metadata for stored blocks. + // ******************************************************************** + ////auto& query = archive(); + ////if (!query.populate(block)) + ////{ + //// ////LOGR("Invalid block (populate) [" << encode_hash(hash) + //// //// << "] from [" << authority() << "]."); + //// ////stop(network::error::protocol_violation); + //// ////return false; + ////} + + ////// TODO: also requires input metadata population. + ////error = block.accept(context, coin.subsidy_interval_blocks, + //// coin.initial_subsidy()); + ////if (error) + ////{ + //// ////LOGR("Invalid block (accept) [" << encode_hash(hash) + //// //// << "] from [" << authority() << "] " << error.message()); + //// ////stop(network::error::protocol_violation); + //// ////return false; + ////} + + ////// Requires only prevout population. + ////error = block.connect(context); + ////if (error) + ////{ + //// ////LOGR("Invalid block (connect) [" << encode_hash(hash) + //// //// << "] from [" << authority() << "] " << error.message()); + //// ////stop(network::error::protocol_violation); + //// ////return false; + ////} + + // ******************************************************************** + // TODO: with all metadata populated, block.confirm may be possible. + // ******************************************************************** } // Compute relative work. // ------------------------------------------------------------------------ + // Block is new top of stale branch (strength not computed). + if (!is_current(header, context.height)) + { + save(block_ptr, context); + return; + } + size_t point{}; uint256_t work{}; hashes tree_branch{}; header_links store_branch{}; - if (!get_branch_work(work, point, tree_branch, store_branch, block->header())) + if (!get_branch_work(work, point, tree_branch, store_branch, header)) { stop(error::store_integrity); return; @@ -151,7 +245,7 @@ void chaser_block::do_organize(const chain::block::cptr& block, if (!strong) { // Block is new top of current weak branch. - save(block, context); + save(block_ptr, context); return; } @@ -197,13 +291,22 @@ void chaser_block::do_organize(const chain::block::cptr& block, } // Push new block as top of candidate chain. - const auto link = push(block, context); + const auto link = push(block_ptr, context); if (link.is_terminal()) { stop(error::store_integrity); return; } + const auto new_top = query.get_top_candidate(); + BC_ASSERT(!is_zero(new_top)); + + const auto candy_fk = query.to_candidate(new_top); + BC_ASSERT(!candy_fk.is_terminal()); + + const auto is_candy = query.is_candidate_block(candy_fk); + BC_ASSERT(is_candy); + // Notify candidate reorganization with branch point. // ------------------------------------------------------------------------ @@ -262,6 +365,8 @@ bool chaser_block::get_branch_work(uint256_t& work, size_t& point, work += system::chain::header::proof(bits); } + // TODO: this is always zero at new store. + // TODO: this could be the result of is_candidate_block being false. // Height of the highest candidate header is the branch point. return query.get_height(point, link); } @@ -339,6 +444,7 @@ bool chaser_block::push(const system::hash_digest& key) NOEXCEPT return query.push_candidate(query.set_link(*node.item, node.context)); } +BC_POP_WARNING() BC_POP_WARNING() } // namespace database diff --git a/src/chasers/chaser_candidate.cpp b/src/chasers/chaser_candidate.cpp index 0d0cb81e..ffb9ce78 100644 --- a/src/chasers/chaser_candidate.cpp +++ b/src/chasers/chaser_candidate.cpp @@ -38,6 +38,10 @@ chaser_candidate::chaser_candidate(full_node& node) NOEXCEPT { } +chaser_candidate::~chaser_candidate() NOEXCEPT +{ +} + // TODO: initialize candidate state. code chaser_candidate::start() NOEXCEPT { diff --git a/src/chasers/chaser_check.cpp b/src/chasers/chaser_check.cpp index c3b979f7..d1541f9e 100644 --- a/src/chasers/chaser_check.cpp +++ b/src/chasers/chaser_check.cpp @@ -39,6 +39,10 @@ chaser_check::chaser_check(full_node& node) NOEXCEPT { } +chaser_check::~chaser_check() NOEXCEPT +{ +} + // TODO: initialize check state. code chaser_check::start() NOEXCEPT { diff --git a/src/chasers/chaser_confirm.cpp b/src/chasers/chaser_confirm.cpp index c6254a0b..49712c14 100644 --- a/src/chasers/chaser_confirm.cpp +++ b/src/chasers/chaser_confirm.cpp @@ -37,6 +37,10 @@ chaser_confirm::chaser_confirm(full_node& node) NOEXCEPT { } +chaser_confirm::~chaser_confirm() NOEXCEPT +{ +} + // TODO: initialize confirm state. code chaser_confirm::start() NOEXCEPT { diff --git a/src/chasers/chaser_connect.cpp b/src/chasers/chaser_connect.cpp index 8e1a3463..f10d60b5 100644 --- a/src/chasers/chaser_connect.cpp +++ b/src/chasers/chaser_connect.cpp @@ -37,6 +37,10 @@ chaser_connect::chaser_connect(full_node& node) NOEXCEPT { } +chaser_connect::~chaser_connect() NOEXCEPT +{ +} + // TODO: initialize connect state. code chaser_connect::start() NOEXCEPT { diff --git a/src/chasers/chaser_header.cpp b/src/chasers/chaser_header.cpp index e43667b3..99f6ed3a 100644 --- a/src/chasers/chaser_header.cpp +++ b/src/chasers/chaser_header.cpp @@ -32,13 +32,18 @@ using namespace network; using namespace system; using namespace std::placeholders; +BC_PUSH_WARNING(NO_NEW_OR_DELETE) BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) chaser_header::chaser_header(full_node& node) NOEXCEPT : chaser(node), - checkpoints_(node.config().bitcoin.checkpoints), - currency_window_(node.node_settings().currency_window()), - use_currency_window_(to_bool(node.node_settings().currency_window_minutes)) + checkpoints_(config().bitcoin.checkpoints), + currency_window_(config().node.currency_window()), + use_currency_window_(to_bool(config().node.currency_window_minutes)) +{ +} + +chaser_header::~chaser_header() NOEXCEPT { } @@ -59,6 +64,10 @@ bool chaser_header::use_currency_window() const NOEXCEPT code chaser_header::start() NOEXCEPT { BC_ASSERT_MSG(node_stranded(), "chaser_header"); + + state_ = archive().get_candidate_chain_state(config().bitcoin); + BC_ASSERT_MSG(state_, "Store not initialized."); + return subscribe(std::bind(&chaser_header::handle_event, this, _1, _2, _3)); } @@ -78,54 +87,91 @@ void chaser_header::do_handle_event(const code&, chase, link) NOEXCEPT BC_ASSERT_MSG(stranded(), "chaser_header"); } -void chaser_header::organize(const chain::header::cptr& header, - chain::context&& context) NOEXCEPT +void chaser_header::organize(const chain::header::cptr& header) NOEXCEPT { boost::asio::post(strand(), std::bind(&chaser_header::do_organize, - this, header, std::move(context))); + this, header)); } // private -void chaser_header::do_organize(const chain::header::cptr& header, - const chain::context& context) NOEXCEPT +void chaser_header::do_organize(const chain::header::cptr& header_ptr) NOEXCEPT { BC_ASSERT_MSG(stranded(), "chaser_header"); - // Determine if work should be computed. + auto& query = archive(); + const auto& header = *header_ptr; + const auto& previous = header.previous_block_hash(); + const auto& coin = config().bitcoin; + const auto hash = header.hash(); + + // Skip existing, fail orphan. // ------------------------------------------------------------------------ - auto& query = archive(); - const auto hash = header->hash(); + // Header already exists. if (tree_.contains(hash) || query.is_header(hash)) - { - // Header already exists. return; - } - auto& previous = header->previous_block_hash(); + // Peer processing should have precluded orphan submission. if (!tree_.contains(previous) && !query.is_header(previous)) { - // Peer processing should have precluded orphan submission. stop(error::orphan_header); return; } - if (!is_current(*header, context.height)) + // Validate header. + // ------------------------------------------------------------------------ + + // Rolling forward chain_state eliminates requery cost. + state_.reset(new chain::chain_state(*state_, header, coin)); + const auto context = state_->context(); + + // Checkpoints are considered chain not block/header validation. + if (chain::checkpoint::is_conflict(coin.checkpoints, hash, + state_->height())) { - // Header is new top of stale branch (strength not computed). - save(header, context); - return; + ////LOGR("Invalid header (checkpoint) [" << encode_hash(hash) + //// << "] from [" << authority() << "]."); + ////stop(network::error::protocol_violation); + ////return false; + } + + // Header validations are not bypassed when under checkpoint/milestone. + + auto error = header.check(coin.timestamp_limit_seconds, + coin.proof_of_work_limit, coin.scrypt_proof_of_work); + if (error) + { + ////LOGR("Invalid header (check) [" << encode_hash(hash) + //// << "] from [" << authority() << "] " << error.message()); + ////stop(network::error::protocol_violation); + ////return false; + } + + error = header.accept(context); + if (error) + { + ////LOGR("Invalid header (accept) [" << encode_hash(hash) + //// << "] from [" << authority() << "] " << error.message()); + ////stop(network::error::protocol_violation); + ////return false; } // Compute relative work. // ------------------------------------------------------------------------ + // Header is new top of stale branch (strength not computed). + if (!is_current(header, context.height)) + { + save(header_ptr, context); + return; + } + size_t point{}; uint256_t work{}; hashes tree_branch{}; header_links store_branch{}; - if (!get_branch_work(work, point, tree_branch, store_branch, *header)) + if (!get_branch_work(work, point, tree_branch, store_branch, header)) { stop(error::store_integrity); return; @@ -138,10 +184,10 @@ void chaser_header::do_organize(const chain::header::cptr& header, return; } + // Header is new top of current weak branch. if (!strong) { - // Header is new top of current weak branch. - save(header, context); + save(header_ptr, context); return; } @@ -187,7 +233,7 @@ void chaser_header::do_organize(const chain::header::cptr& header, } // Push new header as top of candidate chain. - const auto link = push(header, context); + const auto link = push(header_ptr, context); if (link.is_terminal()) { stop(error::store_integrity); @@ -329,6 +375,7 @@ bool chaser_header::push(const system::hash_digest& key) NOEXCEPT return query.push_candidate(query.set_link(*node.item, node.context)); } +BC_POP_WARNING() BC_POP_WARNING() } // namespace database diff --git a/src/chasers/chaser_transaction.cpp b/src/chasers/chaser_transaction.cpp index ee068cb3..ed118430 100644 --- a/src/chasers/chaser_transaction.cpp +++ b/src/chasers/chaser_transaction.cpp @@ -38,6 +38,10 @@ chaser_transaction::chaser_transaction(full_node& node) NOEXCEPT { } +chaser_transaction::~chaser_transaction() NOEXCEPT +{ +} + // TODO: initialize tx graph from store, log and stop on error. code chaser_transaction::start() NOEXCEPT { diff --git a/src/full_node.cpp b/src/full_node.cpp index 004aba19..c4cb4235 100644 --- a/src/full_node.cpp +++ b/src/full_node.cpp @@ -130,16 +130,14 @@ void full_node::do_close() NOEXCEPT // Chasers. // ---------------------------------------------------------------------------- -void full_node::organize(const system::chain::header::cptr& header, - system::chain::context&& context) NOEXCEPT +void full_node::organize(const system::chain::header::cptr& header) NOEXCEPT { - chaser_header_.organize(header, std::move(context)); + chaser_header_.organize(header); } -void full_node::organize(const system::chain::block::cptr& block, - system::chain::context&& context) NOEXCEPT +void full_node::organize(const system::chain::block::cptr& block) NOEXCEPT { - chaser_block_.organize(block, std::move(context)); + chaser_block_.organize(block); } // Properties. diff --git a/src/protocols/protocol.cpp b/src/protocols/protocol.cpp index 8340d3c5..a996fc63 100644 --- a/src/protocols/protocol.cpp +++ b/src/protocols/protocol.cpp @@ -38,16 +38,14 @@ void protocol::performance(uint64_t channel, uint64_t speed, session_.performance(channel, speed, std::move(handler)); } -void protocol::organize(const system::chain::header::cptr& header, - system::chain::context&& context) NOEXCEPT +void protocol::organize(const system::chain::header::cptr& header) NOEXCEPT { - session_.organize(header, std::move(context)); + session_.organize(header); } -void protocol::organize(const system::chain::block::cptr& block, - system::chain::context&& context) NOEXCEPT +void protocol::organize(const system::chain::block::cptr& block) NOEXCEPT { - session_.organize(block, std::move(context)); + session_.organize(block); } const configuration& protocol::config() const NOEXCEPT diff --git a/src/protocols/protocol_block_in.cpp b/src/protocols/protocol_block_in.cpp index e4bff214..02aff7bc 100644 --- a/src/protocols/protocol_block_in.cpp +++ b/src/protocols/protocol_block_in.cpp @@ -115,8 +115,9 @@ void protocol_block_in::start() NOEXCEPT if (started()) return; - state_ = archive().get_candidate_chain_state(config().bitcoin); - BC_ASSERT_MSG(state_, "Store not initialized."); + const auto& query = archive(); + const auto top = query.get_top_candidate(); + top_ = { query.get_header_key(query.to_candidate(top)), top }; if (report_performance_) { @@ -180,7 +181,7 @@ bool protocol_block_in::handle_receive_inventory(const code& ec, to_hashes(getter) }); - // TODO: these must be limited for DOS protection. + // TODO: these should be limited in quantity for DOS protection. // There is one block subscription for each received unexhausted inventory. SUBSCRIBE_CHANNEL3(block, handle_receive_block, _1, _2, tracker); SEND1(getter, handle_send, _1); @@ -202,123 +203,23 @@ bool protocol_block_in::handle_receive_block(const code& ec, return false; } - const auto& block = *message->block_ptr; - const auto& coin = config().bitcoin; - const auto hash = block.hash(); - - // May not have been announced (miner broadcast) or different inv. - if (tracker->hashes.back() != hash) + // Unrequested block, may not have been announced via inventory. + if (tracker->hashes.back() != message->block_ptr->hash()) return true; - // Out of order (orphan). - if (block.header().previous_block_hash() != state_->hash()) + // Out of order or invalid. + if (message->block_ptr->header().previous_block_hash() != top_.hash()) { - // Announcements are assumed to be small in number. - if (tracker->announced > maximum_advertisement) - { - // Treat as invalid inventory. - LOGR("Orphan block inventory [" - << encode_hash(hash) << "] from [" - << authority() << "]."); - stop(network::error::protocol_violation); - return false; - } - else - { - // Block announcements may come before caught-up. - LOGP("Orphan block announcement [" - << encode_hash(hash) - << "] from [" << authority() << "]."); - return false; - } - } - - // Rolling forward chain_state eliminates database cost. - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - state_.reset(new chain::chain_state(*state_, block.header(), coin)); - BC_POP_WARNING() - - // Checkpoints are considered chain not block/header validation. - if (chain::checkpoint::is_conflict(coin.checkpoints, hash, - state_->height())) - { - LOGR("Invalid block (checkpoint) [" << encode_hash(hash) + LOGP("Orphan block [" << encode_hash(message->block_ptr->hash()) << "] from [" << authority() << "]."); - stop(network::error::protocol_violation); return false; } - auto context = state_->context(); - - // Block validations are bypassed when under checkpoint/milestone. - if (!chain::checkpoint::is_under(coin.checkpoints, state_->height())) - { - auto error = block.check(); - if (error) - { - LOGR("Invalid block (check) [" << encode_hash(hash) - << "] from [" << authority() << "] " << error.message()); - stop(network::error::protocol_violation); - return false; - } - - error = block.check(context); - if (error) - { - LOGR("Invalid block (check(context)) [" << encode_hash(hash) - << "] from [" << authority() << "] " << error.message()); - stop(network::error::protocol_violation); - return false; - } - - // Move populate(block) to chaser. - /////////////////////////////////////////////////////////////////////// - ////// Populate prevouts only, internal to block. - ////block.populate(); - - ////// TODO: populate from block tree via chaser. - - ////// Populate stored missing prevouts only, not input metadata. - ////auto& query = archive(); - ////if (!query.populate(block)) - ////{ - //// LOGR("Invalid block (populate) [" << encode_hash(hash) - //// << "] from [" << authority() << "]."); - //// stop(network::error::protocol_violation); - //// return false; - ////} - /////////////////////////////////////////////////////////////////////// - - ////// TODO: also requires input metadata population. - ////error = block.accept(context, coin.subsidy_interval_blocks, - //// coin.initial_subsidy()); - ////if (error) - ////{ - //// LOGR("Invalid block (accept) [" << encode_hash(hash) - //// << "] from [" << authority() << "] " << error.message()); - //// stop(network::error::protocol_violation); - //// return false; - ////} - - // Requires only prevout population. - ////error = block.connect(context); - ////if (error) - ////{ - //// LOGR("Invalid block (connect) [" << encode_hash(hash) - //// << "] from [" << authority() << "] " << error.message()); - //// stop(network::error::protocol_violation); - //// return false; - ////} - } - - // ------------------------------------------------------------------------ - - organize(message->block_ptr, std::move(context)); - - LOGP("Block [" << encode_hash(hash) << "] at (" - << context.height << ") from [" << authority() << "]."); + organize(message->block_ptr); - // ------------------------------------------------------------------------ + top_ = { message->block_ptr->hash(), add1(top_.height()) }; + LOGP("Block [" << encode_hash(top_.hash()) << "] at (" + << top_.height() << ") from [" << authority() << "]."); // Accumulate byte count. bytes_ += message->cached_size; @@ -353,7 +254,7 @@ bool protocol_block_in::handle_receive_block(const code& ec, void protocol_block_in::complete() NOEXCEPT { LOGN("Blocks from [" << authority() << "] complete at (" - << state_->height() << ")."); + << top_.height() << ")."); } // private diff --git a/src/protocols/protocol_header_in_31800.cpp b/src/protocols/protocol_header_in_31800.cpp index e66278ce..a45d8d05 100644 --- a/src/protocols/protocol_header_in_31800.cpp +++ b/src/protocols/protocol_header_in_31800.cpp @@ -49,9 +49,9 @@ void protocol_header_in_31800::start() NOEXCEPT if (started()) return; - // header sync is always CANDIDATEs. - state_ = archive().get_candidate_chain_state(config().bitcoin); - BC_ASSERT_MSG(state_, "Store not initialized."); + const auto& query = archive(); + const auto top = query.get_top_candidate(); + top_ = { query.get_header_key(query.to_candidate(top)), top }; SUBSCRIBE_CHANNEL2(headers, handle_receive_headers, _1, _2); SEND1(create_get_headers(), handle_send, _1); @@ -70,10 +70,8 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, if (stopped(ec)) return false; - const auto& coin = config().bitcoin; - - LOGP("Headers (" << message->header_ptrs.size() - << ") from [" << authority() << "]."); + LOGP("Headers (" << message->header_ptrs.size() << ") from [" + << authority() << "]."); // Store each header, drop channel if invalid. for (const auto& header_ptr: message->header_ptrs) @@ -81,62 +79,20 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, if (stopped()) return false; - const auto& header = *header_ptr; - const auto hash = header.hash(); - if (header.previous_block_hash() != state_->hash()) + if (header_ptr->previous_block_hash() != top_.hash()) { // Out of order or invalid. - LOGP("Orphan header [" << encode_hash(hash) + LOGP("Orphan header [" << encode_hash(header_ptr->hash()) << "] from [" << authority() << "]."); stop(network::error::protocol_violation); return false; } - // Rolling forward chain_state eliminates database cost. - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - state_.reset(new chain::chain_state(*state_, header, coin)); - BC_POP_WARNING() - - // Checkpoints are considered chain not block/header validation. - if (chain::checkpoint::is_conflict(coin.checkpoints, hash, - state_->height())) - { - LOGR("Invalid header (checkpoint) [" << encode_hash(hash) - << "] from [" << authority() << "]."); - stop(network::error::protocol_violation); - return false; - } - - // Header validations are not bypassed when under checkpoint. - - auto error = header.check(coin.timestamp_limit_seconds, - coin.proof_of_work_limit, coin.scrypt_proof_of_work); - if (error) - { - LOGR("Invalid header (check) [" << encode_hash(hash) - << "] from [" << authority() << "] " << error.message()); - stop(network::error::protocol_violation); - return false; - } - - auto context = state_->context(); - error = header.accept(context); - if (error) - { - LOGR("Invalid header (accept) [" << encode_hash(hash) - << "] from [" << authority() << "] " << error.message()); - stop(network::error::protocol_violation); - return false; - } - - // -------------------------------------------------------------------- - - organize(header_ptr, std::move(context)); - - LOGP("Header [" << encode_hash(hash) << "] at (" - << context.height << ") from [" << authority() << "]."); + organize(header_ptr); - // -------------------------------------------------------------------- + top_ = { header_ptr->hash(), add1(top_.height()) }; + LOGP("Header [" << encode_hash(top_.hash()) << "] at (" + << top_.height() << ") from [" << authority() << "]."); } // Protocol presumes max_get_headers unless complete. @@ -159,7 +115,7 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, void protocol_header_in_31800::complete() NOEXCEPT { LOGN("Headers from [" << authority() << "] complete at (" - << state_->height() << ")."); + << top_.height() << ")."); } // private diff --git a/src/sessions/session.cpp b/src/sessions/session.cpp index f4c61636..4f4d543d 100644 --- a/src/sessions/session.cpp +++ b/src/sessions/session.cpp @@ -18,7 +18,7 @@ */ #include -#include +#include #include #include #include @@ -44,16 +44,14 @@ void session::performance(uint64_t, uint64_t, boost::asio::post(node_.strand(), std::bind(handler, error::unknown)); } -void session::organize(const system::chain::header::cptr& header, - system::chain::context&& context) NOEXCEPT +void session::organize(const system::chain::header::cptr& header) NOEXCEPT { - node_.organize(header, std::move(context)); + node_.organize(header); } -void session::organize(const system::chain::block::cptr& block, - system::chain::context&& context) NOEXCEPT +void session::organize(const system::chain::block::cptr& block) NOEXCEPT { - node_.organize(block, std::move(context)); + node_.organize(block); } const configuration& session::config() const NOEXCEPT From ebc6f8c11e43473dba8fb27648009efeb63ec7f1 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 20 Feb 2024 17:06:39 -0500 Subject: [PATCH 7/9] Remove assertions. --- src/chasers/chaser_block.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/chasers/chaser_block.cpp b/src/chasers/chaser_block.cpp index 681c8e8f..b419c5a7 100644 --- a/src/chasers/chaser_block.cpp +++ b/src/chasers/chaser_block.cpp @@ -298,15 +298,6 @@ void chaser_block::do_organize(const chain::block::cptr& block_ptr) NOEXCEPT return; } - const auto new_top = query.get_top_candidate(); - BC_ASSERT(!is_zero(new_top)); - - const auto candy_fk = query.to_candidate(new_top); - BC_ASSERT(!candy_fk.is_terminal()); - - const auto is_candy = query.is_candidate_block(candy_fk); - BC_ASSERT(is_candy); - // Notify candidate reorganization with branch point. // ------------------------------------------------------------------------ From 293a8fb5c3631ba8f40b8799b918cd15956e966f Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 20 Feb 2024 17:07:43 -0500 Subject: [PATCH 8/9] Consistent parameter naming. --- include/bitcoin/node/chasers/chaser_block.hpp | 5 +++-- include/bitcoin/node/chasers/chaser_header.hpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_block.hpp b/include/bitcoin/node/chasers/chaser_block.hpp index e1a28e55..424fe2b8 100644 --- a/include/bitcoin/node/chasers/chaser_block.hpp +++ b/include/bitcoin/node/chasers/chaser_block.hpp @@ -44,9 +44,10 @@ class BCN_API chaser_block virtual code start() NOEXCEPT; - /// Validate and organize next block in sequence relative to caller's peer. + /// Validate and organize next block in sequence relative to caller peer. /// Causes a fault/stop if preceding blocks have not been stored. - virtual void organize(const system::chain::block::cptr& block) NOEXCEPT; + virtual void organize( + const system::chain::block::cptr& block_ptr) NOEXCEPT; protected: struct validated_block diff --git a/include/bitcoin/node/chasers/chaser_header.hpp b/include/bitcoin/node/chasers/chaser_header.hpp index da510c83..132767ac 100644 --- a/include/bitcoin/node/chasers/chaser_header.hpp +++ b/include/bitcoin/node/chasers/chaser_header.hpp @@ -45,9 +45,10 @@ class BCN_API chaser_header virtual code start() NOEXCEPT; - /// Validate and organize next header in sequence relative to caller's peer. + /// Validate and organize next header in sequence relative to caller peer. /// Causes a fault/stop if preceding headers have not been stored. - virtual void organize(const system::chain::header::cptr& header) NOEXCEPT; + virtual void organize( + const system::chain::header::cptr& header_ptr) NOEXCEPT; protected: struct proposed_header From 79878dc8ec91586d28c49a53dbebeeaf9a9d6c32 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 20 Feb 2024 17:08:49 -0500 Subject: [PATCH 9/9] Disable block_in performance ratcheting. --- .../node/protocols/protocol_block_in.hpp | 28 ++-- src/protocols/protocol_block_in.cpp | 131 +++++++++--------- 2 files changed, 82 insertions(+), 77 deletions(-) diff --git a/include/bitcoin/node/protocols/protocol_block_in.hpp b/include/bitcoin/node/protocols/protocol_block_in.hpp index 5464cf09..45284c4a 100644 --- a/include/bitcoin/node/protocols/protocol_block_in.hpp +++ b/include/bitcoin/node/protocols/protocol_block_in.hpp @@ -40,12 +40,12 @@ class BCN_API protocol_block_in const channel_ptr& channel, bool report_performance) NOEXCEPT : node::protocol(session, channel), network::tracker(session.log), - report_performance_(report_performance && - to_bool(session.config().node.sample_period_seconds)), + ////report_performance_(report_performance && + //// to_bool(session.config().node.sample_period_seconds)), block_type_(session.config().network.witness_node() ? - type_id::witness_block : type_id::block), - performance_timer_(std::make_shared(session.log, - channel->strand(), session.config().node.sample_period())) + type_id::witness_block : type_id::block) + ////performance_timer_(std::make_shared(session.log, + //// channel->strand(), session.config().node.sample_period())) { } BC_POP_WARNING() @@ -73,11 +73,11 @@ class BCN_API protocol_block_in const network::messages::block::cptr& message, const track_ptr& tracker) NOEXCEPT; - /// Handle performance timer event. - virtual void handle_performance_timer(const code& ec) NOEXCEPT; + /////// Handle performance timer event. + ////virtual void handle_performance_timer(const code& ec) NOEXCEPT; - /// Handle result of performance reporting. - virtual void handle_performance(const code& ec) NOEXCEPT; + /////// Handle result of performance reporting. + ////virtual void handle_performance(const code& ec) NOEXCEPT; /// Invoked when initial blocks sync is complete. virtual void complete() NOEXCEPT; @@ -96,17 +96,17 @@ class BCN_API protocol_block_in const network::messages::inventory& message) const NOEXCEPT; - void do_handle_performance(const code& ec) NOEXCEPT; + ////void do_handle_performance(const code& ec) NOEXCEPT; // Thread safe. - const bool report_performance_; + ////const bool report_performance_; const network::messages::inventory::type_id block_type_; // Protected by strand. - uint64_t bytes_{ zero }; + ////uint64_t bytes_{ zero }; system::chain::checkpoint top_{}; - network::steady_clock::time_point start_{}; - network::deadline::ptr performance_timer_; + ////network::steady_clock::time_point start_{}; + ////network::deadline::ptr performance_timer_; }; } // namespace node diff --git a/src/protocols/protocol_block_in.cpp b/src/protocols/protocol_block_in.cpp index 02aff7bc..6e159558 100644 --- a/src/protocols/protocol_block_in.cpp +++ b/src/protocols/protocol_block_in.cpp @@ -49,61 +49,66 @@ BC_PUSH_WARNING(NO_NEW_OR_DELETE) BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) -// Performance polling. -// ---------------------------------------------------------------------------- - -void protocol_block_in::handle_performance_timer(const code& ec) NOEXCEPT -{ - BC_ASSERT_MSG(stranded(), "expected channel strand"); - - if (stopped() || ec == network::error::operation_canceled) - return; - - if (ec) - { - LOGF("Performance timer error, " << ec.message()); - stop(ec); - return; - } - - // Compute rate in bytes per second. - const auto now = steady_clock::now(); - const auto gap = std::chrono::duration_cast(now - start_).count(); - const auto rate = floored_divide(bytes_, to_unsigned(gap)); - - // Reset counters and log rate. - bytes_ = zero; - start_ = now; - log.fire(event_block, rate); - - // Bounces to network strand, performs work, then calls handler. - // Channel will continue to process blocks while this call excecutes on the - // network strand. Timer will not be restarted until this call completes. - performance(identifier(), rate, BIND1(handle_performance, ec)); -} - -void protocol_block_in::handle_performance(const code& ec) NOEXCEPT -{ - POST1(do_handle_performance, ec); -} - -void protocol_block_in::do_handle_performance(const code& ec) NOEXCEPT -{ - BC_ASSERT_MSG(stranded(), "expected network strand"); - - if (stopped()) - return; - - // stalled_channel or slow_channel - if (ec) - { - LOGF("Performance action, " << ec.message()); - stop(ec); - return; - }; - - performance_timer_->start(BIND1(handle_performance_timer, _1)); -} +////// Performance polling. +////// ---------------------------------------------------------------------------- +//// +////void protocol_block_in::handle_performance_timer(const code& ec) NOEXCEPT +////{ +//// BC_ASSERT_MSG(stranded(), "expected channel strand"); +//// +//// if (stopped() || ec == network::error::operation_canceled) +//// return; +//// +//// if (ec) +//// { +//// LOGF("Performance timer error, " << ec.message()); +//// stop(ec); +//// return; +//// } +//// +//// // Compute rate in bytes per second. +//// const auto now = steady_clock::now(); +//// const auto gap = std::chrono::duration_cast(now - start_).count(); +//// const auto rate = floored_divide(bytes_, gap); +//// LOGN("Rate [" +//// << identifier() << "] (" +//// << bytes_ << "/" +//// << gap << " = " +//// << rate << ")."); +//// +//// // Reset counters and log rate. +//// bytes_ = zero; +//// start_ = now; +//// ////log.fire(event_block, rate); +//// +//// // Bounces to network strand, performs work, then calls handler. +//// // Channel will continue to process blocks while this call excecutes on the +//// // network strand. Timer will not be restarted until this call completes. +//// performance(identifier(), rate, BIND1(handle_performance, ec)); +////} +//// +////void protocol_block_in::handle_performance(const code& ec) NOEXCEPT +////{ +//// POST1(do_handle_performance, ec); +////} +//// +////void protocol_block_in::do_handle_performance(const code& ec) NOEXCEPT +////{ +//// BC_ASSERT_MSG(stranded(), "expected network strand"); +//// +//// if (stopped()) +//// return; +//// +//// // stalled_channel or slow_channel +//// if (ec) +//// { +//// LOGF("Performance action, " << ec.message()); +//// stop(ec); +//// return; +//// }; +//// +//// performance_timer_->start(BIND1(handle_performance_timer, _1)); +////} // Start/stop. // ---------------------------------------------------------------------------- @@ -119,11 +124,11 @@ void protocol_block_in::start() NOEXCEPT const auto top = query.get_top_candidate(); top_ = { query.get_header_key(query.to_candidate(top)), top }; - if (report_performance_) - { - start_ = steady_clock::now(); - performance_timer_->start(BIND1(handle_performance_timer, _1)); - } + ////if (report_performance_) + ////{ + //// start_ = steady_clock::now(); + //// performance_timer_->start(BIND1(handle_performance_timer, _1)); + ////} // There is one persistent common inventory subscription. SUBSCRIBE_CHANNEL2(inventory, handle_receive_inventory, _1, _2); @@ -134,7 +139,7 @@ void protocol_block_in::start() NOEXCEPT void protocol_block_in::stopping(const code& ec) NOEXCEPT { BC_ASSERT_MSG(stranded(), "protocol_block_in"); - performance_timer_->stop(); + ////performance_timer_->stop(); protocol::stopping(ec); } @@ -221,8 +226,8 @@ bool protocol_block_in::handle_receive_block(const code& ec, LOGP("Block [" << encode_hash(top_.hash()) << "] at (" << top_.height() << ") from [" << authority() << "]."); - // Accumulate byte count. - bytes_ += message->cached_size; + ////// Accumulate byte count. + ////bytes_ += message->cached_size; // Order is reversed, so next is at back. tracker->hashes.pop_back();