diff --git a/include/bitcoin/database/impl/query/archive_write.ipp b/include/bitcoin/database/impl/query/archive_write.ipp index f5aaeafd..15a0041a 100644 --- a/include/bitcoin/database/impl/query/archive_write.ipp +++ b/include/bitcoin/database/impl/query/archive_write.ipp @@ -164,10 +164,10 @@ code CLASS::set_code(const tx_link& tx_fk, const transaction& tx, if (!store_.point.expand(ins_fk + inputs)) return error::tx_point_allocate; - // If dirty we must guard against duplicates. - // Dirty doesn't hold up in the case of an invalidated block, as that - // may result in a duplicated tx. So dirty should be false in the case - // of a non-bypass (valid) block. + // If dirty we must guard against duplicates. Dirty is caused by disk, + // full, disorganized or reorganized block, and tx pooling. It is set + // to true at runtime by any of these and in case of duplicate table + // non-empty at store startup. disorg/reorg indicated by candidate pop. // Must be set after tx.set and before tx.commit, since searchable and // produces association to tx.link, and is also an integral part of tx. if (store_.is_dirty() || !bypass) diff --git a/include/bitcoin/database/impl/query/height.ipp b/include/bitcoin/database/impl/query/height.ipp index 765735cd..b91bdb28 100644 --- a/include/bitcoin/database/impl/query/height.ipp +++ b/include/bitcoin/database/impl/query/height.ipp @@ -343,6 +343,9 @@ bool CLASS::pop_candidate() NOEXCEPT // ======================================================================== const auto scope = store_.get_transactor(); + // Candidate pop implies reorg or disorg, which implies future duplicates. + store_.set_dirty(); + /////////////////////////////////////////////////////////////////////////// std::unique_lock interlock{ candidate_reorganization_mutex_ }; return store_.candidate.truncate(top); diff --git a/include/bitcoin/database/impl/store.ipp b/include/bitcoin/database/impl/store.ipp index 115c70b1..bae4a4fd 100644 --- a/include/bitcoin/database/impl/store.ipp +++ b/include/bitcoin/database/impl/store.ipp @@ -19,6 +19,7 @@ #ifndef LIBBITCOIN_DATABASE_STORE_IPP #define LIBBITCOIN_DATABASE_STORE_IPP +#include #include #include #include @@ -561,7 +562,7 @@ code CLASS::reload(const event_handler& handler) NOEXCEPT { handler(event_t::load_file, table); ec = storage.reload(); - this->dirty_ = true; + this->dirty_.store(true, std::memory_order_relaxed); } } }; @@ -774,7 +775,8 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT load(ec, filter_tx_body_, table_t::filter_tx_body); // create, open, and restore each invoke open_load. - dirty_ = header_body_.size() > schema::header::minrow; + const auto dirty = header_body_.size() > schema::header::minrow; + dirty_.store(dirty, std::memory_order_relaxed); return ec; } @@ -1164,7 +1166,13 @@ const typename CLASS::transactor CLASS::get_transactor() NOEXCEPT TEMPLATE bool CLASS::is_dirty() const NOEXCEPT { - return dirty_; + return dirty_.load(std::memory_order_relaxed); +} + +TEMPLATE +void CLASS::set_dirty() NOEXCEPT +{ + return dirty_.store(true, std::memory_order_relaxed); } TEMPLATE diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index 1ed1d719..38f10519 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -19,6 +19,7 @@ #ifndef LIBBITCOIN_DATABASE_STORE_HPP #define LIBBITCOIN_DATABASE_STORE_HPP +#include #include #include #include @@ -95,6 +96,7 @@ class store /// Determine if the store is non-empty/initialized. bool is_dirty() const NOEXCEPT; + void set_dirty() NOEXCEPT; /// Get first fault code or error::success. code get_fault() const NOEXCEPT; @@ -235,7 +237,9 @@ class store flush_lock flush_lock_; interprocess_lock process_lock_; std::shared_timed_mutex transactor_mutex_{}; - bool dirty_{ true }; + + // This is thread safe. + std::atomic_bool dirty_{ true }; private: using path = std::filesystem::path; diff --git a/test/store.cpp b/test/store.cpp index a7eb11ee..765cdfcb 100644 --- a/test/store.cpp +++ b/test/store.cpp @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(store__is_dirty__uninitialized__true) BOOST_REQUIRE(instance.is_dirty()); } -BOOST_AUTO_TEST_CASE(store__is_dirty__initialized__true) +BOOST_AUTO_TEST_CASE(store__is_dirty__initialized___false) { settings configuration{}; configuration.path = TEST_DIRECTORY; @@ -64,6 +64,20 @@ BOOST_AUTO_TEST_CASE(store__is_dirty__initialized__true) BOOST_REQUIRE(!instance.close(events)); } +BOOST_AUTO_TEST_CASE(store__set_dirty__initialized__is_dirty) +{ + settings configuration{}; + configuration.path = TEST_DIRECTORY; + store instance{ configuration }; + query> query_{ instance }; + BOOST_REQUIRE(!instance.create(events)); + BOOST_REQUIRE(query_.initialize(test::genesis)); + BOOST_REQUIRE(!instance.is_dirty()); + instance.set_dirty(); + BOOST_REQUIRE(instance.is_dirty()); + BOOST_REQUIRE(!instance.close(events)); +} + BOOST_AUTO_TEST_CASE(store__is_dirty__open__false) { settings configuration{};