From 5a1568f2dd1016d23ce994b8883020a619b3a015 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Sun, 16 Aug 2026 21:19:14 -0400 Subject: [PATCH 1/8] Merge address index into outs table as optional search. --- .../bitcoin/database/impl/primitives/body.ipp | 2 +- .../database/impl/primitives/hashmap.ipp | 6 +- .../bitcoin/database/impl/primitives/keys.ipp | 40 +++++- .../impl/query/address/address_history.ipp | 2 +- .../impl/query/archive/chain_reader.ipp | 2 +- .../impl/query/archive/chain_writer.ipp | 35 ++--- .../impl/query/archive/wire_reader.ipp | 2 +- .../impl/query/archive/wire_writer.ipp | 39 ++--- .../bitcoin/database/impl/query/extent.ipp | 9 +- .../impl/query/navigate/navigate_forward.ipp | 4 +- .../impl/query/navigate/navigate_reverse.ipp | 47 ++++-- include/bitcoin/database/impl/store/store.ipp | 8 +- .../database/impl/store/store_backup.ipp | 1 - .../database/impl/store/store_close.ipp | 1 - .../database/impl/store/store_create.ipp | 3 - .../database/impl/store/store_dump.ipp | 1 - .../database/impl/store/store_open.ipp | 1 - .../database/impl/store/store_open_load.ipp | 4 - .../database/impl/store/store_reload.ipp | 2 - .../database/impl/store/store_report.ipp | 5 - .../database/impl/store/store_restore.ipp | 1 - .../database/impl/store/store_snapshot.ipp | 1 - .../impl/store/store_unload_close.ipp | 4 - include/bitcoin/database/primitives/keys.hpp | 26 +++- include/bitcoin/database/query.hpp | 10 +- include/bitcoin/database/store.hpp | 9 +- .../database/tables/archives/output.hpp | 21 +++ .../bitcoin/database/tables/archives/outs.hpp | 58 +++++++- .../database/tables/optionals/address.hpp | 43 +----- include/bitcoin/database/tables/schema.hpp | 44 +++--- include/bitcoin/database/types/type.hpp | 1 - test/mocks/chunk_store.hpp | 15 +- test/mocks/map_store.hpp | 10 -- test/query/archive/chain_writer.cpp | 41 +++++- test/query/extent.cpp | 7 +- test/query/navigate/navigate_reverse.cpp | 10 +- test/store/store.cpp | 4 +- test/tables/optional/address.cpp | 136 +++++++++--------- 38 files changed, 353 insertions(+), 302 deletions(-) diff --git a/include/bitcoin/database/impl/primitives/body.ipp b/include/bitcoin/database/impl/primitives/body.ipp index 1c3fa7b4b..a62b2887b 100644 --- a/include/bitcoin/database/impl/primitives/body.ipp +++ b/include/bitcoin/database/impl/primitives/body.ipp @@ -198,7 +198,7 @@ constexpr size_t CLASS::stride() NOEXCEPT // Slab: link/key incorporated into size (byte-addressed map). return size; } - else if constexpr (is_zero(Column) && is_nonzero(key_size)) + else if constexpr (is_zero(Column) && keys::keyed()) { // Spine of a keyed map: link/key precede the record. return Link::size + key_size + size; diff --git a/include/bitcoin/database/impl/primitives/hashmap.ipp b/include/bitcoin/database/impl/primitives/hashmap.ipp index d5bc8e9b1..aeaa57624 100644 --- a/include/bitcoin/database/impl/primitives/hashmap.ipp +++ b/include/bitcoin/database/impl/primitives/hashmap.ipp @@ -307,12 +307,10 @@ bool CLASS::set(const memory& ptr, const Link& link, const Key& key, return false; // Set element search key. - unsafe_array_cast(std::next(offset, - Link::size)) = key; - iostream stream{ offset, size - position }; finalizer sink{ stream }; - sink.skip_bytes(index_size); + sink.skip_bytes(Link::size); + keys::write(sink, key); // Due to accounting, record hashmaps are limited to set(1). if constexpr (!is_slab) { BC_ASSERT(is_one(element.count())); } diff --git a/include/bitcoin/database/impl/primitives/keys.ipp b/include/bitcoin/database/impl/primitives/keys.ipp index 1fdf08da1..6b93ac1f4 100644 --- a/include/bitcoin/database/impl/primitives/keys.ipp +++ b/include/bitcoin/database/impl/primitives/keys.ipp @@ -44,7 +44,11 @@ template INLINE constexpr size_t size() NOEXCEPT { using namespace system; - if constexpr (is_same_type) + if constexpr (is_search) + { + return Key::width; + } + else if constexpr (is_same_type) { // Index is truncated to three bytes. return sub1(chain::point::serialized_size()); @@ -55,6 +59,13 @@ INLINE constexpr size_t size() NOEXCEPT } } +template +INLINE constexpr bool keyed() NOEXCEPT +{ + using namespace system; + return !is_same_type>; +} + template INLINE Integral bucket(const Key& key, Integral buckets) NOEXCEPT { @@ -80,7 +91,11 @@ template INLINE uint64_t hash(const Key& key) NOEXCEPT { using namespace system; - if constexpr (is_same_type) + if constexpr (is_search) + { + return hash(key.value); + } + else if constexpr (is_same_type) { // Both produce an 85% Poisson distribution. return fnv1a_combine(hash(key.hash()), key.index()); @@ -103,7 +118,11 @@ template INLINE uint64_t thumb(const Key& key) NOEXCEPT { using namespace system; - if constexpr (is_same_type) + if constexpr (is_search) + { + return thumb(key.value); + } + else if constexpr (is_same_type) { // spread point.index across point.hash extraction, as otherwise the // unlikely bucket collisions of points of the same hash will not be @@ -133,7 +152,13 @@ template INLINE void write(writer& sink, const Key& key) NOEXCEPT { using namespace system; - if constexpr (is_same_type) + if constexpr (is_search) + { + if constexpr (is_nonzero(Key::width)) + sink.write_bytes(std::next(key.value.data(), Key::offset), + Key::width); + } + else if constexpr (is_same_type) { sink.write_bytes(key.hash()); sink.write_3_bytes_little_endian(key.index()); @@ -149,7 +174,12 @@ INLINE bool compare(const Array& bytes, const Key& key) NOEXCEPT { using namespace system; static_assert(size() <= array_count); - if constexpr (is_same_type) + if constexpr (is_search) + { + return std::equal(bytes.cbegin(), bytes.cend(), + std::next(key.value.cbegin(), Key::offset)); + } + else if constexpr (is_same_type) { // Index is truncated to three bytes. const auto index = key.index(); diff --git a/include/bitcoin/database/impl/query/address/address_history.ipp b/include/bitcoin/database/impl/query/address/address_history.ipp index 5fe490e0c..4022a3c75 100644 --- a/include/bitcoin/database/impl/query/address/address_history.ipp +++ b/include/bitcoin/database/impl/query/address/address_history.ipp @@ -254,7 +254,7 @@ code CLASS::get_address_txs(const stopper& cancel, tx_links& out, const hash_digest& key, size_t limit) const NOEXCEPT { output_links links{}; - address_link cursor{}; + outs_link cursor{}; if (const auto ec = to_address_outputs(cancel, cursor, links, key, limit)) return ec; diff --git a/include/bitcoin/database/impl/query/archive/chain_reader.ipp b/include/bitcoin/database/impl/query/archive/chain_reader.ipp index 6d170627f..f757cb100 100644 --- a/include/bitcoin/database/impl/query/archive/chain_reader.ipp +++ b/include/bitcoin/database/impl/query/archive/chain_reader.ipp @@ -160,7 +160,7 @@ typename CLASS::transaction::cptr CLASS::get_transaction(const tx_link& link, table::outs::record outs{}; outs.out_fks.resize(tx.outs_count); - if (!store_.outs.get(tx.outs_fk, outs)) + if (!store_.outs.puts.get(tx.outs_fk, outs)) return {}; const auto inputs = to_shared(); diff --git a/include/bitcoin/database/impl/query/archive/chain_writer.ipp b/include/bitcoin/database/impl/query/archive/chain_writer.ipp index 8c1e59398..0bca28396 100644 --- a/include/bitcoin/database/impl/query/archive/chain_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/chain_writer.ipp @@ -133,12 +133,20 @@ code CLASS::set_code(const tx_link& tx_fk, const transaction& tx, insert.reset(); - // Allocate and contiguously store output links. - outs_link outs_fk{}; - if (!store_.outs.put_link(outs_fk, + // Allocate outs/address rows and contiguously store output links. + // Address spine elements are committed into the same rows, below. + auto outs_fk = store_.outs.allocate(outputs); + if (outs_fk.is_terminal()) + return error::tx_outs_put; + + // Outs put is unguarded, the accessor guards its rows against remap. + auto outsert = store_.outs.get_memory(); + if (!outsert || !store_.outs.puts.put(outs_fk, table::outs::put_ref{ {}, out_fk, tx })) return error::tx_outs_put; + outsert.reset(); + // Create tx record. // Commit is deferred for point/address index consistency. if (!store_.tx.set(tx_fk, tx.get_hash(false), table::transaction::put_ref @@ -207,27 +215,14 @@ code CLASS::set_code(const tx_link& tx_fk, const transaction& tx, } } - // Commit address index records (hashmap). + // Commit address index records (hashmap, rows shared with outs). if (address_enabled()) { - auto ad_fk = store_.address.allocate(outputs); - if (ad_fk.is_terminal()) - return error::tx_address_allocate; - - constexpr auto value_parent = sizeof(uint64_t) - tx_link::size; - const auto ptr = store_.address.get_memory(); + auto ad_fk = outs_fk; + const auto ptr = store_.outs.get_memory(); for (const auto& output: *ous) - { - if (!store_.address.put(ptr, ad_fk++, output->script().hash(), - table::address::record{ {}, out_fk })) + if (!store_.outs.commit(ptr, ad_fk++, { output->script().hash() })) return error::tx_address_put; - - // See outs::put_ref. - // Calculate next corresponding output fk from serialized size. - // (variable_size(value) + (value + script)) - (value - parent) - out_fk += variable_size(output->value()) + - output->serialized_size() - value_parent; - } } // Commit tx to search (hashmap). diff --git a/include/bitcoin/database/impl/query/archive/wire_reader.ipp b/include/bitcoin/database/impl/query/archive/wire_reader.ipp index e12f2f6a5..bdb352bce 100644 --- a/include/bitcoin/database/impl/query/archive/wire_reader.ipp +++ b/include/bitcoin/database/impl/query/archive/wire_reader.ipp @@ -88,7 +88,7 @@ bool CLASS::get_wire_tx(bytewriter& sink, const tx_link& link, table::outs::record outs{}; outs.out_fks.resize(tx.outs_count); - if (!store_.outs.get(tx.outs_fk, outs)) + if (!store_.outs.puts.get(tx.outs_fk, outs)) return false; // Point links are contiguous (computed). diff --git a/include/bitcoin/database/impl/query/archive/wire_writer.ipp b/include/bitcoin/database/impl/query/archive/wire_writer.ipp index f570f4fa1..b26f7fb08 100644 --- a/include/bitcoin/database/impl/query/archive/wire_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/wire_writer.ipp @@ -64,8 +64,9 @@ code CLASS::set_code(std::vector& twins, const accessors& ptrs, table::ins_sequence::put_view{ {}, fks.in_fk, fks.tx_fk, tx })) return error::tx_ins_put; - // Contiguously store output links (preallocated). - if (!store_.outs.put(ptrs.outs, fks.outs_fk, + // Contiguously store output links (preallocated, rows shared with the + // address spine). The caller's outs accessor guards against remap. + if (!store_.outs.puts.put(fks.outs_fk, table::outs::put_view{ {}, fks.out_fk, tx })) return error::tx_outs_put; @@ -147,26 +148,21 @@ code CLASS::set_code(std::vector& twins, const accessors& ptrs, if (!isource) return error::tx_null_point_put; - // Commit address index records (hashmap, preallocated). + // Commit address index records (hashmap, rows shared with outs). if (address_enabled()) { - auto ad_fk = fks.ad_fk; - auto out_fk = fks.out_fk; + auto ad_fk = fks.outs_fk; auto outs = tx.get_outputs_stream(); read::bytes::fast osource{ outs }; for (size_t out{}; out < outputs; ++out) { - const auto value = osource.read_8_bytes_little_endian(); + osource.skip_bytes(sizeof(uint64_t)); const auto bytes = osource.read_size(); - if (!store_.address.put(ptrs.address, ad_fk++, - sha256_hash(osource.read_bytes(bytes)), - table::address::record{ {}, out_fk })) + if (!store_.outs.commit(ptrs.outs, ad_fk++, + { sha256_hash(osource.read_bytes(bytes)) })) return error::tx_address_put; - - out_fk += tx_link::size + variable_size(value) + - variable_size(bytes) + bytes; } BC_ASSERT(osource); @@ -282,15 +278,6 @@ code CLASS::set_code(const block_view& block, const header_link& key, if (fks.outs_fk.is_terminal()) return error::tx_outs_put; - const auto address = address_enabled(); - if (address) - { - fks.ad_fk = store_.address.allocate( - possible_narrow_cast(outputs)); - if (fks.ad_fk.is_terminal()) - return error::tx_address_allocate; - } - // Guard all tables against remap for the duration of the block write. // No table may be allocated while any of these accessors are held. accessors ptrs{}; @@ -299,15 +286,12 @@ code CLASS::set_code(const block_view& block, const header_link& key, ptrs.outs = store_.outs.get_memory(); ptrs.input = store_.input.get_memory(); ptrs.output = store_.output.get_memory(); - if (address) - ptrs.address = store_.address.get_memory(); if (!ptrs.input || !ptrs.output || !ptrs.ins || !ptrs.outs || - !ptrs.tx || - (address && !ptrs.address)) + !ptrs.tx) return error::unloaded_file; // Write all txs into their preallocated rows (write order preserved). @@ -327,10 +311,6 @@ code CLASS::set_code(const block_view& block, const header_link& key, fks.outs_fk += tx.outputs(); fks.in_fk += tx.input_table_size(prune); fks.out_fk += out_bytes; - - // Unallocated (disabled) address link is terminal (do not increment). - if (address) - fks.ad_fk += tx.outputs(); } // Release all accessors (subsequent writes allocate). @@ -339,7 +319,6 @@ code CLASS::set_code(const block_view& block, const header_link& key, ptrs.outs.reset(); ptrs.input.reset(); ptrs.output.reset(); - ptrs.address.reset(); // As few duplicates are expected, duplicate domain is only 2^16. // Return of tx_duplicate_put implies link domain has overflowed. diff --git a/include/bitcoin/database/impl/query/extent.ipp b/include/bitcoin/database/impl/query/extent.ipp index c100dad8d..b0d97015a 100644 --- a/include/bitcoin/database/impl/query/extent.ipp +++ b/include/bitcoin/database/impl/query/extent.ipp @@ -98,7 +98,6 @@ size_t CLASS::store_body_size() const NOEXCEPT + prevout_body_size() + validated_bk_body_size() + validated_tx_body_size() - + address_body_size() + filter_bk_body_size() + filter_tx_body_size(); } @@ -130,7 +129,6 @@ size_t CLASS::store_head_size() const NOEXCEPT + prevout_head_size() + validated_bk_head_size() + validated_tx_head_size() - + address_head_size() + filter_bk_head_size() + filter_tx_head_size(); } @@ -159,13 +157,13 @@ DEFINE_SIZES(validated_bk) DEFINE_SIZES(validated_tx) DEFINE_SIZES(filter_bk) DEFINE_SIZES(filter_tx) -DEFINE_SIZES(address) // Buckets (hashmap + arraymap). // ---------------------------------------------------------------------------- DEFINE_BUCKETS(header) DEFINE_BUCKETS(ins) +DEFINE_BUCKETS(outs) DEFINE_BUCKETS(txs) DEFINE_BUCKETS(tx) @@ -176,7 +174,6 @@ DEFINE_BUCKETS(validated_bk) DEFINE_BUCKETS(validated_tx) DEFINE_BUCKETS(filter_bk) DEFINE_BUCKETS(filter_tx) -DEFINE_BUCKETS(address) // Records (arrays). // ---------------------------------------------------------------------------- @@ -195,7 +192,6 @@ DEFINE_RECORDS(silent) DEFINE_RECORDS(duplicate) DEFINE_RECORDS(prevalid) DEFINE_RECORDS(filter_bk) -DEFINE_RECORDS(address) // Counters (archive slabs). // ---------------------------------------------------------------------------- @@ -258,7 +254,8 @@ counts CLASS::put_counts(const tx_links& txs) const NOEXCEPT TEMPLATE bool CLASS::address_enabled() const NOEXCEPT { - return store_.address.enabled(); + // The optional outs head is the address search enablement. + return store_.outs.enabled(); } TEMPLATE diff --git a/include/bitcoin/database/impl/query/navigate/navigate_forward.ipp b/include/bitcoin/database/impl/query/navigate/navigate_forward.ipp index df0e7818a..dcc020246 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_forward.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_forward.ipp @@ -49,7 +49,7 @@ output_link CLASS::to_output(const tx_link& link, return {}; table::outs::get_output outs{}; - if (!store_.outs.get(tx.outs_fk, outs)) + if (!store_.outs.puts.get(tx.outs_fk, outs)) return {}; return outs.out_fk; @@ -88,7 +88,7 @@ output_links CLASS::to_outputs(const tx_link& link) const NOEXCEPT table::outs::record outs{}; outs.out_fks.resize(tx.number); - if (!store_.outs.get(tx.outs_fk, outs)) + if (!store_.outs.puts.get(tx.outs_fk, outs)) return {}; return std::move(outs.out_fks); diff --git a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp index a475f3ca4..6bbafb50b 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp @@ -99,36 +99,65 @@ TEMPLATE code CLASS::to_address_outputs(const stopper& cancel, output_links& out, const hash_digest& key) const NOEXCEPT { - address_link cursor{}; + outs_link cursor{}; return to_address_outputs(cancel, cursor, out, key, max_size_t); } TEMPLATE -code CLASS::to_address_outputs(const stopper& cancel, address_link& cursor, +code CLASS::to_address_outputs(const stopper& cancel, outs_link& cursor, output_links& out, const hash_digest& key, size_t limit) const NOEXCEPT { out.clear(); + + // Limit bounds candidates, verified following iterator disposal. + code deferred{ error::success }; + std::vector candidates{}; + auto found = cursor.is_terminal(); const auto end = cursor; - auto it = store_.address.it(key); + auto it = store_.outs.it({ key }); for (cursor = it.get(); it; ++it) { if (cancel) return error::query_canceled; if (it.get() == end) - return error::success; + { + found = true; + break; + } if (is_zero(limit--)) - return error::depth_limited; + { + deferred = error::depth_limited; + break; + } + + candidates.push_back(*it); + } + + it.reset(); + if (!deferred && !found) + deferred = error::invalid_cursor; + + // Verify candidates by hashing their scripts. + for (const auto& candidate: candidates) + { + if (cancel) + return error::query_canceled; + + table::outs::get_output outs{}; + if (!store_.outs.puts.get(candidate, outs)) + return error::integrity; - table::address::record address{}; - if (!store_.address.get(it, address)) + table::output::get_script_hash output{}; + if (!store_.output.get(outs.out_fk, output)) return error::integrity; - out.push_back(address.output_fk); + if (output.hash == key) + out.push_back(outs.out_fk); } - return end.is_terminal() ? error::success : error::invalid_cursor; + return deferred; } // input|output|prevout->tx[parent] diff --git a/include/bitcoin/database/impl/store/store.ipp b/include/bitcoin/database/impl/store/store.ipp index a9ee300d9..b0298594c 100644 --- a/include/bitcoin/database/impl/store/store.ipp +++ b/include/bitcoin/database/impl/store/store.ipp @@ -43,7 +43,7 @@ CLASS::store(const settings& config) NOEXCEPT ins_head_(head(config.path / schema::dir::heads, schema::archive::ins), head_settings, random), ins_body_(body(config.path, schema::archive::ins), config.ins, sequential, staged), - outs_head_(head(config.path / schema::dir::heads, schema::archive::outs), head_settings, sequential), + outs_head_(head(config.path / schema::dir::heads, schema::archive::outs), head_settings, random), outs_body_(body(config.path, schema::archive::outs), config.outs, sequential, staged), tx_head_(head(config.path / schema::dir::heads, schema::archive::tx), head_settings, random), @@ -96,9 +96,6 @@ CLASS::store(const settings& config) NOEXCEPT // Optionals. // ------------------------------------------------------------------------ - address_head_(head(config.path / schema::dir::heads, schema::optionals::address), head_settings, random), - address_body_(body(config.path, schema::optionals::address), config.address, sequential, staged), - filter_bk_head_(head(config.path / schema::dir::heads, schema::optionals::filter_bk), head_settings, random), filter_bk_body_(body(config.path, schema::optionals::filter_bk), config.filter_bk, sequential, staged), @@ -118,7 +115,7 @@ CLASS::store(const settings& config) NOEXCEPT input(input_head_, input_body_), output(output_head_, output_body_), ins(ins_head_, ins_body_, config.ins.buckets), - outs(outs_head_, outs_body_), + outs(outs_head_, outs_body_, config.address.buckets), tx(tx_head_, tx_body_, config.tx.buckets), txs(txs_head_, txs_body_, config.txs.buckets), @@ -135,7 +132,6 @@ CLASS::store(const settings& config) NOEXCEPT validated_bk(validated_bk_head_, validated_bk_body_, config.validated_bk.buckets), validated_tx(validated_tx_head_, validated_tx_body_, config.validated_tx.buckets), - address(address_head_, address_body_, config.address.buckets), filter_bk(filter_bk_head_, filter_bk_body_, config.filter_bk.buckets), filter_tx(filter_tx_head_, filter_tx_body_, config.filter_tx.buckets) { diff --git a/include/bitcoin/database/impl/store/store_backup.ipp b/include/bitcoin/database/impl/store/store_backup.ipp index b37944a95..fe33a1ac2 100644 --- a/include/bitcoin/database/impl/store/store_backup.ipp +++ b/include/bitcoin/database/impl/store/store_backup.ipp @@ -63,7 +63,6 @@ code CLASS::backup(const event_handler& handler, bool prune) NOEXCEPT backup(ec, validated_bk, table_t::validated_bk_table); backup(ec, validated_tx, table_t::validated_tx_table); - backup(ec, address, table_t::address_table); backup(ec, filter_bk, table_t::filter_bk_table); backup(ec, filter_tx, table_t::filter_tx_table); diff --git a/include/bitcoin/database/impl/store/store_close.ipp b/include/bitcoin/database/impl/store/store_close.ipp index e0c7f1d24..c2aa94fa1 100644 --- a/include/bitcoin/database/impl/store/store_close.ipp +++ b/include/bitcoin/database/impl/store/store_close.ipp @@ -67,7 +67,6 @@ code CLASS::close(const event_handler& handler) NOEXCEPT close(ec, validated_bk, table_t::validated_bk_table); close(ec, validated_tx, table_t::validated_tx_table); - close(ec, address, table_t::address_table); close(ec, filter_bk, table_t::filter_bk_table); close(ec, filter_tx, table_t::filter_tx_table); diff --git a/include/bitcoin/database/impl/store/store_create.ipp b/include/bitcoin/database/impl/store/store_create.ipp index 2ff50e61e..3b17cf911 100644 --- a/include/bitcoin/database/impl/store/store_create.ipp +++ b/include/bitcoin/database/impl/store/store_create.ipp @@ -96,8 +96,6 @@ code CLASS::create(const event_handler& handler) NOEXCEPT create(ec, validated_tx_head_, table_t::validated_tx_head); create(ec, validated_tx_body_, table_t::validated_tx_body); - create(ec, address_head_, table_t::address_head); - create(ec, address_body_, table_t::address_body); create(ec, filter_bk_head_, table_t::filter_bk_head); create(ec, filter_bk_body_, table_t::filter_bk_body); create(ec, filter_tx_head_, table_t::filter_tx_head); @@ -138,7 +136,6 @@ code CLASS::create(const event_handler& handler) NOEXCEPT populate(ec, validated_bk, table_t::validated_bk_table); populate(ec, validated_tx, table_t::validated_tx_table); - populate(ec, address, table_t::address_table); populate(ec, filter_bk, table_t::filter_bk_table); populate(ec, filter_tx, table_t::filter_tx_table); diff --git a/include/bitcoin/database/impl/store/store_dump.ipp b/include/bitcoin/database/impl/store/store_dump.ipp index fb4a75763..4e4a6305c 100644 --- a/include/bitcoin/database/impl/store/store_dump.ipp +++ b/include/bitcoin/database/impl/store/store_dump.ipp @@ -63,7 +63,6 @@ code CLASS::dump(const path& folder, dump(ec, validated_bk_head_, schema::caches::validated_bk, table_t::validated_bk_head); dump(ec, validated_tx_head_, schema::caches::validated_tx, table_t::validated_tx_head); - dump(ec, address_head_, schema::optionals::address, table_t::address_head); dump(ec, filter_bk_head_, schema::optionals::filter_bk, table_t::filter_bk_head); dump(ec, filter_tx_head_, schema::optionals::filter_tx, table_t::filter_tx_head); diff --git a/include/bitcoin/database/impl/store/store_open.ipp b/include/bitcoin/database/impl/store/store_open.ipp index 8a787528a..47edfd70d 100644 --- a/include/bitcoin/database/impl/store/store_open.ipp +++ b/include/bitcoin/database/impl/store/store_open.ipp @@ -81,7 +81,6 @@ code CLASS::open(const event_handler& handler) NOEXCEPT verify(ec, validated_bk, table_t::validated_bk_table); verify(ec, validated_tx, table_t::validated_tx_table); - verify(ec, address, table_t::address_table); verify(ec, filter_bk, table_t::filter_bk_table); verify(ec, filter_tx, table_t::filter_tx_table); diff --git a/include/bitcoin/database/impl/store/store_open_load.ipp b/include/bitcoin/database/impl/store/store_open_load.ipp index ab1904c85..9dfaa3ef2 100644 --- a/include/bitcoin/database/impl/store/store_open_load.ipp +++ b/include/bitcoin/database/impl/store/store_open_load.ipp @@ -77,8 +77,6 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT open(ec, validated_tx_head_, table_t::validated_tx_head); open(ec, validated_tx_body_, table_t::validated_tx_body); - open(ec, address_head_, table_t::address_head); - open(ec, address_body_, table_t::address_body); open(ec, filter_bk_head_, table_t::filter_bk_head); open(ec, filter_bk_body_, table_t::filter_bk_body); open(ec, filter_tx_head_, table_t::filter_tx_head); @@ -132,8 +130,6 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT load(ec, validated_tx_head_, table_t::validated_tx_head); load(ec, validated_tx_body_, table_t::validated_tx_body); - load(ec, address_head_, table_t::address_head); - load(ec, address_body_, table_t::address_body); load(ec, filter_bk_head_, table_t::filter_bk_head); load(ec, filter_bk_body_, table_t::filter_bk_body); load(ec, filter_tx_head_, table_t::filter_tx_head); diff --git a/include/bitcoin/database/impl/store/store_reload.ipp b/include/bitcoin/database/impl/store/store_reload.ipp index 57bcc1ec9..a8f930dd3 100644 --- a/include/bitcoin/database/impl/store/store_reload.ipp +++ b/include/bitcoin/database/impl/store/store_reload.ipp @@ -92,8 +92,6 @@ code CLASS::reload(const event_handler& handler) NOEXCEPT reload(ec, validated_tx_head_, table_t::validated_tx_head); reload(ec, validated_tx_body_, table_t::validated_tx_body); - reload(ec, address_head_, table_t::address_head); - reload(ec, address_body_, table_t::address_body); reload(ec, filter_bk_head_, table_t::filter_bk_head); reload(ec, filter_bk_body_, table_t::filter_bk_body); reload(ec, filter_tx_head_, table_t::filter_tx_head); diff --git a/include/bitcoin/database/impl/store/store_report.ipp b/include/bitcoin/database/impl/store/store_report.ipp index 4319d8329..b3adaa9c2 100644 --- a/include/bitcoin/database/impl/store/store_report.ipp +++ b/include/bitcoin/database/impl/store/store_report.ipp @@ -59,7 +59,6 @@ void CLASS::report(const error_handler& handler) const NOEXCEPT report(prevout_body_, table_t::prevout_body); report(validated_bk_body_, table_t::validated_bk_body); report(validated_tx_body_, table_t::validated_tx_body); - report(address_body_, table_t::address_body); report(filter_bk_body_, table_t::filter_bk_body); report(filter_tx_body_, table_t::filter_tx_body); } @@ -105,8 +104,6 @@ code CLASS::get_fault() const NOEXCEPT if ((ec = validated_bk_body_.get_fault())) return ec; if ((ec = validated_tx_head_.get_fault())) return ec; if ((ec = validated_tx_body_.get_fault())) return ec; - if ((ec = address_head_.get_fault())) return ec; - if ((ec = address_body_.get_fault())) return ec; if ((ec = filter_bk_head_.get_fault())) return ec; if ((ec = filter_bk_body_.get_fault())) return ec; if ((ec = filter_tx_head_.get_fault())) return ec; @@ -160,8 +157,6 @@ size_t CLASS::get_space() const NOEXCEPT space(validated_bk_body_); space(validated_tx_head_); space(validated_tx_body_); - space(address_head_); - space(address_body_); space(filter_bk_head_); space(filter_bk_body_); space(filter_tx_head_); diff --git a/include/bitcoin/database/impl/store/store_restore.ipp b/include/bitcoin/database/impl/store/store_restore.ipp index fc58b30af..465d97814 100644 --- a/include/bitcoin/database/impl/store/store_restore.ipp +++ b/include/bitcoin/database/impl/store/store_restore.ipp @@ -145,7 +145,6 @@ code CLASS::restore(const event_handler& handler) NOEXCEPT restore(ec, validated_bk, table_t::validated_bk_table); restore(ec, validated_tx, table_t::validated_tx_table); - restore(ec, address, table_t::address_table); restore(ec, filter_bk, table_t::filter_bk_table); restore(ec, filter_tx, table_t::filter_tx_table); diff --git a/include/bitcoin/database/impl/store/store_snapshot.ipp b/include/bitcoin/database/impl/store/store_snapshot.ipp index 81ba2c7ff..d7814a325 100644 --- a/include/bitcoin/database/impl/store/store_snapshot.ipp +++ b/include/bitcoin/database/impl/store/store_snapshot.ipp @@ -66,7 +66,6 @@ code CLASS::snapshot(const event_handler& handler, bool prune) NOEXCEPT flush(ec, validated_bk_body_, table_t::validated_bk_body); flush(ec, validated_tx_body_, table_t::validated_tx_body); - flush(ec, address_body_, table_t::address_body); flush(ec, filter_bk_body_, table_t::filter_bk_body); flush(ec, filter_tx_body_, table_t::filter_tx_body); diff --git a/include/bitcoin/database/impl/store/store_unload_close.ipp b/include/bitcoin/database/impl/store/store_unload_close.ipp index 75d32b56e..ad419a319 100644 --- a/include/bitcoin/database/impl/store/store_unload_close.ipp +++ b/include/bitcoin/database/impl/store/store_unload_close.ipp @@ -77,8 +77,6 @@ code CLASS::unload_close(const event_handler& handler) NOEXCEPT unload(ec, validated_tx_head_, table_t::validated_tx_head); unload(ec, validated_tx_body_, table_t::validated_tx_body); - unload(ec, address_head_, table_t::address_head); - unload(ec, address_body_, table_t::address_body); unload(ec, filter_bk_head_, table_t::filter_bk_head); unload(ec, filter_bk_body_, table_t::filter_bk_body); unload(ec, filter_tx_head_, table_t::filter_tx_head); @@ -132,8 +130,6 @@ code CLASS::unload_close(const event_handler& handler) NOEXCEPT close(ec, validated_tx_head_, table_t::validated_tx_head); close(ec, validated_tx_body_, table_t::validated_tx_body); - close(ec, address_head_, table_t::address_head); - close(ec, address_body_, table_t::address_body); close(ec, filter_bk_head_, table_t::filter_bk_head); close(ec, filter_bk_body_, table_t::filter_bk_body); close(ec, filter_tx_head_, table_t::filter_tx_head); diff --git a/include/bitcoin/database/primitives/keys.hpp b/include/bitcoin/database/primitives/keys.hpp index ccf9a36ce..f772cea66 100644 --- a/include/bitcoin/database/primitives/keys.hpp +++ b/include/bitcoin/database/primitives/keys.hpp @@ -25,13 +25,37 @@ namespace libbitcoin { namespace database { namespace keys { -/// en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function +/// Search key, Width bytes stored (loose search if less than the digest). +template +struct search +{ + static_assert(Width == system::hash_size || + Width <= sizeof(uint64_t) + sizeof(uint64_t)); + + static constexpr size_t width = Width; + static constexpr size_t offset = (Width == system::hash_size) ? zero : + sizeof(uint64_t); + + system::hash_digest value; +}; + +/// True if Key is a keys::search<> instantiation. +template +constexpr bool is_search = false; +template +constexpr bool is_search> = true; + +/// en.wikipedia.org/wiki/Fowler�Noll�Vo_hash_function INLINE constexpr uint64_t fnv1a_combine(uint64_t left, uint64_t right); /// Key size in bytes. template INLINE constexpr size_t size() NOEXCEPT; +/// True if the map is keyed (a zero-width stored key is still keyed). +template +INLINE constexpr bool keyed() NOEXCEPT; + /// The hashmap bucket of the key. template INLINE Integral bucket(const Key& key, Integral buckets) NOEXCEPT; diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 47c220f6b..e5639aa48 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -141,7 +141,6 @@ class query size_t validated_tx_head_size() const NOEXCEPT; size_t filter_bk_head_size() const NOEXCEPT; size_t filter_tx_head_size() const NOEXCEPT; - size_t address_head_size() const NOEXCEPT; /// Table body logical byte sizes. size_t header_body_size() const NOEXCEPT; @@ -165,7 +164,6 @@ class query size_t validated_tx_body_size() const NOEXCEPT; size_t filter_bk_body_size() const NOEXCEPT; size_t filter_tx_body_size() const NOEXCEPT; - size_t address_body_size() const NOEXCEPT; /// Table (head + body) logical byte sizes. size_t header_size() const NOEXCEPT; @@ -189,11 +187,11 @@ class query size_t validated_tx_size() const NOEXCEPT; size_t filter_bk_size() const NOEXCEPT; size_t filter_tx_size() const NOEXCEPT; - size_t address_size() const NOEXCEPT; /// Buckets (hashmap + arraymap). size_t header_buckets() const NOEXCEPT; size_t ins_buckets() const NOEXCEPT; + size_t outs_buckets() const NOEXCEPT; size_t txs_buckets() const NOEXCEPT; size_t tx_buckets() const NOEXCEPT; @@ -204,7 +202,6 @@ class query size_t validated_tx_buckets() const NOEXCEPT; size_t filter_bk_buckets() const NOEXCEPT; size_t filter_tx_buckets() const NOEXCEPT; - size_t address_buckets() const NOEXCEPT; /// Records. size_t header_records() const NOEXCEPT; @@ -221,7 +218,6 @@ class query size_t duplicate_records() const NOEXCEPT; size_t prevalid_records() const NOEXCEPT; size_t filter_bk_records() const NOEXCEPT; - size_t address_records() const NOEXCEPT; /// Counters (archive slabs - txs/puts/filter_tx can be derived). size_t input_count(const tx_link& link) const NOEXCEPT; @@ -351,7 +347,7 @@ class query code to_address_outputs(const stopper& cancel, output_links& out, const hash_digest& key) const NOEXCEPT; - code to_address_outputs(const stopper& cancel, address_link& cursor, + code to_address_outputs(const stopper& cancel, outs_link& cursor, output_links& out, const hash_digest& key, size_t limit) const NOEXCEPT; @@ -923,7 +919,6 @@ class query memory ins; memory outs; memory tx; - memory address; }; /// Per-tx allocated table links (bases advanced by the block writer). @@ -934,7 +929,6 @@ class query output_link out_fk; ins_link ins_fk; outs_link outs_fk; - address_link ad_fk; }; code set_code(std::vector& twins, const accessors& ptrs, diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index 541cc47fa..d3af4628c 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -142,9 +142,9 @@ class store Storage ins_head_; table::ins_storage ins_body_; - // array + // aggregate (address spine + outs column) Storage outs_head_; - Storage outs_body_; + table::outs_storage outs_body_; // record hashmap Storage tx_head_; @@ -207,10 +207,6 @@ class store /// Optionals. /// ----------------------------------------------------------------------- - // record hashmap - Storage address_head_; - Storage address_body_; - // record arraymap Storage filter_bk_head_; Storage filter_bk_body_; @@ -288,7 +284,6 @@ class store table::validated_tx validated_tx; /// Optionals. - table::address address; table::filter_bk filter_bk; table::filter_tx filter_tx; }; diff --git a/include/bitcoin/database/tables/archives/output.hpp b/include/bitcoin/database/tables/archives/output.hpp index 3ac00a281..c0f6fa7bd 100644 --- a/include/bitcoin/database/tables/archives/output.hpp +++ b/include/bitcoin/database/tables/archives/output.hpp @@ -132,6 +132,27 @@ struct output system::chain::script::cptr script{}; }; + struct get_script_hash + : public schema::output + { + inline link count() const NOEXCEPT + { + BC_ASSERT(false); + return {}; + } + + inline bool from_data(reader& source) NOEXCEPT + { + using namespace system; + source.skip_bytes(tx::size); + source.skip_variable(); + hash = sha256_hash(source.read_bytes(source.read_size())); + return source; + } + + hash_digest hash{}; + }; + struct get_parent_value : public schema::output { diff --git a/include/bitcoin/database/tables/archives/outs.hpp b/include/bitcoin/database/tables/archives/outs.hpp index d6d78bbc0..5aab67db9 100644 --- a/include/bitcoin/database/tables/archives/outs.hpp +++ b/include/bitcoin/database/tables/archives/outs.hpp @@ -29,14 +29,39 @@ namespace libbitcoin { namespace database { namespace table { -/// Outs is a record output fk records. -struct outs - : public no_map +/// Address spine (column zero): conflict link, no stored value or key. +struct outs_address +{ + using link = schema::address::link; + static constexpr auto width = schema::address::minrow; + static constexpr auto suffix = "address"_t; + + struct record + : public schema::address + { + inline bool from_data(reader& source) NOEXCEPT + { + BC_ASSERT(!source || is_zero(source.get_read_position())); + return source; + } + + inline bool to_data(flipper& sink) const NOEXCEPT + { + BC_ASSERT(!sink || is_zero(sink.get_write_position())); + return sink; + } + }; +}; + +/// Outs column: output fk records (rows aligned with the address spine). +struct outs_puts { using out = schema::output::link; using tx = schema::transaction::link; + using link = schema::outs::link; using output_links = std::vector; - using no_map::nomap; + static constexpr auto width = schema::outs::size; + static constexpr auto suffix = "puts"_t; struct record : public schema::outs @@ -161,6 +186,31 @@ struct outs }; }; +/// The outs association: address spine (searched by output script hash) with +/// the aligned output fk column, shared row count and allocation. +struct outs + : public hash_maps +{ + using base = hash_maps; + using base::hashmaps; + + using out = schema::output::link; + using output_links = std::vector; + + /// Column element aliases. + using record = outs_puts::record; + using get_output = outs_puts::get_output; + using put_ref = outs_puts::put_ref; + using put_view = outs_puts::put_view; + + /// The output fk column (rows aligned with the address spine). + column puts{ *this }; +}; + +/// Aggregate (files). +template