diff --git a/include/bitcoin/database/impl/query/consensus/consensus_states.ipp b/include/bitcoin/database/impl/query/consensus/consensus_states.ipp index e9420335a..61d2d8a23 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_states.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_states.ipp @@ -124,7 +124,7 @@ code CLASS::get_block_state(const header_link& link) const NOEXCEPT TEMPLATE inline bool CLASS::is_validated(const header_link& link) const NOEXCEPT { - // Validated and not invalid (checkpoint/milestone shows false). + // Validated and not invalid (bypassed shows false unless indexed). const auto ec = get_header_state(link); return ec == error::block_valid || ec == error::block_confirmable; } @@ -135,22 +135,20 @@ bool CLASS::is_block_validated(code& ec, const header_link& link, { if (height <= checkpoint || is_milestone(link)) { - if (is_associated(link)) - { - ec = error::bypassed; - return true; - } - else + if (!is_associated(link)) { ec = error::unassociated; return false; } + + // A bypassed block requiring silent indexing is validated to index it. + ec = error::bypassed; + return !is_silent(link, height) || is_validated(link); } else { ec = get_header_state(link); - return ec == error::block_valid - || ec == error::block_confirmable; + return ec == error::block_valid || ec == error::block_confirmable; } } @@ -185,8 +183,7 @@ code CLASS::get_pooled(pooled_tx& out, const tx_link& link, { return pooled_tx::prevout { - prevout::output_tx_fk(merged), - prevout::coinbase(merged) + prevout::output_tx_fk(merged), prevout::coinbase(merged) }; }); diff --git a/include/bitcoin/database/impl/query/extent.ipp b/include/bitcoin/database/impl/query/extent.ipp index 73eadcdb6..d66cb087c 100644 --- a/include/bitcoin/database/impl/query/extent.ipp +++ b/include/bitcoin/database/impl/query/extent.ipp @@ -294,6 +294,15 @@ size_t CLASS::silent_start_height() const NOEXCEPT return store_.get_envelope().silent_start_height; } +// A block is indexed from the start height once taproot is active. +TEMPLATE +bool CLASS::is_silent(const header_link& link, size_t height) const NOEXCEPT +{ + context ctx{}; + return height >= silent_start_height() && get_context(ctx, link) && + ctx.is_enabled(system::chain::flags::bip341_rule); +} + } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/store/store_open.ipp b/include/bitcoin/database/impl/store/store_open.ipp index 7c7375931..5bf23a497 100644 --- a/include/bitcoin/database/impl/store/store_open.ipp +++ b/include/bitcoin/database/impl/store/store_open.ipp @@ -127,6 +127,54 @@ void CLASS::store_envelope(code& ec) NOEXCEPT ec = error::create_table; } +// public +TEMPLATE +code CLASS::read_schema(system::config::version& schema) NOEXCEPT +{ + if (!transactor_mutex_.try_lock()) + return error::transactor_lock; + + auto ec = envelope_head_.open(); + if (!ec) ec = envelope_head_.load(); + if (!ec && !get_schema(schema)) ec = error::verify_table; + + // unload and close errors override ec. + if (const auto unloaded = envelope_head_.unload()) ec = unloaded; + if (const auto closed = envelope_head_.close()) ec = closed; + + transactor_mutex_.unlock(); + return ec; +} + +// The schema is deserialized before it is validated, so the record holds it +// when envelope deserialization fails. +TEMPLATE +bool CLASS::get_schema(system::config::version& schema) const NOEXCEPT +{ + if (is_zero(envelope.head_size())) + return false; + + table::envelope::record record{}; + const auto valid = envelope.get(zero, record); + schema = record.envelope.schema; + return valid || (schema != database::envelope::compiled); +} + +// A created store has no envelope until it is populated. +TEMPLATE +code CLASS::load_schema() NOEXCEPT +{ + if (is_zero(envelope.head_size())) + return error::success; + + system::config::version schema{}; + if (!get_schema(schema)) + return error::verify_table; + + return (schema == database::envelope::compiled) ? error::success : + error::schema_version; +} + TEMPLATE code CLASS::load_envelope() NOEXCEPT { @@ -135,8 +183,7 @@ code CLASS::load_envelope() NOEXCEPT if (!is_zero(envelope.head_size())) { if (!envelope.get(zero, record)) - return record.envelope.schema == envelope_.schema ? - error::verify_table : error::schema_version; + return error::verify_table; envelope_ = record.envelope; } diff --git a/include/bitcoin/database/impl/store/store_open_load.ipp b/include/bitcoin/database/impl/store/store_open_load.ipp index 74d0268b5..fb5ec0e0d 100644 --- a/include/bitcoin/database/impl/store/store_open_load.ipp +++ b/include/bitcoin/database/impl/store/store_open_load.ipp @@ -38,6 +38,21 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT } }; + const auto load = [&handler](code& ec, auto& file, table_t table) NOEXCEPT + { + if (!ec) + { + handler(event_t::load_file, table); + ec = file.load(); + } + }; + + // The schema version governs all other files, so it precedes them. + open(ec, envelope_head_, table_t::envelope_head); + load(ec, envelope_head_, table_t::envelope_head); + if (!ec) + ec = load_schema(); + open(ec, header_head_, table_t::header_head); open(ec, header_body_, table_t::header_body); open(ec, input_head_, table_t::input_head); @@ -68,7 +83,6 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT open(ec, schnorr1_body_, table_t::schnorr1_body); open(ec, silent_head_, table_t::silent_head); open(ec, silent_body_, table_t::silent_body); - open(ec, envelope_head_, table_t::envelope_head); open(ec, duplicate_head_, table_t::duplicate_head); open(ec, duplicate_body_, table_t::duplicate_body); open(ec, prevalid0_head_, table_t::prevalid0_head); @@ -89,15 +103,6 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT open(ec, filter_tx_head_, table_t::filter_tx_head); open(ec, filter_tx_body_, table_t::filter_tx_body); - const auto load = [&handler](code& ec, auto& file, table_t table) NOEXCEPT - { - if (!ec) - { - handler(event_t::load_file, table); - ec = file.load(); - } - }; - load(ec, header_head_, table_t::header_head); load(ec, header_body_, table_t::header_body); load(ec, input_head_, table_t::input_head); @@ -128,7 +133,6 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT load(ec, schnorr1_body_, table_t::schnorr1_body); load(ec, silent_head_, table_t::silent_head); load(ec, silent_body_, table_t::silent_body); - load(ec, envelope_head_, table_t::envelope_head); load(ec, duplicate_head_, table_t::duplicate_head); load(ec, duplicate_body_, table_t::duplicate_body); load(ec, prevalid0_head_, table_t::prevalid0_head); diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 742436b40..0ca7bd6bf 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -260,6 +260,7 @@ class query bool filter_enabled() const NOEXCEPT; bool silent_enabled() const NOEXCEPT; size_t silent_start_height() const NOEXCEPT; + bool is_silent(const header_link& link, size_t height) const NOEXCEPT; size_t interval_span() const NOEXCEPT; /// Initialization (natural-keyed). diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index ae326c55a..067d89db4 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -97,6 +97,9 @@ class store /// Settings envelope stored at store creation. const database::envelope& get_envelope() const NOEXCEPT; + /// Read the stored schema version without opening the store. + code read_schema(system::config::version& schema) NOEXCEPT; + /// Determine if the store is non-empty/initialized. bool is_dirty() const NOEXCEPT; void set_dirty() NOEXCEPT; @@ -128,6 +131,8 @@ class store /// Method helpers. code create_load(const event_handler& handler) NOEXCEPT; code open_load(const event_handler& handler) NOEXCEPT; + bool get_schema(system::config::version& schema) const NOEXCEPT; + code load_schema() NOEXCEPT; code load_envelope() NOEXCEPT; void store_envelope(code& ec) NOEXCEPT; code unload_close(const event_handler& handler) NOEXCEPT; diff --git a/test/mocks/map_store.hpp b/test/mocks/map_store.hpp index 61018c0f9..084b09705 100644 --- a/test/mocks/map_store.hpp +++ b/test/mocks/map_store.hpp @@ -241,6 +241,11 @@ class map_store //// return silent_body_.compressed.file(); ////} + inline const path& envelope_head_file() const NOEXCEPT + { + return envelope_head_.file(); + } + inline const path& duplicate_head_file() const NOEXCEPT { return duplicate_head_.file(); diff --git a/test/query/consensus/consensus_forks.cpp b/test/query/consensus/consensus_forks.cpp index a3473a0d7..92d455b3f 100644 --- a/test/query/consensus/consensus_forks.cpp +++ b/test/query/consensus/consensus_forks.cpp @@ -87,6 +87,40 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__filters_disabled__bypa BOOST_REQUIRE(fork.back().ec == error::bypassed); } +BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__bypassed_silent_pending__stops_at_first_unvalidated) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + settings.filter_tx.buckets = 0; + settings.initialize(system::settings{ system::chain::selection::regtest }, false, 2); + 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.filter_enabled()); + BOOST_REQUIRE_EQUAL(query.silent_start_height(), 2u); + BOOST_REQUIRE(query.set(test::block1, database::context{ system::chain::flags::bip341_rule, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block2, database::context{ system::chain::flags::bip341_rule, 2, 0 }, {}, false, false)); + + const auto link1 = query.to_header(test::block1_hash); + const auto link2 = query.to_header(test::block2_hash); + BOOST_REQUIRE(query.push_candidate(link1)); + BOOST_REQUIRE(query.push_candidate(link2)); + + size_t fork_point{}; + auto fork = query.get_validated_fork(fork_point, 2); + BOOST_REQUIRE_EQUAL(fork_point, 0u); + BOOST_REQUIRE_EQUAL(fork.size(), 1u); + BOOST_REQUIRE(fork.front().link == link1); + BOOST_REQUIRE(fork.front().ec == error::bypassed); + + BOOST_REQUIRE(query.set_block_valid(link2)); + fork = query.get_validated_fork(fork_point, 2); + BOOST_REQUIRE_EQUAL(fork.size(), 2u); + BOOST_REQUIRE(fork.back().link == link2); + BOOST_REQUIRE(fork.back().ec == error::bypassed); +} + BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__milestone_filter_pending__empty) { settings settings{}; diff --git a/test/store/store_open.cpp b/test/store/store_open.cpp index b18836301..191b2b3bb 100644 --- a/test/store/store_open.cpp +++ b/test/store/store_open.cpp @@ -97,4 +97,53 @@ BOOST_AUTO_TEST_CASE(store__open__created__success) BOOST_REQUIRE(!instance.close(test::events)); } +BOOST_AUTO_TEST_CASE(store__open__schema_mismatch__schema_version) +{ + settings configuration{}; + configuration.path = TEST_DIRECTORY; + test::map_store instance{ configuration }; + BOOST_REQUIRE(!instance.create(test::events)); + BOOST_REQUIRE(!instance.close(test::events)); + BOOST_REQUIRE(test::create(instance.envelope_head_file(), std::string(16, ''))); + BOOST_REQUIRE_EQUAL(instance.open(test::events), error::schema_version); +} + +// read_schema +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(store__read_schema__uncreated__failure) +{ + settings configuration{}; + configuration.path = TEST_DIRECTORY; + system::config::version schema{}; + test::map_store instance{ configuration }; + BOOST_REQUIRE(instance.read_schema(schema)); +} + +BOOST_AUTO_TEST_CASE(store__read_schema__created__compiled) +{ + settings configuration{}; + configuration.path = TEST_DIRECTORY; + system::config::version schema{}; + test::map_store instance{ configuration }; + BOOST_REQUIRE(!instance.create(test::events)); + BOOST_REQUIRE(!instance.close(test::events)); + BOOST_REQUIRE(!instance.read_schema(schema)); + BOOST_REQUIRE(schema == envelope::compiled); +} + +BOOST_AUTO_TEST_CASE(store__read_schema__schema_mismatch__stored) +{ + settings configuration{}; + configuration.path = TEST_DIRECTORY; + system::config::version schema{}; + const system::config::version expected{ 0x01010101, 0x01010101, 0x01010101, 0x01010101 }; + test::map_store instance{ configuration }; + BOOST_REQUIRE(!instance.create(test::events)); + BOOST_REQUIRE(!instance.close(test::events)); + BOOST_REQUIRE(test::create(instance.envelope_head_file(), std::string(16, ''))); + BOOST_REQUIRE(!instance.read_schema(schema)); + BOOST_REQUIRE(schema == expected); +} + BOOST_AUTO_TEST_SUITE_END()