diff --git a/include/bitcoin/node/chase.hpp b/include/bitcoin/node/chase.hpp index 07d74af1..7a10f957 100644 --- a/include/bitcoin/node/chase.hpp +++ b/include/bitcoin/node/chase.hpp @@ -68,13 +68,17 @@ enum class chase /// Candidate Chain. /// ----------------------------------------------------------------------- - /// Legacy: A new strong branch exists (height_t). + /// A candidate header has been disassociated due to malleation (header_t). + /// Issued by 'header' and handled by 'check'. + header, + + /// A new candidate branch exists from given branch point (height_t). /// Issued by 'block' and handled by 'confirm'. - block, + blocks, - /// A new candidate branch exists (height_t). + /// A new candidate branch exists from given branch point (height_t). /// Issued by 'header' and handled by 'check'. - header, + headers, /// New candidate headers without txs exist (count_t). /// Issued by 'check' and handled by 'block_in_31800'. diff --git a/include/bitcoin/node/chasers/chaser.hpp b/include/bitcoin/node/chasers/chaser.hpp index ce2e646c..3b9ce538 100644 --- a/include/bitcoin/node/chasers/chaser.hpp +++ b/include/bitcoin/node/chasers/chaser.hpp @@ -125,15 +125,23 @@ class BCN_API chaser /// Header timestamp is within configured span from current time. bool is_current(uint32_t timestamp) const NOEXCEPT; - /// Position keeping. + /// Bypass (requires strand). /// ----------------------------------------------------------------------- - size_t& position() NOEXCEPT; + size_t bypass() const NOEXCEPT; + void set_bypass(size_t height) NOEXCEPT; + bool is_bypassed(size_t height) const NOEXCEPT; + + /// Position (requires strand). + /// ----------------------------------------------------------------------- + + size_t position() const NOEXCEPT; void set_position(size_t height) NOEXCEPT; private: - // This is protected by strand. - size_t position_; + // These are protected by strand. + size_t bypass_{}; + size_t position_{}; // These are thread safe (mostly). full_node& node_; diff --git a/include/bitcoin/node/chasers/chaser_check.hpp b/include/bitcoin/node/chasers/chaser_check.hpp index f738e680..7d32b2a7 100644 --- a/include/bitcoin/node/chasers/chaser_check.hpp +++ b/include/bitcoin/node/chasers/chaser_check.hpp @@ -20,6 +20,7 @@ #define LIBBITCOIN_NODE_CHASERS_CHASER_CHECK_HPP #include +#include #include #include #include @@ -46,6 +47,7 @@ class BCN_API chaser_check /// Initialize chaser state. code start() NOEXCEPT override; + void stopping(const code& ec) NOEXCEPT override; /// Interface for protocols to obtain/return pending download identifiers. /// Identifiers not downloaded must be returned or chain will remain gapped. @@ -54,14 +56,16 @@ class BCN_API chaser_check network::result_handler&& handler) NOEXCEPT; protected: + virtual void handle_purged(const code& ec) NOEXCEPT; virtual bool handle_event(const code& ec, chase event_, event_value value) NOEXCEPT; - virtual void do_bump(height_t branch_point) NOEXCEPT; - virtual void do_header(height_t branch_point) NOEXCEPT; + virtual void do_bump(height_t height) NOEXCEPT; + virtual void do_header(header_t height) NOEXCEPT; virtual void do_checked(height_t height) NOEXCEPT; + virtual void do_headers(height_t branch_point) NOEXCEPT; virtual void do_regressed(height_t branch_point) NOEXCEPT; - virtual void do_malleated(header_t link) NOEXCEPT; + virtual void do_handle_purged(const code& ec) NOEXCEPT; virtual void do_get_hashes(const map_handler& handler) NOEXCEPT; virtual void do_put_hashes(const map_ptr& map, const network::result_handler& handler) NOEXCEPT; @@ -70,8 +74,13 @@ class BCN_API chaser_check typedef std::deque maps; map_ptr get_map() NOEXCEPT; - size_t get_unassociated() NOEXCEPT; + size_t set_unassociated() NOEXCEPT; size_t get_inventory_size() const NOEXCEPT; + bool set_map(const map_ptr& map) NOEXCEPT; + + void start_tracking() NOEXCEPT; + void stop_tracking() NOEXCEPT; + bool purging() const NOEXCEPT; // These are thread safe. const size_t maximum_concurrency_; @@ -81,6 +90,7 @@ class BCN_API chaser_check // These are protected by strand. size_t inventory_{}; size_t requested_{}; + job::ptr job_{}; maps maps_{}; }; diff --git a/include/bitcoin/node/chasers/chaser_confirm.hpp b/include/bitcoin/node/chasers/chaser_confirm.hpp index 6af41fce..19d511ec 100644 --- a/include/bitcoin/node/chasers/chaser_confirm.hpp +++ b/include/bitcoin/node/chasers/chaser_confirm.hpp @@ -45,7 +45,6 @@ class BCN_API chaser_confirm virtual bool handle_event(const code& ec, chase event_, event_value value) NOEXCEPT; - virtual void do_bypass(size_t height) NOEXCEPT; virtual void do_validated(height_t height) NOEXCEPT; private: @@ -57,11 +56,6 @@ class BCN_API chaser_confirm height_t fork_top) const NOEXCEPT; bool get_is_strong(bool& strong, const uint256_t& fork_work, size_t fork_point) const NOEXCEPT; - - bool is_under_bypass(size_t height) const NOEXCEPT; - - // This is protected by strand. - size_t bypass_{}; }; } // namespace node diff --git a/include/bitcoin/node/chasers/chaser_organize.hpp b/include/bitcoin/node/chasers/chaser_organize.hpp index 7d9729da..7987264b 100644 --- a/include/bitcoin/node/chasers/chaser_organize.hpp +++ b/include/bitcoin/node/chasers/chaser_organize.hpp @@ -94,20 +94,20 @@ class chaser_organize virtual bool handle_event(const code&, chase event_, event_value value) NOEXCEPT; - /// Reorganize following Block unconfirmability. - virtual void do_disorganize(header_t header) NOEXCEPT; - /// Reorganize following strong branch discovery. virtual void do_organize(typename Block::cptr& block_ptr, const organize_handler& handler) NOEXCEPT; + /// Reorganize following Block unconfirmability. + virtual void do_disorganize(header_t header) NOEXCEPT; + + /// Disassociate malleated block and notify repeat header in current job. + virtual void do_malleated(header_t link) NOEXCEPT; + /// Store Block to database and push to top of candidate chain. virtual database::header_link push(const Block& block, const system::chain::context& context) const NOEXCEPT; - /// Height represents a candidate block covered by checkpoint or milestone. - virtual inline bool is_under_bypass(size_t height) const NOEXCEPT; - /// Height represents a candidate block covered by active milestone. virtual inline bool is_under_milestone(size_t height) const NOEXCEPT; @@ -130,7 +130,7 @@ class chaser_organize } static constexpr auto chase_object() NOEXCEPT { - return is_block() ? chase::block : chase::header; + return is_block() ? chase::blocks : chase::headers; } // Chain methods. @@ -159,9 +159,6 @@ class chaser_organize // Bypass methods. // ------------------------------------------------------------------------ - // The current bypass height. - inline size_t bypass_height() const NOEXCEPT; - // Set milestone cache if exists in candidate chain, send chase::bypass. bool initialize_bypass() NOEXCEPT; diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index 2ba4b8f6..87a1d87a 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -48,7 +48,6 @@ class BCN_API chaser_validate virtual void do_regressed(height_t branch_point) NOEXCEPT; virtual void do_checked(height_t height) NOEXCEPT; virtual void do_bump(height_t height) NOEXCEPT; - virtual void do_bypass(size_t height) NOEXCEPT; virtual bool enqueue_block(const database::header_link& link) NOEXCEPT; virtual void validate_tx(const database::context& context, @@ -68,15 +67,12 @@ class BCN_API chaser_validate #endif // UNDEFINED // neutrino + void update_position(size_t height) NOEXCEPT; system::hash_digest get_neutrino(size_t height) const NOEXCEPT; bool update_neutrino(const database::header_link& link) NOEXCEPT; bool update_neutrino(const database::header_link& link, const system::chain::block& block) NOEXCEPT; - // positions - void update_position(size_t height) NOEXCEPT; - bool is_under_bypass(size_t height) const NOEXCEPT; - // These are thread safe. const uint64_t initial_subsidy_; const uint32_t subsidy_interval_blocks_; @@ -84,7 +80,6 @@ class BCN_API chaser_validate // These are protected by strand. network::threadpool threadpool_; system::hash_digest neutrino_{}; - size_t bypass_{}; }; } // namespace node diff --git a/include/bitcoin/node/define.hpp b/include/bitcoin/node/define.hpp index f3f710e0..ba6feca9 100644 --- a/include/bitcoin/node/define.hpp +++ b/include/bitcoin/node/define.hpp @@ -57,9 +57,11 @@ typedef std::function organize_handler; typedef database::store store; typedef database::query query; -/// Hash accumulator. +/// Work types. +typedef network::race_all job; typedef std::shared_ptr map_ptr; -typedef std::function map_handler; +typedef std::function map_handler; /// Node events. typedef uint64_t object_key; diff --git a/include/bitcoin/node/impl/chasers/chaser_organize.ipp b/include/bitcoin/node/impl/chasers/chaser_organize.ipp index 97a2c8c9..4eaf0f4e 100644 --- a/include/bitcoin/node/impl/chasers/chaser_organize.ipp +++ b/include/bitcoin/node/impl/chasers/chaser_organize.ipp @@ -122,6 +122,11 @@ bool CLASS::handle_event(const code&, chase event_, event_value value) NOEXCEPT POST(do_disorganize, possible_narrow_cast(value)); break; } + case chase::malleated: + { + POST(do_malleated, possible_narrow_cast(value)); + break; + } case chase::stop: { return false; @@ -282,14 +287,6 @@ void CLASS::do_organize(typename Block::cptr& block_ptr, return; } - // Reorganization, otherwise organization (branch point is top candidate). - // Organize chanser owns the candidate index and can organize it freely. - if (branch_point < top_candidate) - { - // Implies all blocks above branch are weak (clear downloads and wait). - notify(error::success, chase::regressed, branch_point); - } - // Pop down to the branch point. auto index = top_candidate; while (index > branch_point) @@ -303,7 +300,12 @@ void CLASS::do_organize(typename Block::cptr& block_ptr, fire(events::header_reorganized, index--); } - // branch_point + 1 + // BUGBUG: this is insufficient because downloads race ahead. + // BUGBUG: the new branch can become ordered and downloaded under the old + // BUGBUG: milestone while the new is pending in the notification queue. + // BUGBUG: probably need to provide both fork point and old top. + + // branch_point reset_milestone(++index); // Push stored strong headers to candidate chain. @@ -360,10 +362,19 @@ void CLASS::do_organize(typename Block::cptr& block_ptr, // arrivals. This bumps validation for current strong headers. notify(error::success, chase::bump, add1(branch_point)); + // This is just to prevent stall, the check chaser races ahead. // Start block downloads, which upon completion bumps validation. notify(error::success, chase_object(), branch_point); } + // Check chaser may be working on any of the blocks, and subsequent until + // it receives this message. That will reset to the branch point, but the + // work on the new branch is usable. + if (branch_point < top_candidate) + { + notify(error::success, chase::regressed, branch_point); + } + // Logs from candidate block parent to the candidate (forward sequential). log_state_change(*parent, *state); state_ = state; @@ -388,7 +399,7 @@ void CLASS::do_disorganize(header_t link) NOEXCEPT // If header is not a current candidate it has been reorganized out. // If header becomes candidate again its unconfirmable state is handled. auto& query = archive(); - if (!query.is_candidate_block(link)) + if (!query.is_candidate_header(link)) return; size_t height{}; @@ -434,11 +445,6 @@ void CLASS::do_disorganize(header_t link) NOEXCEPT cache(block_ptr, state); } - // Notify check/validate/confirm to stop confirming. - // Organize chanser owns the candidate index and can organize it freely. - const auto top_confirmed = query.get_top_confirmed(); - notify(error::success, chase::disorganized, top_confirmed); - // Pop candidates from top candidate down to above fork point. // ........................................................................ // Can't pop in loop above because state chaining requires forward order. @@ -456,11 +462,16 @@ void CLASS::do_disorganize(header_t link) NOEXCEPT fire(events::header_reorganized, index); } + // BUGBUG: this is insufficient because downloads race ahead. + // BUGBUG: the new branch can become ordered and downloaded under the old + // BUGBUG: milestone while the new is pending in the notification queue. + // BUGBUG: probably need to provide both fork point and old top. reset_milestone(fork_point); // Push confirmed headers from above fork point onto candidate chain. // ........................................................................ + const auto top_confirmed = query.get_top_confirmed(); for (auto index = add1(fork_point); index <= top_confirmed; ++index) { const auto confirmed = query.to_confirmed(index); @@ -481,11 +492,43 @@ void CLASS::do_disorganize(header_t link) NOEXCEPT return; } + // Check chaser may be working on any of the blocks, and subsequent until + // it receives this message. That will reset to the branch point, but the + // work on the new branch is usable. + notify(error::success, chase::disorganized, fork_point); + // Logs from previous top candidate to previous fork point (jumps back). log_state_change(*state_, *state); state_ = state; } +// The archived malleable block was found to be invalid (treat as malleated). +// The block/header hash cannot be marked unconfirmable due to malleability, so +// disassociate the block and then notify check chaser to reisuse the download. +// This must be issued here in order to ensure proper bypass/regress ordering. +TEMPLATE +void CLASS::do_malleated(header_t link) NOEXCEPT +{ + BC_ASSERT(stranded()); + auto& query = archive(); + + // If not disassociated, validation/confirmation will be reattemted. + // This could happen due to shutdown before this step is completed. + if (!query.set_dissasociated(link)) + { + fault(error::set_dissasociated); + return; + } + + // Header is no longer in the candidate chain, so do not announce. + if (!query.is_candidate_header(link)) + return; + + // Announce a singleton header that requires download. + // Since it is in the candidate chain, it must presently be missing. + notify(error::success, chase::header, link); +} + // Private // ---------------------------------------------------------------------------- @@ -540,7 +583,7 @@ bool CLASS::get_branch_work(uint256_t& work, size_t& branch_point, // Sum branch work from store. database::height_link link{}; - for (link = query.to_header(*previous); !query.is_candidate_block(link); + for (link = query.to_header(*previous); !query.is_candidate_header(link); link = query.to_parent(link)) { uint32_t bits{}; @@ -628,19 +671,6 @@ inline bool CLASS::is_under_milestone(size_t height) const NOEXCEPT return height <= active_milestone_height_; } -// protected -TEMPLATE -inline bool CLASS::is_under_bypass(size_t height) const NOEXCEPT -{ - return height <= bypass_height(); -} - -TEMPLATE -inline size_t CLASS::bypass_height() const NOEXCEPT -{ - return std::max(active_milestone_height_, top_checkpoint_height_); -} - TEMPLATE bool CLASS::initialize_bypass() NOEXCEPT { @@ -704,7 +734,8 @@ void CLASS::update_milestone(const system::hash_digest& hash, TEMPLATE void CLASS::notify_bypass() const NOEXCEPT { - notify(error::success, chase::bypass, bypass_height()); + notify(error::success, chase::bypass, + std::max(active_milestone_height_, top_checkpoint_height_)); } // Logging. diff --git a/include/bitcoin/node/protocols/protocol_block_in_31800.hpp b/include/bitcoin/node/protocols/protocol_block_in_31800.hpp index 19b30ac7..94e5c076 100644 --- a/include/bitcoin/node/protocols/protocol_block_in_31800.hpp +++ b/include/bitcoin/node/protocols/protocol_block_in_31800.hpp @@ -66,7 +66,6 @@ class BCN_API protocol_block_in_31800 virtual void do_purge(channel_t) NOEXCEPT; virtual void do_split(channel_t) NOEXCEPT; virtual void do_report(count_t count) NOEXCEPT; - virtual void do_bypass(height_t height) NOEXCEPT; /// Check incoming block message. virtual bool handle_receive_block(const code& ec, @@ -75,25 +74,30 @@ class BCN_API protocol_block_in_31800 private: using type_id = network::messages::inventory::type_id; - bool is_under_bypass(size_t height) const NOEXCEPT; code check(const system::chain::block& block, const system::chain::context& ctx, bool bypass) const NOEXCEPT; - void send_get_data(const map_ptr& map) NOEXCEPT; + void send_get_data(const map_ptr& map, const job::ptr& job, + size_t bypass) NOEXCEPT; network::messages::get_data create_get_data( const map_ptr& map) const NOEXCEPT; void restore(const map_ptr& map) NOEXCEPT; - void handle_put_hashes(const code& ec, size_t count) NOEXCEPT; - void handle_get_hashes(const code& ec, const map_ptr& map) NOEXCEPT; void do_handle_complete(const code& ec) NOEXCEPT; + void handle_put_hashes(const code& ec, size_t count) NOEXCEPT; + void handle_get_hashes(const code& ec, const map_ptr& map, + const job::ptr& job, size_t bypass) NOEXCEPT; + + void set_bypass(height_t height) NOEXCEPT; + bool is_bypassed(size_t height) const NOEXCEPT; // This is thread safe. const network::messages::inventory::type_id block_type_; // These are protected by strand. map_ptr map_; - size_t bypass_{ max_size_t }; + job::ptr job_{}; + size_t bypass_{}; }; } // namespace node diff --git a/src/chasers/chaser.cpp b/src/chasers/chaser.cpp index 8979b8ae..1b8674dc 100644 --- a/src/chasers/chaser.cpp +++ b/src/chasers/chaser.cpp @@ -129,19 +129,42 @@ bool chaser::is_current(uint32_t timestamp) const NOEXCEPT return node_.is_current(timestamp); } -// Position. +// Bypass. // ---------------------------------------------------------------------------- -void chaser::set_position(size_t height) NOEXCEPT +size_t chaser::bypass() const NOEXCEPT { - position_ = height; + BC_ASSERT(stranded()); + return bypass_; } -size_t& chaser::position() NOEXCEPT +void chaser::set_bypass(height_t height) NOEXCEPT { + BC_ASSERT(stranded()); + bypass_ = height; +} + +bool chaser::is_bypassed(size_t height) const NOEXCEPT +{ + BC_ASSERT(stranded()); + return height <= bypass_; +} + +// Position. +// ---------------------------------------------------------------------------- + +size_t chaser::position() const NOEXCEPT +{ + BC_ASSERT(stranded()); return position_; } +void chaser::set_position(size_t height) NOEXCEPT +{ + BC_ASSERT(stranded()); + position_ = height; +} + BC_POP_WARNING() } // namespace node diff --git a/src/chasers/chaser_check.cpp b/src/chasers/chaser_check.cpp index 276e090e..cec4a555 100644 --- a/src/chasers/chaser_check.cpp +++ b/src/chasers/chaser_check.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -65,20 +66,28 @@ map_ptr chaser_check::split(const map_ptr& map) NOEXCEPT return half; } -// start +// start/stop // ---------------------------------------------------------------------------- code chaser_check::start() NOEXCEPT { + start_tracking(); set_position(archive().get_fork()); requested_ = position(); - const auto add = get_unassociated(); - LOGN("Fork point (" << requested_ << ") unassociated (" << add << ")."); + const auto added = set_unassociated(); + LOGN("Fork point (" << requested_ << ") unassociated (" << added << ")."); SUBSCRIBE_EVENTS(handle_event, _1, _2, _3); return error::success; } +void chaser_check::stopping(const code& ec) NOEXCEPT +{ + // Allow job completion as soon as all protocols are closed. + stop_tracking(); + chaser::stopping(ec); +} + bool chaser_check::handle_event(const code&, chase event_, event_value value) NOEXCEPT { @@ -111,15 +120,19 @@ bool chaser_check::handle_event(const code&, chase event_, break; } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - + case chase::bypass: + { + POST(set_bypass, possible_narrow_cast(value)); + break; + } case chase::header: { - POST(do_header, possible_narrow_cast(value)); + POST(do_header, possible_narrow_cast(value)); break; } - case chase::malleated: + case chase::headers: { - POST(do_malleated, possible_narrow_cast(value)); + POST(do_headers, possible_narrow_cast(value)); break; } case chase::stop: @@ -135,21 +148,65 @@ bool chaser_check::handle_event(const code&, chase event_, return true; } -// track downloaded in order (to move download window) +// regression // ---------------------------------------------------------------------------- void chaser_check::do_regressed(height_t branch_point) NOEXCEPT { BC_ASSERT(stranded()); + // Inconsequential regression, work isn't there yet. if (branch_point >= position()) return; - // Update position, purge outstanding work, and wait. + // Update position, purge outstanding work, and wait on track completion. set_position(branch_point); + stop_tracking(); maps_.clear(); notify(error::success, chase::purge, branch_point); } + +void chaser_check::start_tracking() NOEXCEPT +{ + // Called from start. + ////BC_ASSERT(stranded()); + + job_ = std::make_shared(BIND(handle_purged, _1)); +} + +void chaser_check::stop_tracking() NOEXCEPT +{ + BC_ASSERT(stranded()); + + // Resetting our own pointer allows destruct and call to handle_purged. + job_.reset(); +} + +void chaser_check::handle_purged(const code& ec) NOEXCEPT +{ + if (closed()) + return; + + boost::asio::post(strand(), + std::bind(&chaser_check::do_handle_purged, + this, ec)); +} + +void chaser_check::do_handle_purged(const code&) NOEXCEPT +{ + BC_ASSERT(stranded()); + + // TODO: set_unstrong(link) where link of all associated and not malleable + // TODO: from min(candidate_top, bypass) to > branch_point (do_regressed). + // TODO: cannot rely on height index. Probably need to notify with range. + + start_tracking(); + do_bump(height_t{}); +} + +// track downloaded in order (to move download window) +// ---------------------------------------------------------------------------- + void chaser_check::do_checked(height_t height) NOEXCEPT { BC_ASSERT(stranded()); @@ -162,57 +219,58 @@ void chaser_check::do_checked(height_t height) NOEXCEPT void chaser_check::do_bump(height_t) NOEXCEPT { BC_ASSERT(stranded()); + if (purging()) + return; + const auto& query = archive(); // TODO: query.is_associated() is expensive (hashmap search). // Skip checked blocks starting immediately after last checked. while (!closed() && query.is_associated( query.to_candidate(add1(position())))) - ++position(); + set_position(add1(position())); - get_unassociated(); + set_unassociated(); } // add headers // ---------------------------------------------------------------------------- -void chaser_check::do_header(height_t) NOEXCEPT +void chaser_check::do_headers(height_t) NOEXCEPT { BC_ASSERT(stranded()); - const auto add = get_unassociated(); - if (!is_zero(add)) - notify(error::success, chase::download, add); + + const auto added = set_unassociated(); + if (!is_zero(added)) + notify(error::success, chase::download, added); } -// re-download malleated block (invalid but malleable) // The archived malleable block was found to be invalid (treat as malleated). -// The block/header hash cannot be marked unconfirmable due to malleability, so -// disassociate the block and then add the block hash back to the current set. -void chaser_check::do_malleated(header_t link) NOEXCEPT +void chaser_check::do_header(header_t link) NOEXCEPT { BC_ASSERT(stranded()); - auto& query = archive(); association out{}; - if (!query.set_dissasociated(link)) - { - fault(error::set_dissasociated); - return; - } - - if (!query.get_unassociated(out, link)) + if (!archive().get_unassociated(out, link)) { fault(error::get_unassociated); return; } - maps_.push_back(std::make_shared(associations{ out })); - notify(error::success, chase::download, one); + // Add even if purging, as this header applies to the subsequent rage. + const auto map = std::make_shared(associations{ out }); + if (set_map(map) && !purging()) + notify(error::success, chase::download, one); } // get/put hashes // ---------------------------------------------------------------------------- +bool chaser_check::purging() const NOEXCEPT +{ + return !job_; +} + void chaser_check::get_hashes(map_handler&& handler) NOEXCEPT { if (closed()) @@ -237,26 +295,25 @@ void chaser_check::put_hashes(const map_ptr& map, void chaser_check::do_get_hashes(const map_handler& handler) NOEXCEPT { BC_ASSERT(stranded()); - if (closed()) + if (closed() || purging()) return; const auto map = get_map(); - handler(error::success, map); + handler(error::success, map, job_, bypass()); } +// It is possible that this call can be made before a purge has been sent and +// received after. This may result in unnecessary work and incorrect bypass. void chaser_check::do_put_hashes(const map_ptr& map, const result_handler& handler) NOEXCEPT { BC_ASSERT(map->size() <= messages::max_inventory); BC_ASSERT(stranded()); - if (closed()) + if (closed() || purging()) return; - if (!map->empty()) - { - maps_.push_back(map); + if (set_map(map)) notify(error::success, chase::download, map->size()); - } handler(error::success); } @@ -271,20 +328,31 @@ map_ptr chaser_check::get_map() NOEXCEPT return maps_.empty() ? empty_map() : pop_front(maps_); } +bool chaser_check::set_map(const map_ptr& map) NOEXCEPT +{ + BC_ASSERT(stranded()); + BC_ASSERT(map->size() <= messages::max_inventory); + + if (map->empty()) + return false; + + maps_.push_back(map); + return true; +} + // Get all unassociated block records from start to stop heights. // Groups records into table sets by inventory set size, limited by advance. // Return the total number of records obtained and set requested_ to last. -size_t chaser_check::get_unassociated() NOEXCEPT +size_t chaser_check::set_unassociated() NOEXCEPT { // Called from start. ////BC_ASSERT(stranded()); - size_t count{}; - if (closed()) - return count; + if (closed() || purging()) + return {}; // Defer new work issuance until all gaps are filled. if (position() < requested_ || requested_ >= maximum_height_) - return count; + return {}; // Inventory size gets set only once. if (is_zero(inventory_)) @@ -301,6 +369,7 @@ size_t chaser_check::get_unassociated() NOEXCEPT const auto requested = requested_; const auto step = ceilinged_add(position(), maximum_concurrency_); const auto stop = std::min(step, maximum_height_); + size_t count{}; while (true) { @@ -308,11 +377,9 @@ size_t chaser_check::get_unassociated() NOEXCEPT const auto map = std::make_shared( query.get_unassociated_above(requested_, inventory_, stop)); - if (map->empty()) + if (!set_map(map)) break; - BC_ASSERT(map->size() <= messages::max_inventory); - maps_.push_back(map); requested_ = map->top().height; count += map->size(); } diff --git a/src/chasers/chaser_confirm.cpp b/src/chasers/chaser_confirm.cpp index 3fb8e20a..a281d66d 100644 --- a/src/chasers/chaser_confirm.cpp +++ b/src/chasers/chaser_confirm.cpp @@ -61,19 +61,21 @@ bool chaser_confirm::handle_event(const code&, chase event_, // These can come out of order, advance in order synchronously. switch (event_) { - case chase::block: + case chase::blocks: { + // TODO: value is branch point. POST(do_validated, possible_narrow_cast(value)); break; } case chase::valid: { + // value is individual height. POST(do_validated, possible_narrow_cast(value)); break; } case chase::bypass: { - POST(do_bypass, possible_narrow_cast(value)); + POST(set_bypass, possible_narrow_cast(value)); break; } case chase::stop: @@ -173,12 +175,7 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT // Push candidate headers to confirmed chain. for (const auto& link: views_reverse(fork)) { - // Precondition (established by fork construction above). - // .................................................................... - - // Confirm block. - // .................................................................... - + // TODO: skip under bypass and not malleable? auto ec = query.get_block_state(link); if (ec == database::error::integrity) { @@ -186,6 +183,7 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT return; } + // TODO: rollback required. if (ec == database::error::block_unconfirmable) { notify(ec, chase::unconfirmable, link); @@ -195,9 +193,10 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT const auto malleable64 = query.is_malleable64(link); + // TODO: set organized. // error::confirmation_bypass is not used. if (ec == database::error::block_confirmable || - (is_under_bypass(index) && !malleable64)) + (is_bypassed(index) && !malleable64)) { notify(ec, chase::confirmable, index); fire(events::confirm_bypassed, index); @@ -213,16 +212,11 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT if (ec) { - // Transactions are set strong upon archive when under bypass. - if (is_under_bypass(height)) + // TODO: rollback required. + // Transactions are set strong upon archive when under bypass. Only + // malleable blocks are validated under bypass, and not set strong. + if (is_bypassed(height)) { - if (!query.set_unstrong(link)) - { - fault(error::node_confirm); - return; - } - - // Must be malleable64 if validated when under bypass. LOGR("Malleated64 block [" << index << "] " << ec.message()); notify(ec, chase::malleated, link); fire(events::block_malleated, index); @@ -250,7 +244,8 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT return; } - // TODO: compute fees from validation records (optional metadata). + // TODO: compute fees from validation records. + if (!query.set_block_confirmable(link, uint64_t{})) { fault(error::block_confirmable); @@ -368,23 +363,6 @@ bool chaser_confirm::get_is_strong(bool& strong, const uint256_t& fork_work, return true; } -// bypass -// ---------------------------------------------------------------------------- -// Bypassed confirmation checks are implemented in download protocol. Above the -// bypass point the confirmation chaser takes over. - -// protected -void chaser_confirm::do_bypass(height_t height) NOEXCEPT -{ - BC_ASSERT(stranded()); - bypass_ = height; -} - -bool chaser_confirm::is_under_bypass(size_t height) const NOEXCEPT -{ - return height <= bypass_; -} - BC_POP_WARNING() } // namespace node diff --git a/src/chasers/chaser_snapshot.cpp b/src/chasers/chaser_snapshot.cpp index 1c32b998..f171ca7d 100644 --- a/src/chasers/chaser_snapshot.cpp +++ b/src/chasers/chaser_snapshot.cpp @@ -76,12 +76,12 @@ bool chaser_snapshot::handle_event(const code& ec, chase event_, switch (event_) { - case chase::block: + case chase::blocks: case chase::checked: { if (!enabled_bytes_ || ec) break; - // Checked blocks are our of order, so this is probalistic. + // Checked blocks are out of order, so this is probalistic. POST(do_archive, possible_narrow_cast(value)); break; } diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 6fe60245..b481752e 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -96,7 +96,7 @@ bool chaser_validate::handle_event(const code&, chase event_, // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ case chase::bypass: { - POST(do_bypass, possible_narrow_cast(value)); + POST(set_bypass, possible_narrow_cast(value)); break; } case chase::stop: @@ -175,7 +175,7 @@ void chaser_validate::do_bump(height_t) NOEXCEPT // error::validation_bypass is not used because fan-out. if (ec == database::error::block_valid || ec == database::error::block_confirmable || - (is_under_bypass(height) && !query.is_malleable64(link))) + (is_bypassed(height) && !query.is_malleable64(link))) { update_position(height); notify(ec, chase::valid, height); @@ -342,35 +342,39 @@ void chaser_validate::validate_block(const code& ec, const header_link& link, const database::context& ctx) NOEXCEPT { BC_ASSERT(stranded()); + auto& query = archive(); if (ec) { - auto& query = archive(); - - // Transactions are set strong upon archive when under bypass. - if (is_under_bypass(ctx.height)) + // Transactions are set strong upon archive when under bypass. Only + // malleable blocks are validated under bypass, and not set strong. + if (is_bypassed(ctx.height)) { - if (!query.set_unstrong(link)) - { - fault(error::node_validate); - return; - } - - // Must be malleable64 if validated when under bypass. LOGR("Malleated64 block [" << ctx.height << "] " << ec.message()); notify(ec, chase::malleated, link); fire(events::block_malleated, ctx.height); return; } + if (!query.set_block_unconfirmable(link)) + { + fault(error::set_block_unconfirmable); + return; + } + LOGR("Unconfirmable block [" << ctx.height << "] " << ec.message()); notify(ec, chase::unconfirmable, link); fire(events::block_unconfirmable, ctx.height); return; } - // TODO: - // Collect up tx fees and sigops, etc. for block validate with no block. + if (!query.set_block_valid(link)) + { + fault(error::set_block_valid); + return; + } + + // TODO: collect fees and sigops for block validate with no block. // fire event first so that log is ordered. fire(events::block_validated, ctx.height); @@ -432,21 +436,8 @@ bool chaser_validate::update_neutrino(const header_link& link, return query.set_filter(link, neutrino_, filter); } -// position/bypass +// position // ---------------------------------------------------------------------------- -// Bypass accept/connect is a no-op, no metadata is set. - -// protected -void chaser_validate::do_bypass(height_t height) NOEXCEPT -{ - BC_ASSERT(stranded()); - bypass_ = height; -} - -bool chaser_validate::is_under_bypass(size_t height) const NOEXCEPT -{ - return height <= bypass_; -} void chaser_validate::update_position(size_t height) NOEXCEPT { diff --git a/src/protocols/protocol_block_in_31800.cpp b/src/protocols/protocol_block_in_31800.cpp index d083446b..7b30ed1b 100644 --- a/src/protocols/protocol_block_in_31800.cpp +++ b/src/protocols/protocol_block_in_31800.cpp @@ -80,7 +80,7 @@ void protocol_block_in_31800::do_handle_complete(const code& ec) NOEXCEPT if (is_current()) { start_performance(); - get_hashes(BIND(handle_get_hashes, _1, _2)); + get_hashes(BIND(handle_get_hashes, _1, _2, _3, _4)); } } @@ -145,8 +145,8 @@ bool protocol_block_in_31800::handle_event(const code&, chase event_, case chase::download: { // There are count blocks to download at/above given header. - // chase::header is only sent for current candidate chain, and this - // chase::download is only sent as a consequence of chase::header. + // chase::headers is only sent for current candidate chain, and this + // chase::download is only sent as a consequence of chase::headers. POST(do_get_downloads, possible_narrow_cast(value)); break; } @@ -155,11 +155,6 @@ bool protocol_block_in_31800::handle_event(const code&, chase event_, POST(do_report, possible_narrow_cast(value)); break; } - case chase::bypass: - { - POST(do_bypass, possible_narrow_cast(value)); - break; - } case chase::stop: { return false; @@ -184,7 +179,7 @@ void protocol_block_in_31800::do_get_downloads(count_t) NOEXCEPT { // Assume performance was stopped due to exhaustion. start_performance(); - get_hashes(BIND(handle_get_hashes, _1, _2)); + get_hashes(BIND(handle_get_hashes, _1, _2, _3, _4)); } } @@ -223,16 +218,11 @@ void protocol_block_in_31800::do_report(count_t sequence) NOEXCEPT << authority() << "]."); } -void protocol_block_in_31800::do_bypass(height_t height) NOEXCEPT -{ - BC_ASSERT(stranded()); - bypass_ = height; -} - // request hashes // ---------------------------------------------------------------------------- -void protocol_block_in_31800::send_get_data(const map_ptr& map) NOEXCEPT +void protocol_block_in_31800::send_get_data(const map_ptr& map, + const job::ptr& job, size_t bypass) NOEXCEPT { BC_ASSERT(stranded()); @@ -242,18 +232,20 @@ void protocol_block_in_31800::send_get_data(const map_ptr& map) NOEXCEPT return; } + set_bypass(bypass); if (map->empty()) return; - if (is_idle()) + // There are two populated maps, return new and leave old in place. + if (!is_idle()) { - const auto message = create_get_data((map_ = map)); - SEND(message, handle_send, _1); + restore(map); return; } - // There are two populated maps, return the new and leave the old alone. - restore(map); + job_ = job; + map_ = map; + SEND(create_get_data(map_), handle_send, _1); } get_data protocol_block_in_31800::create_get_data( @@ -319,7 +311,7 @@ bool protocol_block_in_31800::handle_receive_block(const code& ec, // Transaction/witness commitments are required under checkpoint. // This ensures that the block/header hash represents expected txs. - const auto bypass = is_under_bypass(ctx.height) && !malleable64; + const auto bypass = is_bypassed(ctx.height) && !malleable64; // Performs full check if block is mally64 (mally32 caught either way). if (const auto code = check(*block_ptr, ctx, bypass)) @@ -372,7 +364,7 @@ bool protocol_block_in_31800::handle_receive_block(const code& ec, const auto size = block_ptr->serialized_size(true); const chain::transactions_cptr txs_ptr{ block_ptr->transactions_ptr() }; - // Transactions are set strong when bypass is true. + // Transactions are set_strong here when bypass is true. if (const auto code = query.set_code(*txs_ptr, link, size, bypass)) { LOGF("Failure storing block [" << encode_hash(hash) << ":" @@ -395,16 +387,14 @@ bool protocol_block_in_31800::handle_receive_block(const code& ec, count(message->cached_size); map_->erase(it); if (is_idle()) - get_hashes(BIND(handle_get_hashes, _1, _2)); + { + job_.reset(); + get_hashes(BIND(handle_get_hashes, _1, _2, _3, _4)); + } return true; } -bool protocol_block_in_31800::is_under_bypass(size_t height) const NOEXCEPT -{ - return height <= bypass_; -} - code protocol_block_in_31800::check(const chain::block& block, const chain::context& ctx, bool bypass) const NOEXCEPT { @@ -442,7 +432,7 @@ void protocol_block_in_31800::handle_put_hashes(const code& ec, } void protocol_block_in_31800::handle_get_hashes(const code& ec, - const map_ptr& map) NOEXCEPT + const map_ptr& map, const job::ptr& job, size_t bypass) NOEXCEPT { LOGV("Got (" << map->size() << ") work for [" << authority() << "]."); @@ -465,7 +455,22 @@ void protocol_block_in_31800::handle_get_hashes(const code& ec, return; } - POST(send_get_data, map); + POST(send_get_data, map, job, bypass); +} + +// bypass +// ---------------------------------------------------------------------------- + +void protocol_block_in_31800::set_bypass(height_t height) NOEXCEPT +{ + BC_ASSERT(stranded()); + bypass_ = height; +} + +bool protocol_block_in_31800::is_bypassed(size_t height) const NOEXCEPT +{ + BC_ASSERT(stranded()); + return height <= bypass_; } BC_POP_WARNING()