diff --git a/include/bitcoin/database/impl/primitives/hashmap.ipp b/include/bitcoin/database/impl/primitives/hashmap.ipp index f8685c14f..3e98719bd 100644 --- a/include/bitcoin/database/impl/primitives/hashmap.ipp +++ b/include/bitcoin/database/impl/primitives/hashmap.ipp @@ -199,30 +199,29 @@ bool CLASS::get(const Link& link, Element& element) const NOEXCEPT // static TEMPLATE template > -bool CLASS::get(const iterator& it, Element& element) NOEXCEPT +bool CLASS::get(const memory_ptr& ptr, const Link& link, + Element& element) NOEXCEPT { - // This override avoids deadlock when holding iterator to the same table. - return read(it.get(), it.self(), element); + return read(ptr, link, element); } // static TEMPLATE template > -bool CLASS::get(const iterator& it, const Link& link, - Element& element) NOEXCEPT +bool CLASS::get(const iterator& it, Element& element) NOEXCEPT { // This override avoids deadlock when holding iterator to the same table. - return read(it.get(), link, element); + return read(it.get(), it.self(), element); } // static TEMPLATE template > -bool CLASS::get(const memory_ptr& ptr, const Link& link, +bool CLASS::get(const iterator& it, const Link& link, Element& element) NOEXCEPT { // This override avoids deadlock when holding iterator to the same table. - return read(ptr, link, element); + return read(it.get(), link, element); } TEMPLATE @@ -307,24 +306,16 @@ template > bool CLASS::put(const Link& link, const Key& key, const Element& element) NOEXCEPT { - using namespace system; - const auto ptr = body_.get(link); - if (!ptr) - return false; - - // iostream.flush is a nop (direct copy). - iostream stream{ *ptr }; - finalizer sink{ stream }; - sink.skip_bytes(Link::size); - sink.write_bytes(key); - - if constexpr (!is_slab) - { - BC_DEBUG_ONLY(sink.set_limit(Size * element.count());) - } + // This override is the normal form. + return write(get_memory(), link, key, element); +} - auto& next = unsafe_array_cast(ptr->begin()); - return element.to_data(sink) && head_.push(link, next, head_.index(key)); +TEMPLATE +template > +bool CLASS::put(const memory_ptr& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + return write(ptr, link, key, element); } TEMPLATE @@ -355,6 +346,31 @@ Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT // protected/static // ---------------------------------------------------------------------------- +TEMPLATE +Link CLASS::first(const memory_ptr& ptr, Link link, const Key& key) NOEXCEPT +{ + if (!ptr) + return {}; + + while (!link.is_terminal()) + { + // get element offset (fault) + const auto offset = ptr->offset(body::link_to_position(link)); + if (is_null(offset)) + return {}; + + // element key matches (found) + if (is_zero(std::memcmp(key.data(), std::next(offset, Link::size), + array_count))) + return link; + + // set next element link (loop) + link = system::unsafe_array_cast(offset); + } + + return link; +} + TEMPLATE template > bool CLASS::read(const memory_ptr& ptr, const Link& link, @@ -387,28 +403,40 @@ bool CLASS::read(const memory_ptr& ptr, const Link& link, } TEMPLATE -Link CLASS::first(const memory_ptr& ptr, Link link, const Key& key) NOEXCEPT +template > +bool CLASS::write(const memory_ptr& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT { - if (!ptr) - return {}; + if (!ptr || link.is_terminal()) + return false; - while (!link.is_terminal()) - { - // get element offset (fault) - const auto offset = ptr->offset(body::link_to_position(link)); - if (is_null(offset)) - return {}; + using namespace system; + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; - // element key matches (found) - if (is_zero(std::memcmp(key.data(), std::next(offset, Link::size), - array_count))) - return link; + const auto size = ptr->size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position > size) + return false; - // set next element link (loop) - link = system::unsafe_array_cast(offset); + const auto offset = ptr->offset(position); + if (is_null(offset)) + return false; + + // iostream.flush is a nop (direct copy). + iostream stream{ offset, size - position }; + finalizer sink{ stream }; + sink.skip_bytes(Link::size); + sink.write_bytes(key); + + if constexpr (!is_slab) + { + BC_DEBUG_ONLY(sink.set_limit(Size * element.count());) } - return link; + auto& next = unsafe_array_cast(ptr->begin()); + return element.to_data(sink) && head_.push(link, next, head_.index(key)); } } // namespace database diff --git a/include/bitcoin/database/impl/primitives/linkage.ipp b/include/bitcoin/database/impl/primitives/linkage.ipp index 4a4a315dd..d3fce4b6b 100644 --- a/include/bitcoin/database/impl/primitives/linkage.ipp +++ b/include/bitcoin/database/impl/primitives/linkage.ipp @@ -44,14 +44,14 @@ inline CLASS::linkage(const bytes& other) NOEXCEPT } TEMPLATE -constexpr linkage& CLASS::operator=(integer other) NOEXCEPT +constexpr CLASS& CLASS::operator=(integer other) NOEXCEPT { value = other; return *this; } TEMPLATE -inline linkage& CLASS::operator=(const bytes& other) NOEXCEPT +inline CLASS& CLASS::operator=(const bytes& other) NOEXCEPT { value = 0; system::unsafe_array_cast(&value) = other; @@ -59,6 +59,21 @@ inline linkage& CLASS::operator=(const bytes& other) NOEXCEPT return *this; } +TEMPLATE +inline CLASS& CLASS::operator++() NOEXCEPT +{ + ++value; + return *this; +} + +TEMPLATE +inline CLASS CLASS::operator++(int) NOEXCEPT +{ + auto self = *this; + ++(*this); + return self; +} + ////TEMPLATE ////constexpr CLASS::operator bool() const NOEXCEPT ////{ diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index 44245670f..d6f18b7c9 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -284,34 +284,34 @@ error::error_t CLASS::spent_prevout(const point_link& link, index index, // Iterate points by point hash (of output tx) because may be conflicts. // Search key must be passed as an l-value as it is held by reference. - const auto point_sk = get_point_key(link); - auto point = store_.point.it(point_sk); - if (!point) - return error::integrity; + ////const auto point_sk = get_point_key(link); + ////auto point = store_.point.it(point_sk); + ////if (!point) + //// return error::integrity; + +////do +////{ + // Iterate all spends of the point to find double spends. + // Search key must be passed as an l-value as it is held by reference. + const auto spend_sk = table::spend::compose(link /*point.self()*/, index); + auto it = store_.spend.it(spend_sk); + if (!it) + return error::success; + table::spend::get_parent spend{}; do { - // Iterate all spends of the point to find double spends. - // Search key must be passed as an l-value as it is held by reference. - const auto spend_sk = table::spend::compose(point.self(), index); - auto it = store_.spend.it(spend_sk); - if (!it) - return error::success; + if (!store_.spend.get(it, spend)) + return error::integrity; - table::spend::get_parent spend{}; - do - { - if (!store_.spend.get(it, spend)) - return error::integrity; - - // is_strong_tx (search) only called in the case of duplicate. - // Other parent tx of spend is strong (confirmed spent prevout). - if ((spend.parent_fk != self) && is_strong_tx(spend.parent_fk)) - return error::confirmed_double_spend; - } - while (it.advance()); + // is_strong_tx (search) only called in the case of duplicate. + // Other parent tx of spend is strong (confirmed spent prevout). + if ((spend.parent_fk != self) && is_strong_tx(spend.parent_fk)) + return error::confirmed_double_spend; } - while (point.advance()); + while (it.advance()); +////} +////while (point.advance()); return error::success; } @@ -391,9 +391,23 @@ code CLASS::unspent_duplicates(const header_link& link, // [txs.find, {tx.iterate}, strong_tx.it] auto coinbases = to_strong_txs(get_tx_key(to_coinbase(link))); - // Current block is not set strong. - if (is_zero(coinbases.size())) - return error::success; + if (prevout_enabled()) + { + // Current block is not set strong. + if (is_zero(coinbases.size())) + return error::success; + } + else + { + // Found only this block's coinbase instance, no duplicates. + if (is_one(coinbases.size())) + return error::success; + + // Remove self (will be not found if current block is not set_strong). + const auto self = std::find(coinbases.begin(), coinbases.end(), link); + if (self == coinbases.end() || coinbases.erase(self) == coinbases.end()) + return error::integrity; + } // [point.first, is_spent_prevout()] const auto spent = [this](const auto& tx) NOEXCEPT @@ -406,121 +420,91 @@ code CLASS::unspent_duplicates(const header_link& link, error::success : error::unspent_coinbase_collision; } -#if defined(UNDEFINED) - // protected TEMPLATE -spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT +bool CLASS::get_spend_set(spend_set& set, const tx_link& link) const NOEXCEPT { - const auto txs = to_transactions(link); - if (txs.empty()) - return {}; - - // Coinbase optimization. - spend_sets out{ txs.size() }; - out.front().tx = txs.front(); - if (is_one(out.size())) - return out; + table::transaction::get_version_puts tx{}; + if (!store_.tx.get(link, tx)) + return false; - spend_sets sets{}; - sets.reserve(sub1(txs.size())); - for (auto tx = std::next(txs.begin()); tx != txs.end(); ++tx) - sets.push_back(to_spend_set(*tx)); + table::puts::get_spends puts{}; + puts.spend_fks.resize(tx.ins_count); + if (!store_.puts.get(tx.puts_fk, puts)) + return false; - return sets; -} + set.tx = link; + set.version = tx.version; + set.spends.clear(); + set.spends.reserve(tx.ins_count); + table::spend::get_prevout_sequence get{}; + const auto ptr = store_.spend.get_memory(); -// Used by node for ASIO concurrency by tx. -TEMPLATE -code CLASS::tx_confirmable(const tx_link& link, - const context& ctx) const NOEXCEPT -{ - // utxo needs spend set for sequence and spend.prevout(), which is the non- - // iterating key to utxo table. This is the identifier of the point:index - // which is used to obtain the output for validation by - // tx.find(get_point_key(link)). However we don't need the output for - // confirmation, just a unique identifier for it. Yet the point:index (fp) - // is not unique, because here are many fps for any given output due to tx - // and therefore point table duplication. So the spend sets identify all of - // the spends of a given tx, but one tx may have a different point for the - // same output. So all outputs of the point must be searched for existence, - // which requires point and tx table traversal just as before. :< - const auto set = to_spend_set(link); - - code ec{}; - for (const auto& spend: set.spends) + // This is not concurrent because get_spend_sets is (by tx). + for (const auto& spend_fk: puts.spend_fks) { - // If utxo exists then it is strong (push own block first). - if ((ec = unspendable_prevout(spend.point_fk, spend.sequence, - set.version, ctx))) - return ec; + if (!store_.spend.get(ptr, spend_fk, get)) + return false; - // This query goes away. - // If utxo exists then it is not spent (push own block first). - if (is_spent_prevout(spend.point_fk, spend.point_index, link)) - return error::confirmed_double_spend; + // Translate result set to public struct. +#if defined(HAVE_CLANG) + // emplace_back aggregate initialization requires clang 16. + set.spends.push_back({ get.point_fk, get.point_index, get.sequence, + table::prevout::tx::integer{}, bool{} }); +#else + set.spends.emplace_back(get.point_fk, get.point_index, get.sequence, + table::prevout::tx::integer{}, bool{}); +#endif } - return error::success; -} - -// Used by node for sequential by block (unused). -// split(0) 403 secs for 400k-410k -TEMPLATE -code CLASS::block_confirmable(const header_link& link) const NOEXCEPT -{ - context ctx{}; - if (!get_context(ctx, link)) - return error::integrity; - - const auto txs = to_transactions(link); - if (txs.empty()) - return error::txs_empty; - - code ec{}; - if ((ec = unspent_duplicates(txs.front(), ctx))) - return ec; - - for (auto tx = std::next(txs.begin()); tx != txs.end(); ++tx) - if ((ec = tx_confirmable(*tx, ctx))) - return ec; - - return ec; + return true; } -#endif - // protected TEMPLATE -spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT +bool CLASS::get_spend_sets(spend_sets& sets, + const header_link& link) const NOEXCEPT { // This is the only search [txs.find]. // Coinbase tx does not spend so is not retrieved. const auto txs = to_spending_transactions(link); - - // Empty here is normal. if (txs.empty()) - return {}; + { + sets.clear(); + return true; + } - spend_sets sets{ txs.size() }; - const auto to_set = [this](const auto& tx) NOEXCEPT + std::atomic fault{}; + const auto to_set = [this, &fault](const auto& tx) NOEXCEPT { - // Empty here implies integrity fault. - return to_spend_set(tx); + spend_set set{}; + if (!get_spend_set(set, tx)) fault.store(true); + return set; }; + sets.resize(txs.size()); + // C++17 incomplete on GCC/CLang, so presently parallel only on MSVC++. std_transform(bc::par_unseq, txs.begin(), txs.end(), sets.begin(), to_set); - const auto count = [](size_t total, const auto& set) NOEXCEPT - { - return system::ceilinged_add(total, set.spends.size()); - }; - const auto spends = std::accumulate(sets.begin(), sets.end(), zero, count); + return prevout_enabled() ? populate_prevouts(sets, link) : + populate_prevouts(sets); +} + +TEMPLATE +bool CLASS::populate_prevouts(spend_sets& sets, + const header_link& link) const NOEXCEPT +{ + const auto sets_size = std::accumulate(sets.begin(), sets.end(), zero, + [](size_t total, const auto& set) NOEXCEPT + { + return system::ceilinged_add(total, set.spends.size()); + }); table::prevout::record_get prevouts{}; - prevouts.values.resize(spends); - store_.prevout.at(link, prevouts); + prevouts.values.resize(sets_size); + if (!store_.prevout.at(link, prevouts)) + return false; size_t index{}; for (auto& set: sets) @@ -530,7 +514,24 @@ spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT spend.prevout_tx_fk = prevouts.output_tx_fk(index++); } - return sets; + return true; +} + +TEMPLATE +bool CLASS::populate_prevouts(spend_sets& sets) const NOEXCEPT +{ + // This technique does not benefit from skipping internal spends, and + // therefore also requires set_strong before query, and self removal. + for (auto& set: sets) + for (auto& spend: set.spends) + { + spend.prevout_tx_fk = to_tx(get_point_key(spend.point_fk)); + spend.coinbase = is_coinbase(spend.prevout_tx_fk); + if (spend.prevout_tx_fk == table::prevout::tx::terminal) + return false; + } + + return true; } // split(3) 219 secs for 400k-410k; split(2) 255 and split(0) 456 (not shown). @@ -541,21 +542,21 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT if (!get_context(ctx, link)) return error::integrity; - // This is never invoked (bip30). code ec{}; if ((ec = unspent_duplicates(link, ctx))) return ec; - // Empty here could imply integrity fault. - const auto sets = to_spend_sets(link); + spend_sets sets{}; + if (!get_spend_sets(sets, link)) + return error::integrity; + if (sets.empty()) - return ec; + return error::success; std::atomic fault{ error::success }; const auto is_unspendable = [this, &ctx, &fault](const auto& set) NOEXCEPT { - // TODO: prevout table optimized, evaluate. error::error_t ec{}; for (const auto& spend: set.spends) { @@ -575,7 +576,6 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT const auto is_spent = [this, &fault](const auto& set) NOEXCEPT { - // TODO: point table optimize via consolidation with spend table. error::error_t ec{}; for (const spend_set::spend& spend: set.spends) { @@ -603,68 +603,6 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT return ec; } -#if defined(UNDEFINED) - -// split(1) 446 secs for 400k-410k -TEMPLATE -code CLASS::block_confirmable(const header_link& link) const NOEXCEPT -{ - context ctx{}; - if (!get_context(ctx, link)) - return error::integrity; - - code ec{}; - if ((ec = unspent_duplicates(to_coinbase(link), ctx))) - return ec; - - const auto sets = to_spend_sets(link); - for (const auto& set: sets) - { - for (const auto& spend: set.spends) - { - if ((ec = unspendable_prevout(spend.point_fk, spend.sequence, - set.version, ctx))) - return ec; - - if (is_spent_prevout(spend.point_fk, spend.point_index, set.tx)) - return error::confirmed_double_spend; - } - } - - return ec; -} - -// split(3) 416 secs for 400k-410k -TEMPLATE -code CLASS::block_confirmable(const header_link& link) const NOEXCEPT -{ - context ctx{}; - if (!get_context(ctx, link)) - return error::integrity; - - code ec{}; - if ((ec = unspent_duplicates(to_coinbase(link), ctx))) - return ec; - - const auto sets = to_spend_sets(link); - for (const auto& set: sets) - for (const auto& spend: set.spends) - if ((ec = unspendable_prevout(spend.point_fk, spend.sequence, - set.version, ctx))) - return ec; - - if (ec) return ec; - - for (const auto& set: sets) - for (const auto& spend: set.spends) - if (is_spent_prevout(spend.point_fk, spend.point_index, set.tx)) - return error::confirmed_double_spend; - - return ec; -} - -#endif // DISABLED - TEMPLATE bool CLASS::is_spent_coinbase(const tx_link& link) const NOEXCEPT { @@ -694,18 +632,24 @@ TEMPLATE bool CLASS::set_strong(const header_link& link, const tx_links& txs, bool positive) NOEXCEPT { - const auto set = [this, &link, positive](const tx_link& tx) NOEXCEPT - { - // TODO: eliminate shared memory pointer reallocation. - return store_.strong_tx.put(tx, table::strong_tx::record + using namespace system; + using link_t = table::strong_tx::link; + using element_t = table::strong_tx::record; + + // Preallocate all strong_tx records for the block and reuse memory ptr. + const auto records = possible_narrow_cast(txs.size()); + auto record = store_.strong_tx.allocate(records); + const auto ptr = store_.strong_tx.get_memory(); + + for (const auto tx: txs) + if (!store_.strong_tx.put(ptr, record++, link_t{ tx }, element_t { {}, link, positive - }); - }; + })) return false; - return std::all_of(txs.begin(), txs.end(), set); + return true; } TEMPLATE @@ -741,6 +685,9 @@ bool CLASS::set_unstrong(const header_link& link) NOEXCEPT TEMPLATE bool CLASS::set_prevouts(const header_link& link, const block& block) NOEXCEPT { + if (!prevout_enabled()) + return true; + // Empty or coinbase only implies no spends. if (block.transactions() <= one) return true; @@ -839,75 +786,218 @@ bool CLASS::pop_confirmed() NOEXCEPT // ======================================================================== } -////// TEMP: testing cached values for confirmation. -////struct cached_point -////{ -//// // input (under validation) -//// foreign_point key; // double-spendness -//// uint32_t parent; // input.parent -//// uint32_t sequence; // bip68 -//// -//// // input->prevout -//// uint32_t tx; // confirmedness -//// uint32_t height; // bip68/maturity -//// uint32_t mtp; // bip68 -//// bool coinbase; // maturity -////}; -////// coinbase aligns at 4 bytes on msvc x64. -////////static_assert(sizeof(cached_point) == 32u); -////using cached_points = std_vector; -////bool create_cached_points(cached_points& out, -//// const header_link& link) const NOEXCEPT; -////TEMPLATE -////code CLASS::point_confirmable(const cached_point& point) const NOEXCEPT -////{ -//// code ec{ error::success }; -//// -//// // height is always needed for coinbase (maturity). -//// // sequence value tells which is needed, height or mtp (bip68). -//// // but both may apply, so need to allow for two (no flags at prevout). -//// const context ctx{ 0, point.height, point.mtp }; -//// if ((ec = unspendable_prevout(point.tx, point.coinbase, point.sequence, ctx))) -//// return ec; -//// -//// // may only be strong-spent by self (and must be but is not checked). -//// if (spent_prevout(point.key, point.parent)) -//// return error::confirmed_double_spend; -//// -//// return ec; -////} -//// -////TEMPLATE -////bool CLASS::create_cached_points(cached_points& out, -//// const header_link& link) const NOEXCEPT -////{ -//// context ctx{}; -//// if (!get_context(ctx, link)) -//// return false; -//// -//// table::input::get_prevout_sequence input{}; -//// for (const auto& in: to_non_coinbase_inputs(link)) -//// { -//// if (!store_.input.get(in, input)) -//// return false; -//// -//// out.emplace_back -//// ( -//// // input (under validation) -//// input.prevout(), -//// input.parent_fk, -//// input.sequence, -//// -//// // input->prevout -//// to_tx(get_point_key(input.point_fk)), -//// system::possible_narrow_cast(ctx.height), -//// ctx.mtp, -//// is_coinbase(spent_fk) -//// ); -//// } -//// -//// return true; -////} +#if defined(UNDEFINED) + +// TEMP: testing cached values for confirmation. +struct cached_point +{ + // input (under validation) + foreign_point key; // double-spendness + uint32_t parent; // input.parent + uint32_t sequence; // bip68 + + // input->prevout + uint32_t tx; // confirmedness + uint32_t height; // bip68/maturity + uint32_t mtp; // bip68 + bool coinbase; // maturity +}; + +// coinbase aligns at 4 bytes on msvc x64. +////static_assert(sizeof(cached_point) == 32u); +using cached_points = std_vector; +bool create_cached_points(cached_points& out, + const header_link& link) const NOEXCEPT; +TEMPLATE +code CLASS::point_confirmable(const cached_point& point) const NOEXCEPT +{ + code ec{ error::success }; + + // height is always needed for coinbase (maturity). + // sequence value tells which is needed, height or mtp (bip68). + // but both may apply, so need to allow for two (no flags at prevout). + const context ctx{ 0, point.height, point.mtp }; + if ((ec = unspendable_prevout(point.tx, point.coinbase, point.sequence, ctx))) + return ec; + + // may only be strong-spent by self (and must be but is not checked). + if (spent_prevout(point.key, point.parent)) + return error::confirmed_double_spend; + + return ec; +} + +TEMPLATE +bool CLASS::create_cached_points(cached_points& out, + const header_link& link) const NOEXCEPT +{ + context ctx{}; + if (!get_context(ctx, link)) + return false; + + table::input::get_prevout_sequence input{}; + for (const auto& in: to_non_coinbase_inputs(link)) + { + if (!store_.input.get(in, input)) + return false; + + out.emplace_back + ( + // input (under validation) + input.prevout(), + input.parent_fk, + input.sequence, + + // input->prevout + to_tx(get_point_key(input.point_fk)), + system::possible_narrow_cast(ctx.height), + ctx.mtp, + is_coinbase(spent_fk) + ); + } + + return true; +} + +// protected +TEMPLATE +spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT +{ + const auto txs = to_transactions(link); + if (txs.empty()) + return {}; + + // Coinbase optimization. + spend_sets out{ txs.size() }; + out.front().tx = txs.front(); + if (is_one(out.size())) + return out; + + spend_sets sets{}; + sets.reserve(sub1(txs.size())); + for (auto tx = std::next(txs.begin()); tx != txs.end(); ++tx) + sets.push_back(to_spend_set(*tx)); + + return sets; +} + +// Used by node for ASIO concurrency by tx. +TEMPLATE +code CLASS::tx_confirmable(const tx_link& link, + const context& ctx) const NOEXCEPT +{ + // utxo needs spend set for sequence and spend.prevout(), which is the non- + // iterating key to utxo table. This is the identifier of the point:index + // which is used to obtain the output for validation by + // tx.find(get_point_key(link)). However we don't need the output for + // confirmation, just a unique identifier for it. Yet the point:index (fp) + // is not unique, because here are many fps for any given output due to tx + // and therefore point table duplication. So the spend sets identify all of + // the spends of a given tx, but one tx may have a different point for the + // same output. So all outputs of the point must be searched for existence, + // which requires point and tx table traversal just as before. :< + const auto set = to_spend_set(link); + + code ec{}; + for (const auto& spend: set.spends) + { + // If utxo exists then it is strong (push own block first). + if ((ec = unspendable_prevout(spend.point_fk, spend.sequence, + set.version, ctx))) + return ec; + + // This query goes away. + // If utxo exists then it is not spent (push own block first). + if (is_spent_prevout(spend.point_fk, spend.point_index, link)) + return error::confirmed_double_spend; + } + + return error::success; +} + +// Used by node for sequential by block (unused). +// split(0) 403 secs for 400k-410k +TEMPLATE +code CLASS::block_confirmable(const header_link& link) const NOEXCEPT +{ + context ctx{}; + if (!get_context(ctx, link)) + return error::integrity; + + const auto txs = to_transactions(link); + if (txs.empty()) + return error::txs_empty; + + code ec{}; + if ((ec = unspent_duplicates(txs.front(), ctx))) + return ec; + + for (auto tx = std::next(txs.begin()); tx != txs.end(); ++tx) + if ((ec = tx_confirmable(*tx, ctx))) + return ec; + + return ec; +} + +// split(1) 446 secs for 400k-410k +TEMPLATE +code CLASS::block_confirmable(const header_link& link) const NOEXCEPT +{ + context ctx{}; + if (!get_context(ctx, link)) + return error::integrity; + + code ec{}; + if ((ec = unspent_duplicates(to_coinbase(link), ctx))) + return ec; + + const auto sets = to_spend_sets(link); + for (const auto& set: sets) + { + for (const auto& spend: set.spends) + { + if ((ec = unspendable_prevout(spend.point_fk, spend.sequence, + set.version, ctx))) + return ec; + + if (is_spent_prevout(spend.point_fk, spend.point_index, set.tx)) + return error::confirmed_double_spend; + } + } + + return ec; +} + +// split(3) 416 secs for 400k-410k +TEMPLATE +code CLASS::block_confirmable(const header_link& link) const NOEXCEPT +{ + context ctx{}; + if (!get_context(ctx, link)) + return error::integrity; + + code ec{}; + if ((ec = unspent_duplicates(to_coinbase(link), ctx))) + return ec; + + const auto sets = to_spend_sets(link); + for (const auto& set: sets) + for (const auto& spend: set.spends) + if ((ec = unspendable_prevout(spend.point_fk, spend.sequence, + set.version, ctx))) + return ec; + + if (ec) return ec; + + for (const auto& set: sets) + for (const auto& spend: set.spends) + if (is_spent_prevout(spend.point_fk, spend.point_index, set.tx)) + return error::confirmed_double_spend; + + return ec; +} + +#endif // UNDEFINED } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/query/extent.ipp b/include/bitcoin/database/impl/query/extent.ipp index e33485b04..4548e05b3 100644 --- a/include/bitcoin/database/impl/query/extent.ipp +++ b/include/bitcoin/database/impl/query/extent.ipp @@ -240,6 +240,12 @@ bool CLASS::neutrino_enabled() const NOEXCEPT return store_.neutrino.enabled(); } +TEMPLATE +bool CLASS::prevout_enabled() const NOEXCEPT +{ + return store_.prevout.enabled(); +} + } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/query/translate.ipp b/include/bitcoin/database/impl/query/translate.ipp index a94eeb46e..0c2431b82 100644 --- a/include/bitcoin/database/impl/query/translate.ipp +++ b/include/bitcoin/database/impl/query/translate.ipp @@ -477,46 +477,6 @@ output_links CLASS::to_prevouts(const tx_link& link) const NOEXCEPT return prevouts; } -// protected -TEMPLATE -spend_set CLASS::to_spend_set(const tx_link& link) const NOEXCEPT -{ - table::transaction::get_version_puts tx{}; - if (!store_.tx.get(link, tx)) - return {}; - - table::puts::get_spends puts{}; - puts.spend_fks.resize(tx.ins_count); - if (!store_.puts.get(tx.puts_fk, puts)) - return {}; - - spend_set set{ link, tx.version, {} }; - set.spends.reserve(tx.ins_count); - table::spend::get_prevout_sequence get{}; - - // This reduced a no-bypass 840k sync/confirmable/confirm run by 8.3%. - const auto ptr = store_.spend.get_memory(); - - // This is not concurrent because to_spend_sets is (by tx). - for (const auto& spend_fk: puts.spend_fks) - { - if (!store_.spend.get(ptr, spend_fk, get)) - return {}; - - // Translate query to public struct. -#if defined(HAVE_CLANG) - // emplace_back aggregate initialization requires clang 16. - set.spends.push_back({ get.point_fk, get.point_index, get.sequence, - table::prevout::tx::integer{}, bool{} }); -#else - set.spends.emplace_back(get.point_fk, get.point_index, get.sequence, - table::prevout::tx::integer{}, bool{}); -#endif - } - - return set; -} - // txs to puts (forward navigation) // ---------------------------------------------------------------------------- diff --git a/include/bitcoin/database/primitives/hashmap.hpp b/include/bitcoin/database/primitives/hashmap.hpp index 90a240a1b..a23068b59 100644 --- a/include/bitcoin/database/primitives/hashmap.hpp +++ b/include/bitcoin/database/primitives/hashmap.hpp @@ -118,6 +118,11 @@ class hashmap template = true> bool get(const Link& link, Element& element) const NOEXCEPT; + /// Get element at link using memory object, false if deserialize error. + template = true> + static bool get(const memory_ptr& ptr, const Link& link, + Element& element) NOEXCEPT; + /// Get element at link, false if deserialize error. /// Iterator must not be terminal, must be guarded by called. template = true> @@ -128,11 +133,6 @@ class hashmap static bool get(const iterator& it, const Link& link, Element& element) NOEXCEPT; - /// Get element at link using memory object, false if deserialize error. - template = true> - static bool get(const memory_ptr& ptr, const Link& link, - Element& element) NOEXCEPT; - /// Set element into previously allocated link (follow with commit). template = true> bool set(const Link& link, const Element& element) NOEXCEPT; @@ -157,19 +157,29 @@ class hashmap template = true> bool put(const Link& link, const Key& key, const Element& element) NOEXCEPT; + /// Set/commit allocated element at link to key, using memory object. + template = true> + bool put(const memory_ptr& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT; + /// Commit previously set element at link to key. bool commit(const Link& link, const Key& key) NOEXCEPT; Link commit_link(const Link& link, const Key& key) NOEXCEPT; protected: + /// Get first element matching key, from top link and whole table memory. + static Link first(const memory_ptr& ptr, Link link, + const Key& key) NOEXCEPT; + /// Get element at link using memory object, false if deserialize error. template = true> static bool read(const memory_ptr& ptr, const Link& link, Element& element) NOEXCEPT; - /// Get first element matching key, from top link and whole table memory. - static Link first(const memory_ptr& ptr, Link link, - const Key& key) NOEXCEPT; + /// Set and commit previously allocated element at link to key. + template = true> + bool write(const memory_ptr& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT; private: static constexpr auto is_slab = (Size == max_size_t); diff --git a/include/bitcoin/database/primitives/linkage.hpp b/include/bitcoin/database/primitives/linkage.hpp index 7b52768de..9fd1b5ff3 100644 --- a/include/bitcoin/database/primitives/linkage.hpp +++ b/include/bitcoin/database/primitives/linkage.hpp @@ -45,6 +45,10 @@ struct linkage constexpr linkage& operator=(integer other) NOEXCEPT; inline linkage& operator=(const bytes& other) NOEXCEPT; + /// Increment operators (not for use with slab links). + inline linkage& operator++() NOEXCEPT; + inline linkage operator++(int) NOEXCEPT; + /// Integral and array cast operators. ////constexpr operator bool() const NOEXCEPT; constexpr operator integer() const NOEXCEPT; diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 5c4bdff7f..dcbf195dd 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -229,6 +229,7 @@ class query /// Optional table state. bool address_enabled() const NOEXCEPT; bool neutrino_enabled() const NOEXCEPT; + bool prevout_enabled() const NOEXCEPT; /// Initialization (natural-keyed). /// ----------------------------------------------------------------------- @@ -543,10 +544,6 @@ class query bool set_filter_head(const header_link& link, const hash_digest& head) NOEXCEPT; -// TODO: protected - spend_set to_spend_set(const tx_link& link) const NOEXCEPT; - spend_sets to_spend_sets(const header_link& link) const NOEXCEPT; - protected: /// Translate. /// ----------------------------------------------------------------------- @@ -580,6 +577,10 @@ class query const context& ctx) const NOEXCEPT; // Critical path + bool populate_prevouts(spend_sets& sets) const NOEXCEPT; + bool populate_prevouts(spend_sets& sets, const header_link& link) const NOEXCEPT; + bool get_spend_set(spend_set& set, const tx_link& link) const NOEXCEPT; + bool get_spend_sets(spend_sets& set, const header_link& link) const NOEXCEPT; bool is_spent_prevout(const point_link& link, index index, const tx_link& self=tx_link::terminal) const NOEXCEPT; error::error_t spent_prevout(const point_link& link, index index, diff --git a/test/mocks/chunk_store.hpp b/test/mocks/chunk_store.hpp index 9f42713e7..ab39d9200 100644 --- a/test/mocks/chunk_store.hpp +++ b/test/mocks/chunk_store.hpp @@ -74,6 +74,16 @@ class chunk_store return output_body_.buffer(); } + system::data_chunk& prevout_head() NOEXCEPT + { + return prevout_head_.buffer(); + } + + system::data_chunk& prevout_body() NOEXCEPT + { + return prevout_body_.buffer(); + } + system::data_chunk& puts_head() NOEXCEPT { return puts_head_.buffer(); @@ -116,16 +126,6 @@ class chunk_store // Indexes. - system::data_chunk& address_head() NOEXCEPT - { - return address_head_.buffer(); - } - - system::data_chunk& address_body() NOEXCEPT - { - return address_body_.buffer(); - } - system::data_chunk& candidate_head() NOEXCEPT { return candidate_head_.buffer(); @@ -178,6 +178,18 @@ class chunk_store return validated_tx_body_.buffer(); } + // Optionals. + + system::data_chunk& address_head() NOEXCEPT + { + return address_head_.buffer(); + } + + system::data_chunk& address_body() NOEXCEPT + { + return address_body_.buffer(); + } + system::data_chunk& neutrino_head() NOEXCEPT { return neutrino_head_.buffer(); @@ -187,26 +199,6 @@ class chunk_store { return neutrino_body_.buffer(); } - - ////system::data_chunk& bootstrap_head() NOEXCEPT - ////{ - //// return bootstrap_head_.buffer(); - ////} - - ////system::data_chunk& bootstrap_body() NOEXCEPT - ////{ - //// return bootstrap_body_.buffer(); - ////} - - ////system::data_chunk& buffer_head() NOEXCEPT - ////{ - //// return buffer_head_.buffer(); - ////} - - ////system::data_chunk& buffer_body() NOEXCEPT - ////{ - //// return buffer_body_.buffer(); - ////} }; using query_accessor = query>; diff --git a/test/primitives/linkage.cpp b/test/primitives/linkage.cpp index 879a07ad7..41c8ebad5 100644 --- a/test/primitives/linkage.cpp +++ b/test/primitives/linkage.cpp @@ -251,6 +251,107 @@ BOOST_AUTO_TEST_CASE(linkage__assign_array__8__expected) BOOST_REQUIRE_EQUAL(instance, expected); } +// pre/post increment + +BOOST_AUTO_TEST_CASE(linkage__increment__0__expected) +{ + linkage<0> instance{ 0x00 }; + + BOOST_REQUIRE_EQUAL(instance++, 0u); + BOOST_REQUIRE_EQUAL(instance, 1u); + + BOOST_REQUIRE_EQUAL(++instance, 2u); + BOOST_REQUIRE_EQUAL(instance, 2u); +} + +BOOST_AUTO_TEST_CASE(linkage__increment__1__expected) +{ + linkage<1> instance{ 0x42 }; + + BOOST_REQUIRE_EQUAL(instance++, 0x42u); + BOOST_REQUIRE_EQUAL(instance, 0x43u); + + BOOST_REQUIRE_EQUAL(++instance, 0x44u); + BOOST_REQUIRE_EQUAL(instance, 0x44u); +} + +BOOST_AUTO_TEST_CASE(linkage__increment__2__expected) +{ + linkage<2> instance{ 0x4200 }; + + BOOST_REQUIRE_EQUAL(instance++, 0x4200u); + BOOST_REQUIRE_EQUAL(instance, 0x4201u); + + BOOST_REQUIRE_EQUAL(++instance, 0x4202u); + BOOST_REQUIRE_EQUAL(instance, 0x4202u); +} + +BOOST_AUTO_TEST_CASE(linkage__increment__3__expected) +{ + linkage<3> instance{ 0x420100 }; + + BOOST_REQUIRE_EQUAL(instance++, 0x420100u); + BOOST_REQUIRE_EQUAL(instance, 0x420101u); + + BOOST_REQUIRE_EQUAL(++instance, 0x420102u); + BOOST_REQUIRE_EQUAL(instance, 0x420102u); +} + +BOOST_AUTO_TEST_CASE(linkage__increment__4__expected) +{ + linkage<4> instance{ 0x42010200 }; + + BOOST_REQUIRE_EQUAL(instance++, 0x42010200u); + BOOST_REQUIRE_EQUAL(instance, 0x42010201u); + + BOOST_REQUIRE_EQUAL(++instance, 0x42010202u); + BOOST_REQUIRE_EQUAL(instance, 0x42010202u); +} + +BOOST_AUTO_TEST_CASE(linkage__increment__5__expected) +{ + linkage<5> instance{ 0x4201020300 }; + + BOOST_REQUIRE_EQUAL(instance++, 0x4201020300u); + BOOST_REQUIRE_EQUAL(instance, 0x4201020301u); + + BOOST_REQUIRE_EQUAL(++instance, 0x4201020302u); + BOOST_REQUIRE_EQUAL(instance, 0x4201020302u); +} + +BOOST_AUTO_TEST_CASE(linkage__increment_6__expected) +{ + linkage<6> instance{ 0x420102030400 }; + + BOOST_REQUIRE_EQUAL(instance++, 0x420102030400u); + BOOST_REQUIRE_EQUAL(instance, 0x420102030401u); + + BOOST_REQUIRE_EQUAL(++instance, 0x420102030402u); + BOOST_REQUIRE_EQUAL(instance, 0x420102030402u); +} + +BOOST_AUTO_TEST_CASE(linkage__increment__7__expected) +{ + linkage<7> instance{ 0x42010203040500 }; + + BOOST_REQUIRE_EQUAL(instance++, 0x42010203040500u); + BOOST_REQUIRE_EQUAL(instance, 0x42010203040501u); + + BOOST_REQUIRE_EQUAL(++instance, 0x42010203040502u); + BOOST_REQUIRE_EQUAL(instance, 0x42010203040502u); +} + +BOOST_AUTO_TEST_CASE(linkage__increment__8__expected) +{ + linkage<8> instance{ 0x4201020304050600 }; + + BOOST_REQUIRE_EQUAL(instance++, 0x4201020304050600u); + BOOST_REQUIRE_EQUAL(instance, 0x4201020304050601u); + + BOOST_REQUIRE_EQUAL(++instance, 0x4201020304050602u); + BOOST_REQUIRE_EQUAL(instance, 0x4201020304050602u); +} + // cast bytes BOOST_AUTO_TEST_CASE(linkage__bytes__0__expected) diff --git a/test/query/extent.cpp b/test/query/extent.cpp index f364eac17..4a9f10dfc 100644 --- a/test/query/extent.cpp +++ b/test/query/extent.cpp @@ -160,6 +160,7 @@ BOOST_AUTO_TEST_CASE(query_extent__optionals_enabled__default__true) BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.address_enabled()); BOOST_REQUIRE(query.neutrino_enabled()); + BOOST_REQUIRE(query.prevout_enabled()); } BOOST_AUTO_TEST_CASE(query_extent__address_enabled__disabled__false) @@ -173,6 +174,7 @@ BOOST_AUTO_TEST_CASE(query_extent__address_enabled__disabled__false) BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(!query.address_enabled()); BOOST_REQUIRE(query.neutrino_enabled()); + BOOST_REQUIRE(query.prevout_enabled()); } BOOST_AUTO_TEST_CASE(query_extent__neutrino_enabled__disabled__false) @@ -186,6 +188,21 @@ BOOST_AUTO_TEST_CASE(query_extent__neutrino_enabled__disabled__false) BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.address_enabled()); BOOST_REQUIRE(!query.neutrino_enabled()); + BOOST_REQUIRE(query.prevout_enabled()); +} + +BOOST_AUTO_TEST_CASE(query_extent__prevout_enabled__disabled__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + settings.prevout_buckets = 0; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.address_enabled()); + BOOST_REQUIRE(query.neutrino_enabled()); + BOOST_REQUIRE(!query.prevout_enabled()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/translate.cpp b/test/query/translate.cpp index c8d8d34f4..711c8b97a 100644 --- a/test/query/translate.cpp +++ b/test/query/translate.cpp @@ -216,20 +216,20 @@ BOOST_AUTO_TEST_CASE(query_translate__to_tx__txs__expected) BOOST_REQUIRE_EQUAL(query.to_tx(test::block3.transactions_ptr()->front()->hash(true)), tx_link::terminal); } -// to_spend_tx/to_spend/to_spends/to_spend_key/to_spend_sets +// to_spend_tx/to_spend/to_spends/to_spend_key/get_spend_sets class accessor : public test::query_accessor { public: using test::query_accessor::query_accessor; - spend_set to_spend_set_(const tx_link& link) const NOEXCEPT + bool get_spend_set_(spend_set& set, const tx_link& link) const NOEXCEPT { - return test::query_accessor::to_spend_set(link); + return test::query_accessor::get_spend_set(set, link); } - spend_sets to_spend_sets_(const header_link& link) const NOEXCEPT + bool get_spend_sets_(spend_sets& sets, const header_link& link) const NOEXCEPT { - return test::query_accessor::to_spend_sets(link); + return test::query_accessor::get_spend_sets(sets, link); } }; @@ -282,63 +282,71 @@ BOOST_AUTO_TEST_CASE(query_translate__to_spend_tx__to_spend__expected) BOOST_REQUIRE_EQUAL(query.to_spends(3), spend_links{ 3 }); BOOST_REQUIRE_EQUAL(query.to_spends(4), expected_links4); - auto spends = query.to_spend_set_(0); - BOOST_REQUIRE_EQUAL(spends.tx, 0u); - BOOST_REQUIRE_EQUAL(spends.spends.size(), 1u); - BOOST_REQUIRE(spends.spends.front().is_null()); - BOOST_REQUIRE_EQUAL(spends.version, test::genesis.transactions_ptr()->front()->version()); - - spends = query.to_spend_set_(1); - BOOST_REQUIRE_EQUAL(spends.tx, 1u); - BOOST_REQUIRE_EQUAL(spends.spends.size(), 1u); - BOOST_REQUIRE(spends.spends.front().is_null()); - BOOST_REQUIRE_EQUAL(spends.version, test::block1.transactions_ptr()->front()->version()); - - spends = query.to_spend_set_(2); - BOOST_REQUIRE_EQUAL(spends.tx, 2u); - BOOST_REQUIRE_EQUAL(spends.spends.size(), 1u); - BOOST_REQUIRE(spends.spends.front().is_null()); - BOOST_REQUIRE_EQUAL(spends.version, test::block2.transactions_ptr()->front()->version()); - - spends = query.to_spend_set_(3); - BOOST_REQUIRE_EQUAL(spends.tx, 3u); - BOOST_REQUIRE_EQUAL(spends.spends.size(), 1u); - BOOST_REQUIRE(spends.spends.front().is_null()); - BOOST_REQUIRE_EQUAL(spends.version, test::block3.transactions_ptr()->front()->version()); + spend_set set{}; + BOOST_REQUIRE(query.get_spend_set_(set, 0)); + BOOST_REQUIRE_EQUAL(set.tx, 0u); + BOOST_REQUIRE_EQUAL(set.spends.size(), 1u); + BOOST_REQUIRE(set.spends.front().is_null()); + BOOST_REQUIRE_EQUAL(set.version, test::genesis.transactions_ptr()->front()->version()); + + BOOST_REQUIRE(query.get_spend_set_(set, 1)); + BOOST_REQUIRE_EQUAL(set.tx, 1u); + BOOST_REQUIRE_EQUAL(set.spends.size(), 1u); + BOOST_REQUIRE(set.spends.front().is_null()); + BOOST_REQUIRE_EQUAL(set.version, test::block1.transactions_ptr()->front()->version()); + + BOOST_REQUIRE(query.get_spend_set_(set, 2)); + BOOST_REQUIRE_EQUAL(set.tx, 2u); + BOOST_REQUIRE_EQUAL(set.spends.size(), 1u); + BOOST_REQUIRE(set.spends.front().is_null()); + BOOST_REQUIRE_EQUAL(set.version, test::block2.transactions_ptr()->front()->version()); + + BOOST_REQUIRE(query.get_spend_set_(set, 3)); + BOOST_REQUIRE_EQUAL(set.tx, 3u); + BOOST_REQUIRE_EQUAL(set.spends.size(), 1u); + BOOST_REQUIRE(set.spends.front().is_null()); + BOOST_REQUIRE_EQUAL(set.version, test::block3.transactions_ptr()->front()->version()); // block1a has no first coinbase. - spends = query.to_spend_set_(4); - BOOST_REQUIRE_EQUAL(spends.tx, 4u); - BOOST_REQUIRE_EQUAL(spends.spends.size(), 3u); - BOOST_REQUIRE(!spends.spends[0].is_null()); - BOOST_REQUIRE(!spends.spends[1].is_null()); - BOOST_REQUIRE(!spends.spends[2].is_null()); - BOOST_REQUIRE_EQUAL(spends.spends[0].sequence, 42u); - BOOST_REQUIRE_EQUAL(spends.spends[1].sequence, 24u); - BOOST_REQUIRE_EQUAL(spends.spends[2].sequence, 25u); - BOOST_REQUIRE_EQUAL(spends.spends[0].point_index, 24u); - BOOST_REQUIRE_EQUAL(spends.spends[1].point_index, 42u); - BOOST_REQUIRE_EQUAL(spends.spends[2].point_index, 43u); - BOOST_REQUIRE_EQUAL(spends.spends[0].point_index, (*test::block1a.transactions_ptr()->front()->inputs_ptr())[0]->point().index()); - BOOST_REQUIRE_EQUAL(spends.spends[1].point_index, (*test::block1a.transactions_ptr()->front()->inputs_ptr())[1]->point().index()); - BOOST_REQUIRE_EQUAL(spends.spends[2].point_index, (*test::block1a.transactions_ptr()->front()->inputs_ptr())[2]->point().index()); - BOOST_REQUIRE_EQUAL(spends.version, test::block1a.transactions_ptr()->front()->version()); + BOOST_REQUIRE(query.get_spend_set_(set, 4)); + BOOST_REQUIRE_EQUAL(set.tx, 4u); + BOOST_REQUIRE_EQUAL(set.spends.size(), 3u); + BOOST_REQUIRE(!set.spends[0].is_null()); + BOOST_REQUIRE(!set.spends[1].is_null()); + BOOST_REQUIRE(!set.spends[2].is_null()); + BOOST_REQUIRE_EQUAL(set.spends[0].sequence, 42u); + BOOST_REQUIRE_EQUAL(set.spends[1].sequence, 24u); + BOOST_REQUIRE_EQUAL(set.spends[2].sequence, 25u); + BOOST_REQUIRE_EQUAL(set.spends[0].point_index, 24u); + BOOST_REQUIRE_EQUAL(set.spends[1].point_index, 42u); + BOOST_REQUIRE_EQUAL(set.spends[2].point_index, 43u); + BOOST_REQUIRE_EQUAL(set.spends[0].point_index, (*test::block1a.transactions_ptr()->front()->inputs_ptr())[0]->point().index()); + BOOST_REQUIRE_EQUAL(set.spends[1].point_index, (*test::block1a.transactions_ptr()->front()->inputs_ptr())[1]->point().index()); + BOOST_REQUIRE_EQUAL(set.spends[2].point_index, (*test::block1a.transactions_ptr()->front()->inputs_ptr())[2]->point().index()); + BOOST_REQUIRE_EQUAL(set.version, test::block1a.transactions_ptr()->front()->version()); // COINBASE TXS! // TODO: All blocks have one transaction. - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(0).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(1).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(2).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(3).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(4).size(), 0u); + spend_sets sets{}; + BOOST_REQUIRE(query.get_spend_sets_(sets, 0)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 1)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 2)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 3)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 4)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); // Past end. BOOST_REQUIRE_EQUAL(query.to_spend_tx(7), tx_link::terminal); BOOST_REQUIRE_EQUAL(query.to_spend(5, 0), spend_link::terminal); BOOST_REQUIRE_EQUAL(query.to_spend_key(spend_link::terminal), foreign_point{}); BOOST_REQUIRE_EQUAL(query.to_spend_key(query.to_spend(5, 0)), foreign_point{}); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(5).size(), 0u); BOOST_REQUIRE(query.to_spends(5).empty()); + BOOST_REQUIRE(query.get_spend_sets_(sets, 5)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); // Verify expectations. const auto spend_head = base16_chunk @@ -421,7 +429,7 @@ BOOST_AUTO_TEST_CASE(query_translate__to_spend_tx__to_spend__expected) BOOST_REQUIRE_EQUAL(store.input_body(), input_body); } -BOOST_AUTO_TEST_CASE(query_translate__to_spend_sets__populated__expected) +BOOST_AUTO_TEST_CASE(query_translate__get_spend_sets__populated__expected) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -430,10 +438,14 @@ BOOST_AUTO_TEST_CASE(query_translate__to_spend_sets__populated__expected) BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); // coinbase only (null and first). + spend_sets sets{}; BOOST_REQUIRE(query.initialize(test::genesis)); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(0).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(1).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(2).size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 0)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 1)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 2)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); BOOST_REQUIRE_EQUAL(store.point_body(), system::base16_chunk("")); BOOST_REQUIRE_EQUAL(store.spend_body(), @@ -445,9 +457,12 @@ BOOST_AUTO_TEST_CASE(query_translate__to_spend_sets__populated__expected) // coinbase only (null and first). BOOST_REQUIRE(query.set(test::block1b, context{ 0, 1, 0 }, false, false)); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(0).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(1).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(2).size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 0)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 1)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 2)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); BOOST_REQUIRE_EQUAL(store.point_body(), system::base16_chunk("")); BOOST_REQUIRE_EQUAL(store.spend_body(), @@ -464,8 +479,10 @@ BOOST_AUTO_TEST_CASE(query_translate__to_spend_sets__populated__expected) // COINBASE TX // 2 inputs (block1b and tx2b). BOOST_REQUIRE(query.set(test::block_spend_internal_2b, context{ 0, 101, 0 }, false, false)); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(0).size(), 0u); - BOOST_REQUIRE_EQUAL(query.to_spend_sets_(1).size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 0)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); + BOOST_REQUIRE(query.get_spend_sets_(sets, 1)); + BOOST_REQUIRE_EQUAL(sets.size(), 0u); // Two points because non-null, but only one is non-first (also coinbase criteria). // block_spend_internal_2b first tx (tx2b) is first but with non-null input. @@ -490,14 +507,28 @@ BOOST_AUTO_TEST_CASE(query_translate__to_spend_sets__populated__expected) "02000000""b1""0179" "03000000""b2""0179")); - // to_spend_sets keys on first-tx-ness, so only one input despite two points. - const auto spends = query.to_spend_sets_(2); - BOOST_REQUIRE_EQUAL(spends.size(), 1u); - BOOST_REQUIRE_EQUAL(spends[0].tx, 3u); - BOOST_REQUIRE_EQUAL(spends[0].spends.size(), 1u); - BOOST_REQUIRE_EQUAL(spends[0].spends[0].point_fk, 1u); - BOOST_REQUIRE_EQUAL(spends[0].spends[0].point_index, 0u); - BOOST_REQUIRE_EQUAL(spends[0].spends[0].sequence, 0xb2u); + // Populated spend requires associated prevout. + BOOST_REQUIRE(!query.get_spend_sets_(sets, 2)); + + // Internal coinbase spend is treated as a transaction lock fault. + // This results in the one spend being initialized (block internal). + BOOST_REQUIRE(!test::block_spend_internal_2b.populate(system::chain::context{})); + + // Internal spends are always set to terminal/coinbase. + BOOST_REQUIRE(query.set_prevouts(2, test::block_spend_internal_2b)); + + // get_spend_sets keys on first-tx-ness, so only one input despite two points. + BOOST_REQUIRE(query.get_spend_sets_(sets, 2)); + BOOST_REQUIRE_EQUAL(sets.size(), 1u); + BOOST_REQUIRE_EQUAL(sets[0].tx, 3u); + BOOST_REQUIRE_EQUAL(sets[0].spends.size(), 1u); + BOOST_REQUIRE_EQUAL(sets[0].spends[0].point_fk, 1u); + BOOST_REQUIRE_EQUAL(sets[0].spends[0].point_index, 0u); + BOOST_REQUIRE_EQUAL(sets[0].spends[0].sequence, 0xb2u); + + // Internal spend is terminal/coinbase. + BOOST_REQUIRE(sets[0].spends[0].coinbase); + BOOST_REQUIRE_EQUAL(sets[0].spends[0].prevout_tx_fk, tx_link::terminal); } // to_output_tx/to_output/to_outputs/to_prevouts/to_block_outputs