diff --git a/include/bitcoin/node/chasers/chaser_block.hpp b/include/bitcoin/node/chasers/chaser_block.hpp index 6ab1688d9..5fdf7b263 100644 --- a/include/bitcoin/node/chasers/chaser_block.hpp +++ b/include/bitcoin/node/chasers/chaser_block.hpp @@ -48,10 +48,6 @@ class BCN_API chaser_block virtual bool get_block(system::chain::block::cptr& out, size_t height) const NOEXCEPT; - /// True if Block should bypass validation, given its candidate height. - virtual bool get_bypass(const system::chain::block& block, - size_t height) const NOEXCEPT; - /// Determine if Block is valid. virtual code validate(const system::chain::block& block, const chain_state& state) const NOEXCEPT; @@ -62,6 +58,9 @@ class BCN_API chaser_block /// Determine if state is top of a storable branch (always true). virtual bool is_storable(const chain_state& state) const NOEXCEPT; + /// True if Block is on a milestone-covered branch. + virtual bool is_under_milestone(size_t height) const NOEXCEPT; + /// Milestone tracking. virtual void update_milestone(const system::chain::header& header, size_t height, size_t branch_point) NOEXCEPT; diff --git a/include/bitcoin/node/chasers/chaser_confirm.hpp b/include/bitcoin/node/chasers/chaser_confirm.hpp index 3ddabe57c..db0c7038a 100644 --- a/include/bitcoin/node/chasers/chaser_confirm.hpp +++ b/include/bitcoin/node/chasers/chaser_confirm.hpp @@ -48,11 +48,16 @@ class BCN_API chaser_confirm virtual void do_validated(height_t height) NOEXCEPT; private: - bool set_organized(header_t link, height_t height) NOEXCEPT; - bool reset_organized(header_t link, height_t height) NOEXCEPT; - bool set_reorganized(header_t link, height_t height) NOEXCEPT; + bool set_organized(const database::header_link& link, + height_t height) NOEXCEPT; + bool reset_organized(const database::header_link& link, + height_t height) NOEXCEPT; + bool set_reorganized(const database::header_link& link, + height_t height) NOEXCEPT; bool roll_back(const header_links& popped, - size_t fork_point, size_t top) NOEXCEPT; + const database::header_link& link, size_t fork_point, + size_t top) NOEXCEPT; + bool get_fork_work(uint256_t& fork_work, header_links& fork, height_t fork_top) const NOEXCEPT; bool get_is_strong(bool& strong, const uint256_t& fork_work, diff --git a/include/bitcoin/node/chasers/chaser_header.hpp b/include/bitcoin/node/chasers/chaser_header.hpp index 2255fdc44..a3032e40f 100644 --- a/include/bitcoin/node/chasers/chaser_header.hpp +++ b/include/bitcoin/node/chasers/chaser_header.hpp @@ -51,10 +51,6 @@ class BCN_API chaser_header virtual bool get_block(system::chain::header::cptr& out, size_t height) const NOEXCEPT; - /// True if Block should bypass validation, given its candidate height. - virtual bool get_bypass(const system::chain::header& header, - size_t height) const NOEXCEPT; - /// Determine if Block is valid. virtual code validate(const system::chain::header& header, const chain_state& state) const NOEXCEPT; @@ -65,20 +61,19 @@ class BCN_API chaser_header /// Determine if state is top of a storable branch. virtual bool is_storable(const chain_state& state) const NOEXCEPT; + /// True if Block is on a milestone-covered branch. + virtual bool is_under_milestone(size_t height) const NOEXCEPT; + /// Milestone tracking. virtual void update_milestone(const system::chain::header& header, size_t height, size_t branch_point) NOEXCEPT; - /// Milestone methods. - bool initialize_milestone() NOEXCEPT; - bool is_under_milestone(size_t height) const NOEXCEPT; - private: - // Storable methods. bool is_checkpoint(const chain_state& state) const NOEXCEPT; bool is_milestone(const chain_state& state) const NOEXCEPT; bool is_current(const chain_state& state) const NOEXCEPT; bool is_hard(const chain_state& state) const NOEXCEPT; + bool initialize_milestone() NOEXCEPT; // This is thread safe. const system::chain::checkpoint& milestone_; diff --git a/include/bitcoin/node/chasers/chaser_organize.hpp b/include/bitcoin/node/chasers/chaser_organize.hpp index ea54b48ac..420e2673c 100644 --- a/include/bitcoin/node/chasers/chaser_organize.hpp +++ b/include/bitcoin/node/chasers/chaser_organize.hpp @@ -70,10 +70,6 @@ class chaser_organize virtual bool get_block(typename Block::cptr& out, size_t height) const NOEXCEPT = 0; - /// True if Block should bypass validation, given its candidate height. - virtual bool get_bypass(const Block& block, - size_t height) const NOEXCEPT = 0; - /// Determine if Block is valid. virtual code validate(const Block& block, const chain_state& state) const NOEXCEPT = 0; @@ -84,6 +80,9 @@ class chaser_organize /// Determine if state is top of a storable branch. virtual bool is_storable(const chain_state& state) const NOEXCEPT = 0; + /// True if Block is on a milestone-covered branch. + virtual bool is_under_milestone(size_t height) const NOEXCEPT = 0; + /// Milestone tracking. virtual void update_milestone(const system::chain::header& header, size_t height, size_t branch_point) NOEXCEPT = 0; @@ -153,10 +152,10 @@ class chaser_organize size_t branch_point) const NOEXCEPT; // Move tree Block to database and push to top of candidate chain. - bool push_block(const system::hash_digest& key) NOEXCEPT; + code push_block(const system::hash_digest& key) NOEXCEPT; /// Store Block to database and push to top of candidate chain. - bool push_block(const Block& block, + code push_block(const Block& block, const system::chain::context& context) const NOEXCEPT; // Logging. diff --git a/include/bitcoin/node/impl/chasers/chaser_organize.ipp b/include/bitcoin/node/impl/chasers/chaser_organize.ipp index aa984f705..c09126485 100644 --- a/include/bitcoin/node/impl/chasers/chaser_organize.ipp +++ b/include/bitcoin/node/impl/chasers/chaser_organize.ipp @@ -262,6 +262,7 @@ void CLASS::do_organize(typename Block::cptr& block_ptr, // A milestone can only be set within a to-be-archived chain of candidate // headers/blocks. Once the milestone block is archived it is not useful. update_milestone(header, height, branch_point); + code ec{}; const auto top_candidate = state_->height(); if (branch_point > top_candidate) @@ -303,9 +304,9 @@ void CLASS::do_organize(typename Block::cptr& block_ptr, // Store strong tree headers and push to candidate chain. for (const auto& key: views_reverse(tree_branch)) { - if (!push_block(key)) + if ((ec = push_block(key))) { - handler(fault(error::node_push), height); + handler(fault(ec), height); return; } @@ -316,9 +317,9 @@ void CLASS::do_organize(typename Block::cptr& block_ptr, // Push new header as top of candidate chain. { - if (!push_block(*block_ptr, state->context())) + if ((ec = push_block(*block_ptr, state->context()))) { - handler(fault(error::node_push), height); + handler(fault(ec), height); return; } @@ -564,23 +565,45 @@ bool CLASS::get_is_strong(bool& strong, const uint256_t& branch_work, } TEMPLATE -bool CLASS::push_block(const Block& block, +code CLASS::push_block(const Block& block, const system::chain::context& context) const NOEXCEPT { auto& query = archive(); - const auto bypass = get_bypass(block, context.height); + const auto milestone = is_under_milestone(context.height); - // TODO: change this to set_code() and return code. - // TODO: add confirm option to set_code() and pass bypass (to both). - // TODO: block bypass/confirm is checkpoints, headers is also milestone. - return query.push_candidate(query.set_link(block, context, bypass)); + // headers-first sets milestone and set_strong ms or cp and not mealleable. + // blocks-first does not set milestone and set_strong cp not malleable. + const auto strong = [&]() NOEXCEPT + { + if constexpr (is_block()) + { + return is_under_checkpoint(context.height) && + !block.is_malleable64(); + } + else + { + return false; + } + }; + + database::header_link link{}; + const auto ec = query.set_code(link, block, context, milestone, strong()); + if (ec) + return ec; + + if (!query.push_candidate(link)) + return error::push_candidate; + + return ec; } TEMPLATE -bool CLASS::push_block(const system::hash_digest& key) NOEXCEPT +code CLASS::push_block(const system::hash_digest& key) NOEXCEPT { const auto handle = tree_.extract(key); - if (!handle) return false; + if (!handle) + return error::internal_error; + const auto& value = handle.mapped(); return push_block(*value.block, value.state->context()); } diff --git a/src/chasers/chaser_block.cpp b/src/chasers/chaser_block.cpp index 46b69d819..a7251e2ae 100644 --- a/src/chasers/chaser_block.cpp +++ b/src/chasers/chaser_block.cpp @@ -47,45 +47,41 @@ bool chaser_block::get_block(block::cptr& out, size_t height) const NOEXCEPT return !is_null(out); } -bool chaser_block::get_bypass(const block& block, size_t height) const NOEXCEPT -{ - // Milestones are not relevant to block-first organization. - // TODO: Can a validated block be malleable64 (i.e. can we ignore here). - return is_under_checkpoint(height) && !block.is_malleable64(); -} - code chaser_block::validate(const block& block, const chain_state& state) const NOEXCEPT { code ec{}; const auto& header = block.header(); + const auto& setting = settings(); + const auto ctx = state.context(); // header.check is never bypassed. // block.check does not invoke header.check. if ((ec = header.check( - settings().timestamp_limit_seconds, - settings().proof_of_work_limit, - settings().forks.scrypt_proof_of_work))) + setting.timestamp_limit_seconds, + setting.proof_of_work_limit, + setting.forks.scrypt_proof_of_work))) return ec; // header.accept is never bypassed. // block.accept does not invoke header.accept. - if ((ec = header.accept(state.context()))) + if ((ec = header.accept(ctx))) return ec; // Transaction/witness commitments are required under checkpoint. // This ensures that the block/header hash represents expected txs. - const auto bypass = get_bypass(block, state.height()); + const auto checked = is_under_checkpoint(state.height()) && + !block.is_malleable64(); - // Transaction commitments and malleated32 are checked under checkpoint. - if ((ec = block.check(bypass))) + // Transaction commitments and malleated32 are checked under bypass. + if ((ec = block.check(checked))) return ec; - // Witnessed tx commitments are checked under checkpoint (if bip141). - if ((ec = block.check(state.context(), bypass))) + // Witnessed tx commitments are checked under bypass (if bip141). + if ((ec = block.check(ctx, checked))) return ec; - if (bypass) + if (checked) return system::error::block_success; // Populate prevouts from self/tree/store (metadata not required). @@ -93,12 +89,12 @@ code chaser_block::validate(const block& block, if (!archive().populate(block)) return network::error::protocol_violation; - if ((ec = block.accept(state.context(), - settings().subsidy_interval_blocks, - settings().initial_subsidy()))) + if ((ec = block.accept(ctx, + setting.subsidy_interval_blocks, + setting.initial_subsidy()))) return ec; - return block.connect(state.context()); + return block.connect(ctx); } // The archived malleable block was found to be invalid (treat as malleated). @@ -127,6 +123,15 @@ bool chaser_block::is_storable(const chain_state&) const NOEXCEPT return true; } + +// Milestone methods. +// ---------------------------------------------------------------------------- + +bool chaser_block::is_under_milestone(size_t) const NOEXCEPT +{ + return false; +} + void chaser_block::update_milestone(const header&, size_t, size_t) NOEXCEPT { } diff --git a/src/chasers/chaser_confirm.cpp b/src/chasers/chaser_confirm.cpp index e38b78942..9e4bdbe1c 100644 --- a/src/chasers/chaser_confirm.cpp +++ b/src/chasers/chaser_confirm.cpp @@ -189,41 +189,42 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT notify(ec, chase::unconfirmable, link); fire(events::block_unconfirmable, index); - // chase::reorganized & events::block_reorganized - // chase::organized & events::block_organized - if (!roll_back(popped, fork_point, index)) + if (!roll_back(popped, link, fork_point, index)) fault(error::node_roll_back); return; } - // checkpointed blocks are set_strong concurrently by block_in protocol. + // Can get malleable64 from block if we have it. const auto malleable64 = query.is_malleable64(link); - const auto stronged = is_under_checkpoint(index) && !malleable64; - - // These are cheap, so do even though checkpoint overlaps bypassed. - auto bypass = stronged || ec == database::error::block_confirmable; - - // malleable64 overrides bypass state. - if (!bypass && !malleable64 && !query.get_bypass(bypass, link)) + const auto checkpoint = is_under_checkpoint(index); + const auto always_strong = checkpoint && !malleable64; + const auto confirmed = [&]() NOEXCEPT { - fault(database::error::integrity); - return; - } + return !malleable64 && (checkpoint || query.is_milestone(link)); + }; // Required for block_confirmable and all confirmed blocks. - if (!stronged && !query.set_strong(link)) + // Only checkpoint && !malleable64 guarantees set_strong is always set. + if (!always_strong && !query.is_strong(link) && !query.set_strong(link)) { fault(error::set_confirmed); return; } - if (bypass) + if (ec == database::error::block_confirmable || confirmed()) { + // TODO: compute fees from validation records. + if ((ec != database::error::block_confirmable) && + !query.set_block_confirmable(link, uint64_t{})) + { + fault(error::block_confirmable); + return; + } + notify(ec, chase::confirmable, index); ////fire(events::confirm_bypassed, index); - // chase::organized & events::block_organized if (!set_organized(link, index)) { fault(error::set_confirmed); @@ -242,25 +243,16 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT if (ec) { - // Roll back current block. - if (!query.set_unstrong(link)) - { - fault(error::node_confirm); - return; - } - - // TODO: Can a validated block be malleable64 (i.e. can we ignore). if (malleable64) { LOGR("Malleated64 block [" << index << "] " << ec.message()); notify(ec, chase::malleated, link); fire(events::block_malleated, index); - - // chase::reorganized & events::block_reorganized - // chase::organized & events::block_organized - // index has not been confirmed, so start prior. - if (!roll_back(popped, fork_point, sub1(index))) + + if (!roll_back(popped, link, fork_point, sub1(index))) + { fault(error::node_roll_back); + } return; } @@ -275,16 +267,15 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT notify(ec, chase::unconfirmable, link); fire(events::block_unconfirmable, index); - // chase::reorganized & events::block_reorganized - // chase::organized & events::block_organized - if (!roll_back(popped, fork_point, index)) + if (!roll_back(popped, link, fork_point, index)) + { fault(error::node_roll_back); + } return; } // TODO: compute fees from validation records. - if (!query.set_block_confirmable(link, uint64_t{})) { fault(error::block_confirmable); @@ -294,7 +285,6 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT notify(error::success, chase::confirmable, index); fire(events::block_confirmed, index); - // chase::organized & events::block_organized if (!set_organized(link, index)) { fault(error::set_confirmed); @@ -309,7 +299,7 @@ void chaser_confirm::do_validated(height_t height) NOEXCEPT // Private // ---------------------------------------------------------------------------- -bool chaser_confirm::set_organized(header_t link, height_t) NOEXCEPT +bool chaser_confirm::set_organized(const header_link& link, height_t) NOEXCEPT { auto& query = archive(); if (!query.push_confirmed(link)) @@ -320,7 +310,8 @@ bool chaser_confirm::set_organized(header_t link, height_t) NOEXCEPT return true; } -bool chaser_confirm::reset_organized(header_t link, height_t height) NOEXCEPT +bool chaser_confirm::reset_organized(const header_link& link, + height_t height) NOEXCEPT { auto& query = archive(); if (!query.set_strong(link) || !query.push_confirmed(link)) @@ -331,7 +322,8 @@ bool chaser_confirm::reset_organized(header_t link, height_t height) NOEXCEPT return true; } -bool chaser_confirm::set_reorganized(header_t link, height_t height) NOEXCEPT +bool chaser_confirm::set_reorganized(const header_link& link, + height_t height) NOEXCEPT { auto& query = archive(); if (!query.pop_confirmed() || !query.set_unstrong(link)) @@ -343,15 +335,20 @@ bool chaser_confirm::set_reorganized(header_t link, height_t height) NOEXCEPT } bool chaser_confirm::roll_back(const header_links& popped, - size_t fork_point, size_t top) NOEXCEPT + const header_link& link, size_t fork_point, size_t top) NOEXCEPT { auto& query = archive(); + + // The current block is set_strong but not confirmed. + if (!query.set_unstrong(link)) + return false; + for (auto height = top; height > fork_point; --height) if (!set_reorganized(query.to_confirmed(height), height)) return false; - for (const auto& link: views_reverse(popped)) - if (!reset_organized(link, ++fork_point)) + for (const auto& fk: views_reverse(popped)) + if (!reset_organized(fk, ++fork_point)) return false; return true; diff --git a/src/chasers/chaser_header.cpp b/src/chasers/chaser_header.cpp index 54ffbc29b..5e828250c 100644 --- a/src/chasers/chaser_header.cpp +++ b/src/chasers/chaser_header.cpp @@ -54,12 +54,6 @@ bool chaser_header::get_block(header::cptr& out, size_t height) const NOEXCEPT return !is_null(out); } -bool chaser_header::get_bypass(const header&, size_t height) const NOEXCEPT -{ - // Malleability is not known for headers, so must be guarded at validation. - return is_under_milestone(height) || is_under_checkpoint(height); -} - code chaser_header::validate(const header& header, const chain_state& state) const NOEXCEPT { @@ -143,14 +137,10 @@ bool chaser_header::is_hard(const chain_state& state) const NOEXCEPT return state.cumulative_work() >= settings().minimum_work; } -// Milestone methods (private). +// Milestone methods. // ---------------------------------------------------------------------------- -bool chaser_header::is_under_milestone(size_t height) const NOEXCEPT -{ - return height <= active_milestone_height_; -} - +// private bool chaser_header::initialize_milestone() NOEXCEPT { active_milestone_height_ = zero; @@ -173,6 +163,11 @@ bool chaser_header::initialize_milestone() NOEXCEPT return true; } +bool chaser_header::is_under_milestone(size_t height) const NOEXCEPT +{ + return height <= active_milestone_height_; +} + void chaser_header::update_milestone(const system::chain::header& header, size_t height, size_t branch_point) NOEXCEPT { diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index e2900dcef..69ad2aada 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -168,21 +168,16 @@ void chaser_validate::do_bump(height_t) NOEXCEPT return; } - // These are cheap, so do even though checkpoint overlaps bypassed. - const auto malleable64 = query.is_malleable64(link); - auto bypass = ( - ec == database::error::block_valid || - ec == database::error::block_confirmable || - (is_under_checkpoint(height) && !malleable64)); - - // malleable64 overrides bypass state because it's set from header only. - if (!bypass && !malleable64 && !query.get_bypass(bypass, link)) + const auto valid = [&]() NOEXCEPT { - fault(database::error::integrity); - return; - } - - if (bypass) + // Can get malleable64 from block if we have it. + return (is_under_checkpoint(height) || query.is_milestone(link)) && + !query.is_malleable64(link); + }; + + if ((ec == database::error::block_valid) || + (ec == database::error::block_confirmable) || + valid()) { update_position(height); ////fire(events::validate_bypassed, height); diff --git a/src/protocols/protocol_block_in_31800.cpp b/src/protocols/protocol_block_in_31800.cpp index df6a0fefa..aae6cab52 100644 --- a/src/protocols/protocol_block_in_31800.cpp +++ b/src/protocols/protocol_block_in_31800.cpp @@ -308,23 +308,11 @@ bool protocol_block_in_31800::handle_receive_block(const code& ec, // Check block. // ........................................................................ - // set_strong checkpointed blocks as these are not regressable. - // checkpointed and malleable64 blocks must be set_strong post-validation. - const auto strong = is_under_checkpoint(ctx.height) && !malleable64; + // Header state checked by organize (neither associated nor unconfirmable). + const auto checked = !malleable64 && (is_under_checkpoint(ctx.height) || + query.is_milestone(link)); - // These are cheap, so do even though checkpoint overlaps bypassed. - auto bypass = strong - || ec == database::error::block_valid - || ec == database::error::block_confirmable; - - // malleable64 overrides bypass state. - if (!bypass && !malleable64 && !query.get_bypass(bypass, link)) - { - stop(fault(database::error::integrity)); - return false; - } - - if (const auto code = check(*block_ptr, ctx, bypass)) + if (const auto code = check(*block_ptr, ctx, checked)) { // Uncommitted blocks have no creation cost, just bogus data. if (code == system::error::invalid_transaction_commitment || @@ -385,14 +373,8 @@ 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() }; - // TODO: Set strong when bypassed and not malleable64. This requires that - // TODO: candidate reorganization must set_unstrong all bypassed and not - // TODO: malleable64 (associated) blocks and must set_strong on any later - // TODO: reassociation of the same, so that confirm chaser can rely. This - // TODO: has to be performed by the organizer, since it owns candidates. - - // Transactions are set_strong here when checkpointed and not malleable64. - if (const auto code = query.set_code(*txs_ptr, link, size, strong)) + // This invokes set_strong () when checked. + if (const auto code = query.set_code(*txs_ptr, link, size, checked)) { LOGF("Failure storing block [" << encode_hash(hash) << ":" << ctx.height << "] from [" << authority() << "] "