From 45992b1f606d5ac85530f340dd3dbe637cc8b7d5 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 16 Feb 2026 21:26:12 -0500 Subject: [PATCH] Obtain applied interval depth from genesis txs byte. --- Makefile.am | 2 + builds/cmake/CMakeLists.txt | 1 + .../libbitcoin-database-test.vcxproj | 1 + .../libbitcoin-database-test.vcxproj.filters | 3 + .../libbitcoin-database.vcxproj | 1 + .../libbitcoin-database.vcxproj.filters | 3 + .../database/impl/query/archive_write.ipp | 6 +- .../bitcoin/database/impl/query/optional.ipp | 147 +----- include/bitcoin/database/impl/query/query.ipp | 31 ++ .../bitcoin/database/impl/query/services.ipp | 168 ++++++ include/bitcoin/database/impl/store.ipp | 4 +- include/bitcoin/database/query.hpp | 21 +- include/bitcoin/database/settings.hpp | 2 +- .../bitcoin/database/tables/archives/txs.hpp | 46 +- test/query/archive_write.cpp | 10 +- test/query/extent.cpp | 2 +- test/query/optional.cpp | 452 ---------------- test/query/services.cpp | 484 ++++++++++++++++++ test/tables/archives/txs.cpp | 92 ++-- 19 files changed, 820 insertions(+), 656 deletions(-) create mode 100644 include/bitcoin/database/impl/query/services.ipp create mode 100644 test/query/services.cpp diff --git a/Makefile.am b/Makefile.am index 305844e41..74c63bc39 100644 --- a/Makefile.am +++ b/Makefile.am @@ -97,6 +97,7 @@ test_libbitcoin_database_test_SOURCES = \ test/query/network.cpp \ test/query/objects.cpp \ test/query/optional.cpp \ + test/query/services.cpp \ test/query/translate.cpp \ test/query/validate.cpp \ test/tables/archives/header.cpp \ @@ -187,6 +188,7 @@ include_bitcoin_database_impl_query_HEADERS = \ include/bitcoin/database/impl/query/objects.ipp \ include/bitcoin/database/impl/query/optional.ipp \ include/bitcoin/database/impl/query/query.ipp \ + include/bitcoin/database/impl/query/services.ipp \ include/bitcoin/database/impl/query/translate.ipp \ include/bitcoin/database/impl/query/validate.ipp diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index 0bbf66263..84c6c5886 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -307,6 +307,7 @@ if (with-tests) "../../test/query/network.cpp" "../../test/query/objects.cpp" "../../test/query/optional.cpp" + "../../test/query/services.cpp" "../../test/query/translate.cpp" "../../test/query/validate.cpp" "../../test/tables/archives/header.cpp" diff --git a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj index 68a94d2c2..2f3d71807 100644 --- a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj @@ -160,6 +160,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters index 6c5cc7060..444136b76 100644 --- a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters @@ -141,6 +141,9 @@ src\query + + src\query + src\query diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj index 101acf5a9..9f0399b7c 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj @@ -226,6 +226,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters index 04231aeed..f4c97428c 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -367,6 +367,9 @@ include\bitcoin\database\impl\query + + include\bitcoin\database\impl\query + include\bitcoin\database\impl\query diff --git a/include/bitcoin/database/impl/query/archive_write.ipp b/include/bitcoin/database/impl/query/archive_write.ipp index 3e1f1cce4..7886372c2 100644 --- a/include/bitcoin/database/impl/query/archive_write.ipp +++ b/include/bitcoin/database/impl/query/archive_write.ipp @@ -398,6 +398,9 @@ code CLASS::set_code(const block& block, const header_link& key, const auto size = block.serialized_size(true); const auto wire = possible_narrow_cast(size); + // Depth is only used for genesis (is_zero(tx_fks[0])). + const auto depth = store_.interval_depth(); + // ======================================================================== const auto scope = store_.get_transactor(); constexpr auto positive = true; @@ -414,7 +417,8 @@ code CLASS::set_code(const block& block, const header_link& key, wire, count, tx_fks, - std::move(interval) + std::move(interval), + depth }) ? error::success : error::txs_txs_put; // ======================================================================== } diff --git a/include/bitcoin/database/impl/query/optional.ipp b/include/bitcoin/database/impl/query/optional.ipp index 799c69d2f..1092baef1 100644 --- a/include/bitcoin/database/impl/query/optional.ipp +++ b/include/bitcoin/database/impl/query/optional.ipp @@ -26,7 +26,7 @@ #include #include - // TODO: address table could use point keys to compress the multimap. +// TODO: address table could use point keys to compress the multimap. namespace libbitcoin { namespace database { @@ -259,151 +259,6 @@ code CLASS::get_confirmed_balance(std::atomic_bool& cancel, uint64_t& balance, return error::success; } -// merkle -// ---------------------------------------------------------------------------- - -// protected -TEMPLATE -size_t CLASS::interval_span() const NOEXCEPT -{ - // span of zero (overflow) is disallowed (division by zero). - // span of one (2^0) caches every block (no optimization, wasted storage). - // span greater than top height eliminates caching (no optimization). - const auto span = system::power2(store_.interval_depth()); - return is_zero(span) ? max_size_t : span; -} - -// protected -TEMPLATE -CLASS::hash_option CLASS::create_interval(header_link link, - size_t height) const NOEXCEPT -{ - // Interval ends at nth block where n is a multiple of span. - // One is a functional but undesirable case (no optimization). - const auto span = interval_span(); - BC_ASSERT(!is_zero(span)); - - // If valid link is provided then empty return implies non-interval height. - if (link.is_terminal() || !system::is_multiple(add1(height), span)) - return {}; - - // Generate the leaf nodes for the span. - hashes leaves(span); - for (auto& leaf: std::views::reverse(leaves)) - { - leaf = get_header_key(link); - link = to_parent(link); - } - - // Generate the merkle root of the interval ending on link header. - return system::merkle_root(std::move(leaves)); -} - -// protected -TEMPLATE -CLASS::hash_option CLASS::get_confirmed_interval(size_t height) const NOEXCEPT -{ - const auto span = interval_span(); - BC_ASSERT(!is_zero(span)); - - if (!system::is_multiple(add1(height), span)) - return {}; - - table::txs::get_interval txs{}; - if (!store_.txs.at(to_confirmed(height), txs)) - return {}; - - return txs.interval; -} - -// static/protected -TEMPLATE -void CLASS::merge_merkle(hashes& to, hashes&& from, size_t first) NOEXCEPT -{ - using namespace system; - for (const auto& row: block::merkle_branch(first, from.size())) - { - BC_ASSERT(row.sibling * add1(row.width) <= from.size()); - const auto it = std::next(from.begin(), row.sibling * row.width); - const auto mover = std::make_move_iterator(it); - to.push_back(merkle_root({ mover, std::next(mover, row.width) })); - } -} - -// protected -TEMPLATE -code CLASS::get_merkle_proof(hashes& proof, hashes roots, size_t target, - size_t waypoint) const NOEXCEPT -{ - const auto span = interval_span(); - BC_ASSERT(!is_zero(span)); - - const auto first = (target / span) * span; - const auto last = std::min(sub1(first + span), waypoint); - auto other = get_confirmed_hashes(first, add1(last - first)); - if (other.empty()) - return error::merkle_proof; - - using namespace system; - proof.reserve(ceilinged_log2(other.size()) + ceilinged_log2(roots.size())); - merge_merkle(proof, std::move(other), target % span); - merge_merkle(proof, std::move(roots), target / span); - return error::success; -} - -// protected -TEMPLATE -code CLASS::get_merkle_tree(hashes& tree, size_t waypoint) const NOEXCEPT -{ - const auto span = interval_span(); - BC_ASSERT(!is_zero(span)); - - const auto range = add1(waypoint); - tree.reserve(system::ceilinged_divide(range, span)); - for (size_t first{}; first < range; first += span) - { - const auto last = std::min(sub1(first + span), waypoint); - const auto size = add1(last - first); - - if (size == span) - { - auto interval = get_confirmed_interval(last); - if (!interval.has_value()) return error::merkle_interval; - tree.push_back(std::move(interval.value())); - } - else - { - auto confirmed = get_confirmed_hashes(first, size); - if (confirmed.empty()) return error::merkle_hashes; - tree.push_back(system::merkle_root(std::move(confirmed))); - } - } - - return error::success; -} - -TEMPLATE -code CLASS::get_merkle_root_and_proof(hash_digest& root, hashes& proof, - size_t target, size_t waypoint) const NOEXCEPT -{ - if (target > waypoint) - return error::merkle_arguments; - - if (waypoint > get_top_confirmed()) - return error::merkle_not_found; - - hashes tree{}; - if (const auto ec = get_merkle_tree(tree, waypoint)) - return ec; - - proof.clear(); - if (const auto ec = get_merkle_proof(proof, tree, target, waypoint)) - return ec; - - root = system::merkle_root(std::move(tree)); - return {}; -} - ////TEMPLATE ////bool CLASS::set_address_output(const output& output, //// const output_link& link) NOEXCEPT diff --git a/include/bitcoin/database/impl/query/query.ipp b/include/bitcoin/database/impl/query/query.ipp index 21860d39b..87dae87dd 100644 --- a/include/bitcoin/database/impl/query/query.ipp +++ b/include/bitcoin/database/impl/query/query.ipp @@ -74,6 +74,37 @@ CLASS::query(Store& store) NOEXCEPT { } +TEMPLATE +size_t CLASS::interval_span() const NOEXCEPT +{ + if (const auto span = span_.load(std::memory_order_relaxed); + is_nonzero(span)) + return span; + + // initialize_span() never returns zero. + span_.store(initialize_span(), std::memory_order_relaxed); + return span_; +} + +// protected +TEMPLATE +size_t CLASS::interval_depth() const NOEXCEPT +{ + table::txs::get_genesis_depth txs{}; + return store_.txs.at(to_txs(0), txs) ? txs.depth : store_.interval_depth(); +} + +// protected +TEMPLATE +size_t CLASS::initialize_span() const NOEXCEPT +{ + // span of zero (overflow) is disallowed (division by zero). + // span of one (2^0) caches every block (no optimization, wasted storage). + // span greater than top height eliminates caching (no optimization). + const auto span = system::power2(interval_depth()); + return is_zero(span) ? max_size_t : span; +} + TEMPLATE code CLASS::get_code() const NOEXCEPT { diff --git a/include/bitcoin/database/impl/query/services.ipp b/include/bitcoin/database/impl/query/services.ipp new file mode 100644 index 000000000..657c3ccf8 --- /dev/null +++ b/include/bitcoin/database/impl/query/services.ipp @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_QUERY_SERVICES_IPP +#define LIBBITCOIN_DATABASE_QUERY_SERVICES_IPP + +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace database { + +// merkle +// ---------------------------------------------------------------------------- + +// protected +TEMPLATE +CLASS::hash_option CLASS::create_interval(header_link link, + size_t height) const NOEXCEPT +{ + // Interval ends at nth block where n is a multiple of span. + // One is a functional but undesirable case (no optimization). + const auto span = interval_span(); + BC_ASSERT(!is_zero(span)); + + // If valid link is provided then empty return implies non-interval height. + if (link.is_terminal() || !system::is_multiple(add1(height), span)) + return {}; + + // Generate the leaf nodes for the span. + hashes leaves(span); + for (auto& leaf: std::views::reverse(leaves)) + { + leaf = get_header_key(link); + link = to_parent(link); + } + + // Generate the merkle root of the interval ending on link header. + return system::merkle_root(std::move(leaves)); +} + +// protected +TEMPLATE +CLASS::hash_option CLASS::get_confirmed_interval(size_t height) const NOEXCEPT +{ + const auto span = interval_span(); + BC_ASSERT(!is_zero(span)); + + if (!system::is_multiple(add1(height), span)) + return {}; + + table::txs::get_interval txs{}; + if (!store_.txs.at(to_confirmed(height), txs)) + return {}; + + return txs.interval; +} + +// static/protected +TEMPLATE +void CLASS::merge_merkle(hashes& to, hashes&& from, size_t first) NOEXCEPT +{ + using namespace system; + for (const auto& row: block::merkle_branch(first, from.size())) + { + BC_ASSERT(row.sibling * add1(row.width) <= from.size()); + const auto it = std::next(from.begin(), row.sibling * row.width); + const auto mover = std::make_move_iterator(it); + to.push_back(merkle_root({ mover, std::next(mover, row.width) })); + } +} + +// protected +TEMPLATE +code CLASS::get_merkle_proof(hashes& proof, hashes roots, size_t target, + size_t waypoint) const NOEXCEPT +{ + const auto span = interval_span(); + BC_ASSERT(!is_zero(span)); + + const auto first = (target / span) * span; + const auto last = std::min(sub1(first + span), waypoint); + auto other = get_confirmed_hashes(first, add1(last - first)); + if (other.empty()) + return error::merkle_proof; + + using namespace system; + proof.reserve(ceilinged_log2(other.size()) + ceilinged_log2(roots.size())); + merge_merkle(proof, std::move(other), target % span); + merge_merkle(proof, std::move(roots), target / span); + return error::success; +} + +// protected +TEMPLATE +code CLASS::get_merkle_tree(hashes& tree, size_t waypoint) const NOEXCEPT +{ + const auto span = interval_span(); + BC_ASSERT(!is_zero(span)); + + const auto range = add1(waypoint); + tree.reserve(system::ceilinged_divide(range, span)); + for (size_t first{}; first < range; first += span) + { + const auto last = std::min(sub1(first + span), waypoint); + const auto size = add1(last - first); + + if (size == span) + { + auto interval = get_confirmed_interval(last); + if (!interval.has_value()) return error::merkle_interval; + tree.push_back(std::move(interval.value())); + } + else + { + auto confirmed = get_confirmed_hashes(first, size); + if (confirmed.empty()) return error::merkle_hashes; + tree.push_back(system::merkle_root(std::move(confirmed))); + } + } + + return error::success; +} + +TEMPLATE +code CLASS::get_merkle_root_and_proof(hash_digest& root, hashes& proof, + size_t target, size_t waypoint) const NOEXCEPT +{ + if (target > waypoint) + return error::merkle_arguments; + + if (waypoint > get_top_confirmed()) + return error::merkle_not_found; + + hashes tree{}; + if (const auto ec = get_merkle_tree(tree, waypoint)) + return ec; + + proof.clear(); + if (const auto ec = get_merkle_proof(proof, tree, target, waypoint)) + return ec; + + root = system::merkle_root(std::move(tree)); + return {}; +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/store.ipp b/include/bitcoin/database/impl/store.ipp index c87a9b7af..90cb01cd9 100644 --- a/include/bitcoin/database/impl/store.ipp +++ b/include/bitcoin/database/impl/store.ipp @@ -235,7 +235,9 @@ bool CLASS::turbo() const NOEXCEPT TEMPLATE uint8_t CLASS::interval_depth() const NOEXCEPT { - return configuration_.interval_depth; + // Configuration uses uint16_t because of boost parser bug for single byte. + // But 2^255 is sufficient given that interval is limited by chain length. + return system::limit(configuration_.interval_depth); } TEMPLATE diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 29839670e..59d1a4bfe 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -210,9 +210,10 @@ class query size_t output_count(const tx_links& txs) const NOEXCEPT; two_counts put_counts(const tx_links& txs) const NOEXCEPT; - /// Optional table state. + /// Optional/configured table state. bool address_enabled() const NOEXCEPT; bool filter_enabled() const NOEXCEPT; + size_t interval_span() const NOEXCEPT; /// Initialization (natural-keyed). /// ----------------------------------------------------------------------- @@ -398,6 +399,12 @@ class query bool populate_without_metadata(const block& block) const NOEXCEPT; bool populate_without_metadata(const transaction& tx) const NOEXCEPT; + /// Services. + /// ----------------------------------------------------------------------- + + code get_merkle_root_and_proof(hash_digest& root, hashes& proof, + size_t target, size_t checkpoint) const NOEXCEPT; + /// Archive writes. /// ----------------------------------------------------------------------- @@ -574,8 +581,6 @@ class query code get_confirmed_balance(std::atomic_bool& cancel, uint64_t& balance, const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_merkle_root_and_proof(hash_digest& root, hashes& proof, - size_t target, size_t checkpoint) const NOEXCEPT; bool is_filtered_body(const header_link& link) const NOEXCEPT; bool get_filter_body(filter& out, const header_link& link) const NOEXCEPT; @@ -718,12 +723,16 @@ class query outpoints& out, const hash_digest& key, uint64_t minimum) const NOEXCEPT; - /// merkle + /// services:merkle /// ----------------------------------------------------------------------- + static void merge_merkle(hashes& branch, hashes&& hashes, size_t first) NOEXCEPT; - size_t interval_span() const NOEXCEPT; + // merkle related configuration + size_t interval_depth() const NOEXCEPT; + size_t initialize_span() const NOEXCEPT; + hash_option get_confirmed_interval(size_t height) const NOEXCEPT; hash_option create_interval(header_link link, size_t height) const NOEXCEPT; code get_merkle_tree(hashes& roots, size_t waypoint) const NOEXCEPT; @@ -750,6 +759,7 @@ class query // These are thread safe. mutable std::shared_mutex candidate_reorganization_mutex_{}; mutable std::shared_mutex confirmed_reorganization_mutex_{}; + mutable std::atomic span_{}; Store& store_; }; @@ -773,6 +783,7 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) #include #include #include +#include #include #include diff --git a/include/bitcoin/database/settings.hpp b/include/bitcoin/database/settings.hpp index f54615b11..c4c5d523c 100644 --- a/include/bitcoin/database/settings.hpp +++ b/include/bitcoin/database/settings.hpp @@ -43,7 +43,7 @@ struct BCD_API settings bool turbo{ false }; /// Depth of electrum merkle tree interval caching. - uint8_t interval_depth{ max_uint8 }; + uint16_t interval_depth{ max_uint8 }; /// Path to the database directory. std::filesystem::path path{ "bitcoin" }; diff --git a/include/bitcoin/database/tables/archives/txs.hpp b/include/bitcoin/database/tables/archives/txs.hpp index c3dda20d6..b93943df7 100644 --- a/include/bitcoin/database/tables/archives/txs.hpp +++ b/include/bitcoin/database/tables/archives/txs.hpp @@ -57,11 +57,17 @@ struct txs struct slab : public schema::txs { + inline bool is_genesis() const NOEXCEPT + { + return !tx_fks.empty() && is_zero(tx_fks.front()); + } + inline link count() const NOEXCEPT { return system::possible_narrow_cast(ct::size + bytes::size + tx::size * tx_fks.size() + - (interval.has_value() ? schema::hash : zero)); + (interval.has_value() ? schema::hash : zero) + + to_int(is_genesis())); } inline bool from_data(reader& source) NOEXCEPT @@ -76,6 +82,7 @@ struct txs }); if (get_right(flagged, offset)) interval = source.read_hash(); + if (is_genesis()) depth = source.read_byte(); BC_ASSERT(!source || source.get_read_position() == count()); return source; } @@ -98,6 +105,7 @@ struct txs }); if (flag) sink.write_bytes(interval.value()); + if (is_genesis()) sink.write_byte(depth); BC_ASSERT(!sink || sink.get_write_position() == count()); return sink; } @@ -112,17 +120,24 @@ struct txs bytes::integer wire{}; keys tx_fks{}; hash interval{}; + uint8_t depth{}; }; // put a contiguous set of tx identifiers. struct put_group : public schema::txs { + inline bool is_genesis() const NOEXCEPT + { + return is_zero(tx_fk); + } + inline link count() const NOEXCEPT { return system::possible_narrow_cast(ct::size + bytes::size + tx::size * number + - (interval.has_value() ? schema::hash : zero)); + (interval.has_value() ? schema::hash : zero) + + to_int(is_zero(tx_fk))); } inline bool to_data(finalizer& sink) const NOEXCEPT @@ -140,6 +155,7 @@ struct txs sink.write_little_endian(fk); if (flag) sink.write_bytes(interval.value()); + if (is_genesis()) sink.write_byte(depth); BC_ASSERT(!sink || sink.get_write_position() == count()); return sink; } @@ -148,6 +164,7 @@ struct txs ct::integer number{}; tx::integer tx_fk{}; hash interval{}; + uint8_t depth{}; }; struct get_interval @@ -172,6 +189,31 @@ struct txs hash interval{}; }; + // This reader is only applicable to the genesis block. + struct get_genesis_depth + : public schema::txs + { + inline link count() const NOEXCEPT + { + BC_ASSERT(false); + return {}; + } + + // Stored at end since only read once (at startup). + inline bool from_data(reader& source) NOEXCEPT + { + const auto number = source.read_little_endian(); + const auto flagged = source.read_little_endian(); + const auto skip = tx::size * number + + (system::get_right(flagged, offset) ? schema::hash : zero); + source.skip_bytes(skip); + depth = source.read_byte(); + return source; + } + + uint8_t depth{}; + }; + struct get_position : public schema::txs { diff --git a/test/query/archive_write.cpp b/test/query/archive_write.cpp index 5fa4a9cbb..f3c7955ab 100644 --- a/test/query/archive_write.cpp +++ b/test/query/archive_write.cpp @@ -518,7 +518,7 @@ BOOST_AUTO_TEST_CASE(query_archive_write__set_block__get_block__expected) "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script "00"); // witness const auto genesis_txs_head = system::base16_chunk( - "0a00000000" // slabs size + "0b00000000" // slab size "0000000000" // pk-> "ffffffffff" "ffffffffff" @@ -538,7 +538,8 @@ BOOST_AUTO_TEST_CASE(query_archive_write__set_block__get_block__expected) const auto genesis_txs_body = system::base16_chunk( "010000" // txs count (1) "1d0100" // txs wire (285) - "00000000"); // transaction[0] + "00000000" // transaction[0] + "ff"); // depth (255) settings settings{}; settings.header_buckets = 8; @@ -669,7 +670,7 @@ BOOST_AUTO_TEST_CASE(query_archive_write__set_block_txs__get_block__expected) "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script "00"); // witness const auto genesis_txs_head = system::base16_chunk( - "0a00000000" // slabs size + "0b00000000" // slab size "0000000000" // pk-> "ffffffffff" "ffffffffff" @@ -689,7 +690,8 @@ BOOST_AUTO_TEST_CASE(query_archive_write__set_block_txs__get_block__expected) const auto genesis_txs_body = system::base16_chunk( "010000" // txs count (1) "1d0100" // txs wire (285) - "00000000"); // transaction[0] + "00000000" // transaction[0] + "ff"); // depth (255) settings settings{}; settings.header_buckets = 8; diff --git a/test/query/extent.cpp b/test/query/extent.cpp index 36418b655..414da5a19 100644 --- a/test/query/extent.cpp +++ b/test/query/extent.cpp @@ -48,7 +48,7 @@ BOOST_AUTO_TEST_CASE(query_extent__body_sizes__genesis__expected) BOOST_REQUIRE_EQUAL(query.point_body_size(), schema::point::minrow); BOOST_REQUIRE_EQUAL(query.ins_body_size(), schema::ins::minrow); BOOST_REQUIRE_EQUAL(query.outs_body_size(), schema::outs::minrow); - BOOST_REQUIRE_EQUAL(query.txs_body_size(), schema::txs::minrow); + BOOST_REQUIRE_EQUAL(query.txs_body_size(), add1(schema::txs::minrow)); BOOST_REQUIRE_EQUAL(query.tx_body_size(), schema::transaction::minrow); BOOST_REQUIRE_EQUAL(query.candidate_body_size(), schema::height::minrow); diff --git a/test/query/optional.cpp b/test/query/optional.cpp index 71ddb04c5..abdb68b59 100644 --- a/test/query/optional.cpp +++ b/test/query/optional.cpp @@ -227,458 +227,6 @@ BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_balance__genesis__expected) BOOST_REQUIRE_EQUAL(out, 5000000000u); } -class merkle_accessor - : public test::query_accessor -{ -public: - using base = test::query_accessor; - using base::base; - using base::interval_span; - using base::create_interval; - using base::get_confirmed_interval; - using base::merge_merkle; - using base::get_merkle_proof; - using base::get_merkle_tree; - using base::get_merkle_root_and_proof; -}; - -// interval_span - -BOOST_AUTO_TEST_CASE(query_optional__interval_span__default__max_size_t) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(settings.interval_depth, max_uint8); - BOOST_REQUIRE_EQUAL(query.interval_span(), max_size_t); -} - -BOOST_AUTO_TEST_CASE(query_optional__interval_span__11__2048) -{ - settings settings{}; - settings.interval_depth = 11; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(query.interval_span(), 2048u); -} - -BOOST_AUTO_TEST_CASE(query_optional__interval_span__0__1) -{ - settings settings{}; - settings.interval_depth = 0; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(query.interval_span(), 1u); -} - -// Merkle root of test blocks [0..1] -constexpr auto root01 = system::base16_hash("abdc2227d02d114b77be15085c1257709252a7a103f9ac0ab3c85d67e12bc0b8"); - -// Merkle root of test blocks [2..4] -constexpr auto root02 = system::base16_hash("f2a2a2907abb326726a2d6500fe494f63772a941b414236c302e920bc1aa9caf"); - -// Merkle root of test blocks [0..4] -constexpr auto root04 = system::sha256::double_hash(root01, root02); - -// create_interval - -BOOST_AUTO_TEST_CASE(query_optional__create_interval__depth_0__block_hash) -{ - settings settings{}; - settings.interval_depth = 0; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - - const auto header0 = query.to_header(test::genesis.hash()); - const auto header1 = query.to_header(test::block1.hash()); - const auto header2 = query.to_header(test::block2.hash()); - const auto header3 = query.to_header(test::block3.hash()); - BOOST_REQUIRE(!header0.is_terminal()); - BOOST_REQUIRE(!header1.is_terminal()); - BOOST_REQUIRE(!header2.is_terminal()); - BOOST_REQUIRE(!header3.is_terminal()); - BOOST_REQUIRE(query.create_interval(header0, 0).has_value()); - BOOST_REQUIRE(query.create_interval(header1, 1).has_value()); - BOOST_REQUIRE(query.create_interval(header2, 2).has_value()); - BOOST_REQUIRE(query.create_interval(header3, 3).has_value()); - BOOST_REQUIRE_EQUAL(query.create_interval(header0, 0).value(), test::genesis.hash()); - BOOST_REQUIRE_EQUAL(query.create_interval(header1, 1).value(), test::block1.hash()); - BOOST_REQUIRE_EQUAL(query.create_interval(header2, 2).value(), test::block2.hash()); - BOOST_REQUIRE_EQUAL(query.create_interval(header3, 3).value(), test::block3.hash()); -} - -BOOST_AUTO_TEST_CASE(query_optional__create_interval__depth_1__expected) -{ - settings settings{}; - settings.interval_depth = 1; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - - const auto header0 = query.to_header(test::genesis.hash()); - const auto header1 = query.to_header(test::block1.hash()); - const auto header2 = query.to_header(test::block2.hash()); - const auto header3 = query.to_header(test::block3.hash()); - BOOST_REQUIRE(!header0.is_terminal()); - BOOST_REQUIRE(!header1.is_terminal()); - BOOST_REQUIRE(!header2.is_terminal()); - BOOST_REQUIRE(!header3.is_terminal()); - BOOST_REQUIRE(!query.create_interval(header0, 0).has_value()); - BOOST_REQUIRE( query.create_interval(header1, 1).has_value()); - BOOST_REQUIRE(!query.create_interval(header2, 2).has_value()); - BOOST_REQUIRE( query.create_interval(header3, 3).has_value()); - BOOST_REQUIRE_EQUAL(query.create_interval(header1, 1).value(), root01); - BOOST_REQUIRE_EQUAL(query.create_interval(header3, 3).value(), root02); -} - -BOOST_AUTO_TEST_CASE(query_optional__create_interval__depth_2__expected) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - - const auto header3 = query.to_header(test::block3.hash()); - BOOST_REQUIRE(!header3.is_terminal()); - BOOST_REQUIRE(query.create_interval(header3, 3).has_value()); - BOOST_REQUIRE_EQUAL(query.create_interval(header3, 3).value(), root04); -} - -// get_confirmed_interval - -BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_interval__not_multiple__no_value) -{ - settings settings{}; - settings.interval_depth = 3; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(query.interval_span(), system::power2(settings.interval_depth)); - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - BOOST_REQUIRE(query.initialize(test::genesis)); - BOOST_REQUIRE(!query.get_confirmed_interval(0).has_value()); - BOOST_REQUIRE(!query.get_confirmed_interval(1).has_value()); - BOOST_REQUIRE(!query.get_confirmed_interval(6).has_value()); - BOOST_REQUIRE(!query.get_confirmed_interval(7).has_value()); - BOOST_REQUIRE(!query.get_confirmed_interval(14).has_value()); -} - -// Interval is set by create_interval(), integral to set(block). -BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_interval__multiple__expected_value) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(query.interval_span(), system::power2(settings.interval_depth)); - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); - BOOST_REQUIRE(!query.get_confirmed_interval(0).has_value()); - BOOST_REQUIRE(!query.get_confirmed_interval(1).has_value()); - BOOST_REQUIRE(!query.get_confirmed_interval(2).has_value()); - BOOST_REQUIRE( query.get_confirmed_interval(3).has_value()); - BOOST_REQUIRE(!query.get_confirmed_interval(4).has_value()); -} - -// merge_merkle - -BOOST_AUTO_TEST_CASE(query_optional__merge_merkle__empty_from__empty_to) -{ - hashes to{}; - merkle_accessor::merge_merkle(to, {}, 0); - BOOST_REQUIRE(to.empty()); - - merkle_accessor::merge_merkle(to, { system::null_hash }, 0); - BOOST_REQUIRE(to.empty()); -} - -BOOST_AUTO_TEST_CASE(query_optional__push_merkle__two_leaves_target_zero__merges_one_sibling) -{ - hashes to{}; - hashes from - { - test::genesis.hash(), - test::block1.hash() - }; - - merkle_accessor::merge_merkle(to, std::move(from), 0); - BOOST_REQUIRE_EQUAL(to.size(), 1u); - BOOST_REQUIRE_EQUAL(to[0], system::merkle_root({ test::block1.hash() })); -} - -BOOST_AUTO_TEST_CASE(query_optional__merge_merkle__three_leaves_target_two__handles_odd_length) -{ - hashes to{}; - hashes from - { - test::genesis.hash(), - test::block1.hash(), - test::block2.hash() - }; - - merkle_accessor::merge_merkle(to, std::move(from), 2); - BOOST_REQUIRE_EQUAL(to.size(), one); - BOOST_REQUIRE_EQUAL(to[0], system::merkle_root({ test::genesis.hash(), test::block1.hash() })); -} - -BOOST_AUTO_TEST_CASE(query_optional__merge_merkle__four_leaves_target_three__merges_two_siblings) -{ - hashes to{}; - hashes from - { - test::genesis.hash(), - test::block1.hash(), - test::block2.hash(), - test::block3.hash() - }; - - merkle_accessor::merge_merkle(to, std::move(from), 3); - BOOST_REQUIRE_EQUAL(to.size(), 2u); - BOOST_REQUIRE_EQUAL(to[0], system::merkle_root({ test::block2.hash() })); - BOOST_REQUIRE_EQUAL(to[1], system::merkle_root({ test::genesis.hash(), test::block1.hash() })); -} - -// get_merkle_proof - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_proof__no_confirmed_blocks__error_merkle_proof) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - BOOST_REQUIRE(query.initialize(test::genesis)); - - hashes proof{}; - BOOST_REQUIRE_EQUAL(query.get_merkle_proof(proof, {}, 5u, 10u), error::merkle_proof); - BOOST_REQUIRE(proof.empty()); -} - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_proof__target_in_first_interval__expected) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); - - hashes proof{}; - BOOST_REQUIRE_EQUAL(query.get_merkle_proof(proof, {}, 3u, 3u), error::success); - BOOST_REQUIRE_EQUAL(proof.size(), 2u); - BOOST_REQUIRE_EQUAL(proof[0], system::merkle_root({ test::block2.hash() })); - BOOST_REQUIRE_EQUAL(proof[1], system::merkle_root({ test::genesis.hash(), test::block1.hash() })); -} - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_proof__multiple_intervals__expected) -{ - settings settings{}; - settings.interval_depth = 1; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); - - hashes proof{}; - const hashes roots - { - system::merkle_root({ test::genesis.hash(), test::block1.hash() }), - system::merkle_root({ test::block2.hash(), test::block3.hash() }) - }; - BOOST_REQUIRE_EQUAL(query.get_merkle_proof(proof, roots, 3u, 3u), error::success); - BOOST_REQUIRE_EQUAL(proof.size(), 2u); - BOOST_REQUIRE_EQUAL(proof[0], system::merkle_root({ test::block2.hash() })); - BOOST_REQUIRE_EQUAL(proof[1], roots[0]); -} - -// get_merkle_tree - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_tree__waypoint_zero__genesis) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - BOOST_REQUIRE(query.initialize(test::genesis)); - - hashes tree{}; - const auto expected = test::genesis.hash(); - BOOST_REQUIRE_EQUAL(query.get_merkle_tree(tree, 0), error::success); - BOOST_REQUIRE_EQUAL(tree.size(), 1u); - BOOST_REQUIRE_EQUAL(tree[0], expected); -} - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_tree__one_full_interval__expected_root) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); - - hashes tree{}; - const auto expected = system::merkle_root( - { - test::genesis.hash(), - test::block1.hash(), - test::block2.hash(), - test::block3.hash() - }); - BOOST_REQUIRE_EQUAL(query.get_merkle_tree(tree, 3), error::success); - BOOST_REQUIRE_EQUAL(tree.size(), 1u); - BOOST_REQUIRE_EQUAL(tree[0], expected); -} - -// get_merkle_root_and_proof - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_root_and_proof__target_equals_waypoint__success) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); - - // Final root is the merkle root of the entire tree. - hashes proof{}; - hash_digest root{}; - const auto expected = system::merkle_root( - { - test::genesis.hash(), - test::block1.hash(), - test::block2.hash(), - test::block3.hash() - }); - BOOST_REQUIRE(!query.get_merkle_root_and_proof(root, proof, 3u, 3u)); - BOOST_REQUIRE_EQUAL(proof.size(), 2u); - BOOST_REQUIRE_EQUAL(proof[0], system::merkle_root({ test::block2.hash() })); - BOOST_REQUIRE_EQUAL(proof[1], system::merkle_root({ test::genesis.hash(), test::block1.hash() })); - BOOST_REQUIRE_EQUAL(root, expected); -} - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_root_and_proof__target_less_than_waypoint__success) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - 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.set(test::block3, context{ 0, 3, 0 }, false, false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); - BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); - - // Deeper path because target is not the rightmost. - hashes proof{}; - hash_digest root{}; - const auto expected = system::merkle_root( - { - test::genesis.hash(), - test::block1.hash(), - test::block2.hash(), - test::block3.hash() - }); - BOOST_REQUIRE(!query.get_merkle_root_and_proof(root, proof, 1u, 3u)); - BOOST_REQUIRE_EQUAL(root, expected); - BOOST_REQUIRE_EQUAL(proof.size(), 2u); - BOOST_REQUIRE_EQUAL(proof[0], system::merkle_root({ test::genesis.hash() })); - BOOST_REQUIRE_EQUAL(proof[1], system::merkle_root({ test::block2.hash(), test::block3.hash() })); -} - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_root_and_proof__target_greater_than_waypoint__error_merkle_arguments) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - BOOST_REQUIRE(query.initialize(test::genesis)); - - hashes proof{}; - hash_digest root{}; - BOOST_REQUIRE_EQUAL(query.get_merkle_root_and_proof(root, proof, 5u, 3u), error::merkle_arguments); -} - -BOOST_AUTO_TEST_CASE(query_optional__get_merkle_root_and_proof__waypoint_beyond_top__error_merkle_not_found) -{ - settings settings{}; - settings.interval_depth = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - merkle_accessor query{ store }; - BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); - BOOST_REQUIRE(query.initialize(test::genesis)); - - hashes proof{}; - hash_digest root{}; - BOOST_REQUIRE_EQUAL(query.get_merkle_root_and_proof(root, proof, 0u, 100u), error::merkle_not_found); -} - ////BOOST_AUTO_TEST_CASE(query_optional__set_filter__get_filter_and_head__expected) ////{ //// const auto& filter_head0 = system::null_hash; diff --git a/test/query/services.cpp b/test/query/services.cpp new file mode 100644 index 000000000..0f85e974b --- /dev/null +++ b/test/query/services.cpp @@ -0,0 +1,484 @@ +/** + * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "../test.hpp" +#include "../mocks/blocks.hpp" +#include "../mocks/chunk_store.hpp" + +BOOST_FIXTURE_TEST_SUITE(query_services_tests, test::directory_setup_fixture) + +// nop event handler. +const auto events_handler = [](auto, auto) {}; + +// Merkle root of test blocks [0..1] +constexpr auto root01 = system::base16_hash("abdc2227d02d114b77be15085c1257709252a7a103f9ac0ab3c85d67e12bc0b8"); + +// Merkle root of test blocks [2..4] +constexpr auto root02 = system::base16_hash("f2a2a2907abb326726a2d6500fe494f63772a941b414236c302e920bc1aa9caf"); + +// Merkle root of test blocks [0..4] +constexpr auto root04 = system::sha256::double_hash(root01, root02); + +class merkle_accessor + : public test::query_accessor +{ +public: + using base = test::query_accessor; + using base::base; + using base::interval_span; + using base::create_interval; + using base::get_confirmed_interval; + using base::merge_merkle; + using base::get_merkle_proof; + using base::get_merkle_tree; + using base::get_merkle_root_and_proof; +}; + +// interval_span + +BOOST_AUTO_TEST_CASE(query_services__interval_span__uninitialized__max_size_t) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(settings.interval_depth, max_uint8); + BOOST_REQUIRE_EQUAL(query.interval_span(), max_size_t); +} + +BOOST_AUTO_TEST_CASE(query_services__interval_span__11__2048) +{ + settings settings{}; + settings.interval_depth = 11; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE_EQUAL(query.interval_span(), 2048u); +} + +BOOST_AUTO_TEST_CASE(query_services__interval_span__0__1) +{ + settings settings{}; + settings.interval_depth = 0; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE_EQUAL(query.interval_span(), 1u); +} + +// create_interval + +BOOST_AUTO_TEST_CASE(query_services__create_interval__depth_0__block_hash) +{ + settings settings{}; + settings.interval_depth = 0; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + + const auto header0 = query.to_header(test::genesis.hash()); + const auto header1 = query.to_header(test::block1.hash()); + const auto header2 = query.to_header(test::block2.hash()); + const auto header3 = query.to_header(test::block3.hash()); + BOOST_REQUIRE(!header0.is_terminal()); + BOOST_REQUIRE(!header1.is_terminal()); + BOOST_REQUIRE(!header2.is_terminal()); + BOOST_REQUIRE(!header3.is_terminal()); + BOOST_REQUIRE(query.create_interval(header0, 0).has_value()); + BOOST_REQUIRE(query.create_interval(header1, 1).has_value()); + BOOST_REQUIRE(query.create_interval(header2, 2).has_value()); + BOOST_REQUIRE(query.create_interval(header3, 3).has_value()); + BOOST_REQUIRE_EQUAL(query.create_interval(header0, 0).value(), test::genesis.hash()); + BOOST_REQUIRE_EQUAL(query.create_interval(header1, 1).value(), test::block1.hash()); + BOOST_REQUIRE_EQUAL(query.create_interval(header2, 2).value(), test::block2.hash()); + BOOST_REQUIRE_EQUAL(query.create_interval(header3, 3).value(), test::block3.hash()); +} + +BOOST_AUTO_TEST_CASE(query_services__create_interval__depth_1__expected) +{ + settings settings{}; + settings.interval_depth = 1; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + + const auto header0 = query.to_header(test::genesis.hash()); + const auto header1 = query.to_header(test::block1.hash()); + const auto header2 = query.to_header(test::block2.hash()); + const auto header3 = query.to_header(test::block3.hash()); + BOOST_REQUIRE(!header0.is_terminal()); + BOOST_REQUIRE(!header1.is_terminal()); + BOOST_REQUIRE(!header2.is_terminal()); + BOOST_REQUIRE(!header3.is_terminal()); + BOOST_REQUIRE(!query.create_interval(header0, 0).has_value()); + BOOST_REQUIRE( query.create_interval(header1, 1).has_value()); + BOOST_REQUIRE(!query.create_interval(header2, 2).has_value()); + BOOST_REQUIRE( query.create_interval(header3, 3).has_value()); + BOOST_REQUIRE_EQUAL(query.create_interval(header1, 1).value(), root01); + BOOST_REQUIRE_EQUAL(query.create_interval(header3, 3).value(), root02); +} + +BOOST_AUTO_TEST_CASE(query_services__create_interval__depth_2__expected) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + + const auto header3 = query.to_header(test::block3.hash()); + BOOST_REQUIRE(!header3.is_terminal()); + BOOST_REQUIRE(query.create_interval(header3, 3).has_value()); + BOOST_REQUIRE_EQUAL(query.create_interval(header3, 3).value(), root04); +} + +// get_confirmed_interval + +BOOST_AUTO_TEST_CASE(query_services__get_confirmed_interval__not_multiple__no_value) +{ + settings settings{}; + settings.interval_depth = 3; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(query.interval_span(), system::power2(settings.interval_depth)); + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(!query.get_confirmed_interval(0).has_value()); + BOOST_REQUIRE(!query.get_confirmed_interval(1).has_value()); + BOOST_REQUIRE(!query.get_confirmed_interval(6).has_value()); + BOOST_REQUIRE(!query.get_confirmed_interval(7).has_value()); + BOOST_REQUIRE(!query.get_confirmed_interval(14).has_value()); +} + +// Interval is set by create_interval(), integral to set(block). +BOOST_AUTO_TEST_CASE(query_services__get_confirmed_interval__multiple__expected_value) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(query.interval_span(), system::power2(settings.interval_depth)); + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); + BOOST_REQUIRE(!query.get_confirmed_interval(0).has_value()); + BOOST_REQUIRE(!query.get_confirmed_interval(1).has_value()); + BOOST_REQUIRE(!query.get_confirmed_interval(2).has_value()); + BOOST_REQUIRE( query.get_confirmed_interval(3).has_value()); + BOOST_REQUIRE(!query.get_confirmed_interval(4).has_value()); +} + +// merge_merkle + +BOOST_AUTO_TEST_CASE(query_services__merge_merkle__empty_from__empty_to) +{ + hashes to{}; + merkle_accessor::merge_merkle(to, {}, 0); + BOOST_REQUIRE(to.empty()); + + merkle_accessor::merge_merkle(to, { system::null_hash }, 0); + BOOST_REQUIRE(to.empty()); +} + +BOOST_AUTO_TEST_CASE(query_services__push_merkle__two_leaves_target_zero__merges_one_sibling) +{ + hashes to{}; + hashes from + { + test::genesis.hash(), + test::block1.hash() + }; + + merkle_accessor::merge_merkle(to, std::move(from), 0); + BOOST_REQUIRE_EQUAL(to.size(), 1u); + BOOST_REQUIRE_EQUAL(to[0], system::merkle_root({ test::block1.hash() })); +} + +BOOST_AUTO_TEST_CASE(query_services__merge_merkle__three_leaves_target_two__handles_odd_length) +{ + hashes to{}; + hashes from + { + test::genesis.hash(), + test::block1.hash(), + test::block2.hash() + }; + + merkle_accessor::merge_merkle(to, std::move(from), 2); + BOOST_REQUIRE_EQUAL(to.size(), one); + BOOST_REQUIRE_EQUAL(to[0], system::merkle_root({ test::genesis.hash(), test::block1.hash() })); +} + +BOOST_AUTO_TEST_CASE(query_services__merge_merkle__four_leaves_target_three__merges_two_siblings) +{ + hashes to{}; + hashes from + { + test::genesis.hash(), + test::block1.hash(), + test::block2.hash(), + test::block3.hash() + }; + + merkle_accessor::merge_merkle(to, std::move(from), 3); + BOOST_REQUIRE_EQUAL(to.size(), 2u); + BOOST_REQUIRE_EQUAL(to[0], system::merkle_root({ test::block2.hash() })); + BOOST_REQUIRE_EQUAL(to[1], system::merkle_root({ test::genesis.hash(), test::block1.hash() })); +} + +// get_merkle_proof + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_proof__no_confirmed_blocks__error_merkle_proof) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + hashes proof{}; + BOOST_REQUIRE_EQUAL(query.get_merkle_proof(proof, {}, 5u, 10u), error::merkle_proof); + BOOST_REQUIRE(proof.empty()); +} + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_proof__target_in_first_interval__expected) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); + + hashes proof{}; + BOOST_REQUIRE_EQUAL(query.get_merkle_proof(proof, {}, 3u, 3u), error::success); + BOOST_REQUIRE_EQUAL(proof.size(), 2u); + BOOST_REQUIRE_EQUAL(proof[0], system::merkle_root({ test::block2.hash() })); + BOOST_REQUIRE_EQUAL(proof[1], system::merkle_root({ test::genesis.hash(), test::block1.hash() })); +} + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_proof__multiple_intervals__expected) +{ + settings settings{}; + settings.interval_depth = 1; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); + + hashes proof{}; + const hashes roots + { + system::merkle_root({ test::genesis.hash(), test::block1.hash() }), + system::merkle_root({ test::block2.hash(), test::block3.hash() }) + }; + BOOST_REQUIRE_EQUAL(query.get_merkle_proof(proof, roots, 3u, 3u), error::success); + BOOST_REQUIRE_EQUAL(proof.size(), 2u); + BOOST_REQUIRE_EQUAL(proof[0], system::merkle_root({ test::block2.hash() })); + BOOST_REQUIRE_EQUAL(proof[1], roots[0]); +} + +// get_merkle_tree + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_tree__waypoint_zero__genesis) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + hashes tree{}; + const auto expected = test::genesis.hash(); + BOOST_REQUIRE_EQUAL(query.get_merkle_tree(tree, 0), error::success); + BOOST_REQUIRE_EQUAL(tree.size(), 1u); + BOOST_REQUIRE_EQUAL(tree[0], expected); +} + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_tree__one_full_interval__expected_root) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); + + hashes tree{}; + const auto expected = system::merkle_root( + { + test::genesis.hash(), + test::block1.hash(), + test::block2.hash(), + test::block3.hash() + }); + BOOST_REQUIRE_EQUAL(query.get_merkle_tree(tree, 3), error::success); + BOOST_REQUIRE_EQUAL(tree.size(), 1u); + BOOST_REQUIRE_EQUAL(tree[0], expected); +} + +// get_merkle_root_and_proof + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_root_and_proof__target_equals_waypoint__success) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); + + // Final root is the merkle root of the entire tree. + hashes proof{}; + hash_digest root{}; + const auto expected = system::merkle_root( + { + test::genesis.hash(), + test::block1.hash(), + test::block2.hash(), + test::block3.hash() + }); + BOOST_REQUIRE(!query.get_merkle_root_and_proof(root, proof, 3u, 3u)); + BOOST_REQUIRE_EQUAL(proof.size(), 2u); + BOOST_REQUIRE_EQUAL(proof[0], system::merkle_root({ test::block2.hash() })); + BOOST_REQUIRE_EQUAL(proof[1], system::merkle_root({ test::genesis.hash(), test::block1.hash() })); + BOOST_REQUIRE_EQUAL(root, expected); +} + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_root_and_proof__target_less_than_waypoint__success) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + 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.set(test::block3, context{ 0, 3, 0 }, false, false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); + BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); + + // Deeper path because target is not the rightmost. + hashes proof{}; + hash_digest root{}; + const auto expected = system::merkle_root( + { + test::genesis.hash(), + test::block1.hash(), + test::block2.hash(), + test::block3.hash() + }); + BOOST_REQUIRE(!query.get_merkle_root_and_proof(root, proof, 1u, 3u)); + BOOST_REQUIRE_EQUAL(root, expected); + BOOST_REQUIRE_EQUAL(proof.size(), 2u); + BOOST_REQUIRE_EQUAL(proof[0], system::merkle_root({ test::genesis.hash() })); + BOOST_REQUIRE_EQUAL(proof[1], system::merkle_root({ test::block2.hash(), test::block3.hash() })); +} + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_root_and_proof__target_greater_than_waypoint__error_merkle_arguments) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + hashes proof{}; + hash_digest root{}; + BOOST_REQUIRE_EQUAL(query.get_merkle_root_and_proof(root, proof, 5u, 3u), error::merkle_arguments); +} + +BOOST_AUTO_TEST_CASE(query_services__get_merkle_root_and_proof__waypoint_beyond_top__error_merkle_not_found) +{ + settings settings{}; + settings.interval_depth = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + merkle_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + hashes proof{}; + hash_digest root{}; + BOOST_REQUIRE_EQUAL(query.get_merkle_root_and_proof(root, proof, 0u, 100u), error::merkle_not_found); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/tables/archives/txs.cpp b/test/tables/archives/txs.cpp index 377134644..60cea7e63 100644 --- a/test/tables/archives/txs.cpp +++ b/test/tables/archives/txs.cpp @@ -21,20 +21,24 @@ BOOST_AUTO_TEST_SUITE(txs_tests) +// Default configuration doesn't result in intervals set here. + using namespace system; const table::txs::slab slab0 { {}, // schema::txs [all const static members] 0x000000, - std::vector { - } + // tx fk 0 uniquely identifies genesis, resulting in depth storage. + 0x00000000_u32 + }, + {}, // interval (unused due to default span) + 0x42 // depth (genesis only) }; const table::txs::slab slab1 { {}, // schema::txs [all const static members] 0x0000ab, - std::vector { 0x56341211_u32 } @@ -43,7 +47,6 @@ const table::txs::slab slab2 { {}, // schema::txs [all const static members] 0x00a00b, - std::vector { 0x56341221_u32, 0x56341222_u32 @@ -53,7 +56,6 @@ const table::txs::slab slab3 { {}, // schema::txs [all const static members] 0x09000b, - std::vector { 0x56341231_u32, 0x56341232_u32, @@ -62,13 +64,17 @@ const table::txs::slab slab3 }; const data_chunk expected0 { - // slab0 (count) [0] - 0x00, 0x00, 0x00, + // slab0 (count) [1] + 0x01, 0x00, 0x00, // slab0 (wire) [0x00] 0x00, 0x00, 0x00, // slab0 (txs) + 0x00, 0x00, 0x00, 0x00, + + // depth (genesis) + 0x42 }; const data_chunk expected1 { @@ -79,7 +85,7 @@ const data_chunk expected1 0xab, 0x00, 0x00, // slab1 (txs) - 0x11, 0x12, 0x34, 0x56, + 0x11, 0x12, 0x34, 0x56 }; const data_chunk expected2 { @@ -91,7 +97,7 @@ const data_chunk expected2 // slab2 0x21, 0x12, 0x34, 0x56, - 0x22, 0x12, 0x34, 0x56, + 0x22, 0x12, 0x34, 0x56 }; const data_chunk expected3 { @@ -114,40 +120,40 @@ BOOST_AUTO_TEST_CASE(txs__put__get__expected) test::chunk_storage head_store{}; test::chunk_storage body_store{}; table::txs instance{ head_store, body_store, 20 }; - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(!instance.exists(key)); - - BOOST_REQUIRE(instance.put(key, slab0)); - BOOST_REQUIRE(instance.exists(key)); - BOOST_REQUIRE(instance.at(key, slab)); - BOOST_REQUIRE(slab == slab0); - BOOST_REQUIRE(instance.get(instance.at(key), slab)); - BOOST_REQUIRE(slab == slab0); - BOOST_REQUIRE_EQUAL(body_store.buffer(), build_chunk({ expected0 })); - - BOOST_REQUIRE(instance.put(key, slab1)); - BOOST_REQUIRE(instance.exists(key)); - BOOST_REQUIRE(instance.at(key, slab)); - BOOST_REQUIRE(slab == slab1); - BOOST_REQUIRE(instance.get(instance.at(key), slab)); - BOOST_REQUIRE(slab == slab1); - BOOST_REQUIRE_EQUAL(body_store.buffer(), build_chunk({ expected0, expected1 })); - - BOOST_REQUIRE(instance.put(key, slab2)); - BOOST_REQUIRE(instance.exists(key)); - BOOST_REQUIRE(instance.at(key, slab)); - BOOST_REQUIRE(slab == slab2); - BOOST_REQUIRE(instance.get(instance.at(key), slab)); - BOOST_REQUIRE(slab == slab2); - BOOST_REQUIRE_EQUAL(body_store.buffer(), build_chunk({ expected0, expected1, expected2 })); - - BOOST_REQUIRE(instance.put(key, slab3)); - BOOST_REQUIRE(instance.exists(key)); - BOOST_REQUIRE(instance.at(key, slab)); - BOOST_REQUIRE(slab == slab3); - BOOST_REQUIRE(instance.get(instance.at(key), slab)); - BOOST_REQUIRE(slab == slab3); - BOOST_REQUIRE_EQUAL(body_store.buffer(), build_chunk({ expected0, expected1, expected2, expected3 })); + BOOST_CHECK(instance.create()); + BOOST_CHECK(!instance.exists(key)); + + BOOST_CHECK(instance.put(key, slab0)); + BOOST_CHECK(instance.exists(key)); + BOOST_CHECK(instance.at(key, slab)); + BOOST_CHECK(slab == slab0); + BOOST_CHECK(instance.get(instance.at(key), slab)); + BOOST_CHECK(slab == slab0); + BOOST_CHECK_EQUAL(body_store.buffer(), build_chunk({ expected0 })); + + BOOST_CHECK(instance.put(key, slab1)); + BOOST_CHECK(instance.exists(key)); + BOOST_CHECK(instance.at(key, slab)); + BOOST_CHECK(slab == slab1); + BOOST_CHECK(instance.get(instance.at(key), slab)); + BOOST_CHECK(slab == slab1); + BOOST_CHECK_EQUAL(body_store.buffer(), build_chunk({ expected0, expected1 })); + + BOOST_CHECK(instance.put(key, slab2)); + BOOST_CHECK(instance.exists(key)); + BOOST_CHECK(instance.at(key, slab)); + BOOST_CHECK(slab == slab2); + BOOST_CHECK(instance.get(instance.at(key), slab)); + BOOST_CHECK(slab == slab2); + BOOST_CHECK_EQUAL(body_store.buffer(), build_chunk({ expected0, expected1, expected2 })); + + BOOST_CHECK(instance.put(key, slab3)); + BOOST_CHECK(instance.exists(key)); + BOOST_CHECK(instance.at(key, slab)); + BOOST_CHECK(slab == slab3); + BOOST_CHECK(instance.get(instance.at(key), slab)); + BOOST_CHECK(slab == slab3); + BOOST_CHECK_EQUAL(body_store.buffer(), build_chunk({ expected0, expected1, expected2, expected3 })); } BOOST_AUTO_TEST_SUITE_END()