diff --git a/include/bitcoin/database/impl/primitives/hashmap.ipp b/include/bitcoin/database/impl/primitives/hashmap.ipp index 21388b6c..f9c4c93f 100644 --- a/include/bitcoin/database/impl/primitives/hashmap.ipp +++ b/include/bitcoin/database/impl/primitives/hashmap.ipp @@ -19,7 +19,7 @@ #ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP #define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP -#include +#include #include #include @@ -39,8 +39,8 @@ TEMPLATE bool CLASS::create() NOEXCEPT { Link count{}; - return head_.create() && - head_.get_body_count(count) && manager_.truncate(count); + return head_.create() && head_.get_body_count(count) && + manager_.truncate(count); } TEMPLATE @@ -59,16 +59,16 @@ TEMPLATE bool CLASS::restore() NOEXCEPT { Link count{}; - return head_.verify() && - head_.get_body_count(count) && manager_.truncate(count); + return head_.verify() && head_.get_body_count(count) && + manager_.truncate(count); } TEMPLATE bool CLASS::verify() const NOEXCEPT { Link count{}; - return head_.verify() && - head_.get_body_count(count) && count == manager_.count(); + return head_.verify() && head_.get_body_count(count) && + (count == manager_.count()); } // sizing @@ -131,7 +131,10 @@ code CLASS::reload() NOEXCEPT TEMPLATE Link CLASS::top(const Link& link) const NOEXCEPT { - return link < head_.buckets() ? head_.top(link) : Link{}; + if (link >= head_.buckets()) + return {}; + + return head_.top(link); } TEMPLATE @@ -143,16 +146,16 @@ bool CLASS::exists(const Key& key) const NOEXCEPT TEMPLATE Link CLASS::first(const Key& key) const NOEXCEPT { - return it(key).self(); + ////return it(key).self(); + // Copied from iterator::to_match(link), avoids normal form it() construct. + return first(manager_.get(), head_.top(key), key); } TEMPLATE typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT { - // TODO: key is copied into iterator, ref may be better for big keys. - // manager.get() is called for each memory access, avoiding deadlock - // risk that would arise if the memory accessor was held. - return { manager_, head_.top(key), key }; + // Expensive construction, avoid unless iteration is necessary. + return { manager_.get(), head_.top(key), key }; } TEMPLATE @@ -164,32 +167,38 @@ Link CLASS::allocate(const Link& size) NOEXCEPT TEMPLATE Key CLASS::get_key(const Link& link) NOEXCEPT { - constexpr auto key_size = array_count; const auto ptr = manager_.get(link); - - // As with link, search key is presumed valid (otherwise null array). - if (!ptr || system::is_lesser(ptr->size(), Link::size + key_size)) + if (!ptr || system::is_lesser(ptr->size(), index_size)) return {}; - return system::unsafe_array_cast(std::next( + return system::unsafe_array_cast>(std::next( ptr->begin(), Link::size)); } TEMPLATE template > -bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +bool CLASS::find(const Key& key, Element& element) const NOEXCEPT { - using namespace system; - const auto ptr = manager_.get(link); - if (!ptr) - return false; + // Renamed to "find()" to avoid collision over link/key. + // This override avoids duplicated memory_ptr construct in get(first()). + const auto ptr = manager_.get(); + return read(ptr, first(ptr, head_.top(key), key), element); +} - iostream stream{ *ptr }; - reader source{ stream }; - source.skip_bytes(Link::size + array_count); +TEMPLATE +template > +bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + // This override is the normal form. + return read(manager_.get(), link, element); +} - if constexpr (!is_slab) { source.set_limit(Size); } - return element.from_data(source); +TEMPLATE +template > +bool CLASS::get(const iterator& it, Element& element) const NOEXCEPT +{ + // This override avoids deadlock when holding iterator to the same table. + return read(it.get(), it.self(), element); } TEMPLATE @@ -203,7 +212,7 @@ bool CLASS::set(const Link& link, const Element& element) NOEXCEPT iostream stream{ *ptr }; finalizer sink{ stream }; - sink.skip_bytes(Link::size + array_count); + sink.skip_bytes(index_size); if constexpr (!is_slab) { sink.set_limit(Size); } return element.to_data(sink); @@ -214,7 +223,10 @@ template > Link CLASS::set_link(const Element& element) NOEXCEPT { Link link{}; - return set_link(link, element) ? link : Link{}; + if (!set_link(link, element)) + return {}; + + return link; } TEMPLATE @@ -230,7 +242,10 @@ template > Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT { Link link{}; - return put_link(link, key, element) ? link : Link{}; + if (!put_link(link, key, element)) + return {}; + + return link; } TEMPLATE @@ -281,6 +296,8 @@ bool CLASS::put(const Link& link, const Key& key, finalizer sink{ stream }; sink.skip_bytes(Link::size); sink.write_bytes(key); + + // The finalizer provides deferred index commit following serialization. sink.set_finalizer([this, link, index = head_.index(key), ptr]() NOEXCEPT { auto& next = unsafe_array_cast(ptr->begin()); @@ -289,7 +306,6 @@ bool CLASS::put(const Link& link, const Key& key, if constexpr (!is_slab) { sink.set_limit(Size * count); } return element.to_data(sink) && sink.finalize(); - return false; } TEMPLATE @@ -300,8 +316,8 @@ bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT return false; // Set element search key. - system::unsafe_array_cast>(std::next( - ptr->begin(), Link::size)) = key; + system::unsafe_array_cast>( + std::next(ptr->begin(), Link::size)) = key; // Commit element to search index. auto& next = system::unsafe_array_cast(ptr->begin()); @@ -311,7 +327,68 @@ bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT TEMPLATE Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT { - return commit(link, key) ? link : Link{}; + if (!commit(link, key)) + return {}; + + return link; +} + +// protected/static +// ---------------------------------------------------------------------------- + +TEMPLATE +template > +bool CLASS::read(const memory_ptr& ptr, const Link& link, + Element& element) NOEXCEPT +{ + if (!ptr || link.is_terminal()) + return false; + + using namespace system; + const auto start = iterator::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr->size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position > size) + return false; + + const auto offset = ptr->offset(position); + if (is_null(offset)) + return false; + + // Stream starts at record and the index is skipped for reader convenience. + iostream stream{ offset, size - position }; + reader source{ stream }; + source.skip_bytes(index_size); + if constexpr (!is_slab) { source.set_limit(Size); } + return element.from_data(source); +} + +TEMPLATE +Link CLASS::first(const memory_ptr& ptr, Link link, const Key& key) NOEXCEPT +{ + if (!ptr) + return {}; + + while (!link.is_terminal()) + { + // get element offset (fault) + const auto offset = ptr->offset(iterator::link_to_position(link)); + if (is_null(offset)) + return {}; + + // element key matches (found) + const auto key_ptr = std::next(offset, Link::size); + if (is_zero(std::memcmp(key.data(), key_ptr, array_count))) + return link; + + // set next element link (loop) + link = system::unsafe_array_cast(offset); + } + + return link; } } // namespace database diff --git a/include/bitcoin/database/impl/primitives/head.ipp b/include/bitcoin/database/impl/primitives/head.ipp index b98c7e88..debe305d 100644 --- a/include/bitcoin/database/impl/primitives/head.ipp +++ b/include/bitcoin/database/impl/primitives/head.ipp @@ -23,6 +23,9 @@ #include #include +// Heads are not subject to resize/remap and therefore do not require memory +// smart pointer with shared remap lock. Using get_raw() saves that allocation. + namespace libbitcoin { namespace database { @@ -100,7 +103,7 @@ bool CLASS::get_body_count(Link& count) const NOEXCEPT if (!ptr) return false; - count = array_cast(*ptr); + count = array_cast(ptr->begin()); return true; } @@ -111,7 +114,7 @@ bool CLASS::set_body_count(const Link& count) NOEXCEPT if (!ptr) return false; - array_cast(*ptr) = count; + array_cast(ptr->begin()) = count; return true; } @@ -124,11 +127,11 @@ Link CLASS::top(const Key& key) const NOEXCEPT TEMPLATE Link CLASS::top(const Link& index) const NOEXCEPT { - const auto ptr = file_.get(offset(index)); - if (!ptr) - return Link::terminal; + const auto ptr = file_.get_raw(offset(index)); + if (is_null(ptr)) + return {}; - const auto& head = array_cast(*ptr); + const auto& head = array_cast(ptr); mutex_.lock_shared(); const auto top = head; @@ -145,11 +148,11 @@ bool CLASS::push(const bytes& current, bytes& next, const Key& key) NOEXCEPT TEMPLATE bool CLASS::push(const bytes& current, bytes& next, const Link& index) NOEXCEPT { - const auto ptr = file_.get(offset(index)); - if (!ptr) + const auto ptr = file_.get_raw(offset(index)); + if (is_null(ptr)) return false; - auto& head = array_cast(*ptr); + auto& head = array_cast(ptr); mutex_.lock(); next = head; diff --git a/include/bitcoin/database/impl/primitives/iterator.ipp b/include/bitcoin/database/impl/primitives/iterator.ipp index 64f1cb5d..f1cf32b5 100644 --- a/include/bitcoin/database/impl/primitives/iterator.ipp +++ b/include/bitcoin/database/impl/primitives/iterator.ipp @@ -19,87 +19,101 @@ #ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ELEMENT_IPP #define LIBBITCOIN_DATABASE_PRIMITIVES_ELEMENT_IPP -#include +#include +#include #include #include -#include namespace libbitcoin { namespace database { TEMPLATE -INLINE CLASS::iterator(const manager& memory, const Link& start, +CLASS::iterator(const memory_ptr& data, const Link& start, const Key& key) NOEXCEPT - : manager_(memory), key_(key), link_(start) + : memory_(data), key_(key), link_(to_match(start)) { - const auto ptr = get_ptr(); - if (!is_match(ptr)) - advance(ptr); } TEMPLATE -INLINE bool CLASS::advance() NOEXCEPT +bool CLASS::advance() NOEXCEPT { - return advance(get_ptr()); + return !((link_ = to_next(link_))).is_terminal(); } TEMPLATE -INLINE const Link& CLASS::self() const NOEXCEPT +const Link& CLASS::self() const NOEXCEPT { return link_; } -// protected -// ---------------------------------------------------------------------------- - TEMPLATE -INLINE memory_ptr CLASS::get_ptr() const NOEXCEPT +const memory_ptr& CLASS::get() const NOEXCEPT { - return manager_.get(); + return memory_; } TEMPLATE -INLINE bool CLASS::advance(const memory_ptr& ptr) NOEXCEPT +CLASS::operator bool() const NOEXCEPT { - if (link_.is_terminal() || !ptr) - return false; - - do - { - link_ = get_next(ptr); - if (is_match(ptr)) - return true; - } - while (!link_.is_terminal()); - return false; + return !link_.is_terminal(); } +// protected +// ---------------------------------------------------------------------------- + TEMPLATE -INLINE bool CLASS::is_match(const memory_ptr& ptr) const NOEXCEPT +Link CLASS::to_match(Link link) const NOEXCEPT { - if (link_.is_terminal() || !ptr) - return false; + // Because of this !link_.is_terminal() subsequently guards both. + if (!memory_) + return {}; - BC_ASSERT(!system::is_add_overflow(link_to_position(link_), Link::size)); - const auto position = ptr->offset(link_to_position(link_) + Link::size); - if (is_null(position)) - return false; + while (!link.is_terminal()) + { + // get element offset (fault) + const auto offset = memory_->offset(link_to_position(link)); + if (is_null(offset)) + return {}; + + // element key matches (found) + const auto key_ptr = std::next(offset, Link::size); + if (is_zero(std::memcmp(key_.data(), key_ptr, key_size))) + return std::move(link); + + // set next element link (loop) + link = system::unsafe_array_cast(offset); + } - return is_zero(std::memcmp(key_.data(), position, key_.size())); - ////return std::equal(key_.begin(), key_.end(), position); + return std::move(link); } TEMPLATE -INLINE Link CLASS::get_next(const memory_ptr& ptr) const NOEXCEPT +Link CLASS::to_next(Link link) const NOEXCEPT { - if (link_.is_terminal() || !ptr) - return Link::terminal; - - const auto position = ptr->offset(link_to_position(link_)); - if (is_null(position)) - return Link::terminal; + while (!link.is_terminal()) + { + // get element offset (fault) + auto offset = memory_->offset(link_to_position(link)); + if (is_null(offset)) + return {}; + + // set next element link (loop) + link = { system::unsafe_array_cast(offset) }; + if (link.is_terminal()) + return std::move(link); + + // get next element offset (fault) + offset = memory_->offset(link_to_position(link)); + if (is_null(offset)) + return {}; + + // next element key matches (found) + const auto key_ptr = std::next(offset, Link::size); + if (is_zero(std::memcmp(key_.data(), key_ptr, key_size))) + return std::move(link); + } - return { system::unsafe_array_cast(position) }; + return std::move(link); } // private @@ -120,6 +134,7 @@ constexpr size_t CLASS::link_to_position(const Link& link) NOEXCEPT // Record implies link/key independent of Size. constexpr auto element_size = Link::size + array_count + Size; BC_ASSERT(!system::is_multiply_overflow(value, element_size)); + return value * element_size; } } diff --git a/include/bitcoin/database/impl/query/archive.ipp b/include/bitcoin/database/impl/query/archive.ipp index b496e581..8f09bd27 100644 --- a/include/bitcoin/database/impl/query/archive.ipp +++ b/include/bitcoin/database/impl/query/archive.ipp @@ -86,13 +86,17 @@ inline bool CLASS::is_malleated64(const block& block) const NOEXCEPT if (!block.is_malleable64()) return false; - auto it = store_.txs.it(to_header(block.hash())); + // block.get_hash() assumes cached or is not thread safe. + auto it = store_.txs.it(to_header(block.get_hash())); + if (!it) + return false; + const auto transactions = *block.transactions_ptr(); do { // Non-malleable is final so don't continue with that type association. table::txs::slab txs{}; - if (!store_.txs.get(it.self(), txs) || !txs.malleable) + if (!store_.txs.get(it, txs) || !txs.malleable) return false; if (txs.tx_fks.size() != transactions.size()) @@ -100,9 +104,10 @@ inline bool CLASS::is_malleated64(const block& block) const NOEXCEPT auto match{ true }; auto tx = transactions.begin(); - for (const auto& link: txs.tx_fks) + for (const auto& tx_fk: txs.tx_fks) { - if (store_.tx.get_key(link) != (*tx++)->hash(false)) + // tx.get_hash() assumes cached or is not thread safe. + if (store_.tx.get_key(tx_fk) != (*tx++)->get_hash(false)) { match = false; break; @@ -120,14 +125,14 @@ TEMPLATE inline bool CLASS::is_malleable64(const header_link& link) const NOEXCEPT { table::txs::get_malleable txs{}; - return store_.txs.get(to_txs_link(link), txs) && txs.malleable; + return store_.txs.find(link, txs) && txs.malleable; } TEMPLATE inline bool CLASS::is_associated(const header_link& link) const NOEXCEPT { table::txs::get_associated txs{}; - return store_.txs.get(to_txs_link(link), txs) && txs.associated; + return store_.txs.find(link, txs) && txs.associated; } TEMPLATE @@ -278,7 +283,7 @@ bool CLASS::populate_with_metadata(const block& block) const NOEXCEPT TEMPLATE hashes CLASS::get_tx_keys(const header_link& link) const NOEXCEPT { - const auto tx_fks = to_txs(link); + const auto tx_fks = to_transactions(link); if (tx_fks.empty()) return {}; @@ -309,9 +314,21 @@ inline hash_digest CLASS::get_tx_key(const tx_link& link) const NOEXCEPT return store_.tx.get_key(link); } +TEMPLATE +bool CLASS::get_height(size_t& out, const hash_digest& key) const NOEXCEPT +{ + const auto height = get_height(key); + if (height >= height_link::terminal) + return false; + + out = system::possible_narrow_cast(height.value); + return true; +} + TEMPLATE bool CLASS::get_height(size_t& out, const header_link& link) const NOEXCEPT { + // Use get_height(..., key) in place of get(to_header(key)). const auto height = get_height(link); if (height >= height_link::terminal) return false; @@ -338,7 +355,7 @@ bool CLASS::get_tx_position(size_t& out, const tx_link& link) const NOEXCEPT // False return below implies an integrity error (tx should be indexed). table::txs::get_position txs{ {}, link }; - if (!store_.txs.get(to_txs_link(block_fk), txs)) + if (!store_.txs.find(block_fk, txs)) return false; out = txs.position; @@ -425,7 +442,7 @@ typename CLASS::transactions_ptr CLASS::get_transactions( const header_link& link) const NOEXCEPT { using namespace system; - const auto txs = to_txs(link); + const auto txs = to_transactions(link); if (txs.empty()) return {}; @@ -477,7 +494,7 @@ TEMPLATE size_t CLASS::get_block_size(const header_link& link) const NOEXCEPT { table::txs::get_block_size txs{}; - return store_.txs.get(to_txs_link(link), txs) ? txs.wire : zero; + return store_.txs.find(link, txs) ? txs.wire : zero; } TEMPLATE @@ -881,7 +898,8 @@ TEMPLATE header_link CLASS::set_link(const header& header, const context& ctx, bool bypass) NOEXCEPT { - const auto key = header.hash(); + // header.get_hash() assumes cached or is not thread safe. + const auto key = header.get_hash(); ////// GUARD (header redundancy) ////// This is only fully effective if there is a single database thread. @@ -944,11 +962,12 @@ header_link CLASS::set_link(const block& block, const context& ctx, // set/unset txs // ---------------------------------------------------------------------------- +// This sets only the txs of a block with header/context already archived. TEMPLATE header_link CLASS::set_link(const block& block) NOEXCEPT { - // This sets only the txs of a block with header/context already archived. - const auto header_fk = to_header(block.hash()); + // block.get_hash() assumes cached or is not thread safe. + const auto header_fk = to_header(block.get_hash()); if (header_fk.is_terminal()) return {}; @@ -990,7 +1009,7 @@ code CLASS::set_code(txs_link& out_fk, const transactions& txs, ////// This is only fully effective if there is a single database thread. ////// Guard must be lifted for an existing top malleable association so ////// that a non-malleable association may be accomplished. - ////out_fk = to_txs_link(key); + ////out_fk = to_txs(key); ////if (!out_fk.is_terminal() && !is_malleable(key)) //// return error::success; diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index 36110a1c..ec52d13f 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -31,6 +31,17 @@ namespace database { // ---------------------------------------------------------------------------- // Not for use in validatation (2 additional gets) or confirmation (height). +// protected +TEMPLATE +height_link CLASS::get_height(const hash_digest& key) const NOEXCEPT +{ + table::header::get_height header{}; + if (!store_.header.find(key, header)) + return {}; + + return header.height; +} + // protected TEMPLATE height_link CLASS::get_height(const header_link& link) const NOEXCEPT @@ -236,24 +247,31 @@ TEMPLATE inline error::error_t CLASS::spent_prevout(const foreign_point& point, const tx_link& self) const NOEXCEPT { + // (2.94%) auto it = store_.spend.it(point); - if (it.self().is_terminal()) + if (!it) return error::success; table::spend::get_parent spend{}; do { - if (!store_.spend.get(it.self(), spend)) + // (0.38%) + if (!store_.spend.get(it, spend)) return error::integrity; + // Free (trivial). // Skip current spend, which is the only one if not double spent. if (spend.parent_fk == self) continue; + // Free (zero iteration without double spend). // If strong spender exists then prevout is confirmed double spent. if (!to_block(spend.parent_fk).is_terminal()) return error::confirmed_double_spend; } + // Expensive (31.19%). + // Iteration exists because we allow double spending, and by design cannot + // preclude it because we download and index concurrently before confirm. while (it.advance()); return error::success; } @@ -263,7 +281,12 @@ TEMPLATE inline error::error_t CLASS::unspendable_prevout(const point_link& link, uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT { - const auto strong = to_strong(get_point_key(link)); + // Modest (1.24%), and with 4.77 conflict ratio. + const auto key = get_point_key(link); + + // Expensize (8.6%). + const auto strong = to_strong(key); + if (strong.block.is_terminal()) return strong.tx.is_terminal() ? error::missing_previous_output : error::unconfirmed_spend; @@ -317,27 +340,34 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT if (!get_context(ctx, link)) return error::integrity; - const auto txs = to_txs(link); + // (0.07%) + const auto txs = to_transactions(link); if (txs.empty()) return error::success; - code ec{}; + // (0.11%) because !bip30. + code ec{}; if ((ec = unspent_duplicates(txs.front(), ctx))) return ec; + // (0.33%) uint32_t version{}; table::spend::get_prevout_sequence spend{}; for (auto tx = std::next(txs.begin()); tx != txs.end(); ++tx) { + // (4.71%) tx.get, puts.get, reduce collision. for (const auto& spend_fk: to_tx_spends(version, *tx)) { + // (3.65%) spend.get, reduce collision. if (!store_.spend.get(spend_fk, spend)) return error::integrity; + // (33.42%) if ((ec = unspendable_prevout(spend.point_fk, spend.sequence, version, ctx))) return ec; + // (34.74%) if ((ec = spent_prevout(spend.prevout(), *tx))) return ec; } @@ -374,7 +404,7 @@ bool CLASS::is_strong(const header_link& link) const NOEXCEPT TEMPLATE bool CLASS::set_strong(const header_link& link) NOEXCEPT { - const auto txs = to_txs(link); + const auto txs = to_transactions(link); if (txs.empty()) return false; @@ -389,7 +419,7 @@ bool CLASS::set_strong(const header_link& link) NOEXCEPT TEMPLATE bool CLASS::set_unstrong(const header_link& link) NOEXCEPT { - const auto txs = to_txs(link); + const auto txs = to_transactions(link); if (txs.empty()) return false; diff --git a/include/bitcoin/database/impl/query/optional.ipp b/include/bitcoin/database/impl/query/optional.ipp index 248689de..842a8596 100644 --- a/include/bitcoin/database/impl/query/optional.ipp +++ b/include/bitcoin/database/impl/query/optional.ipp @@ -36,14 +36,14 @@ bool CLASS::get_confirmed_balance(uint64_t& out, const hash_digest& key) const NOEXCEPT { auto it = store_.address.it(key); - if (it.self().is_terminal()) + if (!it) return false; out = zero; do { table::address::record address{}; - if (!store_.address.get(it.self(), address)) + if (!store_.address.get(it, address)) return false; // Failure or overflow returns maximum value. @@ -66,14 +66,14 @@ bool CLASS::to_address_outputs(output_links& out, const hash_digest& key) const NOEXCEPT { auto it = store_.address.it(key); - if (it.self().is_terminal()) + if (!it) return false; out.clear(); do { table::address::record address{}; - if (!store_.address.get(it.self(), address)) + if (!store_.address.get(it, address)) { out.clear(); return false; @@ -91,14 +91,14 @@ bool CLASS::to_unspent_outputs(output_links& out, const hash_digest& key) const NOEXCEPT { auto it = store_.address.it(key); - if (it.self().is_terminal()) - return {}; + if (!it) + return false; out.clear(); do { table::address::record address{}; - if (!store_.address.get(it.self(), address)) + if (!store_.address.get(it, address)) { out.clear(); return false; @@ -117,15 +117,15 @@ bool CLASS::to_minimum_unspent_outputs(output_links& out, const hash_digest& key, uint64_t minimum) const NOEXCEPT { auto it = store_.address.it(key); - if (it.self().is_terminal()) - return {}; + if (!it) + return false; out.clear(); do { table::address::record address{}; - if (!store_.address.get(it.self(), address)) - return {}; + if (!store_.address.get(it, address)) + return false; // Confirmed and not spent, but possibly immature. if (is_confirmed_output(address.output_fk) && @@ -179,7 +179,7 @@ TEMPLATE bool CLASS::get_filter(filter& out, const header_link& link) const NOEXCEPT { table::neutrino::get_filter neutrino{}; - if (!store_.neutrino.get(store_.neutrino.first(link), neutrino)) + if (!store_.neutrino.find(link, neutrino)) return false; out = std::move(neutrino.filter); @@ -191,7 +191,7 @@ bool CLASS::get_filter_head(hash_digest& out, const header_link& link) const NOEXCEPT { table::neutrino::get_head neutrino{}; - if (!store_.neutrino.get(store_.neutrino.first(link), neutrino)) + if (!store_.neutrino.find(link, neutrino)) return false; out = std::move(neutrino.filter_head); @@ -229,7 +229,7 @@ bool CLASS::set_filter(const header_link& link, const hash_digest& filter_head, //// const tx_link& link) const NOEXCEPT ////{ //// table::buffer::slab_ptr buffer{}; -//// if (!store_.buffer.get(store_.buffer.first(link), buffer)) +//// if (!store_.buffer.find(link, buffer)) //// return {}; //// //// return buffer.tx; diff --git a/include/bitcoin/database/impl/query/translate.ipp b/include/bitcoin/database/impl/query/translate.ipp index 29cf1667..a1e71565 100644 --- a/include/bitcoin/database/impl/query/translate.ipp +++ b/include/bitcoin/database/impl/query/translate.ipp @@ -65,30 +65,35 @@ inline header_link CLASS::to_confirmed(size_t height) const NOEXCEPT TEMPLATE inline header_link CLASS::to_header(const hash_digest& key) const NOEXCEPT { + // Use header.find(key) in place of get(to_header(key)). return store_.header.first(key); } TEMPLATE inline point_link CLASS::to_point(const hash_digest& key) const NOEXCEPT { + // Use point.find(key) in place of get(to_point(key)). return store_.point.first(key); } TEMPLATE inline tx_link CLASS::to_tx(const hash_digest& key) const NOEXCEPT { + // Use tx.find(key) in place of get(to_tx(key)). return store_.tx.first(key); } TEMPLATE -inline txs_link CLASS::to_txs_link(const header_link& key) const NOEXCEPT +inline txs_link CLASS::to_txs(const header_link& key) const NOEXCEPT { + // Use txs.find(key) in place of get(to_txs(key)). return store_.txs.first(key); } TEMPLATE inline filter_link CLASS::to_filter(const header_link& key) const NOEXCEPT { + // Use neutrino.find(key) in place of get(to_filter(key)). return store_.neutrino.first(key); } @@ -196,12 +201,22 @@ header_link CLASS::to_parent(const header_link& link) const NOEXCEPT TEMPLATE header_link CLASS::to_block(const tx_link& link) const NOEXCEPT { + // (10.99%) from block_confirmable->unspendable_prevout->to_strong->to_block. + + // (8.25%) + ////const auto to_strong_tx_link = store_.strong_tx.first(link); + + // (2.68%) table::strong_tx::record strong{}; - if (!store_.strong_tx.get(store_.strong_tx.first(link), strong)) + ////if (!store_.strong_tx.get(to_strong_tx_link, strong)) + if (!store_.strong_tx.find(link, strong)) return {}; // Terminal implies not strong (false). - return strong.positive ? strong.header_fk : header_link::terminal; + if (!strong.positive) + return {}; + + return strong.header_fk; } // protected @@ -211,14 +226,22 @@ header_link CLASS::to_block(const tx_link& link) const NOEXCEPT TEMPLATE inline strong_pair CLASS::to_strong(const hash_digest& tx_hash) const NOEXCEPT { + // (14.21%) from block_confirmable, reduce collision. auto it = store_.tx.it(tx_hash); strong_pair strong{ {}, it.self() }; + if (!it) + return strong; + do { - const auto link = to_block(it.self()); - if (!link.is_terminal()) - return { link, it.self() }; + strong.tx = it.self(); + + // (10.99%) from block_confirmable. + strong.block = to_block(strong.tx); + if (!strong.block.is_terminal()) + return strong; } + // (0.28%) while (it.advance()); return strong; } @@ -234,6 +257,9 @@ TEMPLATE inline strong_pairs CLASS::to_strongs(const hash_digest& tx_hash) const NOEXCEPT { auto it = store_.tx.it(tx_hash); + if (!it) + return {}; + strong_pairs strongs{}; do { @@ -256,12 +282,14 @@ TEMPLATE inline header_links CLASS::to_blocks(const tx_link& link) const NOEXCEPT { auto it = store_.strong_tx.it(link); - block_tx strong{}; + if (!it) + return {}; + block_txs strongs{}; do { - if (store_.strong_tx.get(it.self(), strong) && - !contains(strongs, strong)) + block_tx strong{}; + if (store_.strong_tx.get(it, strong) && !contains(strongs, strong)) strongs.push_back(strong); } while(it.advance()); @@ -370,13 +398,14 @@ TEMPLATE spend_links CLASS::to_spenders(const foreign_point& point) const NOEXCEPT { auto it = store_.spend.it(point); - if (it.self().is_terminal()) + if (!it) return {}; // Iterate transactions that spend the point, saving each spender. spend_links spenders{}; do - { + { // BUGBUG: Deadlock due to holding iterator while querying own table. + // TODO: refactor to make safe and also pass boolean result code. spenders.push_back(to_spender(to_spend_tx(it.self()), point)); } while (it.advance()); @@ -423,12 +452,17 @@ TEMPLATE spend_links CLASS::to_tx_spends(uint32_t& version, const tx_link& link) const NOEXCEPT { + // (4.71%) from block_confirmable. + + // (2.53%) table::transaction::get_version_puts tx{}; if (!store_.tx.get(link, tx)) return {}; version = tx.version; table::puts::get_spends puts{}; + + // (2.1%) puts.spend_fks.resize(tx.ins_count); if (!store_.puts.get(tx.puts_fk, puts)) return {}; @@ -440,10 +474,11 @@ spend_links CLASS::to_tx_spends(uint32_t& version, // ---------------------------------------------------------------------------- TEMPLATE -tx_links CLASS::to_txs(const header_link& link) const NOEXCEPT +tx_links CLASS::to_transactions(const header_link& link) const NOEXCEPT { table::txs::slab txs{}; - if (!store_.txs.get(to_txs_link(link), txs)) + ////if (!store_.txs.get(to_txs(link), txs)) + if (!store_.txs.find(link, txs)) return {}; return std::move(txs.tx_fks); @@ -453,7 +488,8 @@ TEMPLATE tx_link CLASS::to_coinbase(const header_link& link) const NOEXCEPT { table::txs::get_coinbase txs{}; - if (!store_.txs.get(to_txs_link(link), txs)) + ////if (!store_.txs.get(to_txs(link), txs)) + if (!store_.txs.find(link, txs)) return {}; return txs.coinbase_fk; @@ -463,7 +499,7 @@ TEMPLATE spend_links CLASS::to_non_coinbase_spends( const header_link& link) const NOEXCEPT { - const auto txs = to_txs(link); + const auto txs = to_transactions(link); if (txs.size() <= one) return {}; @@ -482,7 +518,7 @@ TEMPLATE spend_links CLASS::to_block_spends(const header_link& link) const NOEXCEPT { spend_links spends{}; - const auto txs = to_txs(link); + const auto txs = to_transactions(link); for (const auto& tx: txs) { @@ -497,7 +533,7 @@ TEMPLATE output_links CLASS::to_block_outputs(const header_link& link) const NOEXCEPT { output_links outputs{}; - const auto txs = to_txs(link); + const auto txs = to_transactions(link); for (const auto& tx: txs) { diff --git a/include/bitcoin/database/impl/query/validate.ipp b/include/bitcoin/database/impl/query/validate.ipp index b58f47d1..dc3bf07d 100644 --- a/include/bitcoin/database/impl/query/validate.ipp +++ b/include/bitcoin/database/impl/query/validate.ipp @@ -170,13 +170,9 @@ bool CLASS::get_bypass(bool& bypass, const header_link& link) const NOEXCEPT TEMPLATE code CLASS::get_header_state(const header_link& link) const NOEXCEPT { - const auto fk = store_.validated_bk.first(link); - if (fk.is_terminal()) - return error::unvalidated; - table::validated_bk::slab_get_code valid{}; - if (!store_.validated_bk.get(fk, valid)) - return error::integrity; + if (!store_.validated_bk.find(link, valid)) + return error::unvalidated; return to_block_code(valid.code); } @@ -184,13 +180,9 @@ code CLASS::get_header_state(const header_link& link) const NOEXCEPT TEMPLATE code CLASS::get_block_state(const header_link& link) const NOEXCEPT { - const auto fk = store_.validated_bk.first(link); - if (fk.is_terminal()) - return is_associated(link) ? error::unvalidated : error::unassociated; - table::validated_bk::slab_get_code valid{}; - if (!store_.validated_bk.get(fk, valid)) - return error::integrity; + if (!store_.validated_bk.find(link, valid)) + return is_associated(link) ? error::unvalidated : error::unassociated; // Fees only valid if block_confirmable. return to_block_code(valid.code); @@ -200,13 +192,9 @@ TEMPLATE code CLASS::get_block_state(uint64_t& fees, const header_link& link) const NOEXCEPT { - const auto fk = store_.validated_bk.first(link); - if (fk.is_terminal()) - return is_associated(link) ? error::unvalidated : error::unassociated; - table::validated_bk::slab valid{}; - if (!store_.validated_bk.get(fk, valid)) - return error::integrity; + if (!store_.validated_bk.find(link, valid)) + return is_associated(link) ? error::unvalidated : error::unassociated; // Fees only valid if block_confirmable. fees = valid.fees; @@ -218,13 +206,13 @@ code CLASS::get_tx_state(const tx_link& link, const context& ctx) const NOEXCEPT { auto it = store_.validated_tx.it(link); - if (it.self().is_terminal()) + if (!it) return error::unvalidated; table::validated_tx::slab_get_code valid{}; do { - if (!store_.validated_tx.get(it.self(), valid)) + if (!store_.validated_tx.get(it, valid)) return error::integrity; if (is_sufficient(ctx, valid.ctx)) @@ -239,13 +227,13 @@ code CLASS::get_tx_state(uint64_t& fee, size_t& sigops, const tx_link& link, const context& ctx) const NOEXCEPT { auto it = store_.validated_tx.it(link); - if (it.self().is_terminal()) + if (!it) return error::unvalidated; table::validated_tx::slab valid{}; do { - if (!store_.validated_tx.get(it.self(), valid)) + if (!store_.validated_tx.get(it, valid)) return error::integrity; if (is_sufficient(ctx, valid.ctx)) @@ -375,7 +363,7 @@ bool CLASS::set_txs_connected(const header_link& link) NOEXCEPT if (!get_context(ctx, link)) return false; - const auto txs = to_txs(link); + const auto txs = to_transactions(link); if (txs.empty()) return false; diff --git a/include/bitcoin/database/memory/interfaces/storage.hpp b/include/bitcoin/database/memory/interfaces/storage.hpp index 5095e9ad..b15567bd 100644 --- a/include/bitcoin/database/memory/interfaces/storage.hpp +++ b/include/bitcoin/database/memory/interfaces/storage.hpp @@ -67,9 +67,12 @@ class storage /// Allocate bytes and return offset to first allocated (or eof). virtual size_t allocate(size_t chunk) NOEXCEPT = 0; - /// Get r/w access to start/offset of memory map (or null). + /// Get remap-protected r/w access to start/offset of memory map (or null). virtual memory_ptr get(size_t offset=zero) const NOEXCEPT = 0; + /// Get unprotected r/w access to start/offset of memory map (or null). + virtual memory::iterator get_raw(size_t offset=zero) const NOEXCEPT = 0; + /// Get the fault condition. virtual code get_fault() const NOEXCEPT = 0; diff --git a/include/bitcoin/database/memory/map.hpp b/include/bitcoin/database/memory/map.hpp index 2792b6b8..5433f678 100644 --- a/include/bitcoin/database/memory/map.hpp +++ b/include/bitcoin/database/memory/map.hpp @@ -90,9 +90,12 @@ class BCD_API map /// Allocate bytes and return offset to first allocated (or eof). size_t allocate(size_t chunk) NOEXCEPT override; - /// Get r/w access to start/offset of memory map (or null). + /// Get remap-protected r/w access to start/offset of memory map (or null). memory_ptr get(size_t offset=zero) const NOEXCEPT override; + /// Get unprotected r/w access to start/offset of memory map (or null). + memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override; + /// Get the fault condition. code get_fault() const NOEXCEPT override; diff --git a/include/bitcoin/database/primitives/hashmap.hpp b/include/bitcoin/database/primitives/hashmap.hpp index a8f6d4fd..679bc2be 100644 --- a/include/bitcoin/database/primitives/hashmap.hpp +++ b/include/bitcoin/database/primitives/hashmap.hpp @@ -92,10 +92,10 @@ class hashmap /// True if an instance of object with key exists. bool exists(const Key& key) const NOEXCEPT; - /// Return first element or terimnal. + /// Return first element link or terimnal if not found/error. Link first(const Key& key) const NOEXCEPT; - /// Iterator walks hashmap conflict stack for matches from top down. + /// Iterator holds shared lock on storage remap. iterator it(const Key& key) const NOEXCEPT; /// Return the link at the top of the conflict list (for table scanning). @@ -107,10 +107,19 @@ class hashmap /// Return the associated search key (terminal link returns default). Key get_key(const Link& link) NOEXCEPT; + /// Get first element matching the search key, false if not found/error. + template = true> + bool find(const Key& key, Element& element) const NOEXCEPT; + /// Get element at link, false if deserialize error. template = true> bool get(const Link& link, Element& element) const NOEXCEPT; + /// Get element at link, false if deserialize error. + /// Iterator must not be terminal, must be guarded by called. + template = true> + bool get(const iterator& it, Element& element) const NOEXCEPT; + /// Set element into previously allocated link (follow with commit). template = true> bool set(const Link& link, const Element& element) NOEXCEPT; @@ -139,8 +148,19 @@ class hashmap bool commit(const Link& link, const Key& key) NOEXCEPT; Link commit_link(const Link& link, const Key& key) NOEXCEPT; +protected: + /// Get element at link using memory object, false if deserialize error. + template = true> + static bool read(const memory_ptr& ptr, const Link& link, + Element& element) NOEXCEPT; + + /// Get first element matching key, from top link and whole table memory. + static Link first(const memory_ptr& ptr, Link link, const Key& key) NOEXCEPT; + private: static constexpr auto is_slab = (Size == max_size_t); + static constexpr auto index_size = Link::size + array_count; + using head = database::head; using manager = database::manager; diff --git a/include/bitcoin/database/primitives/head.hpp b/include/bitcoin/database/primitives/head.hpp index 47864fe9..ed1bf2fb 100644 --- a/include/bitcoin/database/primitives/head.hpp +++ b/include/bitcoin/database/primitives/head.hpp @@ -64,9 +64,9 @@ class head private: template - static auto& array_cast(memory& buffer) NOEXCEPT + static auto& array_cast(memory::iterator buffer) NOEXCEPT { - return system::unsafe_array_cast(buffer.begin()); + return system::unsafe_array_cast(buffer); } static constexpr size_t offset(const Link& index) NOEXCEPT diff --git a/include/bitcoin/database/primitives/iterator.hpp b/include/bitcoin/database/primitives/iterator.hpp index 095899bc..edda5247 100644 --- a/include/bitcoin/database/primitives/iterator.hpp +++ b/include/bitcoin/database/primitives/iterator.hpp @@ -21,12 +21,20 @@ #include #include -#include #include namespace libbitcoin { namespace database { +/// THIS HOLDS A memory_ptr WHICH HOLDS A SHARED REMAP LOCK. IT SHOULD NOT BE +/// HELD WHILE THE HOLDING CODE EXECUTES READS AGAINST THE SAME TABLE. +/// OTHERWISE A DEADLOCK WILL OCCUR WHEN THE TABLE'S FILE IS EXPANDED, WHICH +/// WAITS ON THE RELEASE OF THE SHARED LOCK (REMAP REQUIRES EXCLUSIVE ACCESS). +/// THE hashmap.get(const iterator& it, ...) METHOD EXISTS TO PREVENT A CALL TO +/// manager.get(), WHICH DESPITE BEING A READ WOULD CAUSE A DEADLOCK. THIS IS +/// BECAUSE IT CANNOT COMPLETE ITS READ WHILE REMAP IS WAITING ON ACCESS. + +/// This class is not thread safe. /// Size non-max implies record manager (ordinal record links). template class iterator @@ -34,30 +42,40 @@ class iterator public: DEFAULT_COPY_MOVE_DESTRUCT(iterator); - using manager = database::manager; + static constexpr size_t link_to_position(const Link& link) NOEXCEPT; /// This advances to first match (or terminal). - INLINE iterator(const manager& memory, const Link& start, + /// Key must be passed as an l-value as it is held by reference. + iterator(const memory_ptr& data, const Link& start, const Key& key) NOEXCEPT; /// Advance to and return next iterator. - INLINE bool advance() NOEXCEPT; + bool advance() NOEXCEPT; /// Advance to next match and return false if terminal (not found). - INLINE const Link& self() const NOEXCEPT; + const Link& self() const NOEXCEPT; + + /// Access the underlying memory pointer. + // TODO: for use by hashmap, make exclusive via friend. + const memory_ptr& get() const NOEXCEPT; + + /// True if the iterator is not terminal. + operator bool() const NOEXCEPT; protected: - INLINE memory_ptr get_ptr() const NOEXCEPT; - INLINE bool advance(const memory_ptr& ptr) NOEXCEPT; - INLINE bool is_match(const memory_ptr& ptr) const NOEXCEPT; - INLINE Link get_next(const memory_ptr& ptr) const NOEXCEPT; + Link to_match(Link link) const NOEXCEPT; + Link to_next(Link link) const NOEXCEPT; private: + static constexpr auto key_size = array_count; static constexpr auto is_slab = (Size == max_size_t); - static constexpr size_t link_to_position(const Link& link) NOEXCEPT; - // These are thread safe. - const manager& manager_; + // This is not thread safe, but it's object is not modified here and the + // memory that it refers to is not addressable until written, and writes + // are guarded by allocator, which is protected by mutex. + const memory_ptr memory_; + + // This is thread safe. const Key key_; // This is not thread safe. diff --git a/include/bitcoin/database/primitives/manager.hpp b/include/bitcoin/database/primitives/manager.hpp index 85fecaf1..53cbd213 100644 --- a/include/bitcoin/database/primitives/manager.hpp +++ b/include/bitcoin/database/primitives/manager.hpp @@ -58,7 +58,7 @@ class manager /// memory is writeable. Non-const access implies memory map modify. memory_ptr get(const Link& link) const NOEXCEPT; - /// Return memory object for the full memory map. + /// Return memory object for full memory map (null only if oom or unloaded). memory_ptr get() const NOEXCEPT; /// Get the fault condition. diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 0688c462..e86cdd5c 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -230,7 +230,7 @@ class query inline header_link to_header(const hash_digest& key) const NOEXCEPT; inline point_link to_point(const hash_digest& key) const NOEXCEPT; inline tx_link to_tx(const hash_digest& key) const NOEXCEPT; - inline txs_link to_txs_link(const header_link& key) const NOEXCEPT; + inline txs_link to_txs(const header_link& key) const NOEXCEPT; inline filter_link to_filter(const header_link& key) const NOEXCEPT; /// put to tx (reverse navigation) @@ -260,8 +260,8 @@ class query spend_links to_tx_spends(const tx_link& link) const NOEXCEPT; /// block to txs/puts (forward navigation) - tx_links to_txs(const header_link& link) const NOEXCEPT; tx_link to_coinbase(const header_link& link) const NOEXCEPT; + tx_links to_transactions(const header_link& link) const NOEXCEPT; spend_links to_non_coinbase_spends(const header_link& link) const NOEXCEPT; spend_links to_block_spends(const header_link& link) const NOEXCEPT; output_links to_block_outputs(const header_link& link) const NOEXCEPT; @@ -317,6 +317,7 @@ class query bool get_tx_position(size_t& out, const tx_link& link) const NOEXCEPT; /// False implies fault. + bool get_height(size_t& out, const hash_digest& key) const NOEXCEPT; bool get_height(size_t& out, const header_link& link) const NOEXCEPT; bool get_value(uint64_t& out, const output_link& link) const NOEXCEPT; bool get_unassociated(association& out, const header_link& link) const NOEXCEPT; @@ -491,6 +492,7 @@ class query /// Confirm. /// ----------------------------------------------------------------------- + height_link get_height(const hash_digest& key) const NOEXCEPT; height_link get_height(const header_link& link) const NOEXCEPT; bool is_confirmed_unspent(const output_link& link) const NOEXCEPT; error::error_t mature_prevout(const point_link& link, diff --git a/include/bitcoin/database/tables/archives/txs.hpp b/include/bitcoin/database/tables/archives/txs.hpp index dc0d5a77..6f0bca26 100644 --- a/include/bitcoin/database/tables/archives/txs.hpp +++ b/include/bitcoin/database/tables/archives/txs.hpp @@ -45,7 +45,7 @@ struct txs link count() const NOEXCEPT { return system::possible_narrow_cast(pk + sk + - schema::count_ + schema::bit + bytes::size +tx::size * tx_fks.size()); + schema::count_ + schema::bit + bytes::size + tx::size * tx_fks.size()); } inline bool from_data(reader& source) NOEXCEPT diff --git a/src/memory/map.cpp b/src/memory/map.cpp index 57c1491f..d3afb87b 100644 --- a/src/memory/map.cpp +++ b/src/memory/map.cpp @@ -282,6 +282,17 @@ memory_ptr map::get(size_t offset) const NOEXCEPT return ptr; } +memory::iterator map::get_raw(size_t offset) const NOEXCEPT +{ + // Pointer is otherwise ungaurded, not remap safe (use for table heads). + if (offset > size()) + return nullptr; + + BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) + return memory_map_ + offset; + BC_POP_WARNING() +} + code map::get_fault() const NOEXCEPT { return error_.load(); diff --git a/test/mocks/chunk_storage.cpp b/test/mocks/chunk_storage.cpp index f6aa80c0..9eaa108d 100644 --- a/test/mocks/chunk_storage.cpp +++ b/test/mocks/chunk_storage.cpp @@ -125,13 +125,16 @@ memory_ptr chunk_storage::get(size_t offset) const NOEXCEPT const auto ptr = std::make_shared>(map_mutex_); // With offset > size the assignment is negative (stream is exhausted). - ptr->assign( - std::next(buffer_.data(), offset), - std::next(buffer_.data(), size())); + ptr->assign(get_raw(offset), get_raw(size())); return ptr; } +memory::iterator chunk_storage::get_raw(size_t offset) const NOEXCEPT +{ + return std::next(buffer_.data(), offset); +} + code chunk_storage::get_fault() const NOEXCEPT { return {}; diff --git a/test/mocks/chunk_storage.hpp b/test/mocks/chunk_storage.hpp index 9ca9821a..24f6b950 100644 --- a/test/mocks/chunk_storage.hpp +++ b/test/mocks/chunk_storage.hpp @@ -50,6 +50,7 @@ class chunk_storage bool truncate(size_t size) NOEXCEPT override; size_t allocate(size_t chunk) NOEXCEPT override; memory_ptr get(size_t offset=zero) const NOEXCEPT override; + memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override; code get_fault() const NOEXCEPT override; size_t get_space() const NOEXCEPT override; diff --git a/test/primitives/iterator.cpp b/test/primitives/iterator.cpp index 8eb8af5a..08c1286d 100644 --- a/test/primitives/iterator.cpp +++ b/test/primitives/iterator.cpp @@ -23,71 +23,25 @@ BOOST_AUTO_TEST_SUITE(iterator_tests) using namespace system; -template -class iterator_ - : public iterator -{ -public: - using base = iterator; - - iterator_(const typename base::manager& memory, const Link& start, - const Key& key) NOEXCEPT - : iterator(memory, start, key) - { - } - - bool is_match_() const NOEXCEPT - { - return base::is_match(base::get_ptr()); - } - - Link get_next_() const NOEXCEPT - { - return base::get_next(base::get_ptr()); - } -}; - -BOOST_AUTO_TEST_CASE(iterator__get_next__empty__terminal) +BOOST_AUTO_TEST_CASE(iterator__self__empty__terminal) { using link = linkage<4>; using key = data_array<0>; - using slab_iterate = iterator_; - - constexpr key key0{}; - test::chunk_storage file{}; - slab_iterate::manager manager{ file }; - slab_iterate iterator{ manager, link::terminal, key0 }; - BOOST_REQUIRE(iterator.get_next_().is_terminal()); -} - -BOOST_AUTO_TEST_CASE(iterator__get_next__overflow__terminal) -{ - using link = linkage<4>; - using key = data_array<0>; - using slab_iterate = iterator_; - - constexpr link head{ 13 }; + using slab_iterate = iterator; + constexpr auto start = link::terminal; constexpr key key0{}; - data_chunk data - { - 0x00, 0x01, 0x02, 0x03, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - test::chunk_storage file{ data }; - slab_iterate::manager manager{ file }; - slab_iterate iterator{ manager, head, key0 }; - BOOST_REQUIRE(iterator.get_next_().is_terminal()); + test::chunk_storage file; + const slab_iterate iterator{ file.get(), start, key0 }; + BOOST_REQUIRE(iterator.self().is_terminal()); + BOOST_REQUIRE(!iterator); } -BOOST_AUTO_TEST_CASE(iterator__get__offset0__expected) +BOOST_AUTO_TEST_CASE(iterator__self__overflow__terminal) { using link = linkage<4>; using key = data_array<0>; - using slab_iterate = iterator_; - - constexpr link head{ 0 }; + using slab_iterate = iterator; + constexpr auto start = 13; constexpr key key0{}; data_chunk data { @@ -95,59 +49,18 @@ BOOST_AUTO_TEST_CASE(iterator__get__offset0__expected) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - test::chunk_storage file{ data }; - slab_iterate::manager manager{ file }; - slab_iterate iterator{ manager, head, key0 }; - BOOST_REQUIRE_EQUAL(iterator.get_next_(), 0x03020100_u32); -} - -BOOST_AUTO_TEST_CASE(iterator__get__offset1__expected) -{ - using link = linkage<4>; - using key = data_array<0>; - using slab_iterate = iterator_; - - constexpr link head{ 8 }; - constexpr key key0{}; - data_chunk data - { - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03 - }; - - test::chunk_storage file{ data }; - slab_iterate::manager manager{ file }; - slab_iterate iterator{ manager, head, key0 }; - BOOST_REQUIRE_EQUAL(iterator.get_next_(), 0x03020100_u32); -} - -BOOST_AUTO_TEST_CASE(iterator__get__offset_to_back__expected) -{ - using link = linkage<1>; - using key = data_array<0>; - using slab_iterate = iterator_; - - constexpr link head{ 41 }; - constexpr key key0{}; - data_chunk data(42u, 0xff); - test::chunk_storage file{ data }; - slab_iterate::manager manager{ file }; - slab_iterate iterator{ manager, head, key0 }; - BOOST_REQUIRE_EQUAL(iterator.get_next_(), 0xffu); + const slab_iterate iterator{ file.get(), start, key0 }; + BOOST_REQUIRE(iterator.self().is_terminal()); + BOOST_REQUIRE(!iterator); } -BOOST_AUTO_TEST_CASE(iterator__next__self__expected) +BOOST_AUTO_TEST_CASE(iterator__advance__record__expected) { using link = linkage<1>; using key = data_array<2>; - constexpr size_t value = 1; - using record_iterate = iterator_; - static_assert(link::terminal == 0xff); - - constexpr link head{ 0 }; + using record_iterate = iterator; + constexpr auto start = 0; constexpr key key2{ 0x1a, 0x2a }; data_chunk data { @@ -155,61 +68,42 @@ BOOST_AUTO_TEST_CASE(iterator__next__self__expected) 0x02, 0x1a, 0x2a, 0xee, 0xff, 0xcc, 0xcc, 0xee }; - test::chunk_storage file{ data }; - record_iterate::manager manager{ file }; - record_iterate iterator1{ manager, head, key2 }; - - // First record is 0x00. - BOOST_REQUIRE_EQUAL(iterator1.self(), 0x00u); - - // Next record is 0x01 (data[0]->data[4]). - BOOST_REQUIRE_EQUAL(iterator1.get_next_(), 0x01u); - BOOST_REQUIRE(iterator1.is_match_()); - - // Set self/link to record 0x01. - BOOST_REQUIRE(iterator1.advance()); - BOOST_REQUIRE_EQUAL(iterator1.self(), 0x01u); - BOOST_REQUIRE(iterator1.is_match_()); - - // Next record is 0x02 (data[4]->data[8]). - BOOST_REQUIRE_EQUAL(iterator1.get_next_(), 0x02u); - BOOST_REQUIRE_EQUAL(iterator1.self(), 0x01u); - BOOST_REQUIRE(iterator1.is_match_()); - - // Advance returns false (no match) and self becomes terminal. - BOOST_REQUIRE(!iterator1.advance()); - BOOST_REQUIRE(!iterator1.is_match_()); - BOOST_REQUIRE(iterator1.self().is_terminal()); - BOOST_REQUIRE(!iterator1.advance()); - BOOST_REQUIRE(!iterator1.is_match_()); - BOOST_REQUIRE(iterator1.self().is_terminal()); + record_iterate iterator{ file.get(), start, key2 }; + BOOST_REQUIRE(iterator); + BOOST_REQUIRE_EQUAL(iterator.self(), 0x00u); + BOOST_REQUIRE(iterator.advance()); + BOOST_REQUIRE(iterator); + BOOST_REQUIRE_EQUAL(iterator.self(), 0x01u); + BOOST_REQUIRE(!iterator.advance()); + BOOST_REQUIRE(!iterator); + BOOST_REQUIRE_EQUAL(iterator.self(), link::terminal); } -BOOST_AUTO_TEST_CASE(iterator__next__true__non_terminal) +BOOST_AUTO_TEST_CASE(iterator__advance__slab__expected) { using link = linkage<1>; using key = data_array<2>; - using slab_iterate = iterator_; + using slab_iterate = iterator; - constexpr link head{ 0 }; + constexpr auto start = 0; constexpr key key2{ 0x1a, 0x2a }; data_chunk data { - 0x04, 0x1a, 0x2a, 0xee, - 0x08, 0x1a, 0x2a, 0xee, - 0xff, 0xcc, 0xcc, 0xee + 0x03, 0x1a, 0x2a, + 0x07, 0x1a, 0x2a, 0xee, + 0xff, 0xcc, 0xcc, 0xee, 0xee }; - test::chunk_storage file{ data }; - slab_iterate::manager manager{ file }; - slab_iterate iterator{ manager, head, key2 }; - - BOOST_REQUIRE(!iterator.self().is_terminal()); + slab_iterate iterator{ file.get(), start, key2 }; + BOOST_REQUIRE(iterator); + BOOST_REQUIRE_EQUAL(iterator.self(), 0x00u); BOOST_REQUIRE(iterator.advance()); - BOOST_REQUIRE(!iterator.self().is_terminal()); + BOOST_REQUIRE(iterator); + BOOST_REQUIRE_EQUAL(iterator.self(), 0x03u); BOOST_REQUIRE(!iterator.advance()); - BOOST_REQUIRE(iterator.self().is_terminal()); + BOOST_REQUIRE(!iterator); + BOOST_REQUIRE_EQUAL(iterator.self(), link::terminal); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/archive.cpp b/test/query/archive.cpp index 60c8c2a2..d3d1988d 100644 --- a/test/query/archive.cpp +++ b/test/query/archive.cpp @@ -1217,7 +1217,7 @@ BOOST_AUTO_TEST_CASE(query_archive__get_tx_key__always__expected) BOOST_REQUIRE_EQUAL(query.get_tx_key(3), system::null_hash); } -BOOST_AUTO_TEST_CASE(query_archive__get_height__always__expected) +BOOST_AUTO_TEST_CASE(query_archive__get_height1__always__expected) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -1247,6 +1247,36 @@ BOOST_AUTO_TEST_CASE(query_archive__get_height__always__expected) BOOST_REQUIRE(!query.get_height(out, 6)); } +BOOST_AUTO_TEST_CASE(query_archive__get_height2__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, false)); + BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, false)); + BOOST_REQUIRE(query.set(test::block3, context{ 0, 3, 0 }, false)); + BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, false)); + BOOST_REQUIRE(query.set(test::block2a, context{ 0, 2, 0 }, false)); + + size_t out{}; + BOOST_REQUIRE(query.get_height(out, test::genesis.hash())); + BOOST_REQUIRE_EQUAL(out, 0u); + BOOST_REQUIRE(query.get_height(out, test::block1.hash())); + BOOST_REQUIRE_EQUAL(out, 1u); + BOOST_REQUIRE(query.get_height(out, test::block2.hash())); + BOOST_REQUIRE_EQUAL(out, 2u); + BOOST_REQUIRE(query.get_height(out, test::block3.hash())); + BOOST_REQUIRE_EQUAL(out, 3u); + BOOST_REQUIRE(query.get_height(out, test::block1a.hash())); + BOOST_REQUIRE_EQUAL(out, 1u); + BOOST_REQUIRE(query.get_height(out, test::block2a.hash())); + BOOST_REQUIRE_EQUAL(out, 2u); + BOOST_REQUIRE(!query.get_height(out, system::one_hash)); +} + BOOST_AUTO_TEST_CASE(query_archive__get_tx_height__not_strong__false) { settings settings{}; diff --git a/test/query/translate.cpp b/test/query/translate.cpp index 5fed519e..e263ced3 100644 --- a/test/query/translate.cpp +++ b/test/query/translate.cpp @@ -776,10 +776,10 @@ BOOST_AUTO_TEST_CASE(query_translate__to_txs__always__expected) BOOST_REQUIRE(query.set(test::block2a, test::context, false)); const tx_links expected_links2{ 2, 3 }; - BOOST_REQUIRE_EQUAL(query.to_txs(0), tx_links{ 0 }); - BOOST_REQUIRE_EQUAL(query.to_txs(1), tx_links{ 1 }); - BOOST_REQUIRE_EQUAL(query.to_txs(2), expected_links2); - BOOST_REQUIRE(query.to_txs(3).empty()); + BOOST_REQUIRE_EQUAL(query.to_transactions(0), tx_links{ 0 }); + BOOST_REQUIRE_EQUAL(query.to_transactions(1), tx_links{ 1 }); + BOOST_REQUIRE_EQUAL(query.to_transactions(2), expected_links2); + BOOST_REQUIRE(query.to_transactions(3).empty()); } // to_spenders