diff --git a/include/bitcoin/database/error.hpp b/include/bitcoin/database/error.hpp index e28e8b435..bf7cf27bf 100644 --- a/include/bitcoin/database/error.hpp +++ b/include/bitcoin/database/error.hpp @@ -98,6 +98,7 @@ enum error_t : uint8_t tx_connected, tx_disconnected, block_valid, + block_prevalid, block_confirmable, block_unconfirmable, unassociated, diff --git a/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp b/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp index 74b4e3b3a..b405e17a3 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp @@ -25,6 +25,45 @@ namespace libbitcoin { namespace database { +// get_prevalids +// ---------------------------------------------------------------------------- +// startup query (reorg safety not required) + +TEMPLATE +header_links CLASS::get_prevalids(size_t fork_point) const NOEXCEPT +{ + header_links out{}; + auto height = fork_point; + + while (true) + { + const auto link = to_candidate(++height); + if (link.is_terminal()) + break; + + switch (get_block_state(link).value()) + { + case error::block_prevalid: + { + out.push_back(link); + break; + } + + case error::unassociated: + case error::block_unconfirmable: + return out; + + ////case error::bypassed: + ////case error::unvalidated: + ////case error::block_valid: + ////case error::block_confirmable: + default:; + } + } + + return out; +} + // get_confirmed // ---------------------------------------------------------------------------- @@ -133,8 +172,10 @@ header_states CLASS::get_validated_fork(size_t& fork_point, fork_point = get_fork_(); auto height = add1(fork_point); auto link = to_candidate(height); - while (is_block_validated(ec, link, height, top_checkpoint) && - (!filter || is_filtered_body(link))) + + // Filter body always written before validated, so the check is redundant. + while (is_block_validated(ec, link, height, top_checkpoint) + /*&& (!filter || is_filtered_body(link))*/) { out.emplace_back(link, ec); link = to_candidate(++height); diff --git a/include/bitcoin/database/impl/query/consensus/consensus_states.ipp b/include/bitcoin/database/impl/query/consensus/consensus_states.ipp index 867b95ac5..74ad170ff 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_states.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_states.ipp @@ -43,6 +43,7 @@ bool CLASS::is_validateable(size_t height) const NOEXCEPT return (ec == database::error::unvalidated) || (ec == database::error::block_valid) || + (ec == database::error::block_prevalid) || (ec == database::error::unknown_state) || (ec == database::error::block_confirmable); } @@ -58,6 +59,10 @@ inline code CLASS::to_block_code( case block_state::valid: return error::block_valid; + // Transitional: Satisfies validation rules if signatures verify. + case block_state::prevalid: + return error::block_prevalid; + // Final: Satisfies confirmation rules (prevouts confirmable). case block_state::confirmable: return error::block_confirmable; @@ -134,6 +139,7 @@ code CLASS::get_header_state(const header_link& link) const NOEXCEPT // unassociated // unvalidated // block_valid +// block_prevalid // block_confirmable // block_unconfirmable TEMPLATE @@ -227,6 +233,12 @@ bool CLASS::set_block_valid(const header_link& link) NOEXCEPT return set_block_state(link, block_state::valid); } +TEMPLATE +bool CLASS::set_block_prevalid(const header_link& link) NOEXCEPT +{ + return set_block_state(link, block_state::prevalid); +} + TEMPLATE bool CLASS::set_block_confirmable(const header_link& link) NOEXCEPT { diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 3179aab0f..694beed8f 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -569,6 +569,7 @@ class query /// Setters. bool set_block_valid(const header_link& link) NOEXCEPT; + bool set_block_prevalid(const header_link& link) NOEXCEPT; bool set_block_unconfirmable(const header_link& link) NOEXCEPT; bool set_block_confirmable(const header_link& link) NOEXCEPT; bool set_block_unknown(const header_link& link) NOEXCEPT; @@ -665,6 +666,7 @@ class query header_links get_confirmed_headers(size_t first, size_t limit) const NOEXCEPT; + header_links get_prevalids(size_t fork_point) const NOEXCEPT; height_link get_confirmed_height(const header_link& link) const NOEXCEPT; header_links get_confirmed_fork(const header_link& fork) const NOEXCEPT; header_links get_candidate_fork(size_t& fork_point) const NOEXCEPT; diff --git a/include/bitcoin/database/types/block_state.hpp b/include/bitcoin/database/types/block_state.hpp index 3cd510be2..471cad4bd 100644 --- a/include/bitcoin/database/types/block_state.hpp +++ b/include/bitcoin/database/types/block_state.hpp @@ -32,8 +32,11 @@ enum block_state : uint8_t /// transitional valid = 1, + /// transitional + prevalid = 2, + /// final - unconfirmable = 2, + unconfirmable = 3, /// transitional (debug) block_unknown = 42 diff --git a/src/error.cpp b/src/error.cpp index c5c070ee0..9b3ee9a27 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -92,6 +92,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { tx_connected, "transaction connected" }, { tx_disconnected, "transaction disconnected" }, { block_valid, "block valid" }, + { block_prevalid, "block prevalid" }, { block_confirmable, "block confirmable" }, { block_unconfirmable, "block unconfirmable" }, { unassociated, "unassociated" }, diff --git a/test/error.cpp b/test/error.cpp index ca911ab23..6f2a61337 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -468,6 +468,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__block_valid__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "block valid"); } +BOOST_AUTO_TEST_CASE(error_t__code__block_prevalid__true_expected_message) +{ + constexpr auto value = error::block_prevalid; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "block prevalid"); +} + BOOST_AUTO_TEST_CASE(error_t__code__block_confirmable__true_expected_message) { constexpr auto value = error::block_confirmable; diff --git a/test/query/confirmed.cpp b/test/query/confirmed.cpp index 9c9396d82..f62539d02 100644 --- a/test/query/confirmed.cpp +++ b/test/query/confirmed.cpp @@ -22,6 +22,170 @@ BOOST_FIXTURE_TEST_SUITE(query_confirmed_tests, test::directory_setup_fixture) +static void push_chain(test::query_accessor& query) +{ + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, false, false)); + BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, false, false)); + BOOST_REQUIRE(query.push_candidate(1)); + BOOST_REQUIRE(query.push_candidate(2)); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__empty_chain__empty) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.get_prevalids(0).empty()); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__no_prevalids__empty) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + push_chain(query); + + BOOST_REQUIRE(query.get_prevalids(0).empty()); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__single_prevalid__one_link) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + push_chain(query); + + const auto link1 = query.to_candidate(1); + BOOST_REQUIRE(query.set_block_prevalid(link1)); + + const auto out = query.get_prevalids(0); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + BOOST_REQUIRE(out.front() == link1); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__two_prevalids__both_links) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + push_chain(query); + + const auto link1 = query.to_candidate(1); + const auto link2 = query.to_candidate(2); + BOOST_REQUIRE(query.set_block_prevalid(link1)); + BOOST_REQUIRE(query.set_block_prevalid(link2)); + + const auto out = query.get_prevalids(0); + BOOST_REQUIRE_EQUAL(out.size(), 2u); + BOOST_REQUIRE(out.front() == link1); + BOOST_REQUIRE(out.back() == link2); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__valid_below_prevalid__collects_prevalid) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + push_chain(query); + + const auto link1 = query.to_candidate(1); + const auto link2 = query.to_candidate(2); + + BOOST_REQUIRE(query.set_block_valid(link1)); + BOOST_REQUIRE(query.set_block_prevalid(link2)); + + const auto out = query.get_prevalids(0); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + BOOST_REQUIRE(out.front() == link2); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__confirmable_below_prevalid__collects_prevalid) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + push_chain(query); + + const auto link1 = query.to_candidate(1); + const auto link2 = query.to_candidate(2); + + BOOST_REQUIRE(query.set_block_confirmable(link1)); + BOOST_REQUIRE(query.set_block_prevalid(link2)); + + const auto out = query.get_prevalids(0); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + BOOST_REQUIRE(out.front() == link2); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__fork_above_prevalid__excludes_below) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + push_chain(query); + + const auto link1 = query.to_candidate(1); + const auto link2 = query.to_candidate(2); + BOOST_REQUIRE(query.set_block_prevalid(link1)); + BOOST_REQUIRE(query.set_block_prevalid(link2)); + + const auto out = query.get_prevalids(1); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + BOOST_REQUIRE(out.front() == link2); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__unconfirmable_stops__excludes_above) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + push_chain(query); + + const auto link1 = query.to_candidate(1); + const auto link2 = query.to_candidate(2); + + BOOST_REQUIRE(query.set_block_unconfirmable(link1)); + BOOST_REQUIRE(query.set_block_prevalid(link2)); + BOOST_REQUIRE(query.get_prevalids(0).empty()); +} + +BOOST_AUTO_TEST_CASE(query_prevalids__get_prevalids__unassociated_stops__excludes_above) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1.header(), context{ 0, 1, 0 }, false)); + BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, false, false)); + BOOST_REQUIRE(query.push_candidate(1)); + BOOST_REQUIRE(query.push_candidate(2)); + + const auto link2 = query.to_candidate(2); + BOOST_REQUIRE(query.set_block_prevalid(query.to_candidate(2))); + BOOST_REQUIRE(query.get_prevalids(0).empty()); +} + BOOST_AUTO_TEST_CASE(query_confirmed__is_candidate_block__push_pop_candidate__expected) { settings settings{}; diff --git a/test/query/properties_block.cpp b/test/query/properties_block.cpp index 71043e9a9..ad76cc9b6 100644 --- a/test/query/properties_block.cpp +++ b/test/query/properties_block.cpp @@ -240,6 +240,21 @@ BOOST_AUTO_TEST_CASE(query_properties_block__get_block_state__valid__block_valid BOOST_REQUIRE_EQUAL(query.get_block_state(1), error::block_valid); } +BOOST_AUTO_TEST_CASE(query_properties_block__get_block_state__prevalid__block_prevalid) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1, context{}, false, false)); + + BOOST_REQUIRE(query.set_block_prevalid(1)); + BOOST_REQUIRE_EQUAL(query.get_header_state(1), error::block_prevalid); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), error::block_prevalid); +} + BOOST_AUTO_TEST_CASE(query_properties_block__get_block_state__unconfirmable__block_unconfirmable) { settings settings{};