diff --git a/include/bitcoin/database/error.hpp b/include/bitcoin/database/error.hpp index a8e915c0f..0e8a5505c 100644 --- a/include/bitcoin/database/error.hpp +++ b/include/bitcoin/database/error.hpp @@ -35,6 +35,7 @@ enum error_t : uint8_t /// general success, canceled, + limited, unknown_state, /// integrity (internal fault) diff --git a/include/bitcoin/database/impl/query/address/address_history.ipp b/include/bitcoin/database/impl/query/address/address_history.ipp index feeb50cbb..21e66b1fd 100644 --- a/include/bitcoin/database/impl/query/address/address_history.ipp +++ b/include/bitcoin/database/impl/query/address/address_history.ipp @@ -125,13 +125,23 @@ code CLASS::get_confirmed_history(const stopper& cancel, histories& out, }); } -// server/electrum +// ununsed TEMPLATE code CLASS::get_history(const stopper& cancel, histories& out, const hash_digest& key, bool turbo) const NOEXCEPT +{ + address_link cursor{}; + return get_history(cancel, cursor, out, key, max_size_t, turbo); +} + +// server/electrum +TEMPLATE +code CLASS::get_history(const stopper& cancel, address_link& cursor, + histories& out, const hash_digest& key, size_t limit, + bool turbo) const NOEXCEPT { output_links outs{}; - if (const auto ec = to_address_outputs(cancel, outs, key)) + if (const auto ec = to_address_outputs(cancel, cursor, outs, key, limit)) return ec; tx_links links{}; diff --git a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp index 4c38c8285..1d081ea38 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp @@ -105,7 +105,7 @@ code CLASS::to_address_outputs(const stopper& cancel, output_links& out, TEMPLATE code CLASS::to_address_outputs(const stopper& cancel, address_link& cursor, - output_links& out, const hash_digest& key) const NOEXCEPT + output_links& out, const hash_digest& key, size_t limit) const NOEXCEPT { out.clear(); const auto end = cursor; @@ -118,6 +118,9 @@ code CLASS::to_address_outputs(const stopper& cancel, address_link& cursor, if (it.get() == end) return error::success; + if (is_zero(limit--)) + return error::limited; + table::address::record address{}; if (!store_.address.get(it, address)) return error::integrity; diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index a925ffb48..89487d2d3 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -322,7 +322,8 @@ class query code to_address_outputs(const stopper& cancel, output_links& out, const hash_digest& key) const NOEXCEPT; code to_address_outputs(const stopper& cancel, address_link& cursor, - output_links& out, const hash_digest& key) const NOEXCEPT; + output_links& out, const hash_digest& key, + size_t limit=max_size_t) const NOEXCEPT; /// Archive reads. /// ----------------------------------------------------------------------- @@ -640,6 +641,9 @@ class query const hash_digest& key, bool turbo=false) const NOEXCEPT; code get_history(const stopper& cancel, histories& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; + code get_history(const stopper& cancel, address_link& cursor, + histories& out, const hash_digest& key, size_t limit=max_size_t, + bool turbo=false) const NOEXCEPT; /// Electrum queries (unspents, deduped, electrum sort). code get_unconfirmed_unspent(const stopper& cancel, unspents& out, diff --git a/src/error.cpp b/src/error.cpp index c695a7898..5c38c1981 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -29,6 +29,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) // general { success, "success" }, { canceled, "canceled" }, + { limited, "limited" }, { unknown_state, "unknown state" }, // integrity (internal fault) diff --git a/test/error.cpp b/test/error.cpp index 3bc633f51..34d50656e 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -41,6 +41,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__canceled__true_expected_message) BOOST_REQUIRE_EQUAL(ec.message(), "canceled"); } +BOOST_AUTO_TEST_CASE(error_t__code__limited__true_expected_message) +{ + constexpr auto value = error::limited; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "limited"); +} + BOOST_AUTO_TEST_CASE(error_t__code__unknown_state__true_expected_message) { constexpr auto value = error::unknown_state; diff --git a/test/query/address/address_history.cpp b/test/query/address/address_history.cpp index ba6cfc0c3..79dec4783 100644 --- a/test/query/address/address_history.cpp +++ b/test/query/address/address_history.cpp @@ -89,7 +89,6 @@ BOOST_AUTO_TEST_CASE(query_address__get_unconfirmed_history__turbo_block1a_addre BOOST_REQUIRE(!store.create(test::events_handler)); BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query)); - using namespace system; histories out{}; const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::block1a_address0, true)); @@ -116,6 +115,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_unconfirmed_history__turbo_block1a_addre BOOST_CHECK_EQUAL(out.at(3).position, history::unconfirmed_position); // Unconfirmed system::encode_hash(hash) lexically sorted. + using namespace system; BOOST_CHECK(encode_hash(out.at(0).tx.hash()) < encode_hash(out.at(1).tx.hash())); BOOST_CHECK(encode_hash(out.at(2).tx.hash()) < encode_hash(out.at(3).tx.hash())); @@ -178,7 +178,6 @@ BOOST_AUTO_TEST_CASE(query_address__get_history__turbo_block1a_address0__expecte BOOST_REQUIRE(!store.create(test::events_handler)); BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query)); - using namespace system; histories out{}; const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_history(cancel, out, test::block1a_address0, true)); @@ -219,6 +218,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_history__turbo_block1a_address0__expecte BOOST_REQUIRE_EQUAL(out.at(7).position, history::unconfirmed_position); // tx7 // Unconfirmed system::encode_hash(hash) lexically sorted. + using namespace system; BOOST_REQUIRE(encode_hash(out.at(6).tx.hash()) < encode_hash(out.at(7).tx.hash())); // Fee (not part of sort). @@ -232,6 +232,42 @@ BOOST_AUTO_TEST_CASE(query_address__get_history__turbo_block1a_address0__expecte BOOST_REQUIRE_EQUAL(out.at(7).fee, history::missing_prevout); // tx7 } +BOOST_AUTO_TEST_CASE(query_address__get_history__limited__limited_empty) +{ + 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(test::setup_three_block_confirmed_address_store(query)); + + histories out{}; + address_link cursor{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE_EQUAL(query.get_history(cancel, cursor, out, test::block1a_address0, 0, true), error::limited); + BOOST_REQUIRE(out.empty()); + + cursor = address_link::terminal; + BOOST_REQUIRE_EQUAL(query.get_history(cancel, cursor, out, test::block1a_address0, 1, true), error::limited); + BOOST_REQUIRE(out.empty()); + + cursor = address_link::terminal; + BOOST_REQUIRE_EQUAL(query.get_history(cancel, cursor, out, test::block1a_address0, 7, true), error::limited); + BOOST_REQUIRE(out.empty()); + + cursor = address_link::terminal; + BOOST_REQUIRE_EQUAL(query.get_history(cancel, cursor, out, test::block1a_address0, 8, true), error::limited); + BOOST_REQUIRE(out.empty()); + + cursor = address_link::terminal; + BOOST_REQUIRE_EQUAL(query.get_history(cancel, cursor, out, test::block1a_address0, 9, true), error::success); + BOOST_REQUIRE_EQUAL(out.size(), 8u); + + cursor = address_link::terminal; + BOOST_REQUIRE_EQUAL(query.get_history(cancel, cursor, out, test::block1a_address0, 100, true), error::success); + BOOST_REQUIRE_EQUAL(out.size(), 8u); +} + // get_tx_history1 // get_tx_history2 diff --git a/test/query/navigate/navigate_reverse.cpp b/test/query/navigate/navigate_reverse.cpp index 992783507..3910580b4 100644 --- a/test/query/navigate/navigate_reverse.cpp +++ b/test/query/navigate/navigate_reverse.cpp @@ -177,6 +177,31 @@ BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__terminal__not_reduced) BOOST_REQUIRE_EQUAL(out.at(5), 81u); } +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs3__limit__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{}; + address_link end{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE_EQUAL(query.to_address_outputs(cancel, end, out, test::block1a_address0, 4), error::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_not_found) { settings settings{};