From 60e08e5c935808ec6b65b819b43a1ca44ea6e153 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jan 2025 15:05:38 -0500 Subject: [PATCH 01/12] Add header link to prevout caching. --- .../bitcoin/database/impl/query/confirm.ipp | 8 +++- include/bitcoin/database/query.hpp | 2 +- .../database/tables/caches/prevout.hpp | 31 ++++++++++--- include/bitcoin/database/tables/schema.hpp | 2 +- test/tables/caches/prevout.cpp | 45 +++++++++---------- 5 files changed, 53 insertions(+), 35 deletions(-) diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index 63142950..d904c5b8 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -708,17 +708,21 @@ bool CLASS::set_unstrong(const header_link& link) NOEXCEPT } TEMPLATE -bool CLASS::set_prevouts(size_t height, const block& block) NOEXCEPT +bool CLASS::set_prevouts(const header_link& link, const block& block) NOEXCEPT { // Empty or coinbase only implies no spends. if (block.transactions() <= one) return true; + const auto height = get_height(link); + if (height.is_terminal()) + return false; + // ======================================================================== const auto scope = store_.get_transactor(); // Clean single allocation failure (e.g. disk full). - const table::prevout::record_put_ref prevouts{ {}, block }; + const table::prevout::record_put_ref prevouts{ {}, link, block }; return store_.prevout.put(height, prevouts); // ======================================================================== } diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index c7ce1f6c..7eaf4df2 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -507,7 +507,7 @@ class query /// Block association relies on strong (confirmed or pending). bool set_strong(const header_link& link) NOEXCEPT; bool set_unstrong(const header_link& link) NOEXCEPT; - bool set_prevouts(size_t height, const block& block) NOEXCEPT; + bool set_prevouts(const header_link& link, const block& block) NOEXCEPT; code block_confirmable(const header_link& link) const NOEXCEPT; ////code tx_confirmable(const tx_link& link, const context& ctx) const NOEXCEPT; code unspent_duplicates(const header_link& coinbase, diff --git a/include/bitcoin/database/tables/caches/prevout.hpp b/include/bitcoin/database/tables/caches/prevout.hpp index d2757177..388919a8 100644 --- a/include/bitcoin/database/tables/caches/prevout.hpp +++ b/include/bitcoin/database/tables/caches/prevout.hpp @@ -35,9 +35,14 @@ struct prevout : public array_map { using tx = linkage; + using header = linkage; using array_map::arraymap; static constexpr size_t offset = sub1(to_bits(tx::size)); + // First tx-sized entry overloaded for header link. + static_assert(header::size <= tx::size); + + // This supports only a single record (not too useful). struct record : public schema::prevout { @@ -60,15 +65,17 @@ struct prevout inline bool from_data(reader& source) NOEXCEPT { + header_fk = source.read_little_endian(); value = source.read_little_endian(); - BC_ASSERT(!source || source.get_read_position() == minrow); + BC_ASSERT(!source || source.get_read_position() == count() * minrow); return source; } inline bool to_data(finalizer& sink) const NOEXCEPT { + sink.write_little_endian(header_fk); sink.write_little_endian(value); - BC_ASSERT(!sink || sink.get_write_position() == minrow); + BC_ASSERT(!sink || sink.get_write_position() == count() * minrow); return sink; } @@ -78,6 +85,7 @@ struct prevout && output_tx_fk() == other.output_tx_fk(); } + header::integer header_fk{}; tx::integer value{}; }; @@ -95,7 +103,8 @@ struct prevout // This is called once by put(), and hides base count(). inline link count() const NOEXCEPT { - const auto spends = block.spends(); + // First entry overloaded for header (count is records not values). + const auto spends = add1(block.spends()); BC_ASSERT(spends < link::terminal); return system::possible_narrow_cast(spends); } @@ -120,11 +129,16 @@ struct prevout return std::for_each(ins->begin(), ins->end(), write_spend); }; + // First tx-sized entry overloaded for header link. + sink.write_little_endian(header_fk); + std::for_each(std::next(txs.begin()), txs.end(), write_tx); BC_ASSERT(!sink || (sink.get_write_position() == count() * minrow)); return sink; } + // One entry is written for each spend in the block in order. + header::integer header_fk{}; const system::chain::block& block{}; }; @@ -134,19 +148,23 @@ struct prevout // This is called once by assert, and hides base class count(). inline link count() const NOEXCEPT { - BC_ASSERT(values.size() < link::terminal); - return system::possible_narrow_cast(values.size()); + // First entry overloaded for header (count is records not values). + BC_ASSERT(add1(values.size()) < link::terminal); + return system::possible_narrow_cast(add1(values.size())); } inline bool from_data(reader& source) NOEXCEPT { + // First tx-sized entry overloaded for header link. + header_fk = source.read_little_endian(); + // Values must be set to read size (i.e. using knowledge of spends). std::for_each(values.begin(), values.end(), [&](auto& value) NOEXCEPT { value = source.read_little_endian(); }); - BC_ASSERT(!source || source.get_read_position() == count() * minrow); + BC_ASSERT(!source || source.get_read_position() == add1(count()) * minrow); return source; } @@ -176,6 +194,7 @@ struct prevout } // Spend count is derived in confirmation by summing block.txs.puts. + header::integer header_fk{}; std::vector values{}; }; }; diff --git a/include/bitcoin/database/tables/schema.hpp b/include/bitcoin/database/tables/schema.hpp index 08be2035..521f6a0e 100644 --- a/include/bitcoin/database/tables/schema.hpp +++ b/include/bitcoin/database/tables/schema.hpp @@ -330,7 +330,7 @@ namespace schema static constexpr size_t size = minsize; // This is hidden by derivatives, to avoid virtual methods. - inline linkage count() const NOEXCEPT { return one; } + inline linkage count() const NOEXCEPT { return two; } static_assert(minsize == 4u); static_assert(minrow == 4u); }; diff --git a/test/tables/caches/prevout.cpp b/test/tables/caches/prevout.cpp index 1ad2c1cf..3ca063b1 100644 --- a/test/tables/caches/prevout.cpp +++ b/test/tables/caches/prevout.cpp @@ -134,8 +134,8 @@ BOOST_AUTO_TEST_SUITE(prevout_tests) using namespace system; using namespace system::chain; constexpr auto terminal = linkage::terminal; -constexpr table::prevout::record record1{ {}, 0x01020304_u32 }; -constexpr table::prevout::record record2{ {}, 0xbaadf00d_u32 }; +constexpr table::prevout::record record1{ {}, 0x00112233_u32, 0x01020304_u32 }; +constexpr table::prevout::record record2{ {}, 0x00aabbcc_u32, 0xbaadf00d_u32 }; BOOST_AUTO_TEST_CASE(prevout__put__at1__expected) { @@ -151,7 +151,9 @@ BOOST_AUTO_TEST_CASE(prevout__put__at1__expected) // Dereference at key to get link. BOOST_REQUIRE(instance.at(0).is_terminal()); BOOST_REQUIRE_EQUAL(instance.at(3), 0u); - BOOST_REQUIRE_EQUAL(instance.at(42), 1u); + + // First record is two elements, so link for second record is 3. + BOOST_REQUIRE_EQUAL(instance.at(42), 2u); } BOOST_AUTO_TEST_CASE(prevout__put__at2__expected) @@ -204,10 +206,13 @@ BOOST_AUTO_TEST_CASE(prevout__put__get__expected) BOOST_REQUIRE(instance.put(42, record2)); // Get at link. - BOOST_REQUIRE(!instance.get(2, element)); + // First and second records are two elements each, so link for next record is 4. + BOOST_REQUIRE(!instance.get(4, element)); BOOST_REQUIRE(instance.get(0, element)); BOOST_REQUIRE(element == record1); - BOOST_REQUIRE(instance.get(1, element)); + + // First record is two elements, so link for second record is 3. + BOOST_REQUIRE(instance.get(2, element)); BOOST_REQUIRE(element == record2); } @@ -222,15 +227,16 @@ BOOST_AUTO_TEST_CASE(prevout__put__isolated_values__expected) table::prevout instance{ head_store, body_store, 5 }; BOOST_REQUIRE(instance.create()); - constexpr auto cb_only = table::prevout::record{ {}, 0b10000000'00000000'00000000'00000000_u32 }; + constexpr auto cb_only = table::prevout::record{ {}, 42_u32, 0b10000000'00000000'00000000'00000000_u32 }; BOOST_REQUIRE(instance.put(3, cb_only)); - constexpr auto tx_only = table::prevout::record{ {}, 0b01010101'01010101'01010101'01010101_u32 }; + constexpr auto tx_only = table::prevout::record{ {}, 24_u32, 0b01010101'01010101'01010101'01010101_u32 }; BOOST_REQUIRE(instance.put(42, tx_only)); table::prevout::record element1{}; BOOST_REQUIRE(instance.at(3, element1)); BOOST_REQUIRE(element1.coinbase()); + BOOST_REQUIRE_EQUAL(element1.header_fk, 42_u32); BOOST_REQUIRE_EQUAL(element1.coinbase(), cb_only.coinbase()); BOOST_REQUIRE_EQUAL(element1.output_tx_fk(), cb_only.output_tx_fk()); BOOST_REQUIRE_EQUAL(element1.output_tx_fk(), set_right(cb_only.value, bits, false)); @@ -238,6 +244,7 @@ BOOST_AUTO_TEST_CASE(prevout__put__isolated_values__expected) table::prevout::record element2{}; BOOST_REQUIRE(instance.at(42, element2)); BOOST_REQUIRE(!element2.coinbase()); + BOOST_REQUIRE_EQUAL(element2.header_fk, 24_u32); BOOST_REQUIRE_EQUAL(element2.coinbase(), tx_only.coinbase()); BOOST_REQUIRE_EQUAL(element2.output_tx_fk(), tx_only.output_tx_fk()); BOOST_REQUIRE_EQUAL(element2.output_tx_fk(), set_right(tx_only.value, bits, false)); @@ -252,31 +259,19 @@ BOOST_AUTO_TEST_CASE(prevout__put__merged_values__expected) constexpr auto expected_cb = true; constexpr auto expected_tx = 0b01010101'01010101'01010101'01010101_u32; - auto record = table::prevout::record{ {}, 0_u32 }; + auto record = table::prevout::record{ {}, 42_u32, 0_u32 }; record.set(expected_cb, expected_tx); BOOST_REQUIRE(instance.put(3, record)); table::prevout::record element{}; BOOST_REQUIRE(instance.at(3, element)); + BOOST_REQUIRE_EQUAL(element.header_fk, 42_u32); BOOST_REQUIRE_EQUAL(element.coinbase(), expected_cb); BOOST_REQUIRE_EQUAL(element.output_tx_fk(), expected_tx); } // record_put_ref -// Empty block is now guarded at the query level (set_prevouts(...)). -////BOOST_AUTO_TEST_CASE(prevout__record_put_ref__empty_block__false) -////{ -//// test::chunk_storage head_store{}; -//// test::chunk_storage body_store{}; -//// table::prevout instance{ head_store, body_store, 5 }; -//// BOOST_REQUIRE(instance.create()); -//// -//// const auto genesis = system::settings(selection::mainnet).genesis_block; -//// const auto record = table::prevout::record_put_ref{ {}, genesis }; -//// BOOST_REQUIRE(!instance.put(4, record)); -////} - BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata__inside_spend_terminals) { DECLARE_BOGUS_BLOCK; @@ -286,7 +281,7 @@ BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata table::prevout instance{ head_store, body_store, 5 }; BOOST_REQUIRE(instance.create()); - const auto record = table::prevout::record_put_ref{ {}, bogus_block }; + const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; BOOST_REQUIRE(instance.put(2, record)); table::prevout::record_get element{}; @@ -295,7 +290,7 @@ BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata element.values.resize(spends); BOOST_REQUIRE(instance.at(2, element)); - BOOST_REQUIRE_EQUAL(element.count(), 4u); + BOOST_REQUIRE_EQUAL(element.count(), add1(4u)); // First block.tx is coinbase, no spends, so only 4, all terminal (defaults). BOOST_REQUIRE_EQUAL(element.values.at(0), terminal); @@ -369,7 +364,7 @@ BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_metadata__expect table::prevout instance{ head_store, body_store, 5 }; BOOST_REQUIRE(instance.create()); - const auto record = table::prevout::record_put_ref{ {}, bogus_block }; + const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; BOOST_REQUIRE(instance.put(2, record)); table::prevout::record_get element{}; @@ -378,7 +373,7 @@ BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_metadata__expect element.values.resize(spends); BOOST_REQUIRE(instance.at(2, element)); - BOOST_REQUIRE_EQUAL(element.count(), spends); + BOOST_REQUIRE_EQUAL(element.count(), add1(spends)); // First block.tx is coinbase, no spends, so only 4, none terminal. BOOST_REQUIRE_NE(element.values.at(0), terminal); From e1494a3f26fcf74873c1e7aec9c66ffdb65667dd Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 14 Jan 2025 11:54:25 -0500 Subject: [PATCH 02/12] Prevout optimization WIP. --- .../bitcoin/database/impl/query/confirm.ipp | 69 ++++++++++++------- .../bitcoin/database/impl/query/translate.ipp | 6 +- include/bitcoin/database/query.hpp | 9 ++- .../database/tables/caches/prevout.hpp | 4 ++ 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index d904c5b8..2ce5d778 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -338,31 +338,31 @@ error::error_t CLASS::spent_prevout(const point_link& link, index index, // protected TEMPLATE -error::error_t CLASS::unspendable_prevout(const point_link& link, - uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT +error::error_t CLASS::unspendable_prevout(uint32_t sequence, bool coinbase, + const tx_link& prevout_tx, uint32_t version, + const context& ctx) const NOEXCEPT { - // TODO: If unconfirmed_spend is encountered, perform a search (free). - // It's not possible for a confirmed spend to be the wrong tx instance. - // This eliminates the hash lookup and to_strong(hash) iteration. - // TODO: don't need to return tx link here, just the block (for strong/context). // MOOT: get_point_key(link) is redundant with spent_prevout(). // to_strong has the only searches [tx.iterate, strong.find]. - const auto strong_prevout = to_strong(get_point_key(link)); + ////const auto strong_prevout = to_strong(get_point_key(link)); + //// + ////// prevout is strong if present. + ////if (strong_prevout.block.is_terminal()) + //// return strong_prevout.tx.is_terminal() ? + //// error::missing_previous_output : error::unconfirmed_spend; - // prevout is strong if present. - if (strong_prevout.block.is_terminal()) - return strong_prevout.tx.is_terminal() ? - error::missing_previous_output : error::unconfirmed_spend; + // TODO: If unconfirmed_spend is encountered, perform a search (free). + // It's not possible for a confirmed spend to be the wrong tx instance. + // This eliminates the hash lookup and to_strong(hash) iteration. + const auto block = to_block(prevout_tx); context out{}; - if (!get_context(out, strong_prevout.block)) + if (!get_context(out, block)) return error::integrity; - // All txs with same hash must be coinbase or not, so this query is redundant. - // TODO: Just use the cached value for the prevout, obtained in validation. - if (is_coinbase(strong_prevout.tx) && - !transaction::is_coinbase_mature(out.height, ctx.height)) + // All txs with same hash must be coinbase or not. + if (coinbase && !transaction::is_coinbase_mature(out.height, ctx.height)) return error::coinbase_maturity; if (ctx.is_enabled(system::chain::flags::bip68_rule) && @@ -502,16 +502,37 @@ spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT if (txs.empty()) return {}; - spend_sets out{ txs.size() }; + spend_sets sets{ txs.size() }; const auto to_set = [this](const auto& tx) NOEXCEPT { return to_spend_set(tx); }; // C++17 incomplete on GCC/CLang, so presently parallel only on MSVC++. - std_transform(bc::par_unseq, txs.begin(), txs.end(), out.begin(), to_set); + 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); + + // TODO: deal with exta link element in data set before testing. + table::prevout::record_get prevouts{}; + prevouts.values.resize(spends); + store_.prevout.at(get_height(link), prevouts); - return out; + size_t index{}; + for (auto& set: sets) + { + for (auto& spend: set.spends) + { + spend.coinbase = prevouts.coinbase(index); + spend.prevout_tx_fk = prevouts.output_tx_fk(index++); + } + } + + return sets; } // split(3) 219 secs for 400k-410k; split(2) 255 and split(0) 456 (not shown). @@ -527,7 +548,7 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT if ((ec = unspent_duplicates(link, ctx))) return ec; - // This is eliminated by caching, since each non-internal spend is cached. + // TODO: can be eliminated by caching each non-internal spend link. const auto sets = to_spend_sets(link); if (sets.empty()) return ec; @@ -536,10 +557,11 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT 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) - if ((ec = unspendable_prevout(spend.point_fk, spend.sequence, - set.version, ctx))) + if ((ec = unspendable_prevout(spend.sequence, spend.coinbase, + spend.prevout_tx_fk, set.version, ctx))) { fault.store(ec); return true; @@ -550,8 +572,9 @@ 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 auto& spend: set.spends) + for (const spend_set::spend& spend: set.spends) if ((ec = spent_prevout(spend.point_fk, spend.point_index, set.tx))) { fault.store(ec); diff --git a/include/bitcoin/database/impl/query/translate.ipp b/include/bitcoin/database/impl/query/translate.ipp index b102a0ff..a94eeb46 100644 --- a/include/bitcoin/database/impl/query/translate.ipp +++ b/include/bitcoin/database/impl/query/translate.ipp @@ -506,9 +506,11 @@ spend_set CLASS::to_spend_set(const tx_link& link) const NOEXCEPT // 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 }); + 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); + set.spends.emplace_back(get.point_fk, get.point_index, get.sequence, + table::prevout::tx::integer{}, bool{}); #endif } diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 7eaf4df2..43406437 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -67,9 +67,14 @@ struct spend_set return point_fk == table::spend::pt::terminal; } + // From tx input. table::spend::pt::integer point_fk{}; table::spend::ix::integer point_index{}; uint32_t sequence{}; + + // From prevouts table. + table::prevout::tx::integer prevout_tx_fk{}; + bool coinbase{}; }; tx_link tx{}; @@ -581,8 +586,8 @@ class query const tx_link& self=tx_link::terminal) const NOEXCEPT; error::error_t spent_prevout(const point_link& link, index index, const tx_link& self=tx_link::terminal) const NOEXCEPT; - error::error_t unspendable_prevout(const point_link& link, - uint32_t sequence, uint32_t version, + error::error_t unspendable_prevout(uint32_t sequence, bool coinbase, + const tx_link& prevout_tx, uint32_t version, const context& ctx) const NOEXCEPT; bool set_strong(const header_link& link, const tx_links& txs, bool positive) NOEXCEPT; diff --git a/include/bitcoin/database/tables/caches/prevout.hpp b/include/bitcoin/database/tables/caches/prevout.hpp index 388919a8..57c8e2f9 100644 --- a/include/bitcoin/database/tables/caches/prevout.hpp +++ b/include/bitcoin/database/tables/caches/prevout.hpp @@ -158,6 +158,10 @@ struct prevout // First tx-sized entry overloaded for header link. header_fk = source.read_little_endian(); + // TODO: Can be optimized using unsafe_array_cast copy, as long as + // TODO: endianness lines up. Should have writer methods for native + // TODO: endianness so that both big and little are optimal. But + // TODO: this would prevent store portability across endianness. // Values must be set to read size (i.e. using knowledge of spends). std::for_each(values.begin(), values.end(), [&](auto& value) NOEXCEPT { From 58c6b72c3d29c0b7b4ac65a26b8fd3d3798579c7 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 19 Jan 2025 21:54:13 -0500 Subject: [PATCH 03/12] Disable prevout link inclusion. --- .../bitcoin/database/impl/query/confirm.ipp | 2 +- .../database/tables/caches/prevout.hpp | 14 +- test/tables/caches/prevout.cpp | 296 +++++++++--------- 3 files changed, 156 insertions(+), 156 deletions(-) diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index 2ce5d778..088008de 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -745,7 +745,7 @@ bool CLASS::set_prevouts(const header_link& link, const block& block) NOEXCEPT const auto scope = store_.get_transactor(); // Clean single allocation failure (e.g. disk full). - const table::prevout::record_put_ref prevouts{ {}, link, block }; + const table::prevout::record_put_ref prevouts{ {}, /*link,*/ block }; return store_.prevout.put(height, prevouts); // ======================================================================== } diff --git a/include/bitcoin/database/tables/caches/prevout.hpp b/include/bitcoin/database/tables/caches/prevout.hpp index 57c8e2f9..6fe21d4d 100644 --- a/include/bitcoin/database/tables/caches/prevout.hpp +++ b/include/bitcoin/database/tables/caches/prevout.hpp @@ -104,7 +104,7 @@ struct prevout inline link count() const NOEXCEPT { // First entry overloaded for header (count is records not values). - const auto spends = add1(block.spends()); + const auto spends = /*add1*/(block.spends()); BC_ASSERT(spends < link::terminal); return system::possible_narrow_cast(spends); } @@ -130,7 +130,7 @@ struct prevout }; // First tx-sized entry overloaded for header link. - sink.write_little_endian(header_fk); + ////sink.write_little_endian(header_fk); std::for_each(std::next(txs.begin()), txs.end(), write_tx); BC_ASSERT(!sink || (sink.get_write_position() == count() * minrow)); @@ -138,7 +138,7 @@ struct prevout } // One entry is written for each spend in the block in order. - header::integer header_fk{}; + ////header::integer header_fk{}; const system::chain::block& block{}; }; @@ -149,14 +149,14 @@ struct prevout inline link count() const NOEXCEPT { // First entry overloaded for header (count is records not values). - BC_ASSERT(add1(values.size()) < link::terminal); - return system::possible_narrow_cast(add1(values.size())); + BC_ASSERT(/*add1*/(values.size()) < link::terminal); + return system::possible_narrow_cast(/*add1*/(values.size())); } inline bool from_data(reader& source) NOEXCEPT { // First tx-sized entry overloaded for header link. - header_fk = source.read_little_endian(); + ////header_fk = source.read_little_endian(); // TODO: Can be optimized using unsafe_array_cast copy, as long as // TODO: endianness lines up. Should have writer methods for native @@ -198,7 +198,7 @@ struct prevout } // Spend count is derived in confirmation by summing block.txs.puts. - header::integer header_fk{}; + ////header::integer header_fk{}; std::vector values{}; }; }; diff --git a/test/tables/caches/prevout.cpp b/test/tables/caches/prevout.cpp index 3ca063b1..9f05c8b1 100644 --- a/test/tables/caches/prevout.cpp +++ b/test/tables/caches/prevout.cpp @@ -250,153 +250,153 @@ BOOST_AUTO_TEST_CASE(prevout__put__isolated_values__expected) BOOST_REQUIRE_EQUAL(element2.output_tx_fk(), set_right(tx_only.value, bits, false)); } -BOOST_AUTO_TEST_CASE(prevout__put__merged_values__expected) -{ - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - table::prevout instance{ head_store, body_store, 5 }; - BOOST_REQUIRE(instance.create()); - - constexpr auto expected_cb = true; - constexpr auto expected_tx = 0b01010101'01010101'01010101'01010101_u32; - auto record = table::prevout::record{ {}, 42_u32, 0_u32 }; - record.set(expected_cb, expected_tx); - BOOST_REQUIRE(instance.put(3, record)); - - table::prevout::record element{}; - BOOST_REQUIRE(instance.at(3, element)); - BOOST_REQUIRE_EQUAL(element.header_fk, 42_u32); - BOOST_REQUIRE_EQUAL(element.coinbase(), expected_cb); - BOOST_REQUIRE_EQUAL(element.output_tx_fk(), expected_tx); -} - -// record_put_ref - -BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata__inside_spend_terminals) -{ - DECLARE_BOGUS_BLOCK; - - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - table::prevout instance{ head_store, body_store, 5 }; - BOOST_REQUIRE(instance.create()); - - const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; - BOOST_REQUIRE(instance.put(2, record)); - - table::prevout::record_get element{}; - const auto spends = bogus_block.spends(); - BOOST_REQUIRE_EQUAL(spends, 4u); - - element.values.resize(spends); - BOOST_REQUIRE(instance.at(2, element)); - BOOST_REQUIRE_EQUAL(element.count(), add1(4u)); - - // First block.tx is coinbase, no spends, so only 4, all terminal (defaults). - BOOST_REQUIRE_EQUAL(element.values.at(0), terminal); - BOOST_REQUIRE_EQUAL(element.values.at(1), terminal); - BOOST_REQUIRE_EQUAL(element.values.at(2), terminal); - BOOST_REQUIRE_EQUAL(element.values.at(3), terminal); - - // Block-internal spend. - // Positionally identifies spends not requiring a double spend check. - // Blocks are guarded agianst internal double spends, and tx previously - // confirmed would imply a double spend of it or one of its ancestors. - BOOST_REQUIRE(element.inside(0)); - BOOST_REQUIRE(element.inside(1)); - BOOST_REQUIRE(element.inside(2)); - BOOST_REQUIRE(element.inside(3)); - - // Inside are always reflected as coinbase. - BOOST_REQUIRE(element.coinbase(0)); - BOOST_REQUIRE(element.coinbase(1)); - BOOST_REQUIRE(element.coinbase(2)); - BOOST_REQUIRE(element.coinbase(3)); - - // Inside are always mapped to terminal. - BOOST_REQUIRE_EQUAL(element.output_tx_fk(0), terminal); - BOOST_REQUIRE_EQUAL(element.output_tx_fk(1), terminal); - BOOST_REQUIRE_EQUAL(element.output_tx_fk(2), terminal); - BOOST_REQUIRE_EQUAL(element.output_tx_fk(3), terminal); -} - -BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_metadata__expected) -{ - DECLARE_BOGUS_BLOCK; - - const auto tx0 = bogus_block.transactions_ptr()->at(0); - const auto tx1 = bogus_block.transactions_ptr()->at(1); - const auto tx2 = bogus_block.transactions_ptr()->at(2); - - const auto& in0_0 = *tx0->inputs_ptr()->at(0); - const auto& in0_1 = *tx0->inputs_ptr()->at(1); - const auto& in1_0 = *tx1->inputs_ptr()->at(0); - const auto& in1_1 = *tx1->inputs_ptr()->at(1); - const auto& in2_0 = *tx2->inputs_ptr()->at(0); - const auto& in2_1 = *tx2->inputs_ptr()->at(1); - - // Coinbase identifies parent of prevout (spent), not input (spender). - in0_0.metadata.parent = 0x01234560_u32; - in0_1.metadata.parent = 0x01234561_u32; - in1_0.metadata.parent = 0x01234562_u32; - in1_1.metadata.parent = 0x01234563_u32; - in2_0.metadata.parent = 0x01234564_u32; - in2_1.metadata.parent = 0x01234565_u32; - - // Coinbase identifies prevout (spent), not input (spender). - in0_0.metadata.coinbase = false; - in0_1.metadata.coinbase = true; - in1_0.metadata.coinbase = false; - in1_1.metadata.coinbase = false; - in2_0.metadata.coinbase = false; - in2_1.metadata.coinbase = true; - - // Inside implies prevout spent in block (terminal). - in0_0.metadata.inside = false; - in0_1.metadata.inside = true; - in1_0.metadata.inside = false; - in1_1.metadata.inside = false; - in2_0.metadata.inside = true; - in2_1.metadata.inside = false; - - test::chunk_storage head_store{}; - test::chunk_storage body_store{}; - table::prevout instance{ head_store, body_store, 5 }; - BOOST_REQUIRE(instance.create()); - - const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; - BOOST_REQUIRE(instance.put(2, record)); - - table::prevout::record_get element{}; - const auto spends = bogus_block.spends(); - BOOST_REQUIRE_EQUAL(spends, 4u); - - element.values.resize(spends); - BOOST_REQUIRE(instance.at(2, element)); - BOOST_REQUIRE_EQUAL(element.count(), add1(spends)); - - // First block.tx is coinbase, no spends, so only 4, none terminal. - BOOST_REQUIRE_NE(element.values.at(0), terminal); - BOOST_REQUIRE_NE(element.values.at(1), terminal); - BOOST_REQUIRE_EQUAL(element.values.at(2), terminal); - BOOST_REQUIRE_NE(element.values.at(3), terminal); - - // Inside spends are used as positional placeholders for double spend check bypass. - BOOST_REQUIRE(!element.inside(0)); - BOOST_REQUIRE(!element.inside(1)); - BOOST_REQUIRE(element.inside(2)); - BOOST_REQUIRE(!element.inside(3)); - - BOOST_REQUIRE(!element.coinbase(0)); - BOOST_REQUIRE(!element.coinbase(1)); - BOOST_REQUIRE(element.coinbase(2)); - BOOST_REQUIRE(element.coinbase(3)); - - // Spend ordinal position relative to the block must be preserved (coinbase excluded). - BOOST_REQUIRE_EQUAL(element.output_tx_fk(0), in1_0.metadata.parent); - BOOST_REQUIRE_EQUAL(element.output_tx_fk(1), in1_1.metadata.parent); - BOOST_REQUIRE_EQUAL(element.output_tx_fk(2), terminal); - BOOST_REQUIRE_EQUAL(element.output_tx_fk(3), in2_1.metadata.parent); -} +////BOOST_AUTO_TEST_CASE(prevout__put__merged_values__expected) +////{ +//// test::chunk_storage head_store{}; +//// test::chunk_storage body_store{}; +//// table::prevout instance{ head_store, body_store, 5 }; +//// BOOST_REQUIRE(instance.create()); +//// +//// constexpr auto expected_cb = true; +//// constexpr auto expected_tx = 0b01010101'01010101'01010101'01010101_u32; +//// auto record = table::prevout::record{ {}, 42_u32, 0_u32 }; +//// record.set(expected_cb, expected_tx); +//// BOOST_REQUIRE(instance.put(3, record)); +//// +//// table::prevout::record element{}; +//// BOOST_REQUIRE(instance.at(3, element)); +//// BOOST_REQUIRE_EQUAL(element.header_fk, 42_u32); +//// BOOST_REQUIRE_EQUAL(element.coinbase(), expected_cb); +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(), expected_tx); +////} +//// +////// record_put_ref +//// +////BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata__inside_spend_terminals) +////{ +//// DECLARE_BOGUS_BLOCK; +//// +//// test::chunk_storage head_store{}; +//// test::chunk_storage body_store{}; +//// table::prevout instance{ head_store, body_store, 5 }; +//// BOOST_REQUIRE(instance.create()); +//// +//// const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; +//// BOOST_REQUIRE(instance.put(2, record)); +//// +//// table::prevout::record_get element{}; +//// const auto spends = bogus_block.spends(); +//// BOOST_REQUIRE_EQUAL(spends, 4u); +//// +//// element.values.resize(spends); +//// BOOST_REQUIRE(instance.at(2, element)); +//// BOOST_REQUIRE_EQUAL(element.count(), add1(4u)); +//// +//// // First block.tx is coinbase, no spends, so only 4, all terminal (defaults). +//// BOOST_REQUIRE_EQUAL(element.values.at(0), terminal); +//// BOOST_REQUIRE_EQUAL(element.values.at(1), terminal); +//// BOOST_REQUIRE_EQUAL(element.values.at(2), terminal); +//// BOOST_REQUIRE_EQUAL(element.values.at(3), terminal); +//// +//// // Block-internal spend. +//// // Positionally identifies spends not requiring a double spend check. +//// // Blocks are guarded agianst internal double spends, and tx previously +//// // confirmed would imply a double spend of it or one of its ancestors. +//// BOOST_REQUIRE(element.inside(0)); +//// BOOST_REQUIRE(element.inside(1)); +//// BOOST_REQUIRE(element.inside(2)); +//// BOOST_REQUIRE(element.inside(3)); +//// +//// // Inside are always reflected as coinbase. +//// BOOST_REQUIRE(element.coinbase(0)); +//// BOOST_REQUIRE(element.coinbase(1)); +//// BOOST_REQUIRE(element.coinbase(2)); +//// BOOST_REQUIRE(element.coinbase(3)); +//// +//// // Inside are always mapped to terminal. +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(0), terminal); +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(1), terminal); +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(2), terminal); +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(3), terminal); +////} +//// +////BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_metadata__expected) +////{ +//// DECLARE_BOGUS_BLOCK; +//// +//// const auto tx0 = bogus_block.transactions_ptr()->at(0); +//// const auto tx1 = bogus_block.transactions_ptr()->at(1); +//// const auto tx2 = bogus_block.transactions_ptr()->at(2); +//// +//// const auto& in0_0 = *tx0->inputs_ptr()->at(0); +//// const auto& in0_1 = *tx0->inputs_ptr()->at(1); +//// const auto& in1_0 = *tx1->inputs_ptr()->at(0); +//// const auto& in1_1 = *tx1->inputs_ptr()->at(1); +//// const auto& in2_0 = *tx2->inputs_ptr()->at(0); +//// const auto& in2_1 = *tx2->inputs_ptr()->at(1); +//// +//// // Coinbase identifies parent of prevout (spent), not input (spender). +//// in0_0.metadata.parent = 0x01234560_u32; +//// in0_1.metadata.parent = 0x01234561_u32; +//// in1_0.metadata.parent = 0x01234562_u32; +//// in1_1.metadata.parent = 0x01234563_u32; +//// in2_0.metadata.parent = 0x01234564_u32; +//// in2_1.metadata.parent = 0x01234565_u32; +//// +//// // Coinbase identifies prevout (spent), not input (spender). +//// in0_0.metadata.coinbase = false; +//// in0_1.metadata.coinbase = true; +//// in1_0.metadata.coinbase = false; +//// in1_1.metadata.coinbase = false; +//// in2_0.metadata.coinbase = false; +//// in2_1.metadata.coinbase = true; +//// +//// // Inside implies prevout spent in block (terminal). +//// in0_0.metadata.inside = false; +//// in0_1.metadata.inside = true; +//// in1_0.metadata.inside = false; +//// in1_1.metadata.inside = false; +//// in2_0.metadata.inside = true; +//// in2_1.metadata.inside = false; +//// +//// test::chunk_storage head_store{}; +//// test::chunk_storage body_store{}; +//// table::prevout instance{ head_store, body_store, 5 }; +//// BOOST_REQUIRE(instance.create()); +//// +//// const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; +//// BOOST_REQUIRE(instance.put(2, record)); +//// +//// table::prevout::record_get element{}; +//// const auto spends = bogus_block.spends(); +//// BOOST_REQUIRE_EQUAL(spends, 4u); +//// +//// element.values.resize(spends); +//// BOOST_REQUIRE(instance.at(2, element)); +//// BOOST_REQUIRE_EQUAL(element.count(), add1(spends)); +//// +//// // First block.tx is coinbase, no spends, so only 4, none terminal. +//// BOOST_REQUIRE_NE(element.values.at(0), terminal); +//// BOOST_REQUIRE_NE(element.values.at(1), terminal); +//// BOOST_REQUIRE_EQUAL(element.values.at(2), terminal); +//// BOOST_REQUIRE_NE(element.values.at(3), terminal); +//// +//// // Inside spends are used as positional placeholders for double spend check bypass. +//// BOOST_REQUIRE(!element.inside(0)); +//// BOOST_REQUIRE(!element.inside(1)); +//// BOOST_REQUIRE(element.inside(2)); +//// BOOST_REQUIRE(!element.inside(3)); +//// +//// BOOST_REQUIRE(!element.coinbase(0)); +//// BOOST_REQUIRE(!element.coinbase(1)); +//// BOOST_REQUIRE(element.coinbase(2)); +//// BOOST_REQUIRE(element.coinbase(3)); +//// +//// // Spend ordinal position relative to the block must be preserved (coinbase excluded). +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(0), in1_0.metadata.parent); +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(1), in1_1.metadata.parent); +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(2), terminal); +//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(3), in2_1.metadata.parent); +////} BOOST_AUTO_TEST_SUITE_END() From 6492151565fc0acfdaffd46fce3aabc1432edd8c Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 19 Jan 2025 21:55:46 -0500 Subject: [PATCH 04/12] Bypass confirmability checks for internal spends, comments, style. --- .../bitcoin/database/impl/query/confirm.ipp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index 088008de..d217c049 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -517,20 +517,18 @@ spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT }; const auto spends = std::accumulate(sets.begin(), sets.end(), zero, count); - // TODO: deal with exta link element in data set before testing. + ////// TODO: deal with exta link element in data set before testing. table::prevout::record_get prevouts{}; prevouts.values.resize(spends); store_.prevout.at(get_height(link), prevouts); size_t index{}; for (auto& set: sets) - { for (auto& spend: set.spends) { spend.coinbase = prevouts.coinbase(index); spend.prevout_tx_fk = prevouts.output_tx_fk(index++); } - } return sets; } @@ -547,8 +545,7 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT code ec{}; if ((ec = unspent_duplicates(link, ctx))) return ec; - - // TODO: can be eliminated by caching each non-internal spend link. + const auto sets = to_spend_sets(link); if (sets.empty()) return ec; @@ -560,12 +557,17 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT // TODO: prevout table optimized, evaluate. error::error_t ec{}; for (const auto& spend: set.spends) + { + if (spend.prevout_tx_fk == table::prevout::tx::terminal) + continue; + if ((ec = unspendable_prevout(spend.sequence, spend.coinbase, - spend.prevout_tx_fk, set.version, ctx))) + spend.prevout_tx_fk, set.version, ctx))) { fault.store(ec); return true; } + } return false; }; @@ -575,11 +577,16 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT // TODO: point table optimize via consolidation with spend table. error::error_t ec{}; for (const spend_set::spend& spend: set.spends) + { + if (spend.prevout_tx_fk == table::prevout::tx::terminal) + continue; + if ((ec = spent_prevout(spend.point_fk, spend.point_index, set.tx))) { fault.store(ec); return true; } + } return false; }; From 77936106df018e495c24f61ff79184d1d5cd4b51 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 9 Dec 2024 23:51:12 -0500 Subject: [PATCH 05/12] ignore .vs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 334e771f..c34d6d41 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ build *.vsidx *.lock +/.vs From e66e3575b92555a1e597946e4a66029387950ad8 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 20 Jan 2025 23:14:03 -0500 Subject: [PATCH 06/12] Remove add1 from prevout assert. --- include/bitcoin/database/tables/caches/prevout.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bitcoin/database/tables/caches/prevout.hpp b/include/bitcoin/database/tables/caches/prevout.hpp index 6fe21d4d..f2d1a36f 100644 --- a/include/bitcoin/database/tables/caches/prevout.hpp +++ b/include/bitcoin/database/tables/caches/prevout.hpp @@ -168,7 +168,7 @@ struct prevout value = source.read_little_endian(); }); - BC_ASSERT(!source || source.get_read_position() == add1(count()) * minrow); + BC_ASSERT(!source || source.get_read_position() == /*add1*/(count()) * minrow); return source; } From 9db49ddcf9d5ccfd75ba380180bd3cbae52a1b02 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 20 Jan 2025 23:14:28 -0500 Subject: [PATCH 07/12] Remove self check from unspent_duplicates. --- include/bitcoin/database/impl/query/confirm.ipp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index d217c049..628ed0c4 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -387,15 +387,10 @@ 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))); - // Found only this block's coinbase instance, no duplicates. - if (is_one(coinbases.size())) + // Current block is not set strong. + if (is_zero(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 { From 506fca9dfc4cbc04ed8de073a6f217ac91dffcb8 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 20 Jan 2025 23:51:19 -0500 Subject: [PATCH 08/12] Revert "Remove add1 from prevout assert." This reverts commit e66e3575b92555a1e597946e4a66029387950ad8. --- include/bitcoin/database/tables/caches/prevout.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bitcoin/database/tables/caches/prevout.hpp b/include/bitcoin/database/tables/caches/prevout.hpp index f2d1a36f..6fe21d4d 100644 --- a/include/bitcoin/database/tables/caches/prevout.hpp +++ b/include/bitcoin/database/tables/caches/prevout.hpp @@ -168,7 +168,7 @@ struct prevout value = source.read_little_endian(); }); - BC_ASSERT(!source || source.get_read_position() == /*add1*/(count()) * minrow); + BC_ASSERT(!source || source.get_read_position() == add1(count()) * minrow); return source; } From 08cfb4fd1fd0e823986607e896321cefc48f5dde Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 20 Jan 2025 23:51:20 -0500 Subject: [PATCH 09/12] Revert "Disable prevout link inclusion." This reverts commit 58c6b72c3d29c0b7b4ac65a26b8fd3d3798579c7. --- .../bitcoin/database/impl/query/confirm.ipp | 2 +- .../database/tables/caches/prevout.hpp | 14 +- test/tables/caches/prevout.cpp | 296 +++++++++--------- 3 files changed, 156 insertions(+), 156 deletions(-) diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index 628ed0c4..7d9e2df8 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -747,7 +747,7 @@ bool CLASS::set_prevouts(const header_link& link, const block& block) NOEXCEPT const auto scope = store_.get_transactor(); // Clean single allocation failure (e.g. disk full). - const table::prevout::record_put_ref prevouts{ {}, /*link,*/ block }; + const table::prevout::record_put_ref prevouts{ {}, link, block }; return store_.prevout.put(height, prevouts); // ======================================================================== } diff --git a/include/bitcoin/database/tables/caches/prevout.hpp b/include/bitcoin/database/tables/caches/prevout.hpp index 6fe21d4d..57c8e2f9 100644 --- a/include/bitcoin/database/tables/caches/prevout.hpp +++ b/include/bitcoin/database/tables/caches/prevout.hpp @@ -104,7 +104,7 @@ struct prevout inline link count() const NOEXCEPT { // First entry overloaded for header (count is records not values). - const auto spends = /*add1*/(block.spends()); + const auto spends = add1(block.spends()); BC_ASSERT(spends < link::terminal); return system::possible_narrow_cast(spends); } @@ -130,7 +130,7 @@ struct prevout }; // First tx-sized entry overloaded for header link. - ////sink.write_little_endian(header_fk); + sink.write_little_endian(header_fk); std::for_each(std::next(txs.begin()), txs.end(), write_tx); BC_ASSERT(!sink || (sink.get_write_position() == count() * minrow)); @@ -138,7 +138,7 @@ struct prevout } // One entry is written for each spend in the block in order. - ////header::integer header_fk{}; + header::integer header_fk{}; const system::chain::block& block{}; }; @@ -149,14 +149,14 @@ struct prevout inline link count() const NOEXCEPT { // First entry overloaded for header (count is records not values). - BC_ASSERT(/*add1*/(values.size()) < link::terminal); - return system::possible_narrow_cast(/*add1*/(values.size())); + BC_ASSERT(add1(values.size()) < link::terminal); + return system::possible_narrow_cast(add1(values.size())); } inline bool from_data(reader& source) NOEXCEPT { // First tx-sized entry overloaded for header link. - ////header_fk = source.read_little_endian(); + header_fk = source.read_little_endian(); // TODO: Can be optimized using unsafe_array_cast copy, as long as // TODO: endianness lines up. Should have writer methods for native @@ -198,7 +198,7 @@ struct prevout } // Spend count is derived in confirmation by summing block.txs.puts. - ////header::integer header_fk{}; + header::integer header_fk{}; std::vector values{}; }; }; diff --git a/test/tables/caches/prevout.cpp b/test/tables/caches/prevout.cpp index 9f05c8b1..3ca063b1 100644 --- a/test/tables/caches/prevout.cpp +++ b/test/tables/caches/prevout.cpp @@ -250,153 +250,153 @@ BOOST_AUTO_TEST_CASE(prevout__put__isolated_values__expected) BOOST_REQUIRE_EQUAL(element2.output_tx_fk(), set_right(tx_only.value, bits, false)); } -////BOOST_AUTO_TEST_CASE(prevout__put__merged_values__expected) -////{ -//// test::chunk_storage head_store{}; -//// test::chunk_storage body_store{}; -//// table::prevout instance{ head_store, body_store, 5 }; -//// BOOST_REQUIRE(instance.create()); -//// -//// constexpr auto expected_cb = true; -//// constexpr auto expected_tx = 0b01010101'01010101'01010101'01010101_u32; -//// auto record = table::prevout::record{ {}, 42_u32, 0_u32 }; -//// record.set(expected_cb, expected_tx); -//// BOOST_REQUIRE(instance.put(3, record)); -//// -//// table::prevout::record element{}; -//// BOOST_REQUIRE(instance.at(3, element)); -//// BOOST_REQUIRE_EQUAL(element.header_fk, 42_u32); -//// BOOST_REQUIRE_EQUAL(element.coinbase(), expected_cb); -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(), expected_tx); -////} -//// -////// record_put_ref -//// -////BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata__inside_spend_terminals) -////{ -//// DECLARE_BOGUS_BLOCK; -//// -//// test::chunk_storage head_store{}; -//// test::chunk_storage body_store{}; -//// table::prevout instance{ head_store, body_store, 5 }; -//// BOOST_REQUIRE(instance.create()); -//// -//// const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; -//// BOOST_REQUIRE(instance.put(2, record)); -//// -//// table::prevout::record_get element{}; -//// const auto spends = bogus_block.spends(); -//// BOOST_REQUIRE_EQUAL(spends, 4u); -//// -//// element.values.resize(spends); -//// BOOST_REQUIRE(instance.at(2, element)); -//// BOOST_REQUIRE_EQUAL(element.count(), add1(4u)); -//// -//// // First block.tx is coinbase, no spends, so only 4, all terminal (defaults). -//// BOOST_REQUIRE_EQUAL(element.values.at(0), terminal); -//// BOOST_REQUIRE_EQUAL(element.values.at(1), terminal); -//// BOOST_REQUIRE_EQUAL(element.values.at(2), terminal); -//// BOOST_REQUIRE_EQUAL(element.values.at(3), terminal); -//// -//// // Block-internal spend. -//// // Positionally identifies spends not requiring a double spend check. -//// // Blocks are guarded agianst internal double spends, and tx previously -//// // confirmed would imply a double spend of it or one of its ancestors. -//// BOOST_REQUIRE(element.inside(0)); -//// BOOST_REQUIRE(element.inside(1)); -//// BOOST_REQUIRE(element.inside(2)); -//// BOOST_REQUIRE(element.inside(3)); -//// -//// // Inside are always reflected as coinbase. -//// BOOST_REQUIRE(element.coinbase(0)); -//// BOOST_REQUIRE(element.coinbase(1)); -//// BOOST_REQUIRE(element.coinbase(2)); -//// BOOST_REQUIRE(element.coinbase(3)); -//// -//// // Inside are always mapped to terminal. -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(0), terminal); -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(1), terminal); -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(2), terminal); -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(3), terminal); -////} -//// -////BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_metadata__expected) -////{ -//// DECLARE_BOGUS_BLOCK; -//// -//// const auto tx0 = bogus_block.transactions_ptr()->at(0); -//// const auto tx1 = bogus_block.transactions_ptr()->at(1); -//// const auto tx2 = bogus_block.transactions_ptr()->at(2); -//// -//// const auto& in0_0 = *tx0->inputs_ptr()->at(0); -//// const auto& in0_1 = *tx0->inputs_ptr()->at(1); -//// const auto& in1_0 = *tx1->inputs_ptr()->at(0); -//// const auto& in1_1 = *tx1->inputs_ptr()->at(1); -//// const auto& in2_0 = *tx2->inputs_ptr()->at(0); -//// const auto& in2_1 = *tx2->inputs_ptr()->at(1); -//// -//// // Coinbase identifies parent of prevout (spent), not input (spender). -//// in0_0.metadata.parent = 0x01234560_u32; -//// in0_1.metadata.parent = 0x01234561_u32; -//// in1_0.metadata.parent = 0x01234562_u32; -//// in1_1.metadata.parent = 0x01234563_u32; -//// in2_0.metadata.parent = 0x01234564_u32; -//// in2_1.metadata.parent = 0x01234565_u32; -//// -//// // Coinbase identifies prevout (spent), not input (spender). -//// in0_0.metadata.coinbase = false; -//// in0_1.metadata.coinbase = true; -//// in1_0.metadata.coinbase = false; -//// in1_1.metadata.coinbase = false; -//// in2_0.metadata.coinbase = false; -//// in2_1.metadata.coinbase = true; -//// -//// // Inside implies prevout spent in block (terminal). -//// in0_0.metadata.inside = false; -//// in0_1.metadata.inside = true; -//// in1_0.metadata.inside = false; -//// in1_1.metadata.inside = false; -//// in2_0.metadata.inside = true; -//// in2_1.metadata.inside = false; -//// -//// test::chunk_storage head_store{}; -//// test::chunk_storage body_store{}; -//// table::prevout instance{ head_store, body_store, 5 }; -//// BOOST_REQUIRE(instance.create()); -//// -//// const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; -//// BOOST_REQUIRE(instance.put(2, record)); -//// -//// table::prevout::record_get element{}; -//// const auto spends = bogus_block.spends(); -//// BOOST_REQUIRE_EQUAL(spends, 4u); -//// -//// element.values.resize(spends); -//// BOOST_REQUIRE(instance.at(2, element)); -//// BOOST_REQUIRE_EQUAL(element.count(), add1(spends)); -//// -//// // First block.tx is coinbase, no spends, so only 4, none terminal. -//// BOOST_REQUIRE_NE(element.values.at(0), terminal); -//// BOOST_REQUIRE_NE(element.values.at(1), terminal); -//// BOOST_REQUIRE_EQUAL(element.values.at(2), terminal); -//// BOOST_REQUIRE_NE(element.values.at(3), terminal); -//// -//// // Inside spends are used as positional placeholders for double spend check bypass. -//// BOOST_REQUIRE(!element.inside(0)); -//// BOOST_REQUIRE(!element.inside(1)); -//// BOOST_REQUIRE(element.inside(2)); -//// BOOST_REQUIRE(!element.inside(3)); -//// -//// BOOST_REQUIRE(!element.coinbase(0)); -//// BOOST_REQUIRE(!element.coinbase(1)); -//// BOOST_REQUIRE(element.coinbase(2)); -//// BOOST_REQUIRE(element.coinbase(3)); -//// -//// // Spend ordinal position relative to the block must be preserved (coinbase excluded). -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(0), in1_0.metadata.parent); -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(1), in1_1.metadata.parent); -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(2), terminal); -//// BOOST_REQUIRE_EQUAL(element.output_tx_fk(3), in2_1.metadata.parent); -////} +BOOST_AUTO_TEST_CASE(prevout__put__merged_values__expected) +{ + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + table::prevout instance{ head_store, body_store, 5 }; + BOOST_REQUIRE(instance.create()); + + constexpr auto expected_cb = true; + constexpr auto expected_tx = 0b01010101'01010101'01010101'01010101_u32; + auto record = table::prevout::record{ {}, 42_u32, 0_u32 }; + record.set(expected_cb, expected_tx); + BOOST_REQUIRE(instance.put(3, record)); + + table::prevout::record element{}; + BOOST_REQUIRE(instance.at(3, element)); + BOOST_REQUIRE_EQUAL(element.header_fk, 42_u32); + BOOST_REQUIRE_EQUAL(element.coinbase(), expected_cb); + BOOST_REQUIRE_EQUAL(element.output_tx_fk(), expected_tx); +} + +// record_put_ref + +BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata__inside_spend_terminals) +{ + DECLARE_BOGUS_BLOCK; + + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + table::prevout instance{ head_store, body_store, 5 }; + BOOST_REQUIRE(instance.create()); + + const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; + BOOST_REQUIRE(instance.put(2, record)); + + table::prevout::record_get element{}; + const auto spends = bogus_block.spends(); + BOOST_REQUIRE_EQUAL(spends, 4u); + + element.values.resize(spends); + BOOST_REQUIRE(instance.at(2, element)); + BOOST_REQUIRE_EQUAL(element.count(), add1(4u)); + + // First block.tx is coinbase, no spends, so only 4, all terminal (defaults). + BOOST_REQUIRE_EQUAL(element.values.at(0), terminal); + BOOST_REQUIRE_EQUAL(element.values.at(1), terminal); + BOOST_REQUIRE_EQUAL(element.values.at(2), terminal); + BOOST_REQUIRE_EQUAL(element.values.at(3), terminal); + + // Block-internal spend. + // Positionally identifies spends not requiring a double spend check. + // Blocks are guarded agianst internal double spends, and tx previously + // confirmed would imply a double spend of it or one of its ancestors. + BOOST_REQUIRE(element.inside(0)); + BOOST_REQUIRE(element.inside(1)); + BOOST_REQUIRE(element.inside(2)); + BOOST_REQUIRE(element.inside(3)); + + // Inside are always reflected as coinbase. + BOOST_REQUIRE(element.coinbase(0)); + BOOST_REQUIRE(element.coinbase(1)); + BOOST_REQUIRE(element.coinbase(2)); + BOOST_REQUIRE(element.coinbase(3)); + + // Inside are always mapped to terminal. + BOOST_REQUIRE_EQUAL(element.output_tx_fk(0), terminal); + BOOST_REQUIRE_EQUAL(element.output_tx_fk(1), terminal); + BOOST_REQUIRE_EQUAL(element.output_tx_fk(2), terminal); + BOOST_REQUIRE_EQUAL(element.output_tx_fk(3), terminal); +} + +BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_metadata__expected) +{ + DECLARE_BOGUS_BLOCK; + + const auto tx0 = bogus_block.transactions_ptr()->at(0); + const auto tx1 = bogus_block.transactions_ptr()->at(1); + const auto tx2 = bogus_block.transactions_ptr()->at(2); + + const auto& in0_0 = *tx0->inputs_ptr()->at(0); + const auto& in0_1 = *tx0->inputs_ptr()->at(1); + const auto& in1_0 = *tx1->inputs_ptr()->at(0); + const auto& in1_1 = *tx1->inputs_ptr()->at(1); + const auto& in2_0 = *tx2->inputs_ptr()->at(0); + const auto& in2_1 = *tx2->inputs_ptr()->at(1); + + // Coinbase identifies parent of prevout (spent), not input (spender). + in0_0.metadata.parent = 0x01234560_u32; + in0_1.metadata.parent = 0x01234561_u32; + in1_0.metadata.parent = 0x01234562_u32; + in1_1.metadata.parent = 0x01234563_u32; + in2_0.metadata.parent = 0x01234564_u32; + in2_1.metadata.parent = 0x01234565_u32; + + // Coinbase identifies prevout (spent), not input (spender). + in0_0.metadata.coinbase = false; + in0_1.metadata.coinbase = true; + in1_0.metadata.coinbase = false; + in1_1.metadata.coinbase = false; + in2_0.metadata.coinbase = false; + in2_1.metadata.coinbase = true; + + // Inside implies prevout spent in block (terminal). + in0_0.metadata.inside = false; + in0_1.metadata.inside = true; + in1_0.metadata.inside = false; + in1_1.metadata.inside = false; + in2_0.metadata.inside = true; + in2_1.metadata.inside = false; + + test::chunk_storage head_store{}; + test::chunk_storage body_store{}; + table::prevout instance{ head_store, body_store, 5 }; + BOOST_REQUIRE(instance.create()); + + const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; + BOOST_REQUIRE(instance.put(2, record)); + + table::prevout::record_get element{}; + const auto spends = bogus_block.spends(); + BOOST_REQUIRE_EQUAL(spends, 4u); + + element.values.resize(spends); + BOOST_REQUIRE(instance.at(2, element)); + BOOST_REQUIRE_EQUAL(element.count(), add1(spends)); + + // First block.tx is coinbase, no spends, so only 4, none terminal. + BOOST_REQUIRE_NE(element.values.at(0), terminal); + BOOST_REQUIRE_NE(element.values.at(1), terminal); + BOOST_REQUIRE_EQUAL(element.values.at(2), terminal); + BOOST_REQUIRE_NE(element.values.at(3), terminal); + + // Inside spends are used as positional placeholders for double spend check bypass. + BOOST_REQUIRE(!element.inside(0)); + BOOST_REQUIRE(!element.inside(1)); + BOOST_REQUIRE(element.inside(2)); + BOOST_REQUIRE(!element.inside(3)); + + BOOST_REQUIRE(!element.coinbase(0)); + BOOST_REQUIRE(!element.coinbase(1)); + BOOST_REQUIRE(element.coinbase(2)); + BOOST_REQUIRE(element.coinbase(3)); + + // Spend ordinal position relative to the block must be preserved (coinbase excluded). + BOOST_REQUIRE_EQUAL(element.output_tx_fk(0), in1_0.metadata.parent); + BOOST_REQUIRE_EQUAL(element.output_tx_fk(1), in1_1.metadata.parent); + BOOST_REQUIRE_EQUAL(element.output_tx_fk(2), terminal); + BOOST_REQUIRE_EQUAL(element.output_tx_fk(3), in2_1.metadata.parent); +} BOOST_AUTO_TEST_SUITE_END() From 32f3b2c780620c98582fbf6d50aa0a853556e657 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 21 Jan 2025 00:37:47 -0500 Subject: [PATCH 10/12] Index prevouts by header link vs. height. --- .../bitcoin/database/impl/query/confirm.ipp | 11 ++--- .../database/tables/caches/prevout.hpp | 27 ++-------- include/bitcoin/database/tables/schema.hpp | 2 +- test/tables/caches/prevout.cpp | 49 ++++++++++--------- 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index 7d9e2df8..7c43c902 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -512,10 +512,9 @@ spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT }; const auto spends = std::accumulate(sets.begin(), sets.end(), zero, count); - ////// TODO: deal with exta link element in data set before testing. table::prevout::record_get prevouts{}; prevouts.values.resize(spends); - store_.prevout.at(get_height(link), prevouts); + store_.prevout.at(link, prevouts); size_t index{}; for (auto& set: sets) @@ -739,16 +738,12 @@ bool CLASS::set_prevouts(const header_link& link, const block& block) NOEXCEPT if (block.transactions() <= one) return true; - const auto height = get_height(link); - if (height.is_terminal()) - return false; - // ======================================================================== const auto scope = store_.get_transactor(); // Clean single allocation failure (e.g. disk full). - const table::prevout::record_put_ref prevouts{ {}, link, block }; - return store_.prevout.put(height, prevouts); + const table::prevout::record_put_ref prevouts{ {}, block }; + return store_.prevout.put(link, prevouts); // ======================================================================== } diff --git a/include/bitcoin/database/tables/caches/prevout.hpp b/include/bitcoin/database/tables/caches/prevout.hpp index 57c8e2f9..37160d83 100644 --- a/include/bitcoin/database/tables/caches/prevout.hpp +++ b/include/bitcoin/database/tables/caches/prevout.hpp @@ -28,7 +28,7 @@ namespace libbitcoin { namespace database { namespace table { -/// prevout is an array map index of previous outputs by block. +/// prevout is an array map index of previous outputs by block link. /// The coinbase flag is merged into the tx field, reducing it's domain. /// Masking is from the right in order to accomodate non-integral domain. struct prevout @@ -39,9 +39,6 @@ struct prevout using array_map::arraymap; static constexpr size_t offset = sub1(to_bits(tx::size)); - // First tx-sized entry overloaded for header link. - static_assert(header::size <= tx::size); - // This supports only a single record (not too useful). struct record : public schema::prevout @@ -65,7 +62,6 @@ struct prevout inline bool from_data(reader& source) NOEXCEPT { - header_fk = source.read_little_endian(); value = source.read_little_endian(); BC_ASSERT(!source || source.get_read_position() == count() * minrow); return source; @@ -73,7 +69,6 @@ struct prevout inline bool to_data(finalizer& sink) const NOEXCEPT { - sink.write_little_endian(header_fk); sink.write_little_endian(value); BC_ASSERT(!sink || sink.get_write_position() == count() * minrow); return sink; @@ -85,7 +80,6 @@ struct prevout && output_tx_fk() == other.output_tx_fk(); } - header::integer header_fk{}; tx::integer value{}; }; @@ -103,8 +97,7 @@ struct prevout // This is called once by put(), and hides base count(). inline link count() const NOEXCEPT { - // First entry overloaded for header (count is records not values). - const auto spends = add1(block.spends()); + const auto spends = block.spends(); BC_ASSERT(spends < link::terminal); return system::possible_narrow_cast(spends); } @@ -129,16 +122,11 @@ struct prevout return std::for_each(ins->begin(), ins->end(), write_spend); }; - // First tx-sized entry overloaded for header link. - sink.write_little_endian(header_fk); - std::for_each(std::next(txs.begin()), txs.end(), write_tx); BC_ASSERT(!sink || (sink.get_write_position() == count() * minrow)); return sink; } - // One entry is written for each spend in the block in order. - header::integer header_fk{}; const system::chain::block& block{}; }; @@ -148,16 +136,12 @@ struct prevout // This is called once by assert, and hides base class count(). inline link count() const NOEXCEPT { - // First entry overloaded for header (count is records not values). - BC_ASSERT(add1(values.size()) < link::terminal); - return system::possible_narrow_cast(add1(values.size())); + BC_ASSERT(values.size() < link::terminal); + return system::possible_narrow_cast(values.size()); } inline bool from_data(reader& source) NOEXCEPT { - // First tx-sized entry overloaded for header link. - header_fk = source.read_little_endian(); - // TODO: Can be optimized using unsafe_array_cast copy, as long as // TODO: endianness lines up. Should have writer methods for native // TODO: endianness so that both big and little are optimal. But @@ -168,7 +152,7 @@ struct prevout value = source.read_little_endian(); }); - BC_ASSERT(!source || source.get_read_position() == add1(count()) * minrow); + BC_ASSERT(!source || source.get_read_position() == count() * minrow); return source; } @@ -198,7 +182,6 @@ struct prevout } // Spend count is derived in confirmation by summing block.txs.puts. - header::integer header_fk{}; std::vector values{}; }; }; diff --git a/include/bitcoin/database/tables/schema.hpp b/include/bitcoin/database/tables/schema.hpp index 521f6a0e..08be2035 100644 --- a/include/bitcoin/database/tables/schema.hpp +++ b/include/bitcoin/database/tables/schema.hpp @@ -330,7 +330,7 @@ namespace schema static constexpr size_t size = minsize; // This is hidden by derivatives, to avoid virtual methods. - inline linkage count() const NOEXCEPT { return two; } + inline linkage count() const NOEXCEPT { return one; } static_assert(minsize == 4u); static_assert(minrow == 4u); }; diff --git a/test/tables/caches/prevout.cpp b/test/tables/caches/prevout.cpp index 3ca063b1..25de67c1 100644 --- a/test/tables/caches/prevout.cpp +++ b/test/tables/caches/prevout.cpp @@ -134,8 +134,8 @@ BOOST_AUTO_TEST_SUITE(prevout_tests) using namespace system; using namespace system::chain; constexpr auto terminal = linkage::terminal; -constexpr table::prevout::record record1{ {}, 0x00112233_u32, 0x01020304_u32 }; -constexpr table::prevout::record record2{ {}, 0x00aabbcc_u32, 0xbaadf00d_u32 }; +constexpr table::prevout::record record1{ {}, 0x01020304_u32 }; +constexpr table::prevout::record record2{ {}, 0xbaadf00d_u32 }; BOOST_AUTO_TEST_CASE(prevout__put__at1__expected) { @@ -149,11 +149,12 @@ BOOST_AUTO_TEST_CASE(prevout__put__at1__expected) BOOST_REQUIRE(instance.put(42, record2)); // Dereference at key to get link. - BOOST_REQUIRE(instance.at(0).is_terminal()); BOOST_REQUIRE_EQUAL(instance.at(3), 0u); - - // First record is two elements, so link for second record is 3. - BOOST_REQUIRE_EQUAL(instance.at(42), 2u); + BOOST_REQUIRE_EQUAL(instance.at(42), 1u); + BOOST_REQUIRE(instance.at(0).is_terminal()); + BOOST_REQUIRE(instance.at(1).is_terminal()); + BOOST_REQUIRE(instance.at(2).is_terminal()); + BOOST_REQUIRE(instance.at(4).is_terminal()); } BOOST_AUTO_TEST_CASE(prevout__put__at2__expected) @@ -169,11 +170,14 @@ BOOST_AUTO_TEST_CASE(prevout__put__at2__expected) BOOST_REQUIRE(instance.put(42, record2)); // Dereference at key to get element. - BOOST_REQUIRE(!instance.at(0, element)); BOOST_REQUIRE(instance.at(3, element)); BOOST_REQUIRE(element == record1); BOOST_REQUIRE(instance.at(42, element)); BOOST_REQUIRE(element == record2); + BOOST_REQUIRE(!instance.at(0, element)); + BOOST_REQUIRE(!instance.at(1, element)); + BOOST_REQUIRE(!instance.at(2, element)); + BOOST_REQUIRE(!instance.at(4, element)); } BOOST_AUTO_TEST_CASE(prevout__put__exists__expected) @@ -188,9 +192,12 @@ BOOST_AUTO_TEST_CASE(prevout__put__exists__expected) BOOST_REQUIRE(instance.put(42, record2)); // Exists at key. - BOOST_REQUIRE(!instance.exists(0)); BOOST_REQUIRE(instance.exists(3)); BOOST_REQUIRE(instance.exists(42)); + BOOST_REQUIRE(!instance.exists(0)); + BOOST_REQUIRE(!instance.exists(1)); + BOOST_REQUIRE(!instance.exists(2)); + BOOST_REQUIRE(!instance.exists(4)); } BOOST_AUTO_TEST_CASE(prevout__put__get__expected) @@ -206,14 +213,13 @@ BOOST_AUTO_TEST_CASE(prevout__put__get__expected) BOOST_REQUIRE(instance.put(42, record2)); // Get at link. - // First and second records are two elements each, so link for next record is 4. - BOOST_REQUIRE(!instance.get(4, element)); BOOST_REQUIRE(instance.get(0, element)); BOOST_REQUIRE(element == record1); - - // First record is two elements, so link for second record is 3. - BOOST_REQUIRE(instance.get(2, element)); + BOOST_REQUIRE(instance.get(1, element)); BOOST_REQUIRE(element == record2); + BOOST_REQUIRE(!instance.get(2, element)); + BOOST_REQUIRE(!instance.get(3, element)); + BOOST_REQUIRE(!instance.get(4, element)); } // values @@ -227,16 +233,15 @@ BOOST_AUTO_TEST_CASE(prevout__put__isolated_values__expected) table::prevout instance{ head_store, body_store, 5 }; BOOST_REQUIRE(instance.create()); - constexpr auto cb_only = table::prevout::record{ {}, 42_u32, 0b10000000'00000000'00000000'00000000_u32 }; + constexpr auto cb_only = table::prevout::record{ {}, 0b10000000'00000000'00000000'00000000_u32 }; BOOST_REQUIRE(instance.put(3, cb_only)); - constexpr auto tx_only = table::prevout::record{ {}, 24_u32, 0b01010101'01010101'01010101'01010101_u32 }; + constexpr auto tx_only = table::prevout::record{ {}, 0b01010101'01010101'01010101'01010101_u32 }; BOOST_REQUIRE(instance.put(42, tx_only)); table::prevout::record element1{}; BOOST_REQUIRE(instance.at(3, element1)); BOOST_REQUIRE(element1.coinbase()); - BOOST_REQUIRE_EQUAL(element1.header_fk, 42_u32); BOOST_REQUIRE_EQUAL(element1.coinbase(), cb_only.coinbase()); BOOST_REQUIRE_EQUAL(element1.output_tx_fk(), cb_only.output_tx_fk()); BOOST_REQUIRE_EQUAL(element1.output_tx_fk(), set_right(cb_only.value, bits, false)); @@ -244,7 +249,6 @@ BOOST_AUTO_TEST_CASE(prevout__put__isolated_values__expected) table::prevout::record element2{}; BOOST_REQUIRE(instance.at(42, element2)); BOOST_REQUIRE(!element2.coinbase()); - BOOST_REQUIRE_EQUAL(element2.header_fk, 24_u32); BOOST_REQUIRE_EQUAL(element2.coinbase(), tx_only.coinbase()); BOOST_REQUIRE_EQUAL(element2.output_tx_fk(), tx_only.output_tx_fk()); BOOST_REQUIRE_EQUAL(element2.output_tx_fk(), set_right(tx_only.value, bits, false)); @@ -259,13 +263,12 @@ BOOST_AUTO_TEST_CASE(prevout__put__merged_values__expected) constexpr auto expected_cb = true; constexpr auto expected_tx = 0b01010101'01010101'01010101'01010101_u32; - auto record = table::prevout::record{ {}, 42_u32, 0_u32 }; + auto record = table::prevout::record{ {}, 0_u32 }; record.set(expected_cb, expected_tx); BOOST_REQUIRE(instance.put(3, record)); table::prevout::record element{}; BOOST_REQUIRE(instance.at(3, element)); - BOOST_REQUIRE_EQUAL(element.header_fk, 42_u32); BOOST_REQUIRE_EQUAL(element.coinbase(), expected_cb); BOOST_REQUIRE_EQUAL(element.output_tx_fk(), expected_tx); } @@ -281,7 +284,7 @@ BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata table::prevout instance{ head_store, body_store, 5 }; BOOST_REQUIRE(instance.create()); - const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; + const auto record = table::prevout::record_put_ref{ {}, bogus_block }; BOOST_REQUIRE(instance.put(2, record)); table::prevout::record_get element{}; @@ -290,7 +293,7 @@ BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_default_metadata element.values.resize(spends); BOOST_REQUIRE(instance.at(2, element)); - BOOST_REQUIRE_EQUAL(element.count(), add1(4u)); + BOOST_REQUIRE_EQUAL(element.count(), 4u); // First block.tx is coinbase, no spends, so only 4, all terminal (defaults). BOOST_REQUIRE_EQUAL(element.values.at(0), terminal); @@ -364,7 +367,7 @@ BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_metadata__expect table::prevout instance{ head_store, body_store, 5 }; BOOST_REQUIRE(instance.create()); - const auto record = table::prevout::record_put_ref{ {}, 42_u32, bogus_block }; + const auto record = table::prevout::record_put_ref{ {}, bogus_block }; BOOST_REQUIRE(instance.put(2, record)); table::prevout::record_get element{}; @@ -373,7 +376,7 @@ BOOST_AUTO_TEST_CASE(prevout__put_ref__get_non_empty_block_with_metadata__expect element.values.resize(spends); BOOST_REQUIRE(instance.at(2, element)); - BOOST_REQUIRE_EQUAL(element.count(), add1(spends)); + BOOST_REQUIRE_EQUAL(element.count(), spends); // First block.tx is coinbase, no spends, so only 4, none terminal. BOOST_REQUIRE_NE(element.values.at(0), terminal); From f526dccc00361ad12072d77d9f615575eb5807a1 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 21 Jan 2025 14:24:42 -0500 Subject: [PATCH 11/12] Comments. --- include/bitcoin/database/impl/query/validate.ipp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/database/impl/query/validate.ipp b/include/bitcoin/database/impl/query/validate.ipp index 82b9228d..97ac3bb6 100644 --- a/include/bitcoin/database/impl/query/validate.ipp +++ b/include/bitcoin/database/impl/query/validate.ipp @@ -196,7 +196,6 @@ code CLASS::get_block_state(const header_link& link) const NOEXCEPT if (!store_.validated_bk.find(link, valid)) return is_associated(link) ? error::unvalidated : error::unassociated; - // Fees only valid if block_confirmable. return to_block_code(valid.code); } @@ -208,8 +207,9 @@ code CLASS::get_block_state(uint64_t& fees, if (!store_.validated_bk.find(link, valid)) return is_associated(link) ? error::unvalidated : error::unassociated; - // Fees only valid if block_confirmable. + // TODO: Fees only valid if block_valid is the current state (iterate for valid). fees = valid.fees; + return to_block_code(valid.code); } From f65372b4540798c2310faede402a22315b2d4c33 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 21 Jan 2025 17:08:14 -0500 Subject: [PATCH 12/12] Implement set_filter_head(link). --- .../bitcoin/database/impl/query/optional.ipp | 29 +++++++++++++++++-- include/bitcoin/database/query.hpp | 1 + 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/database/impl/query/optional.ipp b/include/bitcoin/database/impl/query/optional.ipp index 79a66286..ff9578dd 100644 --- a/include/bitcoin/database/impl/query/optional.ipp +++ b/include/bitcoin/database/impl/query/optional.ipp @@ -198,13 +198,38 @@ bool CLASS::get_filter_head(hash_digest& out, return true; } +TEMPLATE +bool CLASS::set_filter_head(const header_link& link) NOEXCEPT +{ + if (!neutrino_enabled()) + return true; + + // The filter body must have been previously stored under the block link. + filter bytes{}; + if (!get_filter_body(bytes, link)) + return false; + + // If genesis then previous is null_hash otherwise get by confirmed height. + hash_digest previous{}; + const auto height = get_height(link); + if (!is_zero(height)) + if (!get_filter_head(previous, to_confirmed(sub1(height)))) + return false; + + // Use the previous head and current body to compute the current head. + return set_filter_head(link, + system::neutrino::compute_filter_header(previous, bytes)); +} + TEMPLATE bool CLASS::set_filter_body(const header_link& link, const block& block) NOEXCEPT { - filter bytes{}; + if (!neutrino_enabled()) + return true; - // compute_filter is only false if prevouts are missing. + // Compute the current filter from the block and store under the link. + filter bytes{}; return system::neutrino::compute_filter(bytes, block) && set_filter_body(link, bytes); } diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 43406437..bf7e5ed6 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -543,6 +543,7 @@ class query bool get_filter_head(hash_digest& out, const header_link& link) const NOEXCEPT; bool set_filter_body(const header_link& link, const block& block) NOEXCEPT; bool set_filter_body(const header_link& link, const filter& body) NOEXCEPT; + bool set_filter_head(const header_link& link) NOEXCEPT; bool set_filter_head(const header_link& link, const hash_digest& head) NOEXCEPT;