diff --git a/include/bitcoin/database/error.hpp b/include/bitcoin/database/error.hpp index ce0392996..a8e915c0f 100644 --- a/include/bitcoin/database/error.hpp +++ b/include/bitcoin/database/error.hpp @@ -45,7 +45,6 @@ enum error_t : uint8_t integrity_get_prevouts, integrity_block_confirmable1, integrity_block_confirmable2, - ////integrity_spent_duplicates, /// memory map open_open, diff --git a/include/bitcoin/database/impl/primitives/keys.ipp b/include/bitcoin/database/impl/primitives/keys.ipp index 697d1f48e..d095aeafe 100644 --- a/include/bitcoin/database/impl/primitives/keys.ipp +++ b/include/bitcoin/database/impl/primitives/keys.ipp @@ -94,7 +94,7 @@ INLINE uint64_t hash(const Key& key) NOEXCEPT constexpr auto bytes = std::min(size(), sizeof(uint64_t)); uint64_t hash{}; - std::copy_n(key.begin(), bytes, byte_cast(hash).begin()); + std::copy_n(key.cbegin(), bytes, byte_cast(hash).begin()); return hash; } } @@ -123,7 +123,7 @@ INLINE uint64_t thumb(const Key& key) NOEXCEPT constexpr auto offset = std::min(size() - bytes, bytes); uint64_t hash{}; - const auto start = std::next(key.begin(), offset); + const auto start = std::next(key.cbegin(), offset); std::copy_n(start, bytes, byte_cast(hash).begin()); return hash; } @@ -160,7 +160,7 @@ INLINE bool compare(const Array& bytes, const Key& key) NOEXCEPT } else if constexpr (is_std_array) { - return std::equal(bytes.begin(), bytes.end(), key.begin()); + return std::equal(bytes.cbegin(), bytes.cend(), key.begin()); } } diff --git a/include/bitcoin/database/impl/query/address/address_balance.ipp b/include/bitcoin/database/impl/query/address/address_balance.ipp index c2ab63771..a594d8416 100644 --- a/include/bitcoin/database/impl/query/address/address_balance.ipp +++ b/include/bitcoin/database/impl/query/address/address_balance.ipp @@ -33,7 +33,7 @@ namespace database { // unused TEMPLATE -code CLASS::get_unconfirmed_balance(stopper& cancel, uint64_t& out, +code CLASS::get_unconfirmed_balance(const stopper& cancel, uint64_t& out, const hash_digest& key, bool turbo) const NOEXCEPT { // While duplicates are easily filtered out, conflict resolution is murky. @@ -46,7 +46,7 @@ code CLASS::get_unconfirmed_balance(stopper& cancel, uint64_t& out, // server/native TEMPLATE -code CLASS::get_confirmed_balance(stopper& cancel, uint64_t& out, +code CLASS::get_confirmed_balance(const stopper& cancel, uint64_t& out, const hash_digest& key, bool turbo) const NOEXCEPT { outpoints outs{}; @@ -57,7 +57,7 @@ code CLASS::get_confirmed_balance(stopper& cancel, uint64_t& out, } // Use of to_confirmed_unspent_outputs() provides necessary deduplication. - out = std::accumulate(outs.begin(), outs.end(), zero, + out = std::accumulate(outs.cbegin(), outs.cend(), zero, [](size_t total, const outpoint& out) NOEXCEPT { return system::ceilinged_add(total, out.value()); @@ -68,7 +68,7 @@ code CLASS::get_confirmed_balance(stopper& cancel, uint64_t& out, // server/electrum TEMPLATE -code CLASS::get_balance(stopper& cancel, uint64_t& confirmed, +code CLASS::get_balance(const stopper& cancel, uint64_t& confirmed, uint64_t& combined, const hash_digest& key, bool turbo) const NOEXCEPT { // See notes on get_unconfirmed_balance(). diff --git a/include/bitcoin/database/impl/query/address/address_history.ipp b/include/bitcoin/database/impl/query/address/address_history.ipp index 8c5b04b1d..8907d42f5 100644 --- a/include/bitcoin/database/impl/query/address/address_history.ipp +++ b/include/bitcoin/database/impl/query/address/address_history.ipp @@ -30,34 +30,169 @@ namespace database { // Address history // ---------------------------------------------------------------------------- // Canonically-sorted/deduped address history. - // root txs (height:zero) sorted before transitive (height:max) txs. // tied-height transactions sorted by base16 txid (not converted). +// All confirmed txs are root, unconfirmed may or may not be root. +// server/electrum TEMPLATE -code CLASS::get_unconfirmed_history(stopper& , histories& , - const hash_digest& , bool ) const NOEXCEPT +code CLASS::get_unconfirmed_history(const stopper& cancel, histories& out, + const hash_digest& key, bool turbo) const NOEXCEPT { - return {}; + output_links outs{}; + if (const auto ec = to_address_outputs(cancel, outs, key)) + return ec; + + tx_links txs{}; + if (const auto ec = to_touched_txs(cancel, txs, outs)) + return ec; + + out.clear(); + out.resize(txs.size()); + return parallel_history_transform(cancel, turbo, out, txs, + [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT -> history + { + if (cancel) + return {}; + + if (const auto block = find_strong(link); is_confirmed_block(block)) + { + constexpr auto excluded = history::excluded_position; + return { system::chain::checkpoint{}, {}, excluded }; + } + else + { + uint64_t fee{}; + auto hash = get_tx_key(link); + if (hash == system::null_hash || !get_tx_fee(fee, link)) + { + fail = true; + } + + const auto height = is_confirmed_all_prevouts(link) ? + history::rooted_height : history::unrooted_height; + + constexpr auto unconfirmed = history::unconfirmed_position; + return { { std::move(hash), height }, fee, unconfirmed }; + } + }); } +// ununsed TEMPLATE -code CLASS::get_confirmed_history(stopper& , histories& , - const hash_digest& , bool ) const NOEXCEPT +code CLASS::get_confirmed_history(const stopper& cancel, histories& out, + const hash_digest& key, bool turbo) const NOEXCEPT { - return {}; + output_links outs{}; + if (const auto ec = to_address_outputs(cancel, outs, key)) + return ec; + + tx_links txs{}; + if (const auto ec = to_touched_txs(cancel, txs, outs)) + return ec; + + out.clear(); + out.resize(txs.size()); + return parallel_history_transform(cancel, turbo, out, txs, + [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT -> history + { + if (cancel) + return {}; + + if (const auto block = find_strong(link); is_confirmed_block(block)) + { + uint64_t fee{}; + size_t height{}, position{}; + auto hash = get_tx_key(link); + if (hash == system::null_hash || !get_tx_fee(fee, link) || + !get_height(height, block) || + !get_tx_position(position, link, block)) + { + fail = true; + } + + return { { std::move(hash), height }, fee, position }; + } + else + { + constexpr auto exclude = history::excluded_position; + return { system::chain::checkpoint{}, {}, exclude }; + } + }); } +// server/electrum TEMPLATE -code CLASS::get_history(stopper& , histories& , - const hash_digest& , bool ) const NOEXCEPT +code CLASS::get_history(const stopper& cancel, histories& out, + const hash_digest& key, bool turbo) const NOEXCEPT { - return {}; + output_links outs{}; + if (const auto ec = to_address_outputs(cancel, outs, key)) + return ec; + + tx_links links{}; + if (const auto ec = to_touched_txs(cancel, links, outs)) + return ec; + + out.clear(); + out.resize(links.size()); + return parallel_history_transform(cancel, turbo, out, links, + [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT -> history + { + if (cancel) + return {}; + + uint64_t fee{}; + auto hash = get_tx_key(link); + if (hash == system::null_hash || !get_tx_fee(fee, link)) + fail = true; + + size_t height{}, position{}; + if (const auto block = find_strong(link); is_confirmed_block(block)) + { + if (!get_height(height, block) || + !get_tx_position(position, link, block)) + fail = true; + } + else + { + height = is_confirmed_all_prevouts(link) ? + history::rooted_height : history::unrooted_height; + } + + return { { std::move(hash), height }, fee, position }; + }); } -// turbos +// utilities // ---------------------------------------------------------------------------- -// protected +// private/static + +TEMPLATE +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); + stopper fail{}; + + out.clear(); + out.resize(txs.size()); + std::transform(policy, txs.cbegin(), txs.cend(), out.begin(), + [&functor, &cancel, &fail](const auto& tx) NOEXCEPT + { + return functor(tx, cancel, fail); + }); + + if (fail) + return error::integrity; + + if (cancel) + return error::canceled; + + history::sort_and_dedup(out); + return error::success; +} } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/query/address/address_outpoints.ipp b/include/bitcoin/database/impl/query/address/address_outpoints.ipp index bc6fcd4d6..9782231ec 100644 --- a/include/bitcoin/database/impl/query/address/address_outpoints.ipp +++ b/include/bitcoin/database/impl/query/address/address_outpoints.ipp @@ -33,7 +33,7 @@ namespace database { // server/native TEMPLATE -code CLASS::get_confirmed_unspent_outputs(stopper& cancel, +code CLASS::get_confirmed_unspent_outputs(const stopper& cancel, outpoints& out, const hash_digest& key, bool turbo) const NOEXCEPT { out.clear(); @@ -41,24 +41,24 @@ code CLASS::get_confirmed_unspent_outputs(stopper& cancel, if (const code ec = to_address_outputs(cancel, links, key)) return ec; - return parallel_address_transform(cancel, turbo, out, links, - [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT + return parallel_outpoint_transform(cancel, turbo, out, links, + [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT -> outpoint { // !is_confirmed_unspent must be filtered out. if (cancel || fail || !is_confirmed_unspent(link)) - return outpoint{}; + return {}; + + const auto outpoint = get_outpoint(link); + if (outpoint.point().is_null()) + fail = true; - auto outpoint = get_outpoint(link); - fail = outpoint.point().is_null(); return outpoint; }); - - return error::success; } // unused TEMPLATE -code CLASS::get_minimum_unspent_outputs(stopper& cancel, +code CLASS::get_minimum_unspent_outputs(const stopper& cancel, outpoints& out, const hash_digest& key, uint64_t minimum, bool turbo) const NOEXCEPT { @@ -67,35 +67,36 @@ code CLASS::get_minimum_unspent_outputs(stopper& cancel, if (const code ec = to_address_outputs(cancel, links, key)) return ec; - return parallel_address_transform(cancel, turbo, out, links, + return parallel_outpoint_transform(cancel, turbo, out, links, [this, minimum](const auto& link, auto& cancel, auto& fail) NOEXCEPT + -> outpoint { // !is_confirmed_unspent must be filtered out. if (cancel || fail || !is_confirmed_unspent(link)) - return outpoint{}; + return {}; uint64_t value{}; if (!get_value(value, link)) { fail = true; - return outpoint{}; + return {}; } // Must be filtered out. if (value < minimum) - return outpoint{}; + return {}; + + const auto outpoint = get_outpoint(link); + if (outpoint.point().is_null()) + fail = true; - auto outpoint = get_outpoint(link); - fail = outpoint.point().is_null(); return outpoint; }); - - return error::success; } // server/native TEMPLATE -code CLASS::get_address_outputs(stopper& cancel, outpoints& out, +code CLASS::get_address_outputs(const stopper& cancel, outpoints& out, const hash_digest& key, bool turbo) const NOEXCEPT { out.clear(); @@ -103,16 +104,18 @@ code CLASS::get_address_outputs(stopper& cancel, outpoints& out, if (const code ec = to_address_outputs(cancel, links, key)) return ec; - return parallel_address_transform(cancel, turbo, out, links, - [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT + return parallel_outpoint_transform(cancel, turbo, out, links, + [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT -> outpoint { - if (cancel || fail) return outpoint{}; - auto outpoint = get_outpoint(link); - fail = outpoint.point().is_null(); + if (cancel || fail) + return {}; + + const auto outpoint = get_outpoint(link); + if (outpoint.point().is_null()) + fail = true; + return outpoint; }); - - return error::success; } // utilities @@ -121,14 +124,16 @@ code CLASS::get_address_outputs(stopper& cancel, outpoints& out, TEMPLATE template -inline code CLASS::parallel_address_transform(stopper& cancel, bool turbo, +code CLASS::parallel_outpoint_transform(const stopper& cancel, bool turbo, outpoints& out, const output_links& links, Functor&& functor) NOEXCEPT { - out.clear(); + const auto policy = poolstl::execution::par_if(turbo); stopper fail{}; + + out.clear(); std::vector outpoints(links.size()); - const auto policy = poolstl::execution::par_if(turbo); - std::transform(policy, links.begin(), links.end(), outpoints.begin(), + + std::transform(policy, links.cbegin(), links.cend(), outpoints.begin(), [&functor, &cancel, &fail](const auto& link) NOEXCEPT { return functor(link, cancel, fail); @@ -140,6 +145,7 @@ inline code CLASS::parallel_address_transform(stopper& cancel, bool turbo, if (cancel) return error::canceled; + // TODO: change outpoints to vector and avoid copy. for (auto& outpoint: outpoints) { if (cancel) diff --git a/include/bitcoin/database/impl/query/address/address_unspent.ipp b/include/bitcoin/database/impl/query/address/address_unspent.ipp index 2d01cac64..2a29b54b0 100644 --- a/include/bitcoin/database/impl/query/address/address_unspent.ipp +++ b/include/bitcoin/database/impl/query/address/address_unspent.ipp @@ -32,22 +32,25 @@ namespace database { // A list of all unspent output transactions in canonical order. // Unconfirmed unspent are included at end of list in consistent order. +// ununsed TEMPLATE -code CLASS::get_unconfirmed_unspent(stopper& , unspents& , +code CLASS::get_unconfirmed_unspent(const stopper& , unspents& , const hash_digest& , bool ) const NOEXCEPT { return {}; } +// ununsed TEMPLATE -code CLASS::get_confirmed_unspent(stopper& , unspents& , +code CLASS::get_confirmed_unspent(const stopper& , unspents& , const hash_digest& , bool ) const NOEXCEPT { return {}; } +// server/electrum TEMPLATE -code CLASS::get_unspent(stopper& , unspents& , +code CLASS::get_unspent(const stopper& , unspents& , const hash_digest& , bool ) const NOEXCEPT { return {}; diff --git a/include/bitcoin/database/impl/query/amounts.ipp b/include/bitcoin/database/impl/query/amounts.ipp index d0d2b885e..be1e536ce 100644 --- a/include/bitcoin/database/impl/query/amounts.ipp +++ b/include/bitcoin/database/impl/query/amounts.ipp @@ -71,7 +71,7 @@ bool CLASS::get_tx_spend(uint64_t& out, const tx_link& link) const NOEXCEPT return !links.empty() && get_outputs_total_value(out, links); } -// unused (disabled in get_tx_fees()) +// server/electrum TEMPLATE bool CLASS::get_tx_fee(uint64_t& out, const tx_link& link) const NOEXCEPT { @@ -101,11 +101,11 @@ bool CLASS::get_block_value(uint64_t& out, return false; stopper fail{}; - const auto begin = std::next(txs.tx_fks.begin()); + const auto begin = std::next(txs.tx_fks.cbegin()); constexpr auto parallel = poolstl::execution::par; constexpr auto relaxed = std::memory_order_relaxed; - out = std::transform_reduce(parallel, begin, txs.tx_fks.end(), 0_u64, + out = std::transform_reduce(parallel, begin, txs.tx_fks.cend(), 0_u64, [](uint64_t left, uint64_t right) NOEXCEPT { return system::ceilinged_add(left, right); @@ -132,11 +132,11 @@ bool CLASS::get_block_spend(uint64_t& out, return false; stopper fail{}; - const auto begin = std::next(txs.tx_fks.begin()); + const auto begin = std::next(txs.tx_fks.cbegin()); constexpr auto parallel = poolstl::execution::par; constexpr auto relaxed = std::memory_order_relaxed; - out = std::transform_reduce(parallel, begin, txs.tx_fks.end(), 0_u64, + out = std::transform_reduce(parallel, begin, txs.tx_fks.cend(), 0_u64, [](uint64_t left, uint64_t right) NOEXCEPT { return system::ceilinged_add(left, right); diff --git a/include/bitcoin/database/impl/query/archive/chain_reader.ipp b/include/bitcoin/database/impl/query/archive/chain_reader.ipp index 6845d643c..2ed1a49fe 100644 --- a/include/bitcoin/database/impl/query/archive/chain_reader.ipp +++ b/include/bitcoin/database/impl/query/archive/chain_reader.ipp @@ -352,7 +352,7 @@ outpoint CLASS::get_outpoint(const output_link& link) const NOEXCEPT TEMPLATE inpoint CLASS::get_spender(const point_link& link) const NOEXCEPT { - const auto tx_fk = to_spending_tx(link); + const auto tx_fk = to_input_tx(link); if (tx_fk.is_terminal()) return {}; @@ -381,7 +381,7 @@ inpoints CLASS::get_spenders(const point& point) const NOEXCEPT TEMPLATE template -inline bool CLASS::push_bool(std_vector& stack, +bool CLASS::push_bool(std_vector& stack, const Bool& element) NOEXCEPT { if (!element) diff --git a/include/bitcoin/database/impl/query/confirmed.ipp b/include/bitcoin/database/impl/query/confirmed.ipp index 732c1e8ee..3cff6b0eb 100644 --- a/include/bitcoin/database/impl/query/confirmed.ipp +++ b/include/bitcoin/database/impl/query/confirmed.ipp @@ -29,6 +29,16 @@ namespace database { // ---------------------------------------------------------------------------- // These ensure both strong and candidate/confirmed indexation. +TEMPLATE +header_link CLASS::find_confirmed_block(const tx_link& link) const NOEXCEPT +{ + const auto block = find_strong(link); + if (is_confirmed_block(block)) + return block; + + return {}; +} + TEMPLATE header_link CLASS::find_confirmed_block( const hash_digest& tx_hash) const NOEXCEPT @@ -98,7 +108,7 @@ TEMPLATE bool CLASS::is_confirmed_input(const point_link& link) const NOEXCEPT { // The spend.tx is strong *and* its block is confirmed (by height). - const auto fk = to_spending_tx(link); + const auto fk = to_input_tx(link); return !fk.is_terminal() && is_confirmed_tx(fk); } @@ -115,12 +125,31 @@ bool CLASS::is_confirmed_spent_output(const output_link& link) const NOEXCEPT { // The spender is strong *and* its block is confirmed (by height). const auto ins = to_spenders(link); - return std::any_of(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT + return std::any_of(ins.cbegin(), ins.cend(), [&](const auto& in) NOEXCEPT { return is_confirmed_input(in); }); } +TEMPLATE +bool CLASS::is_confirmed_all_prevouts(const tx_link& link) const NOEXCEPT +{ + // If tx is confirmed then all prevouts must be confirmed. + if (is_confirmed_tx(link)) + return true; + + // A coinbase must itself be confirmed (one input and null). + if (is_coinbase(link)) + return false; + + // All prevouts of the tx's inputs must be confirmed. + const auto outs = to_prevouts(link); + return std::all_of(outs.cbegin(), outs.cend(), [&](const auto& out) NOEXCEPT + { + return is_confirmed_output(out); + }); +} + } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/query/consensus/consensus_block.ipp b/include/bitcoin/database/impl/query/consensus/consensus_block.ipp index 4f0d4bf48..a06c89d7c 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_block.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_block.ipp @@ -56,7 +56,7 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT stopper fault{}; // Get points for each tx and the total count. - std::transform(parallel, txs.begin(), txs.end(), sets.begin(), + std::transform(parallel, txs.cbegin(), txs.cend(), sets.begin(), [this, &count, &fault](const tx_link& tx) NOEXCEPT { point_set set{}; @@ -80,7 +80,7 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT std::atomic consensus{}; // Checks all spends for spendability (strong, unlocked and mature). - if (std::all_of(parallel, sets.begin(), sets.end(), + if (std::all_of(parallel, sets.cbegin(), sets.cend(), [this, &ctx, &consensus](const point_set& set) NOEXCEPT { for (const auto& point: set.points) diff --git a/include/bitcoin/database/impl/query/consensus/consensus_compact.ipp b/include/bitcoin/database/impl/query/consensus/consensus_compact.ipp index 63e5b4982..1b116ed4b 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_compact.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_compact.ipp @@ -65,7 +65,7 @@ bool CLASS::get_double_spenders(tx_links& out, if (txs.size() <= one) return true; - for (auto tx = std::next(txs.begin()); tx != txs.end(); ++tx) + for (auto tx = std::next(txs.cbegin()); tx != txs.cend(); ++tx) for (const auto& in: *(*tx)->inputs_ptr()) if (!get_double_spenders(out, in->point(), in->metadata.point_link)) return false; diff --git a/include/bitcoin/database/impl/query/consensus/consensus_prevouts.ipp b/include/bitcoin/database/impl/query/consensus/consensus_prevouts.ipp index b6a3356b1..2ca50d407 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_prevouts.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_prevouts.ipp @@ -134,7 +134,7 @@ bool CLASS::get_doubles(tx_links& out, const block& block) const NOEXCEPT if (txs.size() <= one) return true; - for (auto tx = std::next(txs.begin()); tx != txs.end(); ++tx) + for (auto tx = std::next(txs.cbegin()); tx != txs.cend(); ++tx) for (const auto& in: *(*tx)->inputs_ptr()) if (!get_doubles(out, in->point())) return false; diff --git a/include/bitcoin/database/impl/query/consensus/consensus_strong.ipp b/include/bitcoin/database/impl/query/consensus/consensus_strong.ipp index 4a1fa2457..1c484b5c5 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_strong.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_strong.ipp @@ -70,8 +70,8 @@ height_link CLASS::find_strong_spender_height( const point& point) const NOEXCEPT { size_t out{}; - for (const auto& sp: to_spenders(point)) - if (const auto tx = to_spending_tx(sp); get_tx_height(out, tx)) + for (const auto& in: to_spenders(point)) + if (const auto tx = to_input_tx(in); get_tx_height(out, tx)) break; return { system::possible_narrow_cast(out) }; diff --git a/include/bitcoin/database/impl/query/extent.ipp b/include/bitcoin/database/impl/query/extent.ipp index 6139481dc..b330dab21 100644 --- a/include/bitcoin/database/impl/query/extent.ipp +++ b/include/bitcoin/database/impl/query/extent.ipp @@ -226,7 +226,7 @@ size_t CLASS::input_count(const tx_links& txs) const NOEXCEPT { constexpr auto parallel = poolstl::execution::par; const auto fn = [this](auto tx) NOEXCEPT { return input_count(tx); }; - return std::reduce(parallel, txs.begin(), txs.end(), zero, fn); + return std::reduce(parallel, txs.cbegin(), txs.cend(), zero, fn); } TEMPLATE @@ -234,7 +234,7 @@ size_t CLASS::output_count(const tx_links& txs) const NOEXCEPT { constexpr auto parallel = poolstl::execution::par; const auto fn = [this](auto tx) NOEXCEPT { return output_count(tx); }; - return std::reduce(parallel, txs.begin(), txs.end(), zero, fn); + return std::reduce(parallel, txs.cbegin(), txs.cend(), zero, fn); } TEMPLATE diff --git a/include/bitcoin/database/impl/query/fee_rate.ipp b/include/bitcoin/database/impl/query/fee_rate.ipp index 6b6c46fe7..a638b83cd 100644 --- a/include/bitcoin/database/impl/query/fee_rate.ipp +++ b/include/bitcoin/database/impl/query/fee_rate.ipp @@ -56,11 +56,11 @@ bool CLASS::get_block_fees(fee_rates& out, return false; out.resize(sub1(txs.tx_fks.size())); - const auto end = txs.tx_fks.end(); + const auto end = txs.tx_fks.cend(); auto rate = out.begin(); // Skip coinbase. - for (auto tx = std::next(txs.tx_fks.begin()); tx != end; ++tx) + for (auto tx = std::next(txs.tx_fks.cbegin()); tx != end; ++tx) if (!get_tx_fees(*rate++, *tx)) return false; @@ -68,7 +68,7 @@ bool CLASS::get_block_fees(fee_rates& out, } TEMPLATE -bool CLASS::get_branch_fees(stopper& cancel, fee_rate_sets& out, +bool CLASS::get_branch_fees(const stopper& cancel, fee_rate_sets& out, size_t start, size_t count) const NOEXCEPT { out.clear(); @@ -87,7 +87,7 @@ bool CLASS::get_branch_fees(stopper& cancel, fee_rate_sets& out, constexpr auto relaxed = std::memory_order_relaxed; // Parallel execution saves ~50%. - std::for_each(parallel, offsets.begin(), offsets.end(), + std::for_each(parallel, offsets.cbegin(), offsets.cend(), [&](const size_t& offset) NOEXCEPT { if (fail.load(relaxed)) diff --git a/include/bitcoin/database/impl/query/navigate/navigate_forward.ipp b/include/bitcoin/database/impl/query/navigate/navigate_forward.ipp index 4afe36a80..d931208e1 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_forward.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_forward.ipp @@ -146,7 +146,7 @@ output_links CLASS::to_prevouts(const tx_links& txs) const NOEXCEPT output_links outs(ins.size()); constexpr auto parallel = poolstl::execution::par; - std::transform(parallel, ins.begin(), ins.end(), outs.begin(), + std::transform(parallel, ins.cbegin(), ins.cend(), outs.begin(), [&](const auto& spend) NOEXCEPT { return to_previous_output(spend); diff --git a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp index 36f7038ad..3bfaf0c72 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -44,6 +45,63 @@ header_link CLASS::to_parent(const header_link& link) const NOEXCEPT // address->outputs[receivers] // ---------------------------------------------------------------------------- +// There can be multiple spenders of the same output (due to conflicts) and +// multiple instances of an output under distinct links (due to tx dups). + +TEMPLATE +code CLASS::to_touched_txs(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)) + { + out.push_back(to_output_tx(output)); + if (out.back() == tx_link::terminal) + return error::integrity; + + for (const auto& input: std::views::reverse(to_spenders(output))) + { + out.push_back(to_input_tx(input)); + if (out.back() == tx_link::terminal) + return error::integrity; + } + } + + return {}; +} + +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)) + { + if (cancel) + return error::canceled; + + out.push_back(to_output_tx(output)); + if (out.back() == tx_link::terminal) + return error::integrity; + + for (const auto& input: std::views::reverse(to_spenders(output))) + { + out.push_back(to_input_tx(input)); + if (out.back() == tx_link::terminal) + return error::integrity; + } + } + + return {}; +} TEMPLATE code CLASS::to_address_outputs(output_links& out, @@ -64,7 +122,7 @@ code CLASS::to_address_outputs(output_links& out, } TEMPLATE -code CLASS::to_address_outputs(stopper& cancel, output_links& out, +code CLASS::to_address_outputs(const stopper& cancel, output_links& out, const hash_digest& key) const NOEXCEPT { // Pushing into the vector is more efficient than precomputation of size. @@ -88,7 +146,7 @@ code CLASS::to_address_outputs(stopper& cancel, output_links& out, // ---------------------------------------------------------------------------- TEMPLATE -tx_link CLASS::to_spending_tx(const point_link& link) const NOEXCEPT +tx_link CLASS::to_input_tx(const point_link& link) const NOEXCEPT { table::ins::get_parent ins{}; if (!store_.ins.get(link, ins)) @@ -148,7 +206,7 @@ TEMPLATE point_links CLASS::to_spenders(const point& point) const NOEXCEPT { // Avoid returning spend links for coinbase inputs (not spenders). - if (point.index() == point::null_index) + if (point.is_null()) return {}; point_links points{}; diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 1688fa4a0..83b39e9a4 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -246,7 +246,7 @@ class query /// put to tx (reverse navigation) tx_link to_output_tx(const output_link& link) const NOEXCEPT; tx_link to_prevout_tx(const point_link& link) const NOEXCEPT; - tx_link to_spending_tx(const point_link& link) const NOEXCEPT; + tx_link to_input_tx(const point_link& link) const NOEXCEPT; /// point to put (forward navigation) point_link to_point(const tx_link& link, @@ -267,6 +267,7 @@ class query /// find confirmed objects (reverse navigation) header_link find_confirmed_block(const hash_digest& tx_hash) const NOEXCEPT; + header_link find_confirmed_block(const tx_link& link) const NOEXCEPT; point_link find_confirmed_spender(const point& prevout) const NOEXCEPT; /// output to spenders (reverse navigation) @@ -311,10 +312,14 @@ class query point_link top_point(size_t bucket) const NOEXCEPT; tx_link top_tx(size_t bucket) const NOEXCEPT; - /// outputs enumeration + /// address outputs enumeration (reverse navigation) + code to_touched_txs(tx_links& out, + const output_links& outputs) const NOEXCEPT; + code to_touched_txs(const stopper& cancel, tx_links& out, + const output_links& outputs) const NOEXCEPT; code to_address_outputs(output_links& out, const hash_digest& key) const NOEXCEPT; - code to_address_outputs(stopper& cancel, output_links& out, + code to_address_outputs(const stopper& cancel, output_links& out, const hash_digest& key) const NOEXCEPT; /// Archive reads. @@ -438,7 +443,7 @@ class query /// Fee rate tuples by tx, block or branch. bool get_tx_fees(fee_rate& out, const tx_link& link) const NOEXCEPT; bool get_block_fees(fee_rates& out, const header_link& link) const NOEXCEPT; - bool get_branch_fees(stopper& cancel, fee_rate_sets& out, size_t start, + bool get_branch_fees(const stopper& cancel, fee_rate_sets& out, size_t start, size_t count) const NOEXCEPT; /// Merkle. @@ -551,6 +556,7 @@ class query bool is_confirmed_input(const point_link& link) const NOEXCEPT; bool is_confirmed_output(const output_link& link) const NOEXCEPT; bool is_confirmed_spent_output(const output_link& link) const NOEXCEPT; + bool is_confirmed_all_prevouts(const tx_link& link) const NOEXCEPT; /// Height index not used by these. bool is_strong_tx(const tx_link& link) const NOEXCEPT; @@ -612,37 +618,37 @@ class query /// ----------------------------------------------------------------------- /// Native queries (outpoints, deduped, arbitrary sort). - ////code get_unconfirmed_unspent_outputs(stopper& cancel, outpoints& out, + ////code get_unconfirmed_unspent_outputs(const stopper& cancel, outpoints& out, //// const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_confirmed_unspent_outputs(stopper& cancel, outpoints& out, + code get_confirmed_unspent_outputs(const stopper& cancel, outpoints& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_minimum_unspent_outputs(stopper& cancel, outpoints& out, + code get_minimum_unspent_outputs(const stopper& cancel, outpoints& out, const hash_digest& key, uint64_t value, bool turbo=false) const NOEXCEPT; - code get_address_outputs(stopper& cancel, outpoints& out, + code get_address_outputs(const stopper& cancel, outpoints& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; /// Electrum queries (histories, deduped, electrum sort). - code get_unconfirmed_history(stopper& cancel, histories& out, + code get_unconfirmed_history(const stopper& cancel, histories& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_confirmed_history(stopper& cancel, histories& out, + code get_confirmed_history(const stopper& cancel, histories& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_history(stopper& cancel, histories& out, + code get_history(const stopper& cancel, histories& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; /// Electrum queries (unspents, deduped, electrum sort). - code get_unconfirmed_unspent(stopper& cancel, unspents& out, + code get_unconfirmed_unspent(const stopper& cancel, unspents& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_confirmed_unspent(stopper& cancel, unspents& out, + code get_confirmed_unspent(const stopper& cancel, unspents& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_unspent(stopper& cancel, unspents& out, + code get_unspent(const stopper& cancel, unspents& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; /// Balance queries (universal, unconfirmed conflict resolution arbitrary). - code get_unconfirmed_balance(stopper& cancel, uint64_t& out, + code get_unconfirmed_balance(const stopper& cancel, uint64_t& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_confirmed_balance(stopper& cancel, uint64_t& out, + code get_confirmed_balance(const stopper& cancel, uint64_t& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; - code get_balance(stopper& cancel, uint64_t& confirmed, uint64_t& combined, + code get_balance(const stopper& cancel, uint64_t& confirmed, uint64_t& combined, const hash_digest& key, bool turbo=false) const NOEXCEPT; /// Filters. @@ -720,11 +726,6 @@ class query /// Consensus. /// ----------------------------------------------------------------------- - /// Called by block_confirmable (check bip30) - bool is_spent_coinbase(const tx_link& link, size_t count) const NOEXCEPT; - code spent_duplicates(const header_link& coinbase, - const context& ctx) const NOEXCEPT; - /// Called by block_confirmable (populate and check double spends). system::error::transaction_error_t spendable(const point_set::point& point, uint32_t version, const context& ctx) const NOEXCEPT; @@ -816,12 +817,16 @@ class query // Chain objects. template - static inline bool push_bool(std_vector& stack, + static bool push_bool(std_vector& stack, const Bool& element) NOEXCEPT; template - static inline code parallel_address_transform(stopper& cancel, bool turbo, + static code parallel_outpoint_transform(const stopper& cancel, bool turbo, outpoints& out, const output_links& links, Functor&& functor) NOEXCEPT; - static inline point::cptr make_point(hash_digest&& hash, + template + static code parallel_history_transform(const stopper& cancel, bool turbo, + histories& out, const tx_links& links, Functor&& functor) NOEXCEPT; + + static point::cptr make_point(hash_digest&& hash, uint32_t index) NOEXCEPT; // Not thread safe. diff --git a/include/bitcoin/database/tables/archives/input.hpp b/include/bitcoin/database/tables/archives/input.hpp index 3a4fb6e10..2c0b50391 100644 --- a/include/bitcoin/database/tables/archives/input.hpp +++ b/include/bitcoin/database/tables/archives/input.hpp @@ -149,7 +149,7 @@ struct input const auto& ins = *tx_.inputs_ptr(); const auto other = ins.size() * sequence_point_size; - const auto inputs = std::accumulate(ins.begin(), ins.end(), zero, + const auto inputs = std::accumulate(ins.cbegin(), ins.cend(), zero, [](size_t total, const auto& in) NOEXCEPT { // sizes cached, so this is free. @@ -163,7 +163,7 @@ struct input inline bool to_data(flipper& sink) const NOEXCEPT { const auto& ins = *tx_.inputs_ptr(); - std::for_each(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT + std::for_each(ins.cbegin(), ins.cend(), [&](const auto& in) NOEXCEPT { in->script().to_data(sink, true); in->witness().to_data(sink, true); diff --git a/include/bitcoin/database/tables/archives/ins.hpp b/include/bitcoin/database/tables/archives/ins.hpp index 834b482dd..8d00da0f0 100644 --- a/include/bitcoin/database/tables/archives/ins.hpp +++ b/include/bitcoin/database/tables/archives/ins.hpp @@ -113,7 +113,7 @@ struct ins auto in_fk = input_fk; const auto& ins = *tx_.inputs_ptr(); - std::for_each(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT + std::for_each(ins.cbegin(), ins.cend(), [&](const auto& in) NOEXCEPT { sink.write_little_endian(in->sequence()); sink.write_little_endian(in_fk); diff --git a/include/bitcoin/database/tables/archives/output.hpp b/include/bitcoin/database/tables/archives/output.hpp index db6a21d4e..5b1a64fa8 100644 --- a/include/bitcoin/database/tables/archives/output.hpp +++ b/include/bitcoin/database/tables/archives/output.hpp @@ -200,8 +200,8 @@ struct output const auto& outs = *tx_.outputs_ptr(); const auto other = outs.size() * value_parent_difference; - const auto outputs = std::accumulate(outs.begin(), outs.end(), zero, - [](size_t total, const auto& out) NOEXCEPT + const auto outputs = std::accumulate(outs.cbegin(), outs.cend(), + zero, [](size_t total, const auto& out) NOEXCEPT { // size cached, so this is free, includes sizeof(value). return total + variable_size(out->value()) + @@ -216,12 +216,13 @@ struct output inline bool to_data(flipper& sink) const NOEXCEPT { const auto& outs = *tx_.outputs_ptr(); - std::for_each(outs.begin(), outs.end(), [&](const auto& out) NOEXCEPT - { - sink.write_little_endian(parent_fk); - sink.write_variable(out->value()); - out->script().to_data(sink, true); - }); + std::for_each(outs.cbegin(), outs.cend(), + [&](const auto& out) NOEXCEPT + { + sink.write_little_endian(parent_fk); + sink.write_variable(out->value()); + out->script().to_data(sink, true); + }); BC_ASSERT(!sink || sink.get_write_position() == count()); return sink; diff --git a/include/bitcoin/database/tables/archives/outs.hpp b/include/bitcoin/database/tables/archives/outs.hpp index c2886fb75..2159f3983 100644 --- a/include/bitcoin/database/tables/archives/outs.hpp +++ b/include/bitcoin/database/tables/archives/outs.hpp @@ -48,10 +48,11 @@ struct outs inline bool from_data(reader& source) NOEXCEPT { - std::for_each(out_fks.begin(), out_fks.end(), [&](auto& fk) NOEXCEPT - { - fk = source.read_little_endian(); - }); + std::for_each(out_fks.begin(), out_fks.end(), + [&](auto& fk) NOEXCEPT + { + fk = source.read_little_endian(); + }); BC_ASSERT(!source || source.get_read_position() == count() * out::size); return source; @@ -59,10 +60,11 @@ struct outs inline bool to_data(flipper& sink) const NOEXCEPT { - std::for_each(out_fks.begin(), out_fks.end(), [&](const auto& fk) NOEXCEPT - { - sink.write_little_endian(fk); - }); + std::for_each(out_fks.cbegin(), out_fks.cend(), + [&](const auto& fk) NOEXCEPT + { + sink.write_little_endian(fk); + }); BC_ASSERT(!sink || sink.get_write_position() == count() * minrow); return sink; @@ -109,15 +111,16 @@ struct outs auto out_fk = output_fk; const auto& outs = *tx_.outputs_ptr(); - std::for_each(outs.begin(), outs.end(), [&](const auto& out) NOEXCEPT - { - sink.write_little_endian(out_fk); - - // Calculate next corresponding output fk from serialized size. - // (variable_size(value) + (value + script)) - (value - parent) - out_fk += (variable_size(out->value()) + out->serialized_size() - - value_parent_diff); - }); + std::for_each(outs.cbegin(), outs.cend(), + [&](const auto& out) NOEXCEPT + { + sink.write_little_endian(out_fk); + + // Calculate next corresponding output fk from serialized size. + // (variable_size(value) + (value + script)) - (value - parent) + out_fk += (variable_size(out->value()) + + out->serialized_size() - value_parent_diff); + }); BC_ASSERT(!sink || sink.get_write_position() == count() * minrow); return sink; diff --git a/include/bitcoin/database/tables/archives/txs.hpp b/include/bitcoin/database/tables/archives/txs.hpp index f90ab83d7..b23c08227 100644 --- a/include/bitcoin/database/tables/archives/txs.hpp +++ b/include/bitcoin/database/tables/archives/txs.hpp @@ -123,7 +123,7 @@ struct txs // tx fks const auto number = possible_narrow_cast(tx_fks.size()); sink.write_little_endian(number); - std::for_each(tx_fks.begin(), tx_fks.end(), + std::for_each(tx_fks.cbegin(), tx_fks.cend(), [&](const auto& fk) NOEXCEPT { sink.write_little_endian(fk); diff --git a/include/bitcoin/database/tables/caches/prevout.hpp b/include/bitcoin/database/tables/caches/prevout.hpp index bc90fd7ff..4afef5a48 100644 --- a/include/bitcoin/database/tables/caches/prevout.hpp +++ b/include/bitcoin/database/tables/caches/prevout.hpp @@ -100,8 +100,8 @@ struct prevout // Count is written as a tx link so the table can remain an array. sink.write_variable(number); - std::for_each(cons.begin(), cons.end(), write_conflict); - std::for_each(std::next(txs.begin()), txs.end(), write_tx); + std::for_each(cons.cbegin(), cons.cend(), write_conflict); + std::for_each(std::next(txs.cbegin()), txs.cend(), write_tx); BC_ASSERT(!sink || (sink.get_write_position() == count())); return sink; diff --git a/include/bitcoin/database/types.hpp b/include/bitcoin/database/types.hpp index d97762e09..7e571bc44 100644 --- a/include/bitcoin/database/types.hpp +++ b/include/bitcoin/database/types.hpp @@ -70,9 +70,13 @@ using data_chunk = system::data_chunk; /// Common system::chain aliases. /// --------------------------------------------------------------------------- -using inpoint = system::chain::point; -using outpoint = system::chain::outpoint; using checkpoint = system::chain::checkpoint; +using outpoint = system::chain::outpoint; +using inpoint = system::chain::point; + +/// Sorted and deduped. +using outpoints = std::set; +using inpoints = std::set; /// Common carriers. /// --------------------------------------------------------------------------- @@ -104,33 +108,60 @@ struct span struct BCD_API unspent { + static constexpr size_t excluded_position = max_size_t; + struct less_than { bool operator()(const unspent& a, const unspent& b) const NOEXCEPT; }; + struct equal_to + { + bool operator()(const unspent& a, const unspent& b) const NOEXCEPT; + }; + + struct exclude + { + bool operator()(const unspent& element) const NOEXCEPT; + }; + + static void sort_and_dedup(std::vector& unspent) NOEXCEPT; + outpoint tx{}; size_t height{}; size_t position{}; }; +using unspents = std::vector; struct BCD_API history { + static constexpr size_t rooted_height = zero; + static constexpr size_t unrooted_height = max_size_t; + static constexpr size_t excluded_position = max_size_t; + static constexpr size_t unconfirmed_position = zero; + struct less_than { bool operator()(const history& a, const history& b) const NOEXCEPT; }; + struct equal_to + { + bool operator()(const history& a, const history& b) const NOEXCEPT; + }; + + struct exclude + { + bool operator()(const history& element) const NOEXCEPT; + }; + + static void sort_and_dedup(std::vector& history) NOEXCEPT; + checkpoint tx{}; uint64_t fee{}; size_t position{}; }; - -/// Sorted and deduped. -using inpoints = std::set; -using outpoints = std::set; -using unspents = std::set; -using histories = std::set; +using histories = std::vector; } // namespace database } // namespace libbitcoin diff --git a/src/error.cpp b/src/error.cpp index 1e2d82a15..c695a7898 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -37,7 +37,6 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { integrity_get_prevouts, "store corrupted, get_prevouts" }, { integrity_block_confirmable1, "store corrupted, block_confirmable1" }, { integrity_block_confirmable2, "store corrupted, block_confirmable2" }, - ////{ integrity_spent_duplicates, "store corrupted, spent_duplicates" }, // memory map { open_open, "opening open file" }, diff --git a/src/types.cpp b/src/types.cpp index 101a8e63b..785079175 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -18,11 +18,15 @@ */ #include +#include #include namespace libbitcoin { namespace database { +// history +// ---------------------------------------------------------------------------- + // local inline bool hash_less_than(const hash_digest& a, const hash_digest& b) NOEXCEPT { @@ -74,7 +78,31 @@ bool history::less_than::operator()(const history& a, const history& b) const NO return hash_less_than(a.tx.hash(), b.tx.hash()); } -bool unspent::less_than::operator()(const unspent& a, const unspent& b) const NOEXCEPT +bool history::equal_to::operator()(const history& a, const history& b) const NOEXCEPT +{ + history::less_than lesser; + return !lesser(a, b) && !lesser(b, a); +} + +bool history::exclude::operator()(const history& element) const NOEXCEPT +{ + return element.position == max_size_t; +} + +void history::sort_and_dedup(std::vector& out) NOEXCEPT +{ + auto excluded = std::remove_if(out.begin(), out.end(), history::exclude{}); + out.erase(excluded, out.end()); + std::sort(out.begin(), out.end(), history::less_than{}); + auto duplicates = std::unique(out.begin(), out.end(), history::equal_to{}); + out.erase(duplicates, out.end()); +} + +// unspent +// ---------------------------------------------------------------------------- + +bool unspent::less_than::operator()(const unspent& a, + const unspent& b) const NOEXCEPT { const auto a_point = a.tx.point(); const auto b_point = b.tx.point(); @@ -103,5 +131,26 @@ bool unspent::less_than::operator()(const unspent& a, const unspent& b) const NO return a_point < b_point; } +bool unspent::equal_to::operator()(const unspent& a, + const unspent& b) const NOEXCEPT +{ + unspent::less_than lesser; + return !lesser(a, b) && !lesser(b, a); +} + +bool unspent::exclude::operator()(const unspent& element) const NOEXCEPT +{ + return element.position == max_size_t; +} + +void unspent::sort_and_dedup(std::vector& out) NOEXCEPT +{ + auto excluded = std::remove_if(out.begin(), out.end(), unspent::exclude{}); + out.erase(excluded, out.end()); + std::sort(out.begin(), out.end(), unspent::less_than{}); + auto duplicates = std::unique(out.begin(), out.end(), unspent::equal_to{}); + out.erase(duplicates, out.end()); +} + } // namespace database } // namespace libbitcoin diff --git a/test/error.cpp b/test/error.cpp index af549976f..3bc633f51 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -97,15 +97,6 @@ BOOST_AUTO_TEST_CASE(error_t__code__integrity_block_confirmable2__true_expected_ BOOST_REQUIRE_EQUAL(ec.message(), "store corrupted, block_confirmable2"); } -////BOOST_AUTO_TEST_CASE(error_t__code__integrity_spent_duplicates__true_expected_message) -////{ -//// constexpr auto value = error::integrity_spent_duplicates; -//// const auto ec = code(value); -//// BOOST_REQUIRE(ec); -//// BOOST_REQUIRE(ec == value); -//// BOOST_REQUIRE_EQUAL(ec.message(), "store corrupted, spent_duplicates"); -////} - // memory map BOOST_AUTO_TEST_CASE(error_t__code__open_open__true_expected_message) diff --git a/test/mocks/blocks.cpp b/test/mocks/blocks.cpp index d2ca7eaaa..119843b04 100644 --- a/test/mocks/blocks.cpp +++ b/test/mocks/blocks.cpp @@ -25,7 +25,9 @@ using namespace system; constexpr hash_digest two_hash = from_uintx(uint256_t(two)); constexpr database::context context{ 0x01020304, 0x11121314, 0x21222324 }; -constexpr hash_digest genesis_address = base16_hash("740485f380ff6379d11ef6fe7d7cdd68aea7f8bd0d953d9fdf3531fb7d531833"); +constexpr hash_digest genesis_address = base16_hash("740485f380ff6379d11ef6fe7d7cdd68aea7f8bd0d953d9fdf3531fb7d531833"); +constexpr hash_digest block1a_address0 = base16_hash("fab04811b1d0379bf78c2a41902368c200d675788e4bff8c88ff543836e4fca1"); +constexpr hash_digest block1a_address1 = base16_hash("067bd624c5840ed0d5b65597bafcde68f07fa9d87d3b43292b3199e49a514e59"); constexpr hash_digest block0_hash = base16_hash("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); constexpr hash_digest block1_hash = base16_hash("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"); @@ -245,7 +247,7 @@ const block block1a }, input { - point{ two_hash, 0x2b }, // missing prevout + point{ two_hash, 0x2b }, // missing prevout script{ { { opcode::op_return }, { opcode::roll } } }, witness{ "[424242]" }, 0x19 // sequence diff --git a/test/mocks/blocks.hpp b/test/mocks/blocks.hpp index 219c3066b..e54f1cecc 100644 --- a/test/mocks/blocks.hpp +++ b/test/mocks/blocks.hpp @@ -31,6 +31,8 @@ const auto events_handler = [](auto, auto) {}; extern const database::context context; extern const system::hash_digest genesis_address; +extern const system::hash_digest block1a_address0; +extern const system::hash_digest block1a_address1; extern const system::hash_digest block0_hash; extern const system::hash_digest block1_hash; diff --git a/test/query/address/address_balance.cpp b/test/query/address/address_balance.cpp index dd8a2fa68..6c91812b7 100644 --- a/test/query/address/address_balance.cpp +++ b/test/query/address/address_balance.cpp @@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_balance__turbo_genesis__expected) BOOST_REQUIRE(query.initialize(test::genesis)); uint64_t combined{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_unconfirmed_balance(cancel, combined, test::genesis_address, true)); BOOST_REQUIRE_EQUAL(combined, 5000000000u); @@ -60,7 +60,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_balance__genesis__expected) BOOST_REQUIRE(query.initialize(test::genesis)); uint64_t combined{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_unconfirmed_balance(cancel, combined, test::genesis_address)); BOOST_REQUIRE_EQUAL(combined, 5000000000u); diff --git a/test/query/address/address_history.cpp b/test/query/address/address_history.cpp index ecd5c228c..a72b164f4 100644 --- a/test/query/address/address_history.cpp +++ b/test/query/address/address_history.cpp @@ -36,17 +36,17 @@ BOOST_AUTO_TEST_CASE(query_address__get_history__turbo_genesis__expected) BOOST_REQUIRE(query.initialize(test::genesis)); histories out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::genesis_address, true)); BOOST_REQUIRE(out.empty()); out.clear(); BOOST_REQUIRE(!query.get_confirmed_history(cancel, out, test::genesis_address, true)); - BOOST_REQUIRE(out.empty()); + BOOST_CHECK_EQUAL(out.size(), 1u); out.clear(); BOOST_REQUIRE(!query.get_history(cancel, out, test::genesis_address, true)); - BOOST_REQUIRE(out.empty()); + BOOST_CHECK_EQUAL(out.size(), 1u); } BOOST_AUTO_TEST_CASE(query_address__get_history__genesis__expected) @@ -59,17 +59,17 @@ BOOST_AUTO_TEST_CASE(query_address__get_history__genesis__expected) BOOST_REQUIRE(query.initialize(test::genesis)); histories out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::genesis_address)); - BOOST_REQUIRE(out.empty()); + BOOST_CHECK_EQUAL(out.size(), 0u); out.clear(); - BOOST_REQUIRE(!query.get_confirmed_history(cancel, out, test::genesis_address)); - BOOST_REQUIRE(out.empty()); + BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::genesis_address)); + BOOST_CHECK_EQUAL(out.size(), 0u); out.clear(); BOOST_REQUIRE(!query.get_history(cancel, out, test::genesis_address)); - BOOST_REQUIRE(out.empty()); + BOOST_CHECK_EQUAL(out.size(), 1u); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/address/address_outpoints.cpp b/test/query/address/address_outpoints.cpp index 517b8fa7d..499d50edd 100644 --- a/test/query/address/address_outpoints.cpp +++ b/test/query/address/address_outpoints.cpp @@ -34,7 +34,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_confirmed_unspent_outputs__turbo_genesis BOOST_REQUIRE(query.initialize(test::genesis)); outpoints out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_confirmed_unspent_outputs(cancel, out, test::genesis_address, true)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); @@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_confirmed_unspent_outputs__genesis__expe BOOST_REQUIRE(query.initialize(test::genesis)); outpoints out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_confirmed_unspent_outputs(cancel, out, test::genesis_address)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outputs__turbo_above__ex BOOST_REQUIRE(query.initialize(test::genesis)); outpoints out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 5000000001, true)); BOOST_REQUIRE(out.empty()); } @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outputs__above__excluded BOOST_REQUIRE(query.initialize(test::genesis)); outpoints out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 5000000001)); BOOST_REQUIRE(out.empty()); } @@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outputs__at__included) BOOST_REQUIRE(query.initialize(test::genesis)); outpoints out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 5000000000)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); @@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outputs__below__included BOOST_REQUIRE(query.initialize(test::genesis)); outpoints out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 0)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); @@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_address_outputs__turbo_genesis__expected BOOST_REQUIRE(query.initialize(test::genesis)); outpoints out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_address_outputs(cancel, out, test::genesis_address, true)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); @@ -152,7 +152,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_address_outputs__genesis__expected) BOOST_REQUIRE(query.initialize(test::genesis)); outpoints out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_address_outputs(cancel, out, test::genesis_address)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); diff --git a/test/query/address/address_unspent.cpp b/test/query/address/address_unspent.cpp index 069527bfd..84dead70a 100644 --- a/test/query/address/address_unspent.cpp +++ b/test/query/address/address_unspent.cpp @@ -38,7 +38,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_unspent__turbo_genesis__expected) BOOST_REQUIRE(query.initialize(test::genesis)); unspents out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_unconfirmed_unspent(cancel, out, genesis_address, true)); BOOST_REQUIRE(out.empty()); @@ -61,7 +61,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_unspent__genesis__expected) BOOST_REQUIRE(query.initialize(test::genesis)); unspents out{}; - std::atomic_bool cancel{}; + const std::atomic_bool cancel{}; BOOST_REQUIRE(!query.get_unconfirmed_unspent(cancel, out, genesis_address)); BOOST_REQUIRE(out.empty()); diff --git a/test/query/confirmed.cpp b/test/query/confirmed.cpp index 5832163e6..27367710c 100644 --- a/test/query/confirmed.cpp +++ b/test/query/confirmed.cpp @@ -692,4 +692,83 @@ BOOST_AUTO_TEST_CASE(query_confirmed__block_confirmable__unconfirmed_double_spen BOOST_REQUIRE_EQUAL(query.block_confirmable(2), error::success); } +// is_confirmed_all_prevouts + +BOOST_AUTO_TEST_CASE(query_confirmed__is_confirmed_all_prevouts__genesis__true) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(test::events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.is_confirmed_all_prevouts(0)); +} + +BOOST_AUTO_TEST_CASE(query_confirmed__is_confirmed_all_prevouts__unconfirmed_coinbase__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(test::events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + // Block 1b has single null input so archived as (strong) coinbase. + BOOST_REQUIRE(query.set(test::block1b, context{ 0, 1, 0 }, false, true)); + BOOST_REQUIRE(!query.is_confirmed_all_prevouts(1)); +} + +BOOST_AUTO_TEST_CASE(query_confirmed__is_confirmed_all_prevouts__confirmed_coinbase__true) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(test::events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + // Block 1b has single null input so archived as (strong) coinbase. + BOOST_REQUIRE(query.set(test::block1b, context{ 0, 1, 0 }, false, true)); + BOOST_REQUIRE(query.push_confirmed(1, false)); + BOOST_REQUIRE(query.is_confirmed_all_prevouts(1)); +} + +BOOST_AUTO_TEST_CASE(query_confirmed__is_confirmed_all_prevouts__missing_prevouts__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(test::events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + // Tx1 is of block1a and consists of three inputs that do not exist. + BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, false, true)); + BOOST_REQUIRE(!query.is_confirmed_all_prevouts(1)); +} + +BOOST_AUTO_TEST_CASE(query_confirmed__is_confirmed_all_prevouts__prevouts_confirmed__true) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(test::events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + // Block 1a has 1 tx(1) with 2 outputs. + BOOST_REQUIRE(query.set(test::block1a, context{ 0, 1, 0 }, false, true)); + + // Block 2a first tx(2) spends both block 1a outputs. + BOOST_REQUIRE(query.set(test::block2a, context{ 0, 2, 0 }, false, true)); + + // Block 1a tx(1) is strong and not confirmed. + BOOST_REQUIRE(!query.is_confirmed_all_prevouts(2)); + + // Block 1a tx(1) is strong and confirmed. + BOOST_REQUIRE(query.push_confirmed(1, false)); + BOOST_REQUIRE(query.is_confirmed_all_prevouts(2)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/navigate/navigate_reverse.cpp b/test/query/navigate/navigate_reverse.cpp index d5fcb4ae6..b32170706 100644 --- a/test/query/navigate/navigate_reverse.cpp +++ b/test/query/navigate/navigate_reverse.cpp @@ -44,10 +44,82 @@ BOOST_AUTO_TEST_CASE(query_navigate__to_parent__always__expected) BOOST_REQUIRE_EQUAL(query.to_parent(5), header_link::terminal); } +// to_touched_txs1 + +BOOST_AUTO_TEST_CASE(query_navigate__to_touched_txs1__galways__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(test::events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1a, test::context, false, false)); + BOOST_REQUIRE(query.set(test::block2a, test::context, false, false)); + BOOST_REQUIRE(query.set(test::tx4)); + BOOST_REQUIRE(query.set(test::tx5)); + BOOST_REQUIRE(query.set(test::block3a, test::context, false, false)); + + output_links links{}; + BOOST_REQUIRE(!query.to_address_outputs(links, test::block1a_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_EQUAL(store.create(test::events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1a, test::context, false, false)); + BOOST_REQUIRE(query.set(test::block2a, test::context, false, false)); + BOOST_REQUIRE(query.set(test::tx4)); + BOOST_REQUIRE(query.set(test::tx5)); + BOOST_REQUIRE(query.set(test::block3a, test::context, false, false)); + + output_links links{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.to_address_outputs(cancel, links, test::block1a_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 -// to_address_outputs2 -BOOST_AUTO_TEST_CASE(query_address__to_address_outputs__genesis__expected) +BOOST_AUTO_TEST_CASE(query_navigate__to_address_outputs1__always__expected) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -55,15 +127,51 @@ BOOST_AUTO_TEST_CASE(query_address__to_address_outputs__genesis__expected) test::query_accessor query{ store }; BOOST_REQUIRE_EQUAL(store.create(test::events_handler), error::success); BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1a, test::context, false, false)); + BOOST_REQUIRE(query.set(test::block2a, test::context, false, false)); + BOOST_REQUIRE(query.set(test::tx4)); + BOOST_REQUIRE(query.set(test::tx5)); + BOOST_REQUIRE(query.set(test::block3a, test::context, false, false)); output_links out{}; - std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.to_address_outputs(cancel, out, test::genesis_address)); + BOOST_REQUIRE(!query.to_address_outputs(out, test::block1a_address1)); + + // There is 1 instance of the `script{ { { opcode::roll } } }` output. BOOST_REQUIRE_EQUAL(out.size(), 1u); - BOOST_REQUIRE_EQUAL(out.front(), query.to_output(0, 0)); + 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_EQUAL(store.create(test::events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1a, test::context, false, false)); + BOOST_REQUIRE(query.set(test::block2a, test::context, false, false)); + BOOST_REQUIRE(query.set(test::tx4)); + BOOST_REQUIRE(query.set(test::tx5)); + BOOST_REQUIRE(query.set(test::block3a, test::context, false, false)); + + output_links out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.to_address_outputs(cancel, out, test::block1a_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_spending_tx +// to_input_tx // to_output_tx BOOST_AUTO_TEST_CASE(query_navigate__to_output_tx__to_output__expected) diff --git a/test/types.cpp b/test/types.cpp index 94ffa4a10..c640a9720 100644 --- a/test/types.cpp +++ b/test/types.cpp @@ -146,4 +146,84 @@ BOOST_AUTO_TEST_CASE(types__history_less_than__unconfirmed_hash_identical__expec BOOST_REQUIRE(!history::less_than{}(value, value)); } +// history.sort_and_dedup() + +BOOST_AUTO_TEST_CASE(types__history_sort_and_dedup__unsorted_with_duplicates_mixed__sorted_and_deduped) +{ + constexpr auto h1 = base16_hash("0000000000000000000000000000000000000000000000000000000000000001"); + constexpr auto h2 = base16_hash("0000000000000000000000000000000000000000000000000000000000000002"); + std::vector values + { + { { h2, 0 }, 0, 0 }, // unconfirmed + { { hash_digest{}, 200 }, 0, 5 }, // confirmed + { { h1, 0 }, 0, 0 }, // unconfirmed (duplicate will be removed) + { { h1, 0 }, 0, 0 }, // unconfirmed duplicate + { { hash_digest{}, 100 }, 0, 10 }, // confirmed + { { hash_digest{}, 100 }, 0, 10 } // confirmed duplicate + }; + + history::sort_and_dedup(values); + BOOST_REQUIRE_EQUAL(values.size(), 4u); + BOOST_REQUIRE_EQUAL(values[0].tx.height(), 100u); // confirmed, lowest height + BOOST_REQUIRE_EQUAL(values[1].tx.height(), 200u); // confirmed + BOOST_REQUIRE_EQUAL(values[2].tx.height(), 0u); // unconfirmed (h1) + BOOST_REQUIRE_EQUAL(values[3].tx.height(), 0u); // unconfirmed (h2) +} + +BOOST_AUTO_TEST_CASE(history__sort_and_dedup__exclusions__removes_excluded_items) +{ + std::vector items + { + history{ { hash_digest{}, 1 }, 0, max_size_t }, // excluded + history{ { hash_digest{}, 2 }, 0, max_size_t }, // excluded + history{ { hash_digest{}, 3 }, 0, 10 }, // valid + history{ { hash_digest{}, 3 }, 0, 5 }, // valid (same height, lower position) + history{ { hash_digest{}, 3 }, 0, 10 } // duplicate + }; + + history::sort_and_dedup(items); + BOOST_REQUIRE_EQUAL(items.size(), 2u); + BOOST_REQUIRE_EQUAL(items[0].position, 5u); + BOOST_REQUIRE_EQUAL(items[1].position, 10u); +} + +// unspent.sort_and_dedup() + +BOOST_AUTO_TEST_CASE(types__unspent_sort_and_dedup__unsorted_with_duplicates_mixed__sorted_and_deduped) +{ + const outpoint lo{ { {}, 0 }, 0 }; + const outpoint hi{ { {}, 5 }, 0 }; + std::vector values + { + { hi, 0, 0 }, // unconfirmed + { lo, 200, 3 }, // confirmed + { lo, 100, 5 }, // confirmed + { lo, 100, 5 }, // confirmed duplicate + { hi, 0, 0 } // unconfirmed duplicate + }; + + unspent::sort_and_dedup(values); + BOOST_REQUIRE_EQUAL(values.size(), 3u); + BOOST_REQUIRE_EQUAL(values[0].height, 100u); // confirmed, lowest height + BOOST_REQUIRE_EQUAL(values[1].height, 200u); // confirmed + BOOST_REQUIRE_EQUAL(values[2].height, 0u); // unconfirmed +} + +BOOST_AUTO_TEST_CASE(unspent__sort_and_dedup__exclusions__removes_excluded_items) +{ + unspents items + { + unspent{ { {}, 1 }, 10, max_size_t }, // excluded + unspent{ { {}, 2 }, 200, max_size_t }, // excluded + unspent{ { {}, 3 }, 50, 10 }, // valid confirmed + unspent{ { {}, 4 }, 50, 5 }, // valid confirmed (same height, lower position) + unspent{ { {}, 3 }, 50, 10 } // duplicate + }; + + unspent::sort_and_dedup(items); + BOOST_REQUIRE_EQUAL(items.size(), 2u); + BOOST_REQUIRE_EQUAL(items[0].position, 5u); + BOOST_REQUIRE_EQUAL(items[1].position, 10u); +} + BOOST_AUTO_TEST_SUITE_END()