diff --git a/include/bitcoin/database/impl/query/archive_read.ipp b/include/bitcoin/database/impl/query/archive_read.ipp index 520ed716..835d4ffa 100644 --- a/include/bitcoin/database/impl/query/archive_read.ipp +++ b/include/bitcoin/database/impl/query/archive_read.ipp @@ -19,8 +19,8 @@ #ifndef LIBBITCOIN_DATABASE_QUERY_ARCHIVE_READ_IPP #define LIBBITCOIN_DATABASE_QUERY_ARCHIVE_READ_IPP -#include -#include +#include +#include #include #include #include @@ -149,7 +149,7 @@ inline hash_digest CLASS::get_point_hash(const point_link& link) const NOEXCEPT return point.hash; } -// False implies not confirmed. +// Position. // ---------------------------------------------------------------------------- TEMPLATE @@ -175,13 +175,31 @@ bool CLASS::get_tx_position(size_t& out, const tx_link& link) const NOEXCEPT return true; } +// Sizes. +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::get_tx_size(size_t& out, const tx_link& link, + bool witness) const NOEXCEPT +{ + size_t light{}, heavy{}; + if (!get_tx_sizes(light, heavy, link)) + return false; + + out = witness ? heavy : light; + return true; +} + TEMPLATE -size_t CLASS::get_tx_size(const tx_link& link, +bool CLASS::get_block_size(size_t& out, const header_link& link, bool witness) const NOEXCEPT { size_t light{}, heavy{}; - return get_tx_sizes(light, heavy, link) ? (witness ? heavy : light) : - max_uint64; + if (!get_block_sizes(light, heavy, link)) + return false; + + out = witness ? heavy : light; + return true; } TEMPLATE @@ -197,18 +215,6 @@ bool CLASS::get_tx_sizes(size_t& light, size_t& heavy, return true; } -// Terminal implies not found, false implies fault. -// ---------------------------------------------------------------------------- - -TEMPLATE -size_t CLASS::get_block_size(const header_link& link, - bool witness) const NOEXCEPT -{ - size_t light{}, heavy{}; - return get_block_sizes(light, heavy, link) ? (witness ? heavy : light) : - max_uint64; -} - TEMPLATE bool CLASS::get_block_sizes(size_t& light, size_t& heavy, const header_link& link) const NOEXCEPT @@ -222,6 +228,9 @@ bool CLASS::get_block_sizes(size_t& light, size_t& heavy, return true; } +// Heights. +// ---------------------------------------------------------------------------- + TEMPLATE height_link CLASS::get_height(const hash_digest& key) const NOEXCEPT { @@ -265,6 +274,9 @@ bool CLASS::get_height(size_t& out, const header_link& link) const NOEXCEPT return true; } +// Values (value, spend, fees). +// ---------------------------------------------------------------------------- + TEMPLATE bool CLASS::get_value(uint64_t& out, const output_link& link) const NOEXCEPT { @@ -276,31 +288,237 @@ bool CLASS::get_value(uint64_t& out, const output_link& link) const NOEXCEPT return true; } +// protected TEMPLATE -bool CLASS::get_unassociated(association& out, - const header_link& link) const NOEXCEPT +bool CLASS::get_outputs_total_value(uint64_t& out, + const output_links& links) const NOEXCEPT { - if (is_associated(link)) + out = zero; + for (const auto& output_fk: links) + { + uint64_t value{}; + if (!get_value(value, output_fk)) return false; + out = system::ceilinged_add(out, value); + } + + return true; +} + +TEMPLATE +bool CLASS::get_tx_value(uint64_t& out, const tx_link& link) const NOEXCEPT +{ + table::transaction::get_coinbase tx{}; + if (!store_.tx.get(link, tx)) + return false; + + // Shortcircuit coinbase prevout read. + if (tx.coinbase) + { + out = zero; + return true; + } + + // Optimizable due to sequential tx input links. + const auto links = to_prevouts(link); + return !links.empty() && get_outputs_total_value(out, links); +} + +TEMPLATE +bool CLASS::get_tx_spend(uint64_t& out, const tx_link& link) const NOEXCEPT +{ + const auto links = to_outputs(link); + return !links.empty() && get_outputs_total_value(out, links); +} + +TEMPLATE +bool CLASS::get_tx_fee(uint64_t& out, const tx_link& link) const NOEXCEPT +{ +#if defined(SLOW_FEES) + const auto tx = get_transaction(link, false); + if (!tx) return false; - table::header::get_check_context context{}; - if (!store_.header.get(link, context)) + // Prevent coinbase populate failure. + if (tx->is_coinbase()) + { + out = zero; + return true; + } + + if (!populate_without_metadata(*tx)) + return false; + + out = tx->fee(); + return true; +#elif defined(FAST_FEES) + table::transaction::get_coinbase tx{}; + if (!store_.tx.get(link, tx)) return false; - out = + // Prevent coinbase overspend failure. + if (tx.coinbase) { - link, - context.key, - system::chain::context + out = zero; + return true; + } + + uint64_t value{}, spend{}; + if (!get_tx_value(value, link) || !get_tx_spend(spend, link) || + spend > value) + return false; + + out = value - spend; + return true; +#else // FASTER_FEES + table::transaction::get_puts tx{}; + if (!store_.tx.get(link, tx)) + return false; + + // Shortcircuit coinbase prevout read. + if (tx.coinbase) + { + out = zero; + return true; + } + + uint64_t value{}; + auto point_fk = tx.points_fk; + for (size_t index{}; index < tx.ins_count; ++index) + { + table::point::get_composed point{}; + if (!store_.point.get(point_fk++, point)) + return false; + + uint64_t one_value{}; + if (!get_value(one_value, to_output(point.key))) return false; + value = system::ceilinged_add(value, one_value); + } + + table::outs::record outs{}; + outs.out_fks.resize(tx.outs_count); + if (!store_.outs.get(tx.outs_fk, outs)) + return false; + + uint64_t spend{}; + for (const auto& output_fk: outs.out_fks) + { + uint64_t one_spend{}; + if (!get_value(one_spend, output_fk)) return false; + spend = system::ceilinged_add(spend, one_spend); + } + + if (spend > value) + return false; + + out = value - spend; + return true; +#endif // SLOW_FEES +} + +TEMPLATE +bool CLASS::get_block_value(uint64_t& out, + const header_link& link) const NOEXCEPT +{ + table::txs::get_txs txs{}; + if (!store_.txs.at(to_txs(link), txs) || (txs.tx_fks.size() < one)) + return false; + + std::atomic_bool fail{}; + const auto begin = std::next(txs.tx_fks.begin()); + 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, + [](uint64_t left, uint64_t right) NOEXCEPT + { + return system::ceilinged_add(left, right); + }, + [&](const auto& tx_fk) NOEXCEPT + { + uint64_t value{}; + if (!fail.load(relaxed) && !get_tx_value(value, tx_fk)) + fail.store(true, relaxed); + + return value; + }); + + return !fail.load(relaxed); +} + +TEMPLATE +bool CLASS::get_block_spend(uint64_t& out, + const header_link& link) const NOEXCEPT +{ + table::txs::get_txs txs{}; + if (!store_.txs.at(to_txs(link), txs) || (txs.tx_fks.size() < one)) + return false; + + std::atomic_bool fail{}; + const auto begin = std::next(txs.tx_fks.begin()); + 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, + [](uint64_t left, uint64_t right) NOEXCEPT + { + return system::ceilinged_add(left, right); + }, + [&](const auto& tx_fk) NOEXCEPT { - context.ctx.flags, - context.timestamp, - context.ctx.mtp, - system::possible_wide_cast(context.ctx.height) - } - }; + uint64_t spend{}; + if (!fail.load(relaxed) && !get_tx_spend(spend, tx_fk)) + fail.store(true, relaxed); + + return spend; + }); + + return !fail.load(relaxed); +} +TEMPLATE +bool CLASS::get_block_fee(uint64_t& out, const header_link& link) const NOEXCEPT +{ +#if defined(SLOW_FEES) + const auto block = get_block(link, false); + if (!block || !populate_without_metadata(*block)) + return false; + + out = block->fees(); return true; +#elif defined(FAST_FEES) + uint64_t value{}, spend{}; + if (!get_block_value(value, link) || !get_block_spend(spend, link) || + spend > value) + return false; + + out = value - spend; + return true; +#else // FASTER_FEES + table::txs::get_txs txs{}; + if (!store_.txs.at(to_txs(link), txs) || (txs.tx_fks.size() < one)) + return false; + + std::atomic_bool fail{}; + const auto begin = std::next(txs.tx_fks.begin()); + 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, + [](uint64_t left, uint64_t right) NOEXCEPT + { + return system::ceilinged_add(left, right); + }, + [&](const auto& tx_fk) NOEXCEPT + { + uint64_t fee{}; + if (!fail.load(relaxed) && !get_tx_fee(fee, tx_fk)) + fail.store(true, relaxed); + + return fee; + }); + + return !fail.load(relaxed); +#endif // SLOW_FEES } } // namespace database diff --git a/include/bitcoin/database/impl/query/fees.ipp b/include/bitcoin/database/impl/query/fees.ipp index 385abb2b..9df9280d 100644 --- a/include/bitcoin/database/impl/query/fees.ipp +++ b/include/bitcoin/database/impl/query/fees.ipp @@ -27,12 +27,13 @@ #include #include -#define SLOW_FEES +// virtual_size +// ---------------------------------------------------------------------------- namespace libbitcoin { namespace database { -// private +// static/private TEMPLATE constexpr size_t CLASS::virtual_size(size_t light, size_t heavy) NOEXCEPT { @@ -49,7 +50,7 @@ constexpr size_t CLASS::virtual_size(size_t light, size_t heavy) NOEXCEPT } TEMPLATE -bool CLASS::get_tx_virtual_size(uint64_t& out, +bool CLASS::get_tx_virtual_size(size_t& out, const tx_link& link) const NOEXCEPT { size_t light{}, heavy{}; @@ -61,7 +62,7 @@ bool CLASS::get_tx_virtual_size(uint64_t& out, } TEMPLATE -bool CLASS::get_block_virtual_size(uint64_t& out, +bool CLASS::get_block_virtual_size(size_t& out, const header_link& link) const NOEXCEPT { size_t light{}, heavy{}; @@ -72,37 +73,13 @@ bool CLASS::get_block_virtual_size(uint64_t& out, return true; } -TEMPLATE -uint64_t CLASS::get_tx_fee(const tx_link& link) const NOEXCEPT -{ -#if defined(SLOW_FEES) - const auto tx = get_transaction(link, false); - if (!tx) - return max_uint64; - - if (tx->is_coinbase()) - return zero; - - return populate_without_metadata(*tx) ? tx->fee() : max_uint64; -#else - // Get heavy and light sizes for the tx link and compute virtual size. - // Get and total value for each output of the tx link. - // Get all outputs links spent by the tx link (spends). - // Get and total value for each spend (todo: move value to outs). -#endif -} - -TEMPLATE -uint64_t CLASS::get_block_fee(const header_link& link) const NOEXCEPT -{ - const auto block = get_block(link, false); - return block && populate_without_metadata(*block) ? block->fees() : - max_uint64; -} +// tx/block/branch fee rates +// ---------------------------------------------------------------------------- TEMPLATE bool CLASS::get_tx_fees(fee_rate& out, const tx_link& link) const NOEXCEPT { +#if defined(SLOW_FEES) const auto tx = get_transaction(link, false); if (!tx || tx->is_coinbase() || !populate_without_metadata(*tx)) return false; @@ -110,12 +87,20 @@ bool CLASS::get_tx_fees(fee_rate& out, const tx_link& link) const NOEXCEPT out.bytes = tx->virtual_size(); out.fee = tx->fee(); return true; +#else + table::transaction::get_coinbase tx{}; + if (!store_.tx.get(link, tx) || tx.coinbase) + return false; + + return get_tx_virtual_size(out.bytes, link) && get_tx_fee(out.fee, link); +#endif // SLOW_FEES } TEMPLATE bool CLASS::get_block_fees(fee_rates& out, const header_link& link) const NOEXCEPT { +#if defined(SLOW_FEES) out.clear(); const auto block = get_block(link, false); if (!block) @@ -134,6 +119,30 @@ bool CLASS::get_block_fees(fee_rates& out, out.emplace_back((*tx)->virtual_size(), (*tx)->fee()); return true; +#else // FAST_FEES|FASTER_FEES + out.clear(); + table::txs::get_txs txs{}; + if (!store_.txs.at(to_txs(link), txs) || (txs.tx_fks.size() < one)) + return false; + + std::atomic_bool fail{}; + out.resize(sub1(txs.tx_fks.size())); + const auto begin = std::next(txs.tx_fks.begin()); + constexpr auto parallel = poolstl::execution::par; + constexpr auto relaxed = std::memory_order_relaxed; + + std::transform(parallel, begin, txs.tx_fks.end(), out.begin(), + [&](const auto& tx_fk) NOEXCEPT + { + fee_rate rate{}; + if (!fail.load(relaxed) && !get_tx_fees(rate, tx_fk)) + fail.store(true, relaxed); + + return rate; + }); + + return !fail.load(relaxed); +#endif // SLOW_FEES } TEMPLATE diff --git a/include/bitcoin/database/impl/query/height.ipp b/include/bitcoin/database/impl/query/height.ipp index ed2a09e7..bfe52b19 100644 --- a/include/bitcoin/database/impl/query/height.ipp +++ b/include/bitcoin/database/impl/query/height.ipp @@ -38,7 +38,13 @@ size_t CLASS::get_candidate_size(size_t top) const NOEXCEPT { size_t wire{}; for (auto height = zero; height <= top; ++height) - wire += get_block_size(to_candidate(height), true); + { + size_t size{}; + if (!get_block_size(size, to_candidate(height), true)) + return max_size_t; + + wire += size; + } return wire; } @@ -55,7 +61,13 @@ size_t CLASS::get_confirmed_size(size_t top) const NOEXCEPT { size_t wire{}; for (auto height = zero; height <= top; ++height) - wire += get_block_size(to_confirmed(height), true); + { + size_t size{}; + if (!get_block_size(size, to_confirmed(height), true)) + return max_size_t; + + wire += size; + } return wire; } diff --git a/include/bitcoin/database/impl/query/initialize.ipp b/include/bitcoin/database/impl/query/initialize.ipp index 76ca299f..1d463e8c 100644 --- a/include/bitcoin/database/impl/query/initialize.ipp +++ b/include/bitcoin/database/impl/query/initialize.ipp @@ -105,6 +105,34 @@ associations CLASS::get_unassociated_above(size_t height, return out; } +// protected +TEMPLATE +bool CLASS::get_unassociated(association& out, + const header_link& link) const NOEXCEPT +{ + if (is_associated(link)) + return false; + + table::header::get_check_context context{}; + if (!store_.header.get(link, context)) + return false; + + out = + { + link, + context.key, + system::chain::context + { + context.ctx.flags, + context.timestamp, + context.ctx.mtp, + system::possible_wide_cast(context.ctx.height) + } + }; + + return true; +} + TEMPLATE size_t CLASS::get_unassociated_count() const NOEXCEPT { diff --git a/include/bitcoin/database/impl/query/translate.ipp b/include/bitcoin/database/impl/query/translate.ipp index d8b9d75b..0ec7ab6b 100644 --- a/include/bitcoin/database/impl/query/translate.ipp +++ b/include/bitcoin/database/impl/query/translate.ipp @@ -133,7 +133,7 @@ point_link CLASS::to_point(const tx_link& link, if (!store_.tx.get(link, tx)) return {}; - return tx.point_fk; + return tx.points_fk; } TEMPLATE @@ -152,13 +152,13 @@ output_link CLASS::to_output(const tx_link& link, } TEMPLATE -output_link CLASS::to_prevout(const point_link& link) const NOEXCEPT +output_link CLASS::to_previous_output(const point_link& link) const NOEXCEPT { - table::point::record point{}; + table::point::get_composed point{}; if (!store_.point.get(link, point)) return {}; - return to_output(to_tx(point.hash), point.index); + return to_output(point.key); } // block/tx to block (reverse navigation) @@ -343,7 +343,7 @@ point_links CLASS::to_points(const tx_link& link) const NOEXCEPT // Transaction points are stored in a contiguous array of records. point_links points(tx.number); for (auto& point: points) - point = tx.point_fk++; + point = tx.points_fk++; return points; } @@ -370,11 +370,10 @@ output_links CLASS::to_prevouts(const tx_link& link) const NOEXCEPT if (points.empty()) return {}; - // TODO: to_prevout() output_links prevouts{}; prevouts.reserve(points.size()); for (const auto& point: points) - prevouts.push_back(to_prevout(point)); + prevouts.push_back(to_previous_output(point)); return prevouts; } @@ -412,13 +411,15 @@ output_links CLASS::to_outputs(const tx_links& txs) const NOEXCEPT TEMPLATE output_links CLASS::to_prevouts(const tx_links& txs) const NOEXCEPT { - constexpr auto parallel = poolstl::execution::par; - - // to_prevout() const auto ins = to_points(txs); output_links outs(ins.size()); - const auto fn = [this](auto spend) NOEXCEPT{ return to_prevout(spend); }; - std::transform(parallel, ins.begin(), ins.end(), outs.begin(), fn); + constexpr auto parallel = poolstl::execution::par; + + std::transform(parallel, ins.begin(), ins.end(), outs.begin(), + [&](const auto& spend) NOEXCEPT + { + return to_previous_output(spend); + }); return outs; } diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index fa4b8cec..ad0620d5 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -259,7 +259,7 @@ class query uint32_t input_index) const NOEXCEPT; output_link to_output(const tx_link& link, uint32_t output_index) const NOEXCEPT; - output_link to_prevout(const point_link& link) const NOEXCEPT; + output_link to_previous_output(const point_link& link) const NOEXCEPT; /// block/tx to block (reverse navigation) tx_link to_strong_tx(const tx_link& link) const NOEXCEPT; @@ -346,21 +346,29 @@ class query bool get_tx_height(size_t& out, const tx_link& link) const NOEXCEPT; /// Sizes. - size_t get_tx_size(const tx_link& link, bool witness) const NOEXCEPT; - size_t get_block_size(const header_link& link, bool witness) const NOEXCEPT; + bool get_tx_size(size_t& out, const tx_link& link, + bool witness) const NOEXCEPT; + bool get_block_size(size_t& out, const header_link& link, + bool witness) const NOEXCEPT; bool get_block_sizes(size_t& light, size_t& heavy, const header_link& link) const NOEXCEPT; bool get_tx_sizes(size_t& light, size_t& heavy, const tx_link& link) const NOEXCEPT; - /// Terminal implies not found, false implies fault. + /// Heights. height_link get_height(const hash_digest& key) const NOEXCEPT; height_link get_height(const header_link& link) const NOEXCEPT; bool get_height(size_t& out, const hash_digest& key) const NOEXCEPT; bool get_height(size_t& out, const header_link& link) const NOEXCEPT; + + /// Values (value, spend, fees). bool get_value(uint64_t& out, const output_link& link) const NOEXCEPT; - bool get_unassociated(association& out, - const header_link& link) const NOEXCEPT; + bool get_tx_value(uint64_t& out, const tx_link& link) const NOEXCEPT; + bool get_tx_spend(uint64_t& out, const tx_link& link) const NOEXCEPT; + bool get_tx_fee(uint64_t& out, const tx_link& link) const NOEXCEPT; + bool get_block_value(uint64_t& out, const header_link& link) const NOEXCEPT; + bool get_block_spend(uint64_t& out, const header_link& link) const NOEXCEPT; + bool get_block_fee(uint64_t& out, const header_link& link) const NOEXCEPT; /// Objects. /// ----------------------------------------------------------------------- @@ -407,14 +415,8 @@ class query /// ----------------------------------------------------------------------- /// Virtual size incorporates witnesses if archived. - bool get_tx_virtual_size(uint64_t& out, - const tx_link& link) const NOEXCEPT; - bool get_block_virtual_size(uint64_t& out, - const header_link& link) const NOEXCEPT; - - /// Total fee value by tx or block. - uint64_t get_tx_fee(const tx_link& link) const NOEXCEPT; - uint64_t get_block_fee(const header_link& link) const NOEXCEPT; + bool get_tx_virtual_size(size_t& out, const tx_link& link) const NOEXCEPT; + bool get_block_virtual_size(size_t& out, const header_link& link) const NOEXCEPT; /// Fee rate tuples by tx, block or branch. bool get_tx_fees(fee_rate& out, const tx_link& link) const NOEXCEPT; @@ -641,6 +643,10 @@ class query span get_locator_span(const hashes& locator, const hash_digest& stop, size_t limit) const NOEXCEPT; + /// Support unassociated gathering. + bool get_unassociated(association& out, + const header_link& link) const NOEXCEPT; + /// Translate. /// ----------------------------------------------------------------------- header_link to_block(const tx_link& link) const NOEXCEPT; @@ -654,6 +660,9 @@ class query static inline point::cptr make_point(hash_digest&& hash, uint32_t index) NOEXCEPT; + bool get_outputs_total_value(uint64_t& out, + const output_links& links) const NOEXCEPT; + /// Validate. /// ----------------------------------------------------------------------- inline code to_block_code(linkage::integer value) const NOEXCEPT; diff --git a/include/bitcoin/database/tables/archives/transaction.hpp b/include/bitcoin/database/tables/archives/transaction.hpp index cb4329ba..b7f720b3 100644 --- a/include/bitcoin/database/tables/archives/transaction.hpp +++ b/include/bitcoin/database/tables/archives/transaction.hpp @@ -216,23 +216,26 @@ struct transaction ix::integer outs_count{}; }; - struct get_outs + struct get_puts : public schema::transaction { inline bool from_data(reader& source) NOEXCEPT { - source.skip_bytes(skip_to_ins); + const auto merged = source.read_little_endian(); + coinbase = is_coinbase(merged); + source.skip_bytes(skip_to_ins - bytes::size); ins_count = source.read_little_endian(); outs_count = source.read_little_endian(); - point_fk = source.read_little_endian(); + points_fk = source.read_little_endian(); outs_fk = source.read_little_endian(); return source; } ix::integer ins_count{}; ix::integer outs_count{}; - ins::integer point_fk{}; + ins::integer points_fk{}; outs::integer outs_fk{}; + bool coinbase{}; }; struct get_version @@ -272,18 +275,18 @@ struct transaction if (index >= number) { - point_fk = ins::terminal; + points_fk = ins::terminal; return source; } source.skip_bytes(ix::size); - point_fk = source.read_little_endian() + index; + points_fk = source.read_little_endian() + index; return source; } - // Index provides optional offset for point_fk, number is absolute. + // Index provides optional offset for points_fk, number is absolute. const ins::integer index{}; - ins::integer point_fk{}; + ins::integer points_fk{}; ix::integer number{}; }; diff --git a/test/mocks/blocks.hpp b/test/mocks/blocks.hpp index 7e791e44..4b608f83 100644 --- a/test/mocks/blocks.hpp +++ b/test/mocks/blocks.hpp @@ -86,7 +86,7 @@ const block block1a transactions { // This first transaction is *not* a coinbase. - transaction + transaction // tx#1 { 0x2a, // version inputs @@ -144,7 +144,7 @@ const block block2a transactions { // This first transaction is *not* a coinbase. - transaction + transaction // tx#2 { 0xa2, // version inputs @@ -176,7 +176,7 @@ const block block2a }, 0x81 // locktime }, - transaction + transaction // tx#3 { 0xa2, // version inputs @@ -379,7 +379,7 @@ const block block1b transactions { // This first transaction is a coinbase. - transaction + transaction // tx#1 { 0xb1, inputs @@ -423,7 +423,7 @@ const block block2b transactions { // This first transaction is a coinbase. - transaction + transaction // tx#2 { 0xb2, inputs @@ -495,8 +495,8 @@ const block block_spend_internal_2b }, transactions { - tx2b, - transaction + tx2b, // tx#2 + transaction // tx#3 { 0xb2, inputs @@ -535,8 +535,8 @@ const block block_missing_prevout_2b }, transactions { - tx2b, - transaction + tx2b, // tx#2 + transaction // tx#3 { 0xb2, inputs @@ -575,8 +575,8 @@ const block block_valid_spend_internal_2b }, transactions { - tx2b, - transaction + tx2b, // tx#2 + transaction // tx#3 { 0xb2, inputs @@ -599,7 +599,7 @@ const block block_valid_spend_internal_2b }, 0xb2 }, - transaction + transaction // tx#4 { 0xb2, inputs diff --git a/test/query/fees.cpp b/test/query/fees.cpp index 20917b93..629c1c2a 100644 --- a/test/query/fees.cpp +++ b/test/query/fees.cpp @@ -20,6 +20,10 @@ #include "../mocks/blocks.hpp" #include "../mocks/chunk_store.hpp" +// TODO: +// get_tx_virtual_size +// get_block_virtual_size + BOOST_FIXTURE_TEST_SUITE(query_fees_tests, test::directory_setup_fixture) // nop event handler. @@ -27,54 +31,85 @@ const auto events_handler = [](auto, auto) {}; // get_tx_fee // get_tx_fees +// get_tx_value +// get_tx_spend +// get_tx_virtual_size -BOOST_AUTO_TEST_CASE(query_fees__get_tx_fee__invalid__max_uint64) +BOOST_AUTO_TEST_CASE(query_fees__get_tx_fee__invalid__false) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; test::query_accessor query{ store }; BOOST_CHECK(!store.create(events_handler)); BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_tx_fee(42), max_uint64); + BOOST_CHECK(!query.get_tx_fee(out, 42)); + BOOST_CHECK(!query.get_tx_value(out, 42)); + BOOST_CHECK(!query.get_tx_spend(out, 42)); fee_rate rate{}; BOOST_CHECK(!query.get_tx_fees(rate, 42)); } -BOOST_AUTO_TEST_CASE(query_fees__get_tx_fee__missing_prevouts__max_uint64) +BOOST_AUTO_TEST_CASE(query_fees__get_tx_fee__missing_prevouts__false) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; test::query_accessor query{ store }; BOOST_CHECK(!store.create(events_handler)); BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_tx_fee(0), 0u); + BOOST_CHECK(query.get_tx_fee(out, 0)); + BOOST_CHECK_EQUAL(out, 0u); BOOST_CHECK(query.set(test::block1a, test::context, false, false)); BOOST_CHECK(query.set(test::block2a, test::context, false, false)); - BOOST_CHECK_EQUAL(query.get_tx_fee(3), max_uint64); + + // Missing prevout fails value (and therefore also fee) but spend is valid. + BOOST_CHECK(!query.get_tx_fee(out, 3)); + BOOST_CHECK(!query.get_tx_value(out, 3)); + + // output is 50btc but not spendable. + BOOST_CHECK(query.get_tx_spend(out, 0)); + BOOST_CHECK_EQUAL(out, 5'000'000'000u); + BOOST_CHECK(query.get_tx_spend(out, 1)); + BOOST_CHECK_EQUAL(out, 0x18 + 0x2a); + BOOST_CHECK(query.get_tx_spend(out, 2)); + BOOST_CHECK_EQUAL(out, 0x81); + BOOST_CHECK(query.get_tx_spend(out, 3)); + BOOST_CHECK_EQUAL(out, 0x81); + BOOST_CHECK(!query.get_tx_spend(out, 4)); fee_rate rate{}; BOOST_CHECK(!query.get_tx_fees(rate, 3)); } -BOOST_AUTO_TEST_CASE(query_fees__get_tx_fee__genesis__zero) +BOOST_AUTO_TEST_CASE(query_fees__get_tx_fee__coinbase__zero) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; test::query_accessor query{ store }; BOOST_CHECK(!store.create(events_handler)); BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_tx_fee(0), 0u); + + // Coinbase fee and value are always zero. + BOOST_CHECK(query.get_tx_fee(out, 0)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_value(out, 0)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_spend(out, 0)); + BOOST_CHECK_EQUAL(out, 5'000'000'000u); fee_rate rate{}; - BOOST_CHECK(!query.get_tx_fees(rate, 0u)); + BOOST_CHECK(!query.get_tx_fees(rate, 0)); } BOOST_AUTO_TEST_CASE(query_fees__get_tx_fee__valid_non_coinbase__expected) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; @@ -82,34 +117,48 @@ BOOST_AUTO_TEST_CASE(query_fees__get_tx_fee__valid_non_coinbase__expected) BOOST_CHECK(!store.create(events_handler)); BOOST_CHECK(query.initialize(test::genesis)); BOOST_CHECK(query.set(test::block1b, test::context, false, false)); - BOOST_CHECK(query.set(test::tx2b)); - BOOST_CHECK_EQUAL(query.get_tx_fee(2), 0u); + BOOST_CHECK(query.set(test::block_valid_spend_internal_2b, test::context, false, false)); + BOOST_CHECK(query.get_tx_fee(out, 4)); + BOOST_CHECK_EQUAL(out, (0xb1u + 0xb1u) - 0xb2u); + BOOST_CHECK(query.get_tx_value(out, 4)); + BOOST_CHECK_EQUAL(out, 0xb1u + 0xb1u); + BOOST_CHECK(query.get_tx_spend(out, 4)); + BOOST_CHECK_EQUAL(out, 0xb2u); + + size_t virtual_size{}; + BOOST_CHECK(query.get_tx_virtual_size(virtual_size, 2)); fee_rate rate{}; BOOST_CHECK(query.get_tx_fees(rate, 2)); + BOOST_CHECK_EQUAL(rate.bytes, virtual_size); BOOST_CHECK_EQUAL(rate.bytes, test::tx2b.virtual_size()); BOOST_CHECK_EQUAL(rate.fee, test::tx2b.fee()); } // get_block_fee // get_block_fees +// get_block_value +// get_block_spend +// get_block_virtual_size -BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__invalid__max_uint64) +BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__invalid__false) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; test::query_accessor query{ store }; BOOST_CHECK(!store.create(events_handler)); BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_block_fee(24), max_uint64); + BOOST_CHECK(!query.get_block_fee(out, 24)); fee_rates rates{}; BOOST_CHECK(!query.get_block_fees(rates, 24)); } -BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__block_missing_prevout__max_uint64) +BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__block_missing_prevout__false) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; @@ -118,7 +167,7 @@ BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__block_missing_prevout__max_uint6 BOOST_CHECK(query.initialize(test::genesis)); BOOST_CHECK(query.set(test::block1b, test::context, false, false)); BOOST_CHECK(query.set(test::block_missing_prevout_2b, test::context, false, false)); - BOOST_CHECK_EQUAL(query.get_block_fee(2), max_uint64); + BOOST_CHECK(!query.get_block_fee(out, 2)); fee_rates rates{}; BOOST_CHECK(!query.get_block_fees(rates, 2)); @@ -126,6 +175,7 @@ BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__block_missing_prevout__max_uint6 BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__coinbases__zero) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; @@ -134,7 +184,8 @@ BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__coinbases__zero) BOOST_CHECK(query.initialize(test::genesis)); BOOST_CHECK(query.set(test::block1, test::context, false, false)); BOOST_CHECK(query.set(test::block2, test::context, false, false)); - BOOST_CHECK_EQUAL(query.get_block_fee(2), zero); + BOOST_CHECK(query.get_block_fee(out, 2)); + BOOST_CHECK_EQUAL(out, 0u); fee_rates rates{}; BOOST_CHECK(query.get_block_fees(rates, 2)); @@ -143,6 +194,7 @@ BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__coinbases__zero) BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__valid__expected) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; @@ -151,7 +203,12 @@ BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__valid__expected) BOOST_CHECK(query.initialize(test::genesis)); BOOST_CHECK(query.set(test::block1b, test::context, false, false)); BOOST_CHECK(query.set(test::block_valid_spend_internal_2b, test::context, false, false)); - BOOST_CHECK_EQUAL(query.get_block_fee(2), 0xb1u); + BOOST_CHECK(query.get_block_fee(out, 2)); + BOOST_CHECK_EQUAL(out, 0xb1u); + + size_t virtual_size{}; + BOOST_CHECK(query.get_block_virtual_size(virtual_size, 2)); + BOOST_CHECK_EQUAL(virtual_size, test::block_valid_spend_internal_2b.virtual_size()); // 3 txs - 1 coinbase = 2 rates. fee_rates rates{}; @@ -165,13 +222,15 @@ BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__valid__expected) BOOST_AUTO_TEST_CASE(query_fees__get_block_fee__genesis__zero) { + uint64_t out{}; settings settings{}; settings.path = TEST_DIRECTORY; test::chunk_store store{ settings }; test::query_accessor query{ store }; BOOST_CHECK(!store.create(events_handler)); BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_block_fee(0), 0u); + BOOST_CHECK(query.get_block_fee(out, 0)); + BOOST_CHECK_EQUAL(out, 0u); fee_rates rates{}; BOOST_CHECK(query.get_block_fees(rates, 0)); diff --git a/test/tables/archives/transaction.cpp b/test/tables/archives/transaction.cpp index d7232db4..1ea02602 100644 --- a/test/tables/archives/transaction.cpp +++ b/test/tables/archives/transaction.cpp @@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(transaction__put__get_key__expected) BOOST_REQUIRE_EQUAL(instance.get_key(1), key); } -BOOST_AUTO_TEST_CASE(transaction__put__get_outs__expected) +BOOST_AUTO_TEST_CASE(transaction__put__get_puts__expected) { test::chunk_storage head_store{}; test::chunk_storage body_store{}; @@ -115,11 +115,11 @@ BOOST_AUTO_TEST_CASE(transaction__put__get_outs__expected) BOOST_REQUIRE(!instance.put_link(key, expected).is_terminal()); BOOST_REQUIRE_EQUAL(body_store.buffer(), expected_file); - table::transaction::get_outs element{}; + table::transaction::get_puts element{}; BOOST_REQUIRE(instance.get(1, element)); BOOST_REQUIRE_EQUAL(element.ins_count, 0x00341205_u32); BOOST_REQUIRE_EQUAL(element.outs_count, 0x00341206_u32); - BOOST_REQUIRE_EQUAL(element.point_fk, 0x56341207_u32); + BOOST_REQUIRE_EQUAL(element.points_fk, 0x56341207_u32); BOOST_REQUIRE_EQUAL(element.outs_fk, 0x56341208_u32); BOOST_REQUIRE(!is_multiply_overflow(element.ins_count, schema::put)); BOOST_REQUIRE(!is_add_overflow(element.outs_fk, element.ins_count * schema::put));