diff --git a/include/bitcoin/node/chasers/chaser.hpp b/include/bitcoin/node/chasers/chaser.hpp index bea55dc7..d63b6d90 100644 --- a/include/bitcoin/node/chasers/chaser.hpp +++ b/include/bitcoin/node/chasers/chaser.hpp @@ -59,14 +59,14 @@ class BCN_API chaser template auto bind(Method&& method, Args&&... args) NOEXCEPT { - return BIND_THIS(method, args); + return BIND_TO(method, args); } /// Post a method to channel strand (use POST). template auto post(Method&& method, Args&&... args) NOEXCEPT { - return boost::asio::post(strand(), BIND_THIS(method, args)); + return boost::asio::post(strand(), BIND_TO(method, args)); } /// Methods. diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index b403cd2e..7c2c835f 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -47,7 +47,7 @@ class BCN_API chaser_validate inline auto parallel(Method&& method, Args&&... args) NOEXCEPT { return boost::asio::post(validation_threadpool_.service(), - BIND_THIS(method, args)); + BIND_TO(method, args)); } typedef network::race_unity race; @@ -78,8 +78,31 @@ class BCN_API chaser_validate bool stranded() const NOEXCEPT override; private: - system::chain::signatures get_capture( + using atomic_counter = std::atomic; + using atomic_counter_ptr = std::shared_ptr; + using signatures = system::chain::signatures; + using threshold_group = signatures::threshold_group; + using missed = signatures::miss; + + signatures get_capture(const database::header_link& link) NOEXCEPT; + + // Handlers. + void do_log(const system::chain::script& missed) NOEXCEPT; + void do_fire(missed miss, size_t count) NOEXCEPT; + bool do_ecdsa(const system::hash_digest& digest, + const system::ec_compressed& point, const system::ec_signature& sign, const database::header_link& link) NOEXCEPT; + bool do_schnorr(const system::hash_digest& digest, + const system::ec_xonly& point, const system::ec_signature& sign, + const database::header_link& link) NOEXCEPT; + bool do_multisig(const system::hash_digest& digest, + const system::ec_compresseds& points, + const system::ec_signatures& signs, const database::header_link& link, + const atomic_counter_ptr& id) NOEXCEPT; + bool do_threshold(const threshold_group& group, + const database::header_link& link, + const atomic_counter_ptr& id) NOEXCEPT; + void log_capture(const std::string_view& name, size_t captured, size_t missed) const NOEXCEPT; void log_captures() const NOEXCEPT; diff --git a/include/bitcoin/node/impl/chasers/chaser_organize.ipp b/include/bitcoin/node/impl/chasers/chaser_organize.ipp index 0858a75b..807f9353 100644 --- a/include/bitcoin/node/impl/chasers/chaser_organize.ipp +++ b/include/bitcoin/node/impl/chasers/chaser_organize.ipp @@ -90,6 +90,10 @@ bool CLASS::handle_chase(const code&, chase event_, event_value value) NOEXCEPT case chase::unvalid: case chase::unconfirmable: { + // !mark_unconfirmable allows node to stall, preserving log. + if (!node_settings().mark_unconfirmable) + break; + // Roll back the candidate chain to confirmed top (via fork point). BC_ASSERT(std::holds_alternative(value)); POST(do_disorganize, std::get(value)); diff --git a/include/bitcoin/node/settings.hpp b/include/bitcoin/node/settings.hpp index 88983129..10c2df96 100644 --- a/include/bitcoin/node/settings.hpp +++ b/include/bitcoin/node/settings.hpp @@ -41,6 +41,7 @@ class BCN_API settings bool memory_priority; bool allow_overlapped; bool batch_signatures; + bool mark_unconfirmable; bool defer_validation; bool defer_confirmation; float allowed_deviation; diff --git a/src/chasers/chaser_confirm.cpp b/src/chasers/chaser_confirm.cpp index 86df17a2..a0df5117 100644 --- a/src/chasers/chaser_confirm.cpp +++ b/src/chasers/chaser_confirm.cpp @@ -286,7 +286,10 @@ bool chaser_confirm::confirm_block(const header_link& link, size_t height, if (const auto ec = query.block_confirmable(link)) { - if (!query.set_block_unconfirmable(link)) + // !mark_unconfirmable allows node to stall, preserving log. + // Will continue to validate this block and fail to confirm here. + if (node_settings().mark_unconfirmable && + !query.set_block_unconfirmable(link)) { fault(error::confirm9); return false; diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index e8c13475..eed2c0e2 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -200,7 +200,9 @@ void chaser_validate::do_bumped(height_t height) NOEXCEPT void chaser_validate::post_block(const header_link& link, bool bypass) NOEXCEPT { - BC_ASSERT(stranded()); + // may be called by do_bumped (stranded) or complete_block (not stranded). + ///BC_ASSERT(stranded()); + backlog_.fetch_add(one, std::memory_order_relaxed); PARALLEL(validate_block, link, bypass); } @@ -232,13 +234,17 @@ void chaser_validate::validate_block(const header_link& link, } else if ((ec = populate(bypass, *block, ctx))) { - if (!query.set_block_unconfirmable(link)) + // !mark_unconfirmable allows node to stall, preserving log. + if (node_settings().mark_unconfirmable && + !query.set_block_unconfirmable(link)) ec = error::validate4; } else if ((ec = validate(bypass, *block, link, ctx))) { - if (!query.set_block_unconfirmable(link)) - ec = error::validate5; + // !mark_unconfirmable allows node to stall, preserving log. + if (node_settings().mark_unconfirmable && + !query.set_block_unconfirmable(link)) + ec = error::validate5; } complete_block(ec, link, ctx.height, bypass); @@ -329,6 +335,17 @@ void chaser_validate::complete_block(const code& ec, const header_link& link, return; } + if (ec == system::error::block_capture) + { + // At least one unrecoverable (threshold) capture failed during + // script validations, and there was no other failure. This is only + // caused by a store fault - possibly a disk full condition. In the + // case of disk full the node will pause, otherwise it will halt. + // Assume disk full here, requiring a repost for block validation. + post_block(link, bypass); + return; + } + // INVALID BLOCK (not a fault) notify(ec, chase::unvalid, link); fire(events::block_unconfirmable, height); @@ -387,66 +404,27 @@ chain::signatures chaser_validate::get_capture( return {}; // Group identifier for block, incremented for each multisig/threshold. - const auto id = to_shared>(); + const auto id = to_shared(); - using namespace chain; return signatures { // Default struct is disabled. .enabled = true, // Enable for a game of whack-a-mole. - .log = [&](const script& /* LOG_ONLY(missed) */) NOEXCEPT - { - ////LOGA("Sigop @ " << ctx.height << " -> " - //// << missed.to_string(chain::flags::all_rules)); - }, + .log = BIND_THIS(do_log, _1), // Update counters for missed capture. - .fire = [&](signatures::miss miss, size_t count) NOEXCEPT - { - switch (miss) - { - case signatures::miss::ecdsa: - missed_ecdsa_ += count; - break; - case signatures::miss::multisig: - missed_multisig_ += count; - break; - case signatures::miss::schnorr: - missed_schnorr_ += count; - break; - default:; - } - }, + .fire = BIND_THIS(do_fire, _1, _2), // opcode::checksig/verify - .ecdsa = [&](const hash_digest& digest, const ec_compressed& point, - const ec_signature& sign) NOEXCEPT - { - ++ecdsa_; - auto& query = archive(); - return query.set_signature(digest, point, sign, link); - }, + .ecdsa = BIND_THIS(do_ecdsa, _1, _2, _3, link), // opcode::checksigadd | opcode::checksig/verify - .schnorr = [&](const hash_digest& digest, const ec_xonly& point, - const ec_signature& sign) NOEXCEPT - { - ++schnorr_; - auto& query = archive(); - return query.set_signature(digest, point, sign, link); - }, + .schnorr = BIND_THIS(do_schnorr, _1, _2, _3, link), // opcode::checkmultisig/verify - .multisig = [&, id](const hash_digest& digest, - const ec_compresseds& points, const ec_signatures& signs) NOEXCEPT - { - BC_ASSERT(points.size() == signs.size()); - auto& query = archive(); - multisig_ += points.size(); - return query.set_signatures(digest, points, signs, (*id)++, link); - }, + .multisig = BIND_THIS(do_multisig, _1, _2, _3, link, id), // opcode::within // opcode::numequal/verify @@ -456,15 +434,86 @@ chain::signatures chaser_validate::get_capture( // opcode::lessthanorequal // opcode::greaterthanorequal // opcode::checksig (m of m) - .threshold = [&, id](const signatures::threshold_group& group) NOEXCEPT - { - threshold_ += group.entries.size(); - auto& query = archive(); - return query.set_signatures(group, (*id)++, link); - } + .threshold = BIND_THIS(do_threshold, _1, link, id) }; } +// Enable for a game of whack-a-mole. +void chaser_validate::do_log( + const chain::script& /* LOG_ONLY(missed) */) NOEXCEPT +{ + ////LOGA("Sigop @ " << ctx.height << " -> " + //// << missed.to_string(chain::flags::all_rules)); +} + +void chaser_validate::do_fire(missed miss, size_t count) NOEXCEPT +{ + switch (miss) + { + case missed::ecdsa: + missed_ecdsa_ += count; + break; + case missed::multisig: + missed_multisig_ += count; + break; + case missed::schnorr: + missed_schnorr_ += count; + break; + default:; + } +} + +bool chaser_validate::do_ecdsa(const hash_digest& digest, + const ec_compressed& point, const ec_signature& sign, + const header_link& link) NOEXCEPT +{ + ++ecdsa_; + const auto set = archive().set_signature(digest, point, sign, link); + if (!set) fault(system::error::block_capture); + return set; +} + +bool chaser_validate::do_schnorr(const hash_digest& digest, + const ec_xonly& point, const ec_signature& sign, + const header_link& link) NOEXCEPT +{ + ++schnorr_; + const auto set = archive().set_signature(digest, point, sign, link); + if (!set) fault(system::error::block_capture); + return set; +} + +BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) +BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) + +bool chaser_validate::do_multisig(const hash_digest& digest, + const ec_compresseds& points, const ec_signatures& signs, + const header_link& link, const atomic_counter_ptr& id) NOEXCEPT +{ + BC_ASSERT(points.size() == signs.size()); + + multisig_ += points.size(); + const auto set = archive().set_signatures(digest, points, signs, (*id)++, + link); + if (!set) fault(system::error::block_capture); + return set; +} + +bool chaser_validate::do_threshold(const threshold_group& group, + const header_link& link, const atomic_counter_ptr& id) NOEXCEPT +{ + threshold_ += group.entries.size(); + const auto set = archive().set_signatures(group, (*id)++, link); + if (!set) fault(system::error::block_capture); + + // False here sets signatures.fault, causing block.connect(2) to + // return error::block_capture, causing block validation resubmit. + return set; +} + +BC_POP_WARNING() +BC_POP_WARNING() + void chaser_validate::log_capture(const std::string_view& name, size_t captured, size_t missed) const NOEXCEPT { diff --git a/src/protocols/protocol_block_in_31800.cpp b/src/protocols/protocol_block_in_31800.cpp index c6709af5..ba6317df 100644 --- a/src/protocols/protocol_block_in_31800.cpp +++ b/src/protocols/protocol_block_in_31800.cpp @@ -317,7 +317,9 @@ bool protocol_block_in_31800::handle_receive_block(const code& ec, return false; } - if (!query.set_block_unconfirmable(link)) + // !mark_unconfirmable allows node to stall, preserving log. + if (node_settings().mark_unconfirmable && + !query.set_block_unconfirmable(link)) { stop(fault(error::protocol1)); return false; diff --git a/src/settings.cpp b/src/settings.cpp index af900e3f..dd1013fa 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -37,6 +37,7 @@ settings::settings() NOEXCEPT thread_priority{ true }, allow_overlapped{ true }, batch_signatures{ true }, + mark_unconfirmable{ true }, defer_validation{ false }, defer_confirmation{ false }, minimum_fee_rate{ 0.0 }, diff --git a/test/settings.cpp b/test/settings.cpp index f63af133..b22329d3 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -37,6 +37,7 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected) BOOST_REQUIRE_EQUAL(node.thread_priority, true); BOOST_REQUIRE_EQUAL(node.allow_overlapped, true); BOOST_REQUIRE_EQUAL(node.batch_signatures, true); + BOOST_REQUIRE_EQUAL(node.mark_unconfirmable, true); BOOST_REQUIRE_EQUAL(node.defer_validation, false); BOOST_REQUIRE_EQUAL(node.defer_confirmation, false); BOOST_REQUIRE_EQUAL(node.minimum_fee_rate, 0.0);