diff --git a/include/bitcoin/database/impl/query/address/address_history.ipp b/include/bitcoin/database/impl/query/address/address_history.ipp index aead4f595..ad8153fd7 100644 --- a/include/bitcoin/database/impl/query/address/address_history.ipp +++ b/include/bitcoin/database/impl/query/address/address_history.ipp @@ -39,7 +39,7 @@ code CLASS::get_unconfirmed_history(const stopper& cancel, histories& out, const hash_digest& key, size_t limit, bool turbo) const NOEXCEPT { tx_links txs{}; - if (const auto ec = get_address_txs(cancel, txs, key, limit)) + if (const auto ec = get_address_txs(cancel, txs, key, limit, turbo)) return ec; // There is no cursor for unconfirmed, since it's height-based. @@ -62,7 +62,7 @@ code CLASS::get_confirmed_history(const stopper& cancel, height_link& cursor, bool turbo) const NOEXCEPT { tx_links txs{}; - if (const auto ec = get_address_txs(cancel, txs, key, limit)) + if (const auto ec = get_address_txs(cancel, txs, key, limit, turbo)) return ec; // Cursor is still advanced in the case of (integrity) failure. @@ -90,7 +90,7 @@ code CLASS::get_history(const stopper& cancel, height_link& cursor, bool turbo) const NOEXCEPT { tx_links txs{}; - if (const auto ec = get_address_txs(cancel, txs, key, limit)) + if (const auto ec = get_address_txs(cancel, txs, key, limit, turbo)) return ec; // Cursor is still advanced in the case of (integrity) failure. @@ -250,14 +250,15 @@ histories CLASS::get_spenders_history(const hash_digest& key, TEMPLATE code CLASS::get_address_txs(const stopper& cancel, tx_links& out, - const hash_digest& key, size_t limit) const NOEXCEPT + const hash_digest& key, size_t limit, bool turbo) const NOEXCEPT { output_links links{}; + tx_links parents{}; outs_link cursor{}; - if (const auto ec = to_address_outputs(cancel, cursor, links, key, limit)) + if (const auto ec = to_address_outputs(cancel, cursor, links, parents, key, limit)) return ec; - return to_touched_txs(cancel, out, links); + return to_touched_txs(cancel, out, links, parents, turbo); } // private/static @@ -266,7 +267,7 @@ template code CLASS::parallel_history_transform(const stopper& cancel, bool turbo, histories& out, const tx_links& txs, Functor&& functor) NOEXCEPT { - const auto policy = poolstl::execution::par_if(turbo); + const auto policy = poolstl::execution::par_if(turbo, parallel_floor); stopper fail{}; out.clear(); diff --git a/include/bitcoin/database/impl/query/address/address_outpoints.ipp b/include/bitcoin/database/impl/query/address/address_outpoints.ipp index 76809b46e..eb485bf03 100644 --- a/include/bitcoin/database/impl/query/address/address_outpoints.ipp +++ b/include/bitcoin/database/impl/query/address/address_outpoints.ipp @@ -151,7 +151,7 @@ template code CLASS::parallel_outpoint_transform(const stopper& cancel, bool turbo, outpoints& out, const output_links& links, Functor&& functor) NOEXCEPT { - const auto policy = poolstl::execution::par_if(turbo); + const auto policy = poolstl::execution::par_if(turbo, parallel_floor); stopper fail{}; out.clear(); diff --git a/include/bitcoin/database/impl/query/address/address_unspent.ipp b/include/bitcoin/database/impl/query/address/address_unspent.ipp index 136d90ad0..ea5d2530b 100644 --- a/include/bitcoin/database/impl/query/address/address_unspent.ipp +++ b/include/bitcoin/database/impl/query/address/address_unspent.ipp @@ -196,7 +196,7 @@ template code CLASS::parallel_unspent_transform(const stopper& cancel, bool turbo, unspent_outputs& out, const output_links& outs, Functor&& functor) NOEXCEPT { - const auto policy = poolstl::execution::par_if(turbo); + const auto policy = poolstl::execution::par_if(turbo, parallel_floor); stopper fail{}; out.clear(); diff --git a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp index a6ab2cbbb..620ced0e2 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp @@ -80,12 +80,9 @@ TEMPLATE code CLASS::to_touched_txs(const stopper& cancel, tx_links& out, const output_links& outputs) const NOEXCEPT { - // Reserve one for each output tx and one for spending input tx (estimate). - out.clear(); - out.reserve(two * outputs.size()); - - // Orders are reversed due to expected tx_links reversal, for faster sort. - for (const auto& output: std::views::reverse(outputs)) + tx_links parents{}; + parents.reserve(outputs.size()); + for (const auto& output: outputs) { if (cancel) return error::query_canceled; @@ -93,17 +90,59 @@ code CLASS::to_touched_txs(const stopper& cancel, tx_links& out, if (const auto tx = to_output_tx(output); tx.is_terminal()) return error::integrity; else - out.push_back(tx); - - for (const auto& input: std::views::reverse(to_spenders(output))) - { - if (const auto tx = to_input_tx(input); tx.is_terminal()) - return error::integrity; - else - out.push_back(tx); - } + parents.push_back(tx); } + return to_touched_txs(cancel, out, outputs, parents, false); +} + +TEMPLATE +code CLASS::to_touched_txs(const stopper& cancel, tx_links& out, + const output_links& outputs, const tx_links& parents, + bool turbo) const NOEXCEPT +{ + BC_ASSERT(outputs.size() == parents.size()); + const auto policy = poolstl::execution::par_if(turbo, parallel_floor); + stopper fail{}; + + // Each output resolves to its parent tx followed by its spending txs. + std::vector touched(outputs.size()); + std::transform(policy, outputs.cbegin(), outputs.cend(), parents.cbegin(), + touched.begin(), [this, &cancel, &fail](const output_link& output, + const tx_link& parent) NOEXCEPT + { + tx_links txs{}; + if (cancel || fail) + return txs; + + txs.push_back(parent); + for (const auto& input: std::views::reverse(to_spenders(parent, output))) + { + const auto tx = to_input_tx(input); + if (tx.is_terminal()) + { + fail = true; + return txs; + } + + txs.push_back(tx); + } + + return txs; + }); + + if (fail) + return error::integrity; + + if (cancel) + return error::query_canceled; + + // Orders are reversed due to expected tx_links reversal, for faster sort. + out.clear(); + out.reserve(two * outputs.size()); + for (const auto& txs: std::views::reverse(touched)) + out.insert(out.end(), txs.begin(), txs.end()); + return error::success; } @@ -126,8 +165,18 @@ code CLASS::to_address_outputs(const stopper& cancel, output_links& out, TEMPLATE code CLASS::to_address_outputs(const stopper& cancel, outs_link& cursor, output_links& out, const hash_digest& key, size_t limit) const NOEXCEPT +{ + tx_links parents{}; + return to_address_outputs(cancel, cursor, out, parents, key, limit); +} + +TEMPLATE +code CLASS::to_address_outputs(const stopper& cancel, outs_link& cursor, + output_links& out, tx_links& parents, const hash_digest& key, + size_t limit) const NOEXCEPT { out.clear(); + parents.clear(); // Limit bounds candidates, verified following iterator disposal. // The iterator guards the aggregate, so puts is read unguarded. @@ -179,7 +228,10 @@ code CLASS::to_address_outputs(const stopper& cancel, outs_link& cursor, return error::integrity; if (output.match) + { out.push_back(candidate); + parents.push_back(output.parent_fk); + } } return deferred; @@ -238,6 +290,13 @@ ins_links CLASS::to_spenders(const tx_link& output_tx, return to_spenders(get_tx_key(output_tx), output_index); } +TEMPLATE +ins_links CLASS::to_spenders(const tx_link& output_tx, + const output_link& link) const NOEXCEPT +{ + return to_spenders(output_tx, to_output_index(output_tx, link)); +} + TEMPLATE ins_links CLASS::to_spenders(const hash_digest& point_hash, uint32_t output_index) const NOEXCEPT diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index dc9a14c2c..335c2ccf3 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -375,6 +375,8 @@ class query uint32_t output_index) const NOEXCEPT; ins_links to_spenders(const tx_link& output_tx, uint32_t output_index) const NOEXCEPT; + ins_links to_spenders(const tx_link& output_tx, + const output_link& link) const NOEXCEPT; /// tx to puts (forward navigation) ins_links to_points(const tx_link& link) const NOEXCEPT; @@ -416,6 +418,9 @@ class query const output_links& outputs) const NOEXCEPT; code to_touched_txs(const stopper& cancel, tx_links& out, const output_links& outputs) const NOEXCEPT; + code to_touched_txs(const stopper& cancel, tx_links& out, + const output_links& outputs, const tx_links& parents, + bool turbo) const NOEXCEPT; code to_address_outputs(output_links& out, const hash_digest& key) const NOEXCEPT; @@ -425,6 +430,9 @@ class query code to_address_outputs(const stopper& cancel, outs_link& cursor, output_links& out, const hash_digest& key, size_t limit) const NOEXCEPT; + code to_address_outputs(const stopper& cancel, outs_link& cursor, + output_links& out, tx_links& parents, const hash_digest& key, + size_t limit) const NOEXCEPT; /// Archive reads. /// ----------------------------------------------------------------------- @@ -1338,12 +1346,15 @@ class query history get_tx_unconfirmed_history(hash_digest&& key, const tx_link& link) const NOEXCEPT; code get_address_txs(const stopper& cancel, tx_links& out, - const hash_digest& key, size_t limit) const NOEXCEPT; + const hash_digest& key, size_t limit, bool turbo) const NOEXCEPT; private: // This value should never be read, but may be useful in debugging. static constexpr uint32_t unspecified_timestamp = max_uint32; + // Minimum elements per parallel task for single-read transforms. + static constexpr size_t parallel_floor = 32; + // Fewest pool records hashed by one task of the compact short id scan. static constexpr size_t short_id_minimum_records = system::power2(10u); diff --git a/include/bitcoin/database/tables/archives/output.hpp b/include/bitcoin/database/tables/archives/output.hpp index e78b9784b..19bd3bd0f 100644 --- a/include/bitcoin/database/tables/archives/output.hpp +++ b/include/bitcoin/database/tables/archives/output.hpp @@ -133,7 +133,8 @@ struct output { using namespace system; - // Skip parent fk and value, and read the script size. + // Read parent fk, skip value, and read the script size. + parent_fk = unsafe_from_little_endian(start); const auto* position = std::next(start, tx::size); unsafe_from_variable(position); const auto scrypt_size = unsafe_from_variable(position); @@ -143,6 +144,7 @@ struct output } const system::hash_digest& key; + tx::integer parent_fk{}; bool match{}; }; diff --git a/test/query/navigate/navigate_reverse.cpp b/test/query/navigate/navigate_reverse.cpp index ba11377d1..d9870d612 100644 --- a/test/query/navigate/navigate_reverse.cpp +++ b/test/query/navigate/navigate_reverse.cpp @@ -1,448 +1,488 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * 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_navigate_tests, test::directory_setup_fixture) - -const auto& address0 = test::block1a_address0; -const auto& address1 = test::block1a_address1; - -// to_parent - -BOOST_AUTO_TEST_CASE(query_navigate__to_parent__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(query.initialize(test::genesis)); - BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block2a, context{ 0, 2, 0 }, {}, false, false)); - BOOST_REQUIRE_EQUAL(query.to_parent(0), header_link::terminal); - BOOST_REQUIRE_EQUAL(query.to_parent(1), 0u); - BOOST_REQUIRE_EQUAL(query.to_parent(2), 1u); - BOOST_REQUIRE_EQUAL(query.to_parent(3), 0u); - BOOST_REQUIRE_EQUAL(query.to_parent(4), 3u); - BOOST_REQUIRE_EQUAL(query.to_parent(5), header_link::terminal); -} - -// to_touched_txs1 - -BOOST_AUTO_TEST_CASE(query_navigate__to_touched_txs1__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - - output_links links{}; - BOOST_REQUIRE(!query.to_address_outputs(links, address1)); - - tx_links out{}; - BOOST_REQUIRE(!query.to_touched_txs(out, links)); - BOOST_REQUIRE_EQUAL(out.size(), 4u); - - // owners - BOOST_REQUIRE_EQUAL(out.at(0), 1u); // block1a (tx1) - - // spenders of (0) - BOOST_REQUIRE_EQUAL(out.at(1), 2u); // block2a (tx2/3) - BOOST_REQUIRE_EQUAL(out.at(2), 4u); // tx4 - BOOST_REQUIRE_EQUAL(out.at(3), 6u); // block3a (tx6) -} - -// to_touched_txs2 - -BOOST_AUTO_TEST_CASE(query_navigate__to_touched_txs2__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - - output_links links{}; - const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.to_address_outputs(cancel, links, address0)); - - tx_links out{}; - BOOST_REQUIRE(!query.to_touched_txs(cancel, out, links)); - BOOST_REQUIRE_EQUAL(out.size(), 10u); - - // owners - BOOST_REQUIRE_EQUAL(out.at(0), 1u); // block1a (tx1) - - // spenders of (0) - BOOST_REQUIRE_EQUAL(out.at(1), 2u); // block2a (tx2/3) - BOOST_REQUIRE_EQUAL(out.at(2), 4u); // tx4 - BOOST_REQUIRE_EQUAL(out.at(3), 5u); // tx5 - BOOST_REQUIRE_EQUAL(out.at(4), 6u); // block3a (tx6) - - // owners (unspent) - BOOST_REQUIRE_EQUAL(out.at(5), 2u); // block2a (tx2) [duplicate] - BOOST_REQUIRE_EQUAL(out.at(6), 3u); // block2a (tx3) - BOOST_REQUIRE_EQUAL(out.at(7), 4u); // tx4 [duplicate] - BOOST_REQUIRE_EQUAL(out.at(8), 5u); // tx5 [duplicate] - BOOST_REQUIRE_EQUAL(out.at(9), 6u); // block3a (tx6) [duplicate] -} - -// to_address_outputs1 - -BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs1__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - - output_links out{}; - BOOST_REQUIRE(!query.to_address_outputs(out, address1)); - - // There is 1 instance of the `script{ { { opcode::roll } } }` output. - BOOST_REQUIRE_EQUAL(out.size(), 1u); - BOOST_REQUIRE_EQUAL(out.front(), 88u); -} - -// to_address_outputs2 - -BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs2__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - - output_links out{}; - const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.to_address_outputs(cancel, out, address0)); - - // There are 6 instances of the `script{ { { opcode::pick } } }` output. - BOOST_REQUIRE_EQUAL(out.size(), 6u); - BOOST_REQUIRE_EQUAL(out.at(0), 123u); - BOOST_REQUIRE_EQUAL(out.at(1), 116u); - BOOST_REQUIRE_EQUAL(out.at(2), 109u); - BOOST_REQUIRE_EQUAL(out.at(3), 102u); - BOOST_REQUIRE_EQUAL(out.at(4), 95u); - BOOST_REQUIRE_EQUAL(out.at(5), 81u); -} - -// to_address_outputs3 - -BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__terminal__not_reduced) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - - output_links out{}; - outs_link end{}; - const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.to_address_outputs(cancel, end, out, address0, max_size_t)); - - // There are 6 instances of the `script{ { { opcode::pick } } }` output. - BOOST_REQUIRE_EQUAL(out.size(), 6u); - BOOST_REQUIRE_EQUAL(out.at(0), 123u); - BOOST_REQUIRE_EQUAL(out.at(1), 116u); - BOOST_REQUIRE_EQUAL(out.at(2), 109u); - BOOST_REQUIRE_EQUAL(out.at(3), 102u); - BOOST_REQUIRE_EQUAL(out.at(4), 95u); - BOOST_REQUIRE_EQUAL(out.at(5), 81u); -} - -BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__limit__depth_limited) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - - output_links out{}; - outs_link end{}; - const std::atomic_bool cancel{}; - BOOST_REQUIRE_EQUAL(query.to_address_outputs(cancel, end, out, address0, 4), error::depth_limited); - - // The limit is applied before deduplication. - // There are 6 instances of the `script{ { { opcode::pick } } }` output, limited to 4. - BOOST_REQUIRE_EQUAL(out.size(), 4u); - BOOST_REQUIRE_EQUAL(out.at(0), 123u); - BOOST_REQUIRE_EQUAL(out.at(1), 116u); - BOOST_REQUIRE_EQUAL(out.at(2), 109u); - BOOST_REQUIRE_EQUAL(out.at(3), 102u); - ////BOOST_REQUIRE_EQUAL(out.at(4), 95u); - ////BOOST_REQUIRE_EQUAL(out.at(5), 81u); -} - -BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__stop_mismatch__populated_invalid_cursor) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - - output_links out{}; - outs_link cursor{ 4242 }; - const std::atomic_bool cancel{}; - const auto ec = query.to_address_outputs(cancel, cursor, out, address0, max_size_t); - BOOST_REQUIRE_EQUAL(ec, error::invalid_cursor); - - // The end was not found but the full list is returned. - BOOST_REQUIRE_EQUAL(out.size(), 6u); - BOOST_REQUIRE_EQUAL(out.at(0), 123u); - BOOST_REQUIRE_EQUAL(out.at(1), 116u); - BOOST_REQUIRE_EQUAL(out.at(2), 109u); - BOOST_REQUIRE_EQUAL(out.at(3), 102u); - BOOST_REQUIRE_EQUAL(out.at(4), 95u); - BOOST_REQUIRE_EQUAL(out.at(5), 81u); -} - -BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__stop_match__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - - output_links out{}; - outs_link cursor{ 3 }; - const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); - BOOST_REQUIRE_EQUAL(cursor.value, 7u); - - // The stop was found so partial list is returned. - BOOST_REQUIRE_EQUAL(out.size(), 4u); - BOOST_REQUIRE_EQUAL(out.at(0), 123u); - BOOST_REQUIRE_EQUAL(out.at(1), 116u); - BOOST_REQUIRE_EQUAL(out.at(2), 109u); - BOOST_REQUIRE_EQUAL(out.at(3), 102u); -} - -BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__progression__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - - output_links out{}; - outs_link cursor{}; - const std::atomic_bool cancel{}; - - // Add three unconfirmed blocks and two txs, with 7 outputs, 6 matching address. - BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); - BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); - BOOST_REQUIRE_EQUAL(cursor.value, 7u); - BOOST_REQUIRE_EQUAL(out.size(), 6u); - BOOST_REQUIRE_EQUAL(out.at(0), 123u); - BOOST_REQUIRE_EQUAL(out.at(1), 116u); - BOOST_REQUIRE_EQUAL(out.at(2), 109u); - BOOST_REQUIRE_EQUAL(out.at(3), 102u); - BOOST_REQUIRE_EQUAL(out.at(4), 95u); - BOOST_REQUIRE_EQUAL(out.at(5), 81u); - - // Add two unconfirmed blocks with 3 outputs, all matching address. - BOOST_REQUIRE(query.set(test::block1b, database::context{ 0, 1, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block2b, database::context{ 0, 2, 0 }, {}, false, false)); - BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); - BOOST_REQUIRE_EQUAL(cursor.value, 10u); - BOOST_REQUIRE_EQUAL(out.size(), 3u); - BOOST_REQUIRE_EQUAL(out.at(0), 144u); - BOOST_REQUIRE_EQUAL(out.at(1), 137u); - BOOST_REQUIRE_EQUAL(out.at(2), 130u); - - // Add one tx with one output, matching address. - BOOST_REQUIRE(query.set(test::tx2b)); - BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); - BOOST_REQUIRE_EQUAL(cursor.value, 11u); - BOOST_REQUIRE_EQUAL(out.size(), 1u); - BOOST_REQUIRE_EQUAL(out.at(0), 151u); - - // No changes to this address since cursor. - BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); - BOOST_REQUIRE_EQUAL(cursor.value, 11u); - BOOST_REQUIRE(out.empty()); -} - -// to_input_tx -// to_output_tx - -BOOST_AUTO_TEST_CASE(query_navigate__to_output_tx__to_output__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(query.initialize(test::genesis)); - BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block2, context{ 0, 1, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block3, context{ 0, 1, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, {}, false, false)); - - // All 5 blocks have one transaction with 1 output. - BOOST_REQUIRE_EQUAL(query.to_output_tx(0 * 0x51), 0u); - BOOST_REQUIRE_EQUAL(query.to_output_tx(1 * 0x51), 1u); - BOOST_REQUIRE_EQUAL(query.to_output_tx(2 * 0x51), 2u); - BOOST_REQUIRE_EQUAL(query.to_output_tx(3 * 0x51), 3u); - BOOST_REQUIRE_EQUAL(query.to_output_tx(4 * 0x51), 4u); - BOOST_REQUIRE_EQUAL(query.to_output_tx(4 * 0x51 + 7u), 4u); - - BOOST_REQUIRE_EQUAL(query.to_output(0, 0), 0u * 0x51u); - BOOST_REQUIRE_EQUAL(query.to_output(1, 0), 1u * 0x51u); - BOOST_REQUIRE_EQUAL(query.to_output(2, 0), 2u * 0x51u); - BOOST_REQUIRE_EQUAL(query.to_output(3, 0), 3u * 0x51u); - BOOST_REQUIRE_EQUAL(query.to_output(4, 0), 4u * 0x51u); - BOOST_REQUIRE_EQUAL(query.to_output(4, 1), 4u * 0x51u + 7u); - - const output_links expected_outputs4{ 4 * 0x51, 4 * 0x51 + 7 }; - BOOST_REQUIRE_EQUAL(query.to_outputs(0), output_links{ 0 * 0x51 }); - BOOST_REQUIRE_EQUAL(query.to_outputs(1), output_links{ 1 * 0x51 }); - BOOST_REQUIRE_EQUAL(query.to_outputs(2), output_links{ 2 * 0x51 }); - BOOST_REQUIRE_EQUAL(query.to_outputs(3), output_links{ 3 * 0x51 }); - BOOST_REQUIRE_EQUAL(query.to_outputs(4), expected_outputs4); - - // All blocks have one transaction. - BOOST_REQUIRE_EQUAL(query.to_block_outputs(0), output_links{ 0 * 0x51 }); - BOOST_REQUIRE_EQUAL(query.to_block_outputs(1), output_links{ 1 * 0x51 }); - BOOST_REQUIRE_EQUAL(query.to_block_outputs(2), output_links{ 2 * 0x51 }); - BOOST_REQUIRE_EQUAL(query.to_block_outputs(3), output_links{ 3 * 0x51 }); - BOOST_REQUIRE_EQUAL(query.to_block_outputs(4), expected_outputs4); - - // No prevouts that exist. - const output_links expected_prevouts4{ output_link::terminal, output_link::terminal, output_link::terminal }; - BOOST_REQUIRE_EQUAL(query.to_prevouts(0), output_links{ output_link::terminal }); - BOOST_REQUIRE_EQUAL(query.to_prevouts(1), output_links{ output_link::terminal }); - BOOST_REQUIRE_EQUAL(query.to_prevouts(2), output_links{ output_link::terminal }); - BOOST_REQUIRE_EQUAL(query.to_prevouts(3), output_links{ output_link::terminal }); - BOOST_REQUIRE_EQUAL(query.to_prevouts(4), expected_prevouts4); - - // All blocks have one transaction. - BOOST_REQUIRE_EQUAL(query.to_block_prevouts(0), output_links{}); - BOOST_REQUIRE_EQUAL(query.to_block_prevouts(1), output_links{}); - BOOST_REQUIRE_EQUAL(query.to_block_prevouts(2), output_links{}); - BOOST_REQUIRE_EQUAL(query.to_block_prevouts(3), output_links{}); - BOOST_REQUIRE_EQUAL(query.to_block_prevouts(4), output_links{}); - - // Past end. - BOOST_REQUIRE_EQUAL(query.to_output_tx(4 * 0x51 + 14), tx_link::terminal); - BOOST_REQUIRE_EQUAL(query.to_output(5, 0), output_link::terminal); - BOOST_REQUIRE(query.to_outputs(5).empty()); - BOOST_REQUIRE(query.to_block_outputs(5).empty()); -} - -// to_prevout_tx - -BOOST_AUTO_TEST_CASE(query_navigate__to_prevout_tx__to_prevout__expected) -{ - settings settings{}; - settings.tx.buckets = 3; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - BOOST_REQUIRE(query.initialize(test::genesis)); - BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block2a, context{ 0, 1, 0 }, {}, false, false)); - - // inputs in link order. - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(0), tx_link::terminal); - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(1), tx_link::terminal); - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(2), tx_link::terminal); - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(3), tx_link::terminal); - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(4), 1u); // block1a:0 (second serialized tx) - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(5), 1u); // block1a:0 (second serialized tx) - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(6), tx_link::terminal); - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(7), tx_link::terminal); - - const output_links expected_prevouts1{ output_link::terminal }; - const output_links expected_prevouts2{ output_link::terminal, output_link::terminal }; - const output_links expected_prevouts3{ output_link::terminal, output_link::terminal, output_link::terminal }; - const output_links expected_prevouts{ 0x51u, 0x51u + 7u }; - BOOST_REQUIRE_EQUAL(query.to_prevouts(0), expected_prevouts1); - BOOST_REQUIRE_EQUAL(query.to_prevouts(1), expected_prevouts3); - BOOST_REQUIRE_EQUAL(query.to_prevouts(2), expected_prevouts); - BOOST_REQUIRE_EQUAL(query.to_prevouts(3), expected_prevouts2); - - // First tx is coinbase, or tx has undefined prevouts. - BOOST_REQUIRE_EQUAL(query.to_block_prevouts(0), output_links{}); - BOOST_REQUIRE_EQUAL(query.to_block_prevouts(1), output_links{}); - BOOST_REQUIRE_EQUAL(query.to_block_prevouts(2), expected_prevouts2); - - // Past end. - BOOST_REQUIRE_EQUAL(query.to_prevout_tx(8), tx_link::terminal); -} - -// to_spenders1 -// to_spenders2 -// to_duplicates - -// to_block - -BOOST_AUTO_TEST_CASE(query_navigate__to_block__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - - class accessor - : public test::query_accessor - { - public: - using test::query_accessor::query_accessor; - using test::query_accessor::to_block; - }; - - accessor query{ store }; - BOOST_REQUIRE(!store.create(test::events_handler)); - 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, 1, 0 }, {}, false, false)); - - // Either not strong or not found, except genesis. - BOOST_REQUIRE(!query.to_block(0).is_terminal()); - BOOST_REQUIRE(query.to_block(1).is_terminal()); - BOOST_REQUIRE(query.to_block(2).is_terminal()); - BOOST_REQUIRE(query.to_block(3).is_terminal()); -} - -// to_input_index -// to_output_index - -BOOST_AUTO_TEST_SUITE_END() +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * 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_navigate_tests, test::directory_setup_fixture) + +const auto& address0 = test::block1a_address0; +const auto& address1 = test::block1a_address1; + +// to_parent + +BOOST_AUTO_TEST_CASE(query_navigate__to_parent__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block2a, context{ 0, 2, 0 }, {}, false, false)); + BOOST_REQUIRE_EQUAL(query.to_parent(0), header_link::terminal); + BOOST_REQUIRE_EQUAL(query.to_parent(1), 0u); + BOOST_REQUIRE_EQUAL(query.to_parent(2), 1u); + BOOST_REQUIRE_EQUAL(query.to_parent(3), 0u); + BOOST_REQUIRE_EQUAL(query.to_parent(4), 3u); + BOOST_REQUIRE_EQUAL(query.to_parent(5), header_link::terminal); +} + +// to_touched_txs1 + +BOOST_AUTO_TEST_CASE(query_navigate__to_touched_txs1__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links links{}; + BOOST_REQUIRE(!query.to_address_outputs(links, address1)); + + tx_links out{}; + BOOST_REQUIRE(!query.to_touched_txs(out, links)); + BOOST_REQUIRE_EQUAL(out.size(), 4u); + + // owners + BOOST_REQUIRE_EQUAL(out.at(0), 1u); // block1a (tx1) + + // spenders of (0) + BOOST_REQUIRE_EQUAL(out.at(1), 2u); // block2a (tx2/3) + BOOST_REQUIRE_EQUAL(out.at(2), 4u); // tx4 + BOOST_REQUIRE_EQUAL(out.at(3), 6u); // block3a (tx6) +} + +// to_touched_txs2 + +BOOST_AUTO_TEST_CASE(query_navigate__to_touched_txs2__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links links{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.to_address_outputs(cancel, links, address0)); + + tx_links out{}; + BOOST_REQUIRE(!query.to_touched_txs(cancel, out, links)); + BOOST_REQUIRE_EQUAL(out.size(), 10u); + + // owners + BOOST_REQUIRE_EQUAL(out.at(0), 1u); // block1a (tx1) + + // spenders of (0) + BOOST_REQUIRE_EQUAL(out.at(1), 2u); // block2a (tx2/3) + BOOST_REQUIRE_EQUAL(out.at(2), 4u); // tx4 + BOOST_REQUIRE_EQUAL(out.at(3), 5u); // tx5 + BOOST_REQUIRE_EQUAL(out.at(4), 6u); // block3a (tx6) + + // owners (unspent) + BOOST_REQUIRE_EQUAL(out.at(5), 2u); // block2a (tx2) [duplicate] + BOOST_REQUIRE_EQUAL(out.at(6), 3u); // block2a (tx3) + BOOST_REQUIRE_EQUAL(out.at(7), 4u); // tx4 [duplicate] + BOOST_REQUIRE_EQUAL(out.at(8), 5u); // tx5 [duplicate] + BOOST_REQUIRE_EQUAL(out.at(9), 6u); // block3a (tx6) [duplicate] +} + +// to_touched_txs3 + +BOOST_AUTO_TEST_CASE(query_navigate__to_touched_txs3__parents__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links links{}; + tx_links parents{}; + outs_link cursor{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, links, parents, address0, max_size_t)); + BOOST_REQUIRE_EQUAL(links.size(), 6u); + BOOST_REQUIRE_EQUAL(parents.size(), 6u); + BOOST_REQUIRE_EQUAL(parents.at(0), 6u); + BOOST_REQUIRE_EQUAL(parents.at(1), 5u); + BOOST_REQUIRE_EQUAL(parents.at(2), 4u); + BOOST_REQUIRE_EQUAL(parents.at(3), 3u); + BOOST_REQUIRE_EQUAL(parents.at(4), 2u); + BOOST_REQUIRE_EQUAL(parents.at(5), 1u); + + tx_links out{}; + BOOST_REQUIRE(!query.to_touched_txs(cancel, out, links, parents, true)); + BOOST_REQUIRE_EQUAL(out.size(), 10u); + BOOST_REQUIRE_EQUAL(out.at(0), 1u); + BOOST_REQUIRE_EQUAL(out.at(1), 2u); + BOOST_REQUIRE_EQUAL(out.at(2), 4u); + BOOST_REQUIRE_EQUAL(out.at(3), 5u); + BOOST_REQUIRE_EQUAL(out.at(4), 6u); + BOOST_REQUIRE_EQUAL(out.at(5), 2u); + BOOST_REQUIRE_EQUAL(out.at(6), 3u); + BOOST_REQUIRE_EQUAL(out.at(7), 4u); + BOOST_REQUIRE_EQUAL(out.at(8), 5u); + BOOST_REQUIRE_EQUAL(out.at(9), 6u); +} + +// to_address_outputs1 + +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs1__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links out{}; + BOOST_REQUIRE(!query.to_address_outputs(out, address1)); + + // There is 1 instance of the `script{ { { opcode::roll } } }` output. + BOOST_REQUIRE_EQUAL(out.size(), 1u); + BOOST_REQUIRE_EQUAL(out.front(), 88u); +} + +// to_address_outputs2 + +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs2__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.to_address_outputs(cancel, out, address0)); + + // There are 6 instances of the `script{ { { opcode::pick } } }` output. + BOOST_REQUIRE_EQUAL(out.size(), 6u); + BOOST_REQUIRE_EQUAL(out.at(0), 123u); + BOOST_REQUIRE_EQUAL(out.at(1), 116u); + BOOST_REQUIRE_EQUAL(out.at(2), 109u); + BOOST_REQUIRE_EQUAL(out.at(3), 102u); + BOOST_REQUIRE_EQUAL(out.at(4), 95u); + BOOST_REQUIRE_EQUAL(out.at(5), 81u); +} + +// to_address_outputs3 + +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__terminal__not_reduced) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links out{}; + outs_link end{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.to_address_outputs(cancel, end, out, address0, max_size_t)); + + // There are 6 instances of the `script{ { { opcode::pick } } }` output. + BOOST_REQUIRE_EQUAL(out.size(), 6u); + BOOST_REQUIRE_EQUAL(out.at(0), 123u); + BOOST_REQUIRE_EQUAL(out.at(1), 116u); + BOOST_REQUIRE_EQUAL(out.at(2), 109u); + BOOST_REQUIRE_EQUAL(out.at(3), 102u); + BOOST_REQUIRE_EQUAL(out.at(4), 95u); + BOOST_REQUIRE_EQUAL(out.at(5), 81u); +} + +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__limit__depth_limited) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links out{}; + outs_link end{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE_EQUAL(query.to_address_outputs(cancel, end, out, address0, 4), error::depth_limited); + + // The limit is applied before deduplication. + // There are 6 instances of the `script{ { { opcode::pick } } }` output, limited to 4. + BOOST_REQUIRE_EQUAL(out.size(), 4u); + BOOST_REQUIRE_EQUAL(out.at(0), 123u); + BOOST_REQUIRE_EQUAL(out.at(1), 116u); + BOOST_REQUIRE_EQUAL(out.at(2), 109u); + BOOST_REQUIRE_EQUAL(out.at(3), 102u); + ////BOOST_REQUIRE_EQUAL(out.at(4), 95u); + ////BOOST_REQUIRE_EQUAL(out.at(5), 81u); +} + +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__stop_mismatch__populated_invalid_cursor) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links out{}; + outs_link cursor{ 4242 }; + const std::atomic_bool cancel{}; + const auto ec = query.to_address_outputs(cancel, cursor, out, address0, max_size_t); + BOOST_REQUIRE_EQUAL(ec, error::invalid_cursor); + + // The end was not found but the full list is returned. + BOOST_REQUIRE_EQUAL(out.size(), 6u); + BOOST_REQUIRE_EQUAL(out.at(0), 123u); + BOOST_REQUIRE_EQUAL(out.at(1), 116u); + BOOST_REQUIRE_EQUAL(out.at(2), 109u); + BOOST_REQUIRE_EQUAL(out.at(3), 102u); + BOOST_REQUIRE_EQUAL(out.at(4), 95u); + BOOST_REQUIRE_EQUAL(out.at(5), 81u); +} + +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__stop_match__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + + output_links out{}; + outs_link cursor{ 3 }; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); + BOOST_REQUIRE_EQUAL(cursor.value, 7u); + + // The stop was found so partial list is returned. + BOOST_REQUIRE_EQUAL(out.size(), 4u); + BOOST_REQUIRE_EQUAL(out.at(0), 123u); + BOOST_REQUIRE_EQUAL(out.at(1), 116u); + BOOST_REQUIRE_EQUAL(out.at(2), 109u); + BOOST_REQUIRE_EQUAL(out.at(3), 102u); +} + +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__progression__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + + output_links out{}; + outs_link cursor{}; + const std::atomic_bool cancel{}; + + // Add three unconfirmed blocks and two txs, with 7 outputs, 6 matching address. + BOOST_REQUIRE(setup_three_block_unconfirmed_address_store(query)); + BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); + BOOST_REQUIRE_EQUAL(cursor.value, 7u); + BOOST_REQUIRE_EQUAL(out.size(), 6u); + BOOST_REQUIRE_EQUAL(out.at(0), 123u); + BOOST_REQUIRE_EQUAL(out.at(1), 116u); + BOOST_REQUIRE_EQUAL(out.at(2), 109u); + BOOST_REQUIRE_EQUAL(out.at(3), 102u); + BOOST_REQUIRE_EQUAL(out.at(4), 95u); + BOOST_REQUIRE_EQUAL(out.at(5), 81u); + + // Add two unconfirmed blocks with 3 outputs, all matching address. + BOOST_REQUIRE(query.set(test::block1b, database::context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block2b, database::context{ 0, 2, 0 }, {}, false, false)); + BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); + BOOST_REQUIRE_EQUAL(cursor.value, 10u); + BOOST_REQUIRE_EQUAL(out.size(), 3u); + BOOST_REQUIRE_EQUAL(out.at(0), 144u); + BOOST_REQUIRE_EQUAL(out.at(1), 137u); + BOOST_REQUIRE_EQUAL(out.at(2), 130u); + + // Add one tx with one output, matching address. + BOOST_REQUIRE(query.set(test::tx2b)); + BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); + BOOST_REQUIRE_EQUAL(cursor.value, 11u); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + BOOST_REQUIRE_EQUAL(out.at(0), 151u); + + // No changes to this address since cursor. + BOOST_REQUIRE(!query.to_address_outputs(cancel, cursor, out, address0, max_size_t)); + BOOST_REQUIRE_EQUAL(cursor.value, 11u); + BOOST_REQUIRE(out.empty()); +} + +// to_input_tx +// to_output_tx + +BOOST_AUTO_TEST_CASE(query_navigate__to_output_tx__to_output__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block2, context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block3, context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, {}, false, false)); + + // All 5 blocks have one transaction with 1 output. + BOOST_REQUIRE_EQUAL(query.to_output_tx(0 * 0x51), 0u); + BOOST_REQUIRE_EQUAL(query.to_output_tx(1 * 0x51), 1u); + BOOST_REQUIRE_EQUAL(query.to_output_tx(2 * 0x51), 2u); + BOOST_REQUIRE_EQUAL(query.to_output_tx(3 * 0x51), 3u); + BOOST_REQUIRE_EQUAL(query.to_output_tx(4 * 0x51), 4u); + BOOST_REQUIRE_EQUAL(query.to_output_tx(4 * 0x51 + 7u), 4u); + + BOOST_REQUIRE_EQUAL(query.to_output(0, 0), 0u * 0x51u); + BOOST_REQUIRE_EQUAL(query.to_output(1, 0), 1u * 0x51u); + BOOST_REQUIRE_EQUAL(query.to_output(2, 0), 2u * 0x51u); + BOOST_REQUIRE_EQUAL(query.to_output(3, 0), 3u * 0x51u); + BOOST_REQUIRE_EQUAL(query.to_output(4, 0), 4u * 0x51u); + BOOST_REQUIRE_EQUAL(query.to_output(4, 1), 4u * 0x51u + 7u); + + const output_links expected_outputs4{ 4 * 0x51, 4 * 0x51 + 7 }; + BOOST_REQUIRE_EQUAL(query.to_outputs(0), output_links{ 0 * 0x51 }); + BOOST_REQUIRE_EQUAL(query.to_outputs(1), output_links{ 1 * 0x51 }); + BOOST_REQUIRE_EQUAL(query.to_outputs(2), output_links{ 2 * 0x51 }); + BOOST_REQUIRE_EQUAL(query.to_outputs(3), output_links{ 3 * 0x51 }); + BOOST_REQUIRE_EQUAL(query.to_outputs(4), expected_outputs4); + + // All blocks have one transaction. + BOOST_REQUIRE_EQUAL(query.to_block_outputs(0), output_links{ 0 * 0x51 }); + BOOST_REQUIRE_EQUAL(query.to_block_outputs(1), output_links{ 1 * 0x51 }); + BOOST_REQUIRE_EQUAL(query.to_block_outputs(2), output_links{ 2 * 0x51 }); + BOOST_REQUIRE_EQUAL(query.to_block_outputs(3), output_links{ 3 * 0x51 }); + BOOST_REQUIRE_EQUAL(query.to_block_outputs(4), expected_outputs4); + + // No prevouts that exist. + const output_links expected_prevouts4{ output_link::terminal, output_link::terminal, output_link::terminal }; + BOOST_REQUIRE_EQUAL(query.to_prevouts(0), output_links{ output_link::terminal }); + BOOST_REQUIRE_EQUAL(query.to_prevouts(1), output_links{ output_link::terminal }); + BOOST_REQUIRE_EQUAL(query.to_prevouts(2), output_links{ output_link::terminal }); + BOOST_REQUIRE_EQUAL(query.to_prevouts(3), output_links{ output_link::terminal }); + BOOST_REQUIRE_EQUAL(query.to_prevouts(4), expected_prevouts4); + + // All blocks have one transaction. + BOOST_REQUIRE_EQUAL(query.to_block_prevouts(0), output_links{}); + BOOST_REQUIRE_EQUAL(query.to_block_prevouts(1), output_links{}); + BOOST_REQUIRE_EQUAL(query.to_block_prevouts(2), output_links{}); + BOOST_REQUIRE_EQUAL(query.to_block_prevouts(3), output_links{}); + BOOST_REQUIRE_EQUAL(query.to_block_prevouts(4), output_links{}); + + // Past end. + BOOST_REQUIRE_EQUAL(query.to_output_tx(4 * 0x51 + 14), tx_link::terminal); + BOOST_REQUIRE_EQUAL(query.to_output(5, 0), output_link::terminal); + BOOST_REQUIRE(query.to_outputs(5).empty()); + BOOST_REQUIRE(query.to_block_outputs(5).empty()); +} + +// to_prevout_tx + +BOOST_AUTO_TEST_CASE(query_navigate__to_prevout_tx__to_prevout__expected) +{ + settings settings{}; + settings.tx.buckets = 3; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set(test::block2a, context{ 0, 1, 0 }, {}, false, false)); + + // inputs in link order. + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(0), tx_link::terminal); + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(1), tx_link::terminal); + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(2), tx_link::terminal); + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(3), tx_link::terminal); + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(4), 1u); // block1a:0 (second serialized tx) + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(5), 1u); // block1a:0 (second serialized tx) + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(6), tx_link::terminal); + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(7), tx_link::terminal); + + const output_links expected_prevouts1{ output_link::terminal }; + const output_links expected_prevouts2{ output_link::terminal, output_link::terminal }; + const output_links expected_prevouts3{ output_link::terminal, output_link::terminal, output_link::terminal }; + const output_links expected_prevouts{ 0x51u, 0x51u + 7u }; + BOOST_REQUIRE_EQUAL(query.to_prevouts(0), expected_prevouts1); + BOOST_REQUIRE_EQUAL(query.to_prevouts(1), expected_prevouts3); + BOOST_REQUIRE_EQUAL(query.to_prevouts(2), expected_prevouts); + BOOST_REQUIRE_EQUAL(query.to_prevouts(3), expected_prevouts2); + + // First tx is coinbase, or tx has undefined prevouts. + BOOST_REQUIRE_EQUAL(query.to_block_prevouts(0), output_links{}); + BOOST_REQUIRE_EQUAL(query.to_block_prevouts(1), output_links{}); + BOOST_REQUIRE_EQUAL(query.to_block_prevouts(2), expected_prevouts2); + + // Past end. + BOOST_REQUIRE_EQUAL(query.to_prevout_tx(8), tx_link::terminal); +} + +// to_spenders1 +// to_spenders2 +// to_duplicates + +// to_block + +BOOST_AUTO_TEST_CASE(query_navigate__to_block__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + + class accessor + : public test::query_accessor + { + public: + using test::query_accessor::query_accessor; + using test::query_accessor::to_block; + }; + + accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + 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, 1, 0 }, {}, false, false)); + + // Either not strong or not found, except genesis. + BOOST_REQUIRE(!query.to_block(0).is_terminal()); + BOOST_REQUIRE(query.to_block(1).is_terminal()); + BOOST_REQUIRE(query.to_block(2).is_terminal()); + BOOST_REQUIRE(query.to_block(3).is_terminal()); +} + +// to_input_index +// to_output_index + +BOOST_AUTO_TEST_SUITE_END()