diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 83fffbc66..f631c9629 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -58,6 +58,7 @@ src_libbitcoin_database_la_SOURCES = \ ${srcdir}/../../src/memory/mman.cpp \ ${srcdir}/../../src/memory/mstage.cpp \ ${srcdir}/../../src/memory/utilities.cpp \ + ${srcdir}/../../src/types/envelope.cpp \ ${srcdir}/../../src/types/history.cpp \ ${srcdir}/../../src/types/multisig_view.cpp \ ${srcdir}/../../src/types/unspent.cpp @@ -325,6 +326,7 @@ include_bitcoin_database_types_HEADERS = \ ${srcdir}/../../include/bitcoin/database/types/associations.hpp \ ${srcdir}/../../include/bitcoin/database/types/block_state.hpp \ ${srcdir}/../../include/bitcoin/database/types/difference_set.hpp \ + ${srcdir}/../../include/bitcoin/database/types/envelope.hpp \ ${srcdir}/../../include/bitcoin/database/types/fee_rate.hpp \ ${srcdir}/../../include/bitcoin/database/types/header_state.hpp \ ${srcdir}/../../include/bitcoin/database/types/history.hpp \ diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj index 4688b3b64..5e9ae2f36 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj @@ -136,6 +136,7 @@ $(IntDir)src_memory_utilities.obj + @@ -212,6 +213,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters index dbf332312..69fa481f1 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -132,6 +132,9 @@ src + + src\types + src\types @@ -356,6 +359,9 @@ include\bitcoin\database\types + + include\bitcoin\database\types + include\bitcoin\database\types diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj index 5aa1ad631..28cc7e71f 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj @@ -136,6 +136,7 @@ $(IntDir)src_memory_utilities.obj + @@ -212,6 +213,7 @@ + diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters index dbf332312..69fa481f1 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -132,6 +132,9 @@ src + + src\types + src\types @@ -356,6 +359,9 @@ include\bitcoin\database\types + + include\bitcoin\database\types + include\bitcoin\database\types diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp index ed11b0c6e..2b1ac7a74 100644 --- a/include/bitcoin/database.hpp +++ b/include/bitcoin/database.hpp @@ -86,6 +86,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/database/impl/query/archive/chain_writer.ipp b/include/bitcoin/database/impl/query/archive/chain_writer.ipp index 0bca28396..35b24c6d3 100644 --- a/include/bitcoin/database/impl/query/archive/chain_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/chain_writer.ipp @@ -391,9 +391,6 @@ code CLASS::set_code(const block& block, const header_link& key, // Optional hash, only has value on height intervals. auto interval = create_interval(key, height); - // Depth is only set by writer for genesis (is_zero(tx_fks[0])). - const auto depth = store_.interval_depth(); - using bytes = linkage::integer; const auto light = possible_narrow_cast( block.serialized_size(false)); @@ -418,7 +415,7 @@ code CLASS::set_code(const block& block, const header_link& key, count, tx_fks, std::move(interval), - depth + store_.envelope() }) ? error::success : error::txs_txs_put; // ======================================================================== } diff --git a/include/bitcoin/database/impl/query/archive/wire_writer.ipp b/include/bitcoin/database/impl/query/archive/wire_writer.ipp index b26f7fb08..a6f4fac1f 100644 --- a/include/bitcoin/database/impl/query/archive/wire_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/wire_writer.ipp @@ -238,10 +238,6 @@ code CLASS::set_code(const block_view& block, const header_link& key, // Optional hash, only has value on height intervals. auto interval = create_interval(key, height); - // Depth/forks only set by writer for genesis (is_zero(tx_fks[0])). - const auto depth = store_.interval_depth(); - const auto forks = store_.fork_flags(); - using bytes = linkage::integer; const auto count = possible_narrow_cast>(txs); const auto light = possible_narrow_cast(block.serialized_size(false)); @@ -343,8 +339,7 @@ code CLASS::set_code(const block_view& block, const header_link& key, count, tx_fks, std::move(interval), - depth, - forks + store_.envelope() }) ? error::success : error::txs_txs_put; // ======================================================================== } diff --git a/include/bitcoin/database/impl/query/initialize.ipp b/include/bitcoin/database/impl/query/initialize.ipp index ea5b6f993..7c877b633 100644 --- a/include/bitcoin/database/impl/query/initialize.ipp +++ b/include/bitcoin/database/impl/query/initialize.ipp @@ -29,12 +29,10 @@ namespace database { // Initialization (natural-keyed). // ---------------------------------------------------------------------------- -// server (warning) TEMPLATE -uint32_t CLASS::initialized_forks() const NOEXCEPT +const database::envelope& CLASS::envelope() const NOEXCEPT { - table::txs::get_genesis_forks txs{}; - return store_.txs.at(to_txs(0), txs) ? txs.forks : store_.fork_flags(); + return store_.envelope(); } // server/dumps diff --git a/include/bitcoin/database/impl/query/merkle.ipp b/include/bitcoin/database/impl/query/merkle.ipp index e8afcc625..54c1ad342 100644 --- a/include/bitcoin/database/impl/query/merkle.ipp +++ b/include/bitcoin/database/impl/query/merkle.ipp @@ -295,8 +295,7 @@ size_t CLASS::interval_span() const NOEXCEPT TEMPLATE size_t CLASS::interval_depth() const NOEXCEPT { - table::txs::get_genesis_depth txs{}; - return store_.txs.at(to_txs(0), txs) ? txs.depth : store_.interval_depth(); + return store_.interval_depth(); } // protected diff --git a/include/bitcoin/database/impl/query/unspent.ipp b/include/bitcoin/database/impl/query/unspent.ipp index 4572db3e7..040eaaf93 100644 --- a/include/bitcoin/database/impl/query/unspent.ipp +++ b/include/bitcoin/database/impl/query/unspent.ipp @@ -60,7 +60,7 @@ bool CLASS::is_bip30_exception(bool& out, return true; // With the rule unconfigured duplicates are unconstrained (no exception). - if (!script::is_enabled(initialized_forks(), chain::flags::bip30_rule)) + if (!store_.envelope().forks.bip30) return true; const auto key = get_header_key(link); diff --git a/include/bitcoin/database/impl/store/store.ipp b/include/bitcoin/database/impl/store/store.ipp index 4eef24afb..05e694ac0 100644 --- a/include/bitcoin/database/impl/store/store.ipp +++ b/include/bitcoin/database/impl/store/store.ipp @@ -27,6 +27,7 @@ namespace database { TEMPLATE CLASS::store(const settings& config) NOEXCEPT : configuration_(config), + envelope_(config.envelope), // Archive. // ------------------------------------------------------------------------ @@ -132,6 +133,7 @@ CLASS::store(const settings& config) NOEXCEPT filter_bk(filter_bk_head_, filter_bk_body_, config.filter_bk.buckets), filter_tx(filter_tx_head_, filter_tx_body_, config.filter_tx.buckets) { + envelope_.set(config); } TEMPLATE @@ -151,13 +153,13 @@ uint8_t CLASS::interval_depth() const NOEXCEPT { // Configuration uses uint16_t because of boost parser bug for single byte. // But 2^255 is sufficient given that interval is limited by chain length. - return system::limit(configuration_.interval_depth); + return system::limit(envelope_.interval_depth); } TEMPLATE -uint32_t CLASS::fork_flags() const NOEXCEPT +const database::envelope& CLASS::envelope() const NOEXCEPT { - return configuration_.fork_flags; + return envelope_; } TEMPLATE diff --git a/include/bitcoin/database/impl/store/store_open.ipp b/include/bitcoin/database/impl/store/store_open.ipp index 47edfd70d..e7ed5d8f6 100644 --- a/include/bitcoin/database/impl/store/store_open.ipp +++ b/include/bitcoin/database/impl/store/store_open.ipp @@ -84,6 +84,19 @@ code CLASS::open(const event_handler& handler) NOEXCEPT verify(ec, filter_bk, table_t::filter_bk_table); verify(ec, filter_tx, table_t::filter_tx_table); + if (!ec) + { + // The stored creation envelope governs, configuration is not read. + table::txs::get_envelope genesis{}; + if (!is_zero(txs.body_size())) + { + if (txs.at(zero, genesis)) + envelope_ = genesis.envelope; + else + ec = error::verify_table; + } + } + if (ec) { /* code */ unload_close(handler); diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 503a0208d..9bee6051d 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -103,8 +103,8 @@ class query /// Count of puts not resulting in table body search to detect duplication. size_t negative_search_count() const NOEXCEPT; - /// Forks configured at store creation. - uint32_t initialized_forks() const NOEXCEPT; + /// Settings envelope stored at store creation. + const database::envelope& envelope() const NOEXCEPT; /// Store extent. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/database/settings.hpp b/include/bitcoin/database/settings.hpp index 0d5f1117a..c47381c06 100644 --- a/include/bitcoin/database/settings.hpp +++ b/include/bitcoin/database/settings.hpp @@ -20,6 +20,7 @@ #define LIBBITCOIN_DATABASE_SETTINGS_HPP #include +#include #include #include @@ -66,8 +67,8 @@ struct BCD_API settings /// Depth of merkle tree interval caching (used at store create only). uint16_t interval_depth{ max_uint8 }; - /// Fork flags upon store creation (used at store create only). - uint32_t fork_flags{}; + /// Creation settings envelope (used at store create only). + database::envelope envelope{}; /// Path to the database directory. std::filesystem::path path{ "bitcoin" }; diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index ca4408a5a..888a2388f 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -95,8 +95,8 @@ class store /// Depth of electrum merkle tree interval caching upon store creation. uint8_t interval_depth() const NOEXCEPT; - /// Fork flags upon store creation. - uint32_t fork_flags() const NOEXCEPT; + /// Settings envelope stored at store creation. + const database::envelope& envelope() const NOEXCEPT; /// Determine if the store is non-empty/initialized. bool is_dirty() const NOEXCEPT; @@ -124,6 +124,9 @@ class store // This is thread safe. const settings& configuration_; + // This is set at create/open and read-only thereafter. + database::envelope envelope_{}; + /// Archives. /// ----------------------------------------------------------------------- // These are thread safe. diff --git a/include/bitcoin/database/tables/archives/header.hpp b/include/bitcoin/database/tables/archives/header.hpp index cc8ad5ed3..1b750d0e3 100644 --- a/include/bitcoin/database/tables/archives/header.hpp +++ b/include/bitcoin/database/tables/archives/header.hpp @@ -110,17 +110,7 @@ struct header return sink; } - inline bool operator==(const record& other) const NOEXCEPT - { - return ctx == other.ctx - && milestone == other.milestone - && parent_fk == other.parent_fk - && version == other.version - && timestamp == other.timestamp - && bits == other.bits - && nonce == other.nonce - && merkle_root == other.merkle_root; - } + inline bool operator==(const record&) const NOEXCEPT = default; context ctx{}; bool milestone{}; diff --git a/include/bitcoin/database/tables/archives/input.hpp b/include/bitcoin/database/tables/archives/input.hpp index 214b856db..38f67353a 100644 --- a/include/bitcoin/database/tables/archives/input.hpp +++ b/include/bitcoin/database/tables/archives/input.hpp @@ -66,11 +66,7 @@ struct input return sink; } - inline bool operator==(const slab& other) const NOEXCEPT - { - return script == other.script - && witness == other.witness; - } + inline bool operator==(const slab&) const NOEXCEPT = default; system::chain::script script{}; system::chain::witness witness{}; diff --git a/include/bitcoin/database/tables/archives/ins.hpp b/include/bitcoin/database/tables/archives/ins.hpp index e0599d675..dbe9c2816 100644 --- a/include/bitcoin/database/tables/archives/ins.hpp +++ b/include/bitcoin/database/tables/archives/ins.hpp @@ -66,11 +66,7 @@ struct ins_point return index == system::chain::point::null_index; } - inline bool operator==(const record& other) const NOEXCEPT - { - return hash == other.hash - && index == other.index; - } + inline bool operator==(const record&) const NOEXCEPT = default; hash_digest hash{}; ix::integer index{}; @@ -142,12 +138,7 @@ struct ins_sequence return sink; } - inline bool operator==(const record& other) const NOEXCEPT - { - return sequence == other.sequence - && input_fk == other.input_fk - && parent_fk == other.parent_fk; - } + inline bool operator==(const record&) const NOEXCEPT = default; uint32_t sequence{}; in::integer input_fk{}; diff --git a/include/bitcoin/database/tables/archives/output.hpp b/include/bitcoin/database/tables/archives/output.hpp index e85e68f99..09431420a 100644 --- a/include/bitcoin/database/tables/archives/output.hpp +++ b/include/bitcoin/database/tables/archives/output.hpp @@ -70,12 +70,7 @@ struct output return sink; } - inline bool operator==(const slab& other) const NOEXCEPT - { - return parent_fk == other.parent_fk - && value == other.value - && script == other.script; - } + inline bool operator==(const slab&) const NOEXCEPT = default; tx::integer parent_fk{}; uint64_t value{}; diff --git a/include/bitcoin/database/tables/archives/outs.hpp b/include/bitcoin/database/tables/archives/outs.hpp index 5aab67db9..6b3c70abe 100644 --- a/include/bitcoin/database/tables/archives/outs.hpp +++ b/include/bitcoin/database/tables/archives/outs.hpp @@ -93,10 +93,7 @@ struct outs_puts return sink; } - inline bool operator==(const record& other) const NOEXCEPT - { - return out_fks == other.out_fks; - } + inline bool operator==(const record&) const NOEXCEPT = default; output_links out_fks{}; }; diff --git a/include/bitcoin/database/tables/archives/transaction.hpp b/include/bitcoin/database/tables/archives/transaction.hpp index b9fd9b003..6373b4a5e 100644 --- a/include/bitcoin/database/tables/archives/transaction.hpp +++ b/include/bitcoin/database/tables/archives/transaction.hpp @@ -116,18 +116,7 @@ struct transaction return sink; } - inline bool operator==(const record& other) const NOEXCEPT - { - return coinbase == other.coinbase - && light == other.light - && heavy == other.heavy - && locktime == other.locktime - && version == other.version - && ins_count == other.ins_count - && outs_count == other.outs_count - && point_fk == other.point_fk - && outs_fk == other.outs_fk; - } + inline bool operator==(const record&) const NOEXCEPT = default; bool coinbase{}; bytes::integer light{}; diff --git a/include/bitcoin/database/tables/archives/txs.hpp b/include/bitcoin/database/tables/archives/txs.hpp index 4fb5695dc..2ac8b3677 100644 --- a/include/bitcoin/database/tables/archives/txs.hpp +++ b/include/bitcoin/database/tables/archives/txs.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -84,7 +85,7 @@ struct txs return system::possible_narrow_cast( skip_sizes + ct::size + (tx_fks.size() * tx::size) + (interval.has_value() ? schema::hash : zero) + - (is_genesis() ? one + flags::size : zero)); + (is_genesis() ? envelope.serialized_size() : zero)); } inline bool from_data(reader& source) NOEXCEPT @@ -105,12 +106,9 @@ struct txs interval.reset(); if (is_interval(merged)) interval = source.read_hash(); - // depth/forks (genesis only) + // envelope (genesis only) if (is_genesis()) - { - depth = source.read_byte(); - forks = source.read_little_endian(); - } + envelope.from_data(source); BC_ASSERT(!source || source.get_read_position() == count()); return source; @@ -137,33 +135,21 @@ struct txs // interval (when specified) if (interval.has_value()) sink.write_bytes(interval.value()); - // depth/forks (genesis only) + // envelope (genesis only) if (is_genesis()) - { - sink.write_byte(depth); - sink.write_little_endian(forks); - } + envelope.to_data(sink); BC_ASSERT(!sink || sink.get_write_position() == count()); return sink; } - inline bool operator==(const slab& other) const NOEXCEPT - { - return light == other.light - && heavy == other.heavy - && tx_fks == other.tx_fks - && interval == other.interval - && depth == other.depth - && forks == other.forks; - } + inline bool operator==(const slab&) const NOEXCEPT = default; bytes::integer light{}; bytes::integer heavy{}; keys tx_fks{}; hash interval{}; - uint8_t depth{}; - flags::integer forks{}; + database::envelope envelope{}; }; // put a contiguous set of tx identifiers. @@ -180,7 +166,7 @@ struct txs return system::possible_narrow_cast( skip_sizes + ct::size + (number * tx::size) + (interval.has_value() ? schema::hash : zero) + - (is_genesis() ? one + flags::size : zero)); + (is_genesis() ? envelope.serialized_size() : zero)); } inline bool to_data(finalizer& sink) const NOEXCEPT @@ -198,12 +184,9 @@ struct txs // interval (when specified) if (interval.has_value()) sink.write_bytes(interval.value()); - // depth/forks (genesis only) + // envelope (genesis only) if (is_genesis()) - { - sink.write_byte(depth); - sink.write_little_endian(forks); - } + envelope.to_data(sink); BC_ASSERT(!sink || sink.get_write_position() == count()); return sink; @@ -214,8 +197,7 @@ struct txs ct::integer number{}; tx::integer tx_fk{}; hash interval{}; - uint8_t depth{}; - flags::integer forks{}; + database::envelope envelope{}; }; struct get_interval @@ -247,39 +229,7 @@ struct txs }; // This reader is only applicable to the genesis block. - struct get_genesis_depth - : public schema::txs - { - inline link count() const NOEXCEPT - { - BC_ASSERT(false); - return {}; - } - - // Stored at end since only read once (at startup). - inline bool from_data(reader& source) NOEXCEPT - { - // tx sizes - const auto merged = source.read_little_endian(); - source.skip_bytes(bytes::size); - - // tx fks - const auto number = source.read_little_endian(); - source.skip_bytes(number * tx::size); - - // interval - source.skip_bytes(is_interval(merged) ? schema::hash : zero); - - // depth - depth = source.read_byte(); - return source; - } - - uint8_t depth{}; - }; - - // This reader is only applicable to the genesis block. - struct get_genesis_forks + struct get_envelope : public schema::txs { inline link count() const NOEXCEPT @@ -302,15 +252,11 @@ struct txs // interval source.skip_bytes(is_interval(merged) ? schema::hash : zero); - // depth - source.skip_byte(); - - // forks - forks = source.read_little_endian(); - return source; + // envelope + return envelope.from_data(source); } - flags::integer forks{}; + database::envelope envelope{}; }; struct get_position diff --git a/include/bitcoin/database/tables/caches/prevalid.hpp b/include/bitcoin/database/tables/caches/prevalid.hpp index 0685c9c66..47129e5ea 100644 --- a/include/bitcoin/database/tables/caches/prevalid.hpp +++ b/include/bitcoin/database/tables/caches/prevalid.hpp @@ -57,10 +57,7 @@ struct prevalid return sink; } - inline bool operator==(const record& other) const NOEXCEPT - { - return header_fk == other.header_fk; - } + inline bool operator==(const record&) const NOEXCEPT = default; header::integer header_fk{}; }; diff --git a/include/bitcoin/database/tables/caches/validated_bk.hpp b/include/bitcoin/database/tables/caches/validated_bk.hpp index 2b07d5dad..7741b8fd5 100644 --- a/include/bitcoin/database/tables/caches/validated_bk.hpp +++ b/include/bitcoin/database/tables/caches/validated_bk.hpp @@ -51,10 +51,7 @@ struct validated_bk return sink; } - inline bool operator==(const record& other) const NOEXCEPT - { - return code == other.code; - } + inline bool operator==(const record&) const NOEXCEPT = default; coding::integer code{}; }; diff --git a/include/bitcoin/database/tables/caches/validated_tx.hpp b/include/bitcoin/database/tables/caches/validated_tx.hpp index 0c321a595..fbf0fe203 100644 --- a/include/bitcoin/database/tables/caches/validated_tx.hpp +++ b/include/bitcoin/database/tables/caches/validated_tx.hpp @@ -71,13 +71,7 @@ struct validated_tx return sink; } - inline bool operator==(const slab& other) const NOEXCEPT - { - return ctx == other.ctx - && code == other.code - && fee == other.fee - && sigops == other.sigops; - } + inline bool operator==(const slab&) const NOEXCEPT = default; context ctx{}; coding::integer code{}; diff --git a/include/bitcoin/database/tables/context.hpp b/include/bitcoin/database/tables/context.hpp index b1f13d703..39eee2be3 100644 --- a/include/bitcoin/database/tables/context.hpp +++ b/include/bitcoin/database/tables/context.hpp @@ -52,12 +52,7 @@ struct context return system::chain::script::is_enabled(flags, rule); } - inline bool operator==(const context& other) const NOEXCEPT - { - return flags == other.flags - && height == other.height - && mtp == other.mtp; - } + inline bool operator==(const context&) const NOEXCEPT = default; height_t::integer flags{}; flag_t::integer height{}; diff --git a/include/bitcoin/database/tables/schema.hpp b/include/bitcoin/database/tables/schema.hpp index 0bddef3cb..55a6ac5bf 100644 --- a/include/bitcoin/database/tables/schema.hpp +++ b/include/bitcoin/database/tables/schema.hpp @@ -99,6 +99,7 @@ struct header static_assert(minrow == 97u); static_assert(link::size == 3u); static_assert(cell == 4u); + bool operator==(const header&) const NOEXCEPT = default; }; // record hashmap @@ -126,6 +127,7 @@ struct transaction static_assert(minrow == 64u); static_assert(link::size == 4u); static_assert(cell == 4u); + bool operator==(const transaction&) const NOEXCEPT = default; }; // blob @@ -143,6 +145,7 @@ struct input static_assert(minsize == 2u); static_assert(minrow == 2u); static_assert(link::size == 5u); + bool operator==(const input&) const NOEXCEPT = default; }; // blob @@ -161,6 +164,7 @@ struct output static_assert(minsize == 6u); static_assert(minrow == 6u); static_assert(link::size == 5u); + bool operator==(const output&) const NOEXCEPT = default; }; // record multimap aggregate (point spine with sequence column) @@ -180,6 +184,7 @@ struct ins static_assert(minrow == 39u); static_assert(link::size == 4u); static_assert(cell == 8u); + bool operator==(const ins&) const NOEXCEPT = default; }; // ins sequence column @@ -198,6 +203,7 @@ struct ins_sequence static_assert(minsize == 13u); static_assert(minrow == 13u); static_assert(link::size == 4u); + bool operator==(const ins_sequence&) const NOEXCEPT = default; }; // record multimap spine (optional head, output script hash key) @@ -217,6 +223,7 @@ struct address static_assert(minrow == 4u); static_assert(link::size == 4u); static_assert(cell == 4u); + bool operator==(const address&) const NOEXCEPT = default; }; // address column (output fk) @@ -232,6 +239,7 @@ struct outs static_assert(minsize == 5u); static_assert(minrow == 5u); static_assert(link::size == 4u); + bool operator==(const outs&) const NOEXCEPT = default; }; // slab arraymap @@ -252,6 +260,7 @@ struct txs static_assert(minsize == 12u); static_assert(minrow == 12u); static_assert(link::size == 5u); + bool operator==(const txs&) const NOEXCEPT = default; }; /// Index tables. @@ -272,6 +281,7 @@ struct height static_assert(minrow == 3u); static_assert(link::size == 3u); static_assert(cell == 4u); + bool operator==(const height&) const NOEXCEPT = default; }; // record hashmap @@ -369,6 +379,7 @@ struct validated_bk static_assert(minsize == 1u); static_assert(minrow == 1u); static_assert(link::size == 3u); + bool operator==(const validated_bk&) const NOEXCEPT = default; }; // slab modest (sk:4) multimap, with low multiple rate. @@ -392,6 +403,7 @@ struct validated_tx static_assert(minsize == 14u); static_assert(minrow == 23u); static_assert(link::size == 5u); + bool operator==(const validated_tx&) const NOEXCEPT = default; }; /// Optional tables. diff --git a/include/bitcoin/database/types/envelope.hpp b/include/bitcoin/database/types/envelope.hpp new file mode 100644 index 000000000..d02f7b1ff --- /dev/null +++ b/include/bitcoin/database/types/envelope.hpp @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_TYPES_ENVELOPE_HPP +#define LIBBITCOIN_DATABASE_TYPES_ENVELOPE_HPP + +#include +#include + +namespace libbitcoin { +namespace database { + +struct settings; + +/// Store creation settings, written at create and read at load. The stored +/// values govern the store, configuration is consumed only at creation. +struct BCD_API envelope +{ + static constexpr uint8_t current{ 1 }; + + envelope() NOEXCEPT = default; + envelope(const system::settings& bitcoin, + const settings& database) NOEXCEPT; + + void set(const settings& database) NOEXCEPT; + + bool from_data(reader& source) NOEXCEPT; + bool to_data(finalizer& sink) const NOEXCEPT; + size_t serialized_size() const NOEXCEPT; + bool operator==(const envelope& other) const NOEXCEPT = default; + + /// Database settings. + uint16_t interval_depth{}; + uint32_t header_buckets{}; + uint32_t ins_buckets{}; + uint32_t outs_buckets{}; + uint32_t tx_buckets{}; + uint32_t strong_tx_buckets{}; + uint32_t duplicate_buckets{}; + uint32_t validated_tx_buckets{}; + bool filter{}; + + /// System settings. + system::forks forks{}; + uint64_t initial_subsidy_bitcoin{}; + uint32_t subsidy_interval_blocks{}; + uint32_t timestamp_limit_seconds{}; + uint32_t retargeting_factor{}; + uint32_t retargeting_interval_seconds{}; + uint32_t block_spacing_seconds{}; + uint32_t proof_of_work_limit{}; + uint32_t first_version{}; + uint32_t bip34_version{}; + uint32_t bip66_version{}; + uint32_t bip65_version{}; + uint32_t bip9_version_bit0{}; + uint32_t bip9_version_bit1{}; + uint32_t bip9_version_bit2{}; + uint32_t bip9_version_base{}; + uint32_t bip16_activation_time{}; + size_t bip34_activation_threshold{}; + size_t bip34_enforcement_threshold{}; + size_t bip34_activation_sample{}; + size_t bip90_bip34_height{}; + size_t bip90_bip65_height{}; + size_t bip90_bip66_height{}; + size_t bip30_reactivate_height{}; + system::chain::checkpoint bip30_deactivate_checkpoint{}; + system::chain::checkpoint bip9_bit0_active_checkpoint{}; + system::chain::checkpoint bip9_bit1_active_checkpoint{}; + system::chain::checkpoint bip9_bit2_active_checkpoint{}; +}; + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/types/types.hpp b/include/bitcoin/database/types/types.hpp index feb2ed375..25c32b45e 100644 --- a/include/bitcoin/database/types/types.hpp +++ b/include/bitcoin/database/types/types.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/src/settings.cpp b/src/settings.cpp index d936fe468..76d7d090d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -27,6 +27,7 @@ using namespace bc::system; settings::settings() NOEXCEPT { + envelope.set(*this); } settings::settings(chain::selection context) NOEXCEPT diff --git a/src/types/envelope.cpp b/src/types/envelope.cpp new file mode 100644 index 000000000..455eb7b5c --- /dev/null +++ b/src/types/envelope.cpp @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include + +#include +#include + +namespace libbitcoin { +namespace database { + +using namespace system; + +void envelope::set(const settings& database) NOEXCEPT +{ + interval_depth = database.interval_depth; + header_buckets = database.header.buckets; + ins_buckets = database.ins.buckets; + outs_buckets = database.outs.buckets; + tx_buckets = database.tx.buckets; + strong_tx_buckets = database.strong_tx.buckets; + duplicate_buckets = database.duplicate.buckets; + validated_tx_buckets = database.validated_tx.buckets; + filter = to_bool(database.filter_bk.buckets) && + to_bool(database.filter_tx.buckets); +} + +envelope::envelope(const system::settings& bitcoin, + const settings& database) NOEXCEPT + : forks(bitcoin.forks), + initial_subsidy_bitcoin(bitcoin.initial_subsidy_bitcoin), + subsidy_interval_blocks(bitcoin.subsidy_interval_blocks), + timestamp_limit_seconds(bitcoin.timestamp_limit_seconds), + retargeting_factor(bitcoin.retargeting_factor), + retargeting_interval_seconds(bitcoin.retargeting_interval_seconds), + block_spacing_seconds(bitcoin.block_spacing_seconds), + proof_of_work_limit(bitcoin.proof_of_work_limit), + first_version(bitcoin.first_version), + bip34_version(bitcoin.bip34_version), + bip66_version(bitcoin.bip66_version), + bip65_version(bitcoin.bip65_version), + bip9_version_bit0(bitcoin.bip9_version_bit0), + bip9_version_bit1(bitcoin.bip9_version_bit1), + bip9_version_bit2(bitcoin.bip9_version_bit2), + bip9_version_base(bitcoin.bip9_version_base), + bip16_activation_time(bitcoin.bip16_activation_time), + bip34_activation_threshold(bitcoin.bip34_activation_threshold), + bip34_enforcement_threshold(bitcoin.bip34_enforcement_threshold), + bip34_activation_sample(bitcoin.bip34_activation_sample), + bip90_bip34_height(bitcoin.bip90_bip34_height), + bip90_bip65_height(bitcoin.bip90_bip65_height), + bip90_bip66_height(bitcoin.bip90_bip66_height), + bip30_reactivate_height(bitcoin.bip30_reactivate_height), + bip30_deactivate_checkpoint(bitcoin.bip30_deactivate_checkpoint), + bip9_bit0_active_checkpoint(bitcoin.bip9_bit0_active_checkpoint), + bip9_bit1_active_checkpoint(bitcoin.bip9_bit1_active_checkpoint), + bip9_bit2_active_checkpoint(bitcoin.bip9_bit2_active_checkpoint) +{ + set(database); +} + +bool envelope::from_data(reader& source) NOEXCEPT +{ + if (source.read_byte() != current) + { + source.invalidate(); + return false; + } + + interval_depth = source.read_little_endian(); + header_buckets = source.read_little_endian(); + ins_buckets = source.read_little_endian(); + outs_buckets = source.read_little_endian(); + tx_buckets = source.read_little_endian(); + strong_tx_buckets = source.read_little_endian(); + duplicate_buckets = source.read_little_endian(); + validated_tx_buckets = source.read_little_endian(); + filter = to_bool(source.read_byte()); + + forks.bip16 = to_bool(source.read_byte()); + forks.bip90 = to_bool(source.read_byte()); + forks.bip30 = to_bool(source.read_byte()); + forks.bip30_deactivate = to_bool(source.read_byte()); + forks.bip30_reactivate = to_bool(source.read_byte()); + forks.bip42 = to_bool(source.read_byte()); + forks.bip34 = to_bool(source.read_byte()); + forks.bip65 = to_bool(source.read_byte()); + forks.bip66 = to_bool(source.read_byte()); + forks.bip68 = to_bool(source.read_byte()); + forks.bip112 = to_bool(source.read_byte()); + forks.bip113 = to_bool(source.read_byte()); + forks.bip141 = to_bool(source.read_byte()); + forks.bip143 = to_bool(source.read_byte()); + forks.bip147 = to_bool(source.read_byte()); + forks.bip341 = to_bool(source.read_byte()); + forks.bip342 = to_bool(source.read_byte()); + forks.retarget = to_bool(source.read_byte()); + forks.difficult = to_bool(source.read_byte()); + forks.time_warp_patch = to_bool(source.read_byte()); + forks.block_storm_patch = to_bool(source.read_byte()); + forks.ltc_time_warp_patch = to_bool(source.read_byte()); + forks.ltc_retarget_overflow_patch = to_bool(source.read_byte()); + forks.ltc_scrypt_proof_of_work = to_bool(source.read_byte()); + + initial_subsidy_bitcoin = source.read_little_endian(); + subsidy_interval_blocks = source.read_little_endian(); + timestamp_limit_seconds = source.read_little_endian(); + retargeting_factor = source.read_little_endian(); + retargeting_interval_seconds = source.read_little_endian(); + block_spacing_seconds = source.read_little_endian(); + proof_of_work_limit = source.read_little_endian(); + first_version = source.read_little_endian(); + bip34_version = source.read_little_endian(); + bip66_version = source.read_little_endian(); + bip65_version = source.read_little_endian(); + bip9_version_bit0 = source.read_little_endian(); + bip9_version_bit1 = source.read_little_endian(); + bip9_version_bit2 = source.read_little_endian(); + bip9_version_base = source.read_little_endian(); + bip16_activation_time = source.read_little_endian(); + bip34_activation_threshold = source.read_variable(); + bip34_enforcement_threshold = source.read_variable(); + bip34_activation_sample = source.read_variable(); + bip90_bip34_height = source.read_variable(); + bip90_bip65_height = source.read_variable(); + bip90_bip66_height = source.read_variable(); + bip30_reactivate_height = source.read_variable(); + + const auto read_checkpoint = [&]() NOEXCEPT + { + const auto hash = source.read_hash(); + return chain::checkpoint{ hash, source.read_variable() }; + }; + + bip30_deactivate_checkpoint = read_checkpoint(); + bip9_bit0_active_checkpoint = read_checkpoint(); + bip9_bit1_active_checkpoint = read_checkpoint(); + bip9_bit2_active_checkpoint = read_checkpoint(); + return source; +} + +bool envelope::to_data(finalizer& sink) const NOEXCEPT +{ + sink.write_byte(current); + + sink.write_little_endian(interval_depth); + sink.write_little_endian(header_buckets); + sink.write_little_endian(ins_buckets); + sink.write_little_endian(outs_buckets); + sink.write_little_endian(tx_buckets); + sink.write_little_endian(strong_tx_buckets); + sink.write_little_endian(duplicate_buckets); + sink.write_little_endian(validated_tx_buckets); + sink.write_byte(to_int(filter)); + + sink.write_byte(to_int(forks.bip16)); + sink.write_byte(to_int(forks.bip90)); + sink.write_byte(to_int(forks.bip30)); + sink.write_byte(to_int(forks.bip30_deactivate)); + sink.write_byte(to_int(forks.bip30_reactivate)); + sink.write_byte(to_int(forks.bip42)); + sink.write_byte(to_int(forks.bip34)); + sink.write_byte(to_int(forks.bip65)); + sink.write_byte(to_int(forks.bip66)); + sink.write_byte(to_int(forks.bip68)); + sink.write_byte(to_int(forks.bip112)); + sink.write_byte(to_int(forks.bip113)); + sink.write_byte(to_int(forks.bip141)); + sink.write_byte(to_int(forks.bip143)); + sink.write_byte(to_int(forks.bip147)); + sink.write_byte(to_int(forks.bip341)); + sink.write_byte(to_int(forks.bip342)); + sink.write_byte(to_int(forks.retarget)); + sink.write_byte(to_int(forks.difficult)); + sink.write_byte(to_int(forks.time_warp_patch)); + sink.write_byte(to_int(forks.block_storm_patch)); + sink.write_byte(to_int(forks.ltc_time_warp_patch)); + sink.write_byte(to_int(forks.ltc_retarget_overflow_patch)); + sink.write_byte(to_int(forks.ltc_scrypt_proof_of_work)); + + sink.write_little_endian(initial_subsidy_bitcoin); + sink.write_little_endian(subsidy_interval_blocks); + sink.write_little_endian(timestamp_limit_seconds); + sink.write_little_endian(retargeting_factor); + sink.write_little_endian(retargeting_interval_seconds); + sink.write_little_endian(block_spacing_seconds); + sink.write_little_endian(proof_of_work_limit); + sink.write_little_endian(first_version); + sink.write_little_endian(bip34_version); + sink.write_little_endian(bip66_version); + sink.write_little_endian(bip65_version); + sink.write_little_endian(bip9_version_bit0); + sink.write_little_endian(bip9_version_bit1); + sink.write_little_endian(bip9_version_bit2); + sink.write_little_endian(bip9_version_base); + sink.write_little_endian(bip16_activation_time); + sink.write_variable(bip34_activation_threshold); + sink.write_variable(bip34_enforcement_threshold); + sink.write_variable(bip34_activation_sample); + sink.write_variable(bip90_bip34_height); + sink.write_variable(bip90_bip65_height); + sink.write_variable(bip90_bip66_height); + sink.write_variable(bip30_reactivate_height); + + const auto write_checkpoint = [&](const chain::checkpoint& in) NOEXCEPT + { + sink.write_bytes(in.hash()); + sink.write_variable(in.height()); + }; + + write_checkpoint(bip30_deactivate_checkpoint); + write_checkpoint(bip9_bit0_active_checkpoint); + write_checkpoint(bip9_bit1_active_checkpoint); + write_checkpoint(bip9_bit2_active_checkpoint); + return sink; +} + +size_t envelope::serialized_size() const NOEXCEPT +{ + constexpr auto forks_size = 24_size; + constexpr auto fixed = one + sizeof(uint16_t) + (7 * sizeof(uint32_t)) + + one + forks_size + sizeof(uint64_t) + (15 * sizeof(uint32_t)); + + const auto checkpoint_size = [](const chain::checkpoint& in) NOEXCEPT + { + return hash_size + variable_size(in.height()); + }; + + return fixed + + variable_size(bip34_activation_threshold) + + variable_size(bip34_enforcement_threshold) + + variable_size(bip34_activation_sample) + + variable_size(bip90_bip34_height) + + variable_size(bip90_bip65_height) + + variable_size(bip90_bip66_height) + + variable_size(bip30_reactivate_height) + + checkpoint_size(bip30_deactivate_checkpoint) + + checkpoint_size(bip9_bit0_active_checkpoint) + + checkpoint_size(bip9_bit1_active_checkpoint) + + checkpoint_size(bip9_bit2_active_checkpoint); +} + +} // namespace database +} // namespace libbitcoin diff --git a/test/query/archive/chain_writer.cpp b/test/query/archive/chain_writer.cpp index 0711935b2..bd174a4c3 100644 --- a/test/query/archive/chain_writer.cpp +++ b/test/query/archive/chain_writer.cpp @@ -1,1547 +1,1571 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#include "../../test.hpp" -#include "../../mocks/blocks.hpp" -#include "../../mocks/chunk_store.hpp" - -// ensure context::flags is same size as chain_context::flags. -static_assert(is_same_type < database::context::flag_t::integer, decltype(system::chain::context{}.flags) > ); - -BOOST_FIXTURE_TEST_SUITE(query_chain_writer_tests, test::directory_setup_fixture) - -// slow test (mmap) -BOOST_AUTO_TEST_CASE(query_chain_writer__set_header__mmap_get_header__expected) -{ - constexpr auto milestone = false; - constexpr auto parent = system::null_hash; - constexpr auto merkle_root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); - constexpr auto block_hash = system::base16_array("85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2"); - const system::chain::header header - { - 0x31323334, // version - parent, // previous_block_hash - merkle_root,// merkle_root - 0x41424344, // timestamp - 0x51525354, // bits - 0x61626364 // nonce - }; - - settings settings{}; - settings.header.buckets = 16; - settings.path = TEST_DIRECTORY; - store store{ settings }; - query> query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.set(header, test::context, milestone)); - - table::header::record element1{}; - BOOST_CHECK(store.header.get(query.to_header(block_hash), element1)); - - const auto pointer = query.get_header(query.to_header(block_hash)); - BOOST_CHECK(pointer); - BOOST_CHECK(*pointer == header); - - // must open/close mmap - BOOST_CHECK(!store.close(test::events_handler)); - BOOST_CHECK_EQUAL(element1.ctx.height, system::mask_left(test::context.height, byte_bits)); - BOOST_CHECK_EQUAL(element1.ctx.flags, test::context.flags); - BOOST_CHECK_EQUAL(element1.ctx.mtp, test::context.mtp); - BOOST_CHECK_EQUAL(element1.milestone, milestone); - BOOST_CHECK_EQUAL(element1.version, header.version()); - BOOST_CHECK_EQUAL(element1.parent_fk, schema::header::link::terminal); - BOOST_CHECK_EQUAL(element1.merkle_root, header.merkle_root()); - BOOST_CHECK_EQUAL(element1.timestamp, header.timestamp()); - BOOST_CHECK_EQUAL(element1.bits, header.bits()); - BOOST_CHECK_EQUAL(element1.nonce, header.nonce()); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__set_link_header__is_header__expected) -{ - constexpr auto milestone = true; - constexpr auto merkle_root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); - constexpr auto block_hash = system::base16_array("85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2"); - const system::chain::header header - { - 0x31323334, // version - system::null_hash, // previous_block_hash - merkle_root,// merkle_root - 0x41424344, // timestamp - 0x51525354, // bits - 0x61626364 // nonce - }; - - // nosh - const auto expected_header_head = system::base16_chunk( - "010000ff" // record count - "ffffffff" // bucket[0]... - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "000080" // filter[8], pk-> - "de" // filter[0-7] - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - - const auto expected_header_body = system::base16_chunk( - "ffff7f" // next-> - "85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2" // sk (block.hash) - "04030201" // flags - "141312" // height - "24232221" // mtp - "ffffff" // previous_block_hash (header_fk - not found) (milestone true) - "34333231" // version - "44434241" // timestamp - "54535251" // bits - "64636261" // nonce - "119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); //merkle_root - - header_link link{}; - settings settings{}; - settings.header.buckets = 16; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - - // store open/close flushes record count to head. - BOOST_CHECK(!query.is_header(header.hash())); - BOOST_CHECK(!query.is_associated(0)); - BOOST_CHECK(!query.set_code(link, header, test::context, milestone)); - BOOST_CHECK(!link.is_terminal()); - BOOST_CHECK(query.is_header(header.hash())); - BOOST_CHECK(!query.is_associated(0)); - table::header::record element1{}; - BOOST_CHECK(store.header.get(query.to_header(block_hash), element1)); - BOOST_CHECK(!store.close(test::events_handler)); - BOOST_CHECK_EQUAL(store.header_head(), expected_header_head); - BOOST_CHECK_EQUAL(store.header_body(), expected_header_body); - - BOOST_CHECK_EQUAL(element1.ctx.height, system::mask_left(test::context.height, byte_bits)); - BOOST_CHECK_EQUAL(element1.ctx.flags, test::context.flags); - BOOST_CHECK_EQUAL(element1.ctx.mtp, test::context.mtp); - BOOST_CHECK_EQUAL(element1.milestone, milestone); - BOOST_CHECK_EQUAL(element1.version, header.version()); - BOOST_CHECK_EQUAL(element1.parent_fk, schema::header::link::terminal); - BOOST_CHECK_EQUAL(element1.merkle_root, header.merkle_root()); - BOOST_CHECK_EQUAL(element1.timestamp, header.timestamp()); - BOOST_CHECK_EQUAL(element1.bits, header.bits()); - BOOST_CHECK_EQUAL(element1.nonce, header.nonce()); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__set_tx__empty__expected) -{ - const system::chain::transaction tx{}; - const auto expected_head5_array = system::base16_chunk("0000000000"); - const auto expected_outs_head = system::base16_chunk( - "00000000" // record count - "ffffffff" // bucket[0] - "ffffffff");// bucket[1] - const auto expected_head4_hash = system::base16_chunk( - "01000000" // record count - "ffffffff" // bucket[0]... - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - const auto expected_head5_hash = system::base16_chunk( - "0000000000" // record count - "ffffffffff" // bucket[0]... - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff"); - - // data_chunk store. - settings settings{}; - settings.tx.buckets = 8; - settings.outs.buckets = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - - // store open/close flushes record count to heads. - BOOST_CHECK(!query.set(tx)); - BOOST_CHECK(!store.close(test::events_handler)); - BOOST_CHECK_EQUAL(store.tx_head(), expected_head4_hash); - BOOST_CHECK_EQUAL(store.input_head(), expected_head5_array); - BOOST_CHECK_EQUAL(store.output_head(), expected_head5_array); - BOOST_CHECK_EQUAL(store.outs_head(), expected_outs_head); - BOOST_CHECK_EQUAL(store.tx_body().size(), schema::transaction::minrow); - BOOST_CHECK(store.ins_body().empty()); - BOOST_CHECK(store.input_body().empty()); - BOOST_CHECK(store.output_body().empty()); - BOOST_CHECK(store.outs_body().empty()); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__set_link_tx__null_input__expected) -{ - using namespace system::chain; - const transaction tx - { - 0x01020304, // version - inputs - { - input{ point{}, script{}, witness{}, 0 } - }, - outputs - { - output{ 0, script{} } - }, - 0x11121314 // locktime - }; - const auto expected_tx_head = system::base16_chunk( - "01000000" // record count - "00000000" // bucket[0]... pk-> - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - const auto expected_tx_body = system::base16_chunk( - "ffffff7f" // next-> - "601f0fa54d6de8362c17dc883cc047e1f3ae0523d732598a05e3010fac591f62" // sk (tx.hash(false)) - "3c0080" // light (coinbase merged) - "3c0000" // heavy - "14131211" // locktime - "04030201" // version - "010000" // ins_count - "010000" // outs_count - "00000000" // point_fk-> (ins_fk) - "00000000"); // outs_fk-> - const auto expected_outs_head = system::base16_chunk( - "01000000" // record count - "00000000" // bucket[0] (link->0) - "ffffffff");// bucket[1] - const auto expected_address_body = system::base16_chunk( - "ffffffff"); // next->end - const auto expected_outs_body = system::base16_chunk( - "0000000000"); // output0_fk-> - const auto expected_output_head = system::base16_chunk("0600000000"); - const auto expected_output_body = system::base16_chunk( - "00000000" // parent_fk-> - "00" // value - "00"); // script - const auto expected_ins_body = system::base16_chunk( - "ffffffff" // next-> - "0000000000000000000000000000000000000000000000000000000000000000" - "ffffff"); // index - ////const auto expected_spend_head = system::base16_chunk( - //// "01000000" // record count - //// "00000000" // pk-> - //// "ffffffff" - //// "ffffffff" - //// "ffffffff" - //// "ffffffff"); - ////const auto expected_spend_body = system::base16_chunk( - //// "ffffffff" // terminal-> - //// "00000000" // fp: point_stub - //// "ffffff" // fp: point_index (null) - //// "ffffffff" // point_fk-> - //// "00000000" // parent_fk-> - //// "00000000" // sequence - //// "0000000000"); // input_fk-> - const auto expected_input_head = system::base16_chunk("0200000000"); - const auto expected_input_body = system::base16_chunk( - "00" // script - "00"); // witness - constexpr auto tx_hash = system::base16_array("601f0fa54d6de8362c17dc883cc047e1f3ae0523d732598a05e3010fac591f62"); - BOOST_CHECK_EQUAL(tx_hash, tx.hash(false)); - - // data_chunk store. - settings settings{}; - settings.tx.buckets = 8; - settings.ins.buckets = 8; - settings.outs.buckets = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(!query.set_code(tx)); - BOOST_CHECK(!store.close(test::events_handler)); - - BOOST_CHECK_EQUAL(store.tx_head(), expected_tx_head); - BOOST_CHECK_EQUAL(store.input_head(), expected_input_head); - BOOST_CHECK_EQUAL(store.output_head(), expected_output_head); - BOOST_CHECK_EQUAL(store.outs_head(), expected_outs_head); - ////BOOST_CHECK_EQUAL(store.spend_head(), expected_spend_head); - - BOOST_CHECK_EQUAL(store.tx_body(), expected_tx_body); - BOOST_CHECK_EQUAL(store.ins_body(), expected_ins_body); - BOOST_CHECK_EQUAL(store.input_body(), expected_input_body); - BOOST_CHECK_EQUAL(store.output_body(), expected_output_body); - BOOST_CHECK_EQUAL(store.outs_body(), expected_outs_body); - BOOST_CHECK_EQUAL(store.address_body(), expected_address_body); - ////BOOST_CHECK_EQUAL(store.spend_body(), expected_spend_body); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__set_tx__get_tx__expected) -{ - using namespace system::chain; - const transaction tx - { - 0x2a, // version - inputs - { - input - { - point{ system::one_hash, 0x18 }, - script{ { { opcode::op_return }, { opcode::pick } } }, - witness{ "[242424]" }, - 0x2a // sequence - }, - input - { - point{ system::one_hash, 0x2a }, - script{ { { opcode::op_return }, { opcode::roll } } }, - witness{ "[424242]" }, - 0x18 // sequence - } - }, - outputs - { - output - { - 0x18, // value - script{ { { opcode::pick } } } - }, - output - { - 0x2a, // value - script{ { { opcode::roll } } } - } - }, - 0x18 // locktime - }; - const auto expected_tx_head = system::base16_chunk( - "01000000" // record count - "00000000" // bucket[0]... pk-> - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - const auto expected_tx_body = system::base16_chunk( - "ffffff7f" // next-> - "d80f19b9c0f649081c0b279d9183b0fae35b41b72a34eb181001f82afe22043a" // sk (tx.hash(false)) - "740000" // light (not coinbase) - "800000" // heavy - "18000000" // locktime - "2a000000" // version - "020000" // ins_count - "020000" // outs_count - "00000000" // point_fk-> (ins_fk) - "00000000"); // outs_fk-> - const auto expected_outs_head = system::base16_chunk( - "02000000" // record count - "ffffffff" // bucket[0] - "01000000");// bucket[1] (link->1) - const auto expected_address_body = system::base16_chunk( - "ffffffff" // next->end - "00000000"); // next->0 - const auto expected_outs_body = system::base16_chunk( - "0000000000" // output0_fk-> - "0700000000"); // output1_fk-> - const auto expected_output_head = system::base16_chunk("0e00000000"); - const auto expected_output_body = system::base16_chunk( - "00000000" // parent_fk-> - "18" // value - "0179" // script - "00000000" // parent_fk-> - "2a" // value - "017a"); // script - const auto expected_ins_body = system::base16_chunk( - "ffffffff" // next-> - "0100000000000000000000000000000000000000000000000000000000000000" - "180000" // index - "ffffffff" // next-> - "0100000000000000000000000000000000000000000000000000000000000000" - "2a0000"); // index - ////const auto expected_spend_head = system::base16_chunk( - //// "02000000" // record count - //// "00000000" // spend0_fk-> - //// "ffffffff" - //// "ffffffff" - //// "01000000" // spend1_fk-> - //// "ffffffff"); - ////const auto expected_spend_body = system::base16_chunk( - //// "ffffffff" // terminal-> - //// "01000000" // fp: point_stub - //// "180000" // fp: point_index - //// "00000000" // point_fk-> - //// "00000000" // parent_fk-> - //// "2a000000" // sequence - //// "0000000000" // input_fk-> - //// - //// "ffffffff" // terminal-> - //// "01000000" // fp: point_stub - //// "2a0000" // fp: point_index - //// "01000000" // point_fk-> - //// "00000000" // parent_fk-> - //// "18000000" // sequence - //// "0800000000"); // input_fk-> - const auto expected_input_head = system::base16_chunk("1000000000"); - const auto expected_input_body = system::base16_chunk( - "026a79" // script - "0103242424" // witness - "026a7a" // script - "0103424242"); // witness - - constexpr auto tx_hash = system::base16_array("d80f19b9c0f649081c0b279d9183b0fae35b41b72a34eb181001f82afe22043a"); - BOOST_CHECK_EQUAL(tx_hash, tx.hash(false)); - - // data_chunk store. - settings settings{}; - settings.tx.buckets = 8; - settings.ins.buckets = 8; - settings.outs.buckets = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(!query.is_tx(tx.hash(false))); - BOOST_CHECK(query.set(tx)); - BOOST_CHECK(query.is_tx(tx.hash(false))); - - const auto pointer = query.get_transaction(query.to_tx(tx_hash), true); - BOOST_CHECK(pointer); - BOOST_CHECK(*pointer == tx); - BOOST_CHECK_EQUAL(pointer->hash(false), tx_hash); - BOOST_CHECK(!store.close(test::events_handler)); - - BOOST_CHECK_EQUAL(store.tx_head(), expected_tx_head); - BOOST_CHECK_EQUAL(store.input_head(), expected_input_head); - BOOST_CHECK_EQUAL(store.output_head(), expected_output_head); - BOOST_CHECK_EQUAL(store.outs_head(), expected_outs_head); - ////BOOST_CHECK_EQUAL(store.spend_head(), expected_spend_head); - - BOOST_CHECK_EQUAL(store.tx_body(), expected_tx_body); - BOOST_CHECK_EQUAL(store.ins_body(), expected_ins_body); - BOOST_CHECK_EQUAL(store.input_body(), expected_input_body); - BOOST_CHECK_EQUAL(store.output_body(), expected_output_body); - BOOST_CHECK_EQUAL(store.outs_body(), expected_outs_body); - BOOST_CHECK_EQUAL(store.address_body(), expected_address_body); - ////BOOST_CHECK_EQUAL(store.spend_body(), expected_spend_body); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__set_block__get_block__expected) -{ - constexpr auto milestone = true; - const auto genesis_header_head = system::base16_chunk( - "010000ff" // record count - "ffffffff" // bucket[0]... - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "000080" // filter[8], pk-> - "da"); // filter[0-7] - const auto genesis_header_body = system::base16_chunk( - "ffff7f" // next-> - "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" // sk (block.hash) - "04030201" // flags - "141312" // height - "24232221" // mtp - "ffffff" // previous_block_hash (header_fk - not found) (milestone true) - "01000000" // version - "29ab5f49" // timestamp - "ffff001d" // bits - "1dac2b7c" // nonce - "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"); // merkle_root - const auto genesis_tx_head = system::base16_chunk( - "01000000" // record count - "ffffffff" // bucket[0]... - "ffffffff" - "ffffffff" - "00000000" // pk-> - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - const auto genesis_tx_body = system::base16_chunk( - "ffffff7f" // next-> - "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" // sk (tx.hash(false)) - "cc0080" // light (coinbase merged) - "cc0000" // heavy - "00000000" // locktime - "01000000" // version - "010000" // ins_count - "010000" // outs_count - "00000000" // point_fk-> (ins_fk) - "00000000"); // outs_fk-> - const auto genesis_outs_head = system::base16_chunk( - "01000000" // record count - "ffffffff" // bucket[0] - "00000000");// bucket[1] (link->0) - const auto genesis_address_body = system::base16_chunk( - "ffffffff"); // next->end - const auto genesis_outs_body = system::base16_chunk( - "0000000000"); // output0_fk-> - const auto genesis_output_head = system::base16_chunk("5100000000"); - const auto genesis_output_body = system::base16_chunk( - "00000000" // parent_fk-> - "ff00f2052a01000000" // value - "434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"); // script - const auto genesis_ins_body = system::base16_chunk( - "ffffffff" // next-> - "0000000000000000000000000000000000000000000000000000000000000000" - "ffffff"); // index - const auto genesis_spend_head = system::base16_chunk( - "01000000" // record count - "00000000" // spend0_fk-> - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - const auto genesis_spend_body = system::base16_chunk( - "ffffffff" // terminal-> - "00000000" // fp: point_stub - "ffffff" // fp: point_index (null) - "ffffffff" // point_fk-> - "00000000" // parent_fk-> - "ffffffff" // sequence - "0000000000"); // input_fk-> (coinbase) - const auto genesis_input_head = system::base16_chunk("4f00000000"); - const auto genesis_input_body = system::base16_chunk( - "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script - "00"); // witness - const auto genesis_txs_head = system::base16_chunk( - "1100000000" // slab size - "0000000000" // pk-> - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff"); - const auto genesis_txs_body = system::base16_chunk( - "1d0100" // size light (285) - "1d0100" // size heavy (285) - "0100" // txs count (1) - "00000000" // transaction[0] - "ff" // depth (255) - genesis only - "00000000"); // forks (0) - genesis only - - settings settings{}; - settings.header.buckets = 8; - settings.tx.buckets = 8; - settings.ins.buckets = 8; - settings.txs.buckets = 16; - settings.outs.buckets = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - - // Set block (header/txs). - BOOST_CHECK(!query.is_block(test::genesis.hash())); - BOOST_CHECK(query.set(test::genesis, test::context, milestone, false)); - BOOST_CHECK(query.is_block(test::genesis.hash())); - - table::header::record element1{}; - BOOST_CHECK(store.header.get(query.to_header(test::genesis.hash()), element1)); - BOOST_CHECK(!store.close(test::events_handler)); - - BOOST_CHECK_EQUAL(store.header_head(), genesis_header_head); - BOOST_CHECK_EQUAL(store.tx_head(), genesis_tx_head); - BOOST_CHECK_EQUAL(store.input_head(), genesis_input_head); - BOOST_CHECK_EQUAL(store.output_head(), genesis_output_head); - BOOST_CHECK_EQUAL(store.outs_head(), genesis_outs_head); - ////BOOST_CHECK_EQUAL(store.spend_head(), genesis_spend_head); - BOOST_CHECK_EQUAL(store.txs_head(), genesis_txs_head); - - BOOST_CHECK_EQUAL(store.header_body(), genesis_header_body); - BOOST_CHECK_EQUAL(store.tx_body(), genesis_tx_body); - BOOST_CHECK_EQUAL(store.ins_body(), genesis_ins_body); - BOOST_CHECK_EQUAL(store.input_body(), genesis_input_body); - BOOST_CHECK_EQUAL(store.output_body(), genesis_output_body); - ////BOOST_CHECK_EQUAL(store.spend_body(), genesis_spend_body); - BOOST_CHECK_EQUAL(store.txs_body(), genesis_txs_body); - - const auto pointer = query.get_block(query.to_header(test::genesis.hash()), false); - BOOST_CHECK(pointer); - BOOST_CHECK(*pointer == test::genesis); - - const auto hashes = query.get_tx_keys(query.to_header(test::genesis.hash())); - BOOST_CHECK_EQUAL(hashes.size(), 1u); - BOOST_CHECK_EQUAL(hashes, test::genesis.transaction_hashes(false)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__set_block_txs__get_block__expected) -{ - constexpr auto milestone = true; - const auto genesis_header_head = system::base16_chunk( - "010000ff" // record count - "ffffffff" // bucket[0]... - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "000080" // filter[8], pk-> - "da"); // filter[0-7] - const auto genesis_header_body = system::base16_chunk( - "ffff7f" // next-> - "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" // sk (block.hash) - "04030201" // flags - "141312" // height - "24232221" // mtp - "ffffff" // previous_block_hash (header_fk - not found) (milestone true) - "01000000" // version - "29ab5f49" // timestamp - "ffff001d" // bits - "1dac2b7c" // nonce - "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"); // merkle_root - const auto genesis_tx_head = system::base16_chunk( - "01000000" // record count - "ffffffff" // bucket[0]... - "ffffffff" - "ffffffff" - "00000000" // pk-> - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - const auto genesis_tx_body = system::base16_chunk( - "ffffff7f" // next-> - "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" // sk (tx.hash(false)) - "cc0080" // light (coinbase merged) - "cc0000" // heavy - "00000000" // locktime - "01000000" // version - "010000" // ins_count - "010000" // outs_count - "00000000" // point_fk-> (ins_fk) - "00000000"); // outs_fk-> - const auto genesis_outs_head = system::base16_chunk( - "01000000" // record count - "ffffffff" // bucket[0] - "00000000");// bucket[1] (link->0) - const auto genesis_outs_body = system::base16_chunk( - "00000000" // spend0_fk-> - "0000000000"); // output0_fk-> - const auto genesis_output_head = system::base16_chunk("5100000000"); - const auto genesis_output_body = system::base16_chunk( - "00000000" // parent_fk-> - "ff00f2052a01000000" // value - "434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"); // script - const auto genesis_ins_body = system::base16_chunk( - "ffffffff" // next-> - "0000000000000000000000000000000000000000000000000000000000000000" - "ffffff"); // index - const auto genesis_spend_head = system::base16_chunk( - "01000000" // record count - "00000000" // spend0_fk-> - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - const auto genesis_spend_body = system::base16_chunk( - "ffffffff" // terminal-> - "00000000" // fp: point_hash - "ffffff" // fp: point_index (null) - "ffffffff" // point_fk-> - "00000000" // parent_fk-> - "ffffffff" // sequence - "0000000000"); // input_fk-> (coinbase) - const auto genesis_input_head = system::base16_chunk("4f00000000"); - const auto genesis_input_body = system::base16_chunk( - "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script - "00"); // witness - const auto genesis_txs_head = system::base16_chunk( - "1100000000" // slab size - "0000000000" // pk-> - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff" - "ffffffffff"); - const auto genesis_txs_body = system::base16_chunk( - "1d0100" // size light (285) - "1d0100" // size heavy (285) - "0100" // txs count (1) - "00000000" // transaction[0] - "ff" // depth (255) - genesis only - "00000000"); // forks (0) - genesis only - - settings settings{}; - settings.header.buckets = 8; - settings.tx.buckets = 8; - settings.ins.buckets = 8; - settings.txs.buckets = 16; - settings.outs.buckets = 2; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - - // Set header and then txs. - BOOST_CHECK(!query.is_block(test::genesis.hash())); - BOOST_CHECK(query.set(test::genesis.header(), test::context, milestone)); - BOOST_CHECK(!query.is_associated(0)); - BOOST_CHECK(query.set(test::genesis, false, false)); - BOOST_CHECK(query.is_block(test::genesis.hash())); - BOOST_CHECK(query.is_associated(0)); - - table::header::record element1{}; - BOOST_CHECK(store.header.get(query.to_header(test::genesis.hash()), element1)); - BOOST_CHECK(!store.close(test::events_handler)); - - BOOST_CHECK_EQUAL(store.header_head(), genesis_header_head); - BOOST_CHECK_EQUAL(store.tx_head(), genesis_tx_head); - BOOST_CHECK_EQUAL(store.input_head(), genesis_input_head); - BOOST_CHECK_EQUAL(store.output_head(), genesis_output_head); - BOOST_CHECK_EQUAL(store.outs_head(), genesis_outs_head); - ////BOOST_CHECK_EQUAL(store.spend_head(), genesis_spend_head); - BOOST_CHECK_EQUAL(store.txs_head(), genesis_txs_head); - - BOOST_CHECK_EQUAL(store.header_body(), genesis_header_body); - BOOST_CHECK_EQUAL(store.tx_body(), genesis_tx_body); - BOOST_CHECK_EQUAL(store.ins_body(), genesis_ins_body); - BOOST_CHECK_EQUAL(store.input_body(), genesis_input_body); - BOOST_CHECK_EQUAL(store.output_body(), genesis_output_body); - ////BOOST_CHECK_EQUAL(store.spend_body(), genesis_spend_body); - BOOST_CHECK_EQUAL(store.txs_body(), genesis_txs_body); - - const auto pointer = query.get_block(query.to_header(test::genesis.hash()), false); - BOOST_CHECK(pointer); - BOOST_CHECK(*pointer == test::genesis); - - const auto hashes = query.get_tx_keys(query.to_header(test::genesis.hash())); - BOOST_CHECK_EQUAL(hashes.size(), 1u); - BOOST_CHECK_EQUAL(hashes, test::genesis.transaction_hashes(false)); -} - -// populate_with_metadata -// ---------------------------------------------------------------------------- - -// METADATA IS SETTABLE ON CONST TEST OBJECTS. -// COPY CONSTRUCTION CREATES SHARED POINTER REFERENCES. -// SO POPULATE ON CONST OBJECTS HAS SIDE EFFECTS IF THE OBJECTS SPAN TESTS. -const auto& clean_(const auto& block_or_tx) NOEXCEPT -{ - const auto inputs = block_or_tx.inputs_ptr(); - for (const auto& input: *inputs) - { - input->prevout.reset(); - input->metadata = system::chain::prevout{}; - } - - return block_or_tx; -} - -// First four blocks have only coinbase txs. -BOOST_AUTO_TEST_CASE(query_chain_writer__populate_with_metadata__null_prevouts__true) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1, test::context, false, false)); - BOOST_CHECK(query.set(test::block2, test::context, false, false)); - BOOST_CHECK(query.set(test::block3, test::context, false, false)); - - const auto& copy = clean_(test::genesis); - const auto& copy1 = clean_(test::block1); - const auto& copy2 = clean_(test::block2); - const auto& copy3 = clean_(test::block3); - - BOOST_CHECK(query.populate_with_metadata(copy)); - BOOST_CHECK(query.populate_with_metadata(copy1)); - BOOST_CHECK(query.populate_with_metadata(copy2)); - BOOST_CHECK(query.populate_with_metadata(copy3)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__populate_with_metadata__partial_prevouts__false) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1a, test::context, false, false)); - BOOST_CHECK(query.set(test::block2a, test::context, false, false)); - BOOST_CHECK(query.set(test::tx4)); - - const auto& block1a = clean_(test::block1a); - - // Block populate treates first tx as null point. - BOOST_CHECK( query.populate_with_metadata(block1a)); - BOOST_CHECK(!query.populate_with_metadata(*block1a.transactions_ptr()->at(0))); - BOOST_CHECK(!query.populate_with_metadata(*block1a.inputs_ptr()->at(0))); - BOOST_CHECK(!query.populate_with_metadata(*block1a.inputs_ptr()->at(2))); - - // Block populate treates first tx as null point and other has missing prevouts. - const auto& block2a = clean_(test::block2a); - BOOST_CHECK(!query.populate_with_metadata(block2a)); - BOOST_CHECK( query.populate_with_metadata(*block2a.transactions_ptr()->at(0))); - BOOST_CHECK(!query.populate_with_metadata(*block2a.transactions_ptr()->at(1))); - BOOST_CHECK( query.populate_with_metadata(*block2a.inputs_ptr()->at(0))); - BOOST_CHECK(!query.populate_with_metadata(*block2a.inputs_ptr()->at(3))); - - const auto& tx4 = clean_(test::tx4); - BOOST_CHECK(query.populate_with_metadata(tx4)); - BOOST_CHECK(query.populate_with_metadata(*tx4.inputs_ptr()->at(0))); - BOOST_CHECK(query.populate_with_metadata(*tx4.inputs_ptr()->at(1))); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__populate_with_metadata__metadata__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1a, test::context, false, false)); - BOOST_CHECK(query.set(test::block2a, test::context, false, false)); - BOOST_CHECK(query.set(test::tx4)); - BOOST_CHECK(!query.is_coinbase(1)); - - // Genesis only has coinbase, which does not spend. - const auto& genesis = clean_(test::genesis); - - BOOST_CHECK(!genesis.inputs_ptr()->at(0)->prevout); - BOOST_CHECK( genesis.inputs_ptr()->at(0)->metadata.coinbase); - BOOST_CHECK_EQUAL(genesis.inputs_ptr()->at(0)->metadata.parent_tx, max_uint32); - - BOOST_CHECK(query.populate_with_metadata(genesis)); - - BOOST_CHECK(!genesis.inputs_ptr()->at(0)->prevout); - BOOST_CHECK( genesis.inputs_ptr()->at(0)->metadata.coinbase); - BOOST_CHECK_EQUAL(genesis.inputs_ptr()->at(0)->metadata.parent_tx, max_uint32); - - // Transaction population. - const auto& tx4 = clean_(test::tx4); - - BOOST_CHECK(!tx4.inputs_ptr()->at(0)->prevout); - BOOST_CHECK( tx4.inputs_ptr()->at(0)->metadata.coinbase); - BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(0)->metadata.parent_tx, max_uint32); - BOOST_CHECK(!tx4.inputs_ptr()->at(1)->prevout); - BOOST_CHECK( tx4.inputs_ptr()->at(1)->metadata.coinbase); - BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(1)->metadata.parent_tx, max_uint32); - - BOOST_CHECK(query.populate_with_metadata(tx4)); - - // TODO: test non-coinbase and other parent. - // spent/mtp are defaults, coinbase/parent are set (to non-default values). - BOOST_CHECK( tx4.inputs_ptr()->at(0)->prevout); - BOOST_CHECK(!tx4.inputs_ptr()->at(0)->metadata.coinbase); - BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(0)->metadata.parent_tx, 1u); - BOOST_CHECK( tx4.inputs_ptr()->at(1)->prevout); - BOOST_CHECK(!tx4.inputs_ptr()->at(1)->metadata.coinbase); - BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(1)->metadata.parent_tx, 1u); -} - -// populate_without_metadata -// ---------------------------------------------------------------------------- - -// First four blocks have only coinbase txs. -BOOST_AUTO_TEST_CASE(query_chain_writer__populate_without_metadata__null_prevouts__true) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1, test::context, false, false)); - BOOST_CHECK(query.set(test::block2, test::context, false, false)); - BOOST_CHECK(query.set(test::block3, test::context, false, false)); - BOOST_CHECK(query.populate_without_metadata(test::genesis)); - BOOST_CHECK(query.populate_without_metadata(test::block1)); - BOOST_CHECK(query.populate_without_metadata(test::block2)); - BOOST_CHECK(query.populate_without_metadata(test::block3)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__populate_without_metadata__partial_prevouts__false) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1a, test::context, false, false)); - BOOST_CHECK(query.set(test::block2a, test::context, false, false)); - BOOST_CHECK(query.set(test::tx4)); - - // Block populate treates first tx as null point. - BOOST_CHECK( query.populate_without_metadata(test::block1a)); - BOOST_CHECK(!query.populate_without_metadata(*test::block1a.transactions_ptr()->at(0))); - BOOST_CHECK(!query.populate_without_metadata(*test::block1a.inputs_ptr()->at(0))); - BOOST_CHECK(!query.populate_without_metadata(*test::block1a.inputs_ptr()->at(2))); - - // Block populate treates first tx as null point and other has missing prevouts. - BOOST_CHECK(!query.populate_without_metadata(test::block2a)); - BOOST_CHECK( query.populate_without_metadata(*test::block2a.transactions_ptr()->at(0))); - BOOST_CHECK(!query.populate_without_metadata(*test::block2a.transactions_ptr()->at(1))); - BOOST_CHECK( query.populate_without_metadata(*test::block2a.inputs_ptr()->at(0))); - BOOST_CHECK(!query.populate_without_metadata(*test::block2a.inputs_ptr()->at(3))); - - BOOST_CHECK(query.populate_without_metadata(test::tx4)); - BOOST_CHECK(query.populate_without_metadata(*test::tx4.inputs_ptr()->at(0))); - BOOST_CHECK(query.populate_without_metadata(*test::tx4.inputs_ptr()->at(1))); -} - -// ---------------------------------------------------------------------------- - -// archive_write (foreign-keyed) - -BOOST_AUTO_TEST_CASE(query_chain_writer__is_coinbase__coinbase__true) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1, context{}, false, false)); - BOOST_CHECK(query.set(test::block2, context{}, false, false)); - BOOST_CHECK(query.set(test::block3, context{}, false, false)); - BOOST_CHECK(query.is_coinbase(0)); - BOOST_CHECK(query.is_coinbase(1)); - BOOST_CHECK(query.is_coinbase(2)); - BOOST_CHECK(query.is_coinbase(3)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__is_coinbase__non_coinbase__false) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1a, context{}, false, false)); - BOOST_CHECK(query.set(test::block2a, context{}, false, false)); - BOOST_CHECK(!query.is_coinbase(1)); - BOOST_CHECK(!query.is_coinbase(2)); - BOOST_CHECK(!query.is_coinbase(3)); - BOOST_CHECK(!query.is_coinbase(4)); - BOOST_CHECK(!query.is_coinbase(5)); - BOOST_CHECK(!query.is_coinbase(42)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__is_milestone__genesis__false) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK_EQUAL(store.create(test::events_handler), error::success); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(!query.is_milestone(0)); - BOOST_CHECK(!query.is_milestone(1)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__is_milestone__set__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK_EQUAL(store.create(test::events_handler), error::success); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1, context{}, true, false)); - BOOST_CHECK(query.set(test::block2, context{}, false, false));; - BOOST_CHECK(!query.is_milestone(0)); - BOOST_CHECK(query.is_milestone(1)); - BOOST_CHECK(!query.is_milestone(2)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_header__invalid_parent__expected) -{ - constexpr auto root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); - constexpr auto block_hash = system::base16_array("85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2"); - const system::chain::header header - { - 0x31323334, // version - system::null_hash, // previous_block_hash - root, // merkle_root - 0x41424344, // timestamp - 0x51525354, // bits - 0x61626364 // nonce - }; - const auto expected_header_head = system::base16_chunk( - "010000" // record count - "ffffff" // bucket[0]... - "000000" // pk-> - "ffffff" - "ffffff" - "ffffff" - "ffffff" - "ffffff" - "ffffff" - "ffffff" - "ffffff"); - const auto expected_header_body = system::base16_chunk( - "ffffff" // next-> - "85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2" // sk (block.hash) - "14131211" // flags - "040302" // height - "24232221" // mtp - "424242" // previous_block_hash (header_fk - invalid) (milestone false) - "34333231" // version - "44434241" // timestamp - "54535251" // bits - "64636261" // nonce - "119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); // merkle_root - - settings settings{}; - settings.header.buckets = 16; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - - store.header_head() = expected_header_head; - store.header_body() = expected_header_body; - BOOST_CHECK(!query.get_header(query.to_header(block_hash))); - BOOST_CHECK(!query.get_header(header_link::terminal)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_header__default__expected) -{ - constexpr auto root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); - constexpr auto block_hash = system::base16_array("85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2"); - static_assert(0x45d6f6162ab0d085_u64 % 10u == 5u); - const system::chain::header header - { - 0x31323334, // version - system::null_hash, // previous_block_hash - root, // merkle_root - 0x41424344, // timestamp - 0x51525354, // bits - 0x61626364 // nonce - }; - - // TODO: heads may be undersized due to the change to base2 sizing. - const auto expected_header_head = system::base16_chunk( - "000000ff" // record count - "ffffffff" // bucket[0]... - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "000080" // filter[8], pk-> - "de" // filter[0-7] - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff" - "ffffffff"); - - const auto expected_header_body = system::base16_chunk( - "ffff7f" // next-> - "85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2" // sk (block.hash) - "14131211" // flags - "040302" // height - "24232221" // mtp - "ffffff" // previous_block_hash (header_fk - terminal) (milestone true) - "34333231" // version - "44434241" // timestamp - "54535251" // bits - "64636261" // nonce - "119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); // merkle_root - - settings settings{}; - settings.header.buckets = 16; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - - ////store.header_head() = expected_header_head; - ////store.header_body() = expected_header_body; - BOOST_CHECK(query.set(header, context{ 0x11121314, 0x01020304, 0x21222324 }, true)); - BOOST_CHECK_EQUAL(store.header_head(), expected_header_head); - BOOST_CHECK_EQUAL(store.header_body(), expected_header_body); - - const auto foo = query.to_header(block_hash); - const auto pointer1 = query.get_header(foo); - BOOST_CHECK(pointer1); - BOOST_CHECK(*pointer1 == header); - - // Verify hash caching. - BOOST_CHECK_EQUAL(pointer1->hash(), block_hash); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_keys__not_found__empty) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.get_tx_keys(query.to_header(system::null_hash)).empty()); - BOOST_CHECK(!store.close(test::events_handler)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_header_key__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_header_key(0), test::genesis.hash()); - BOOST_CHECK_EQUAL(query.get_header_key(1), system::null_hash); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_point_key__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_point_hash(0), system::null_hash); - - // tx4/5 prevouts are all block1a.tx1. - BOOST_CHECK(query.set(test::tx4)); - BOOST_CHECK(query.set(test::tx5)); - BOOST_CHECK_EQUAL(query.get_point_hash(1), test::block1a.transactions_ptr()->front()->hash(false)); - BOOST_CHECK_EQUAL(query.get_point_hash(2), test::block1a.transactions_ptr()->front()->hash(false)); - - // block1a adds three prevouts of two txs. - BOOST_CHECK(query.set(test::block1a, context{}, false, false)); - BOOST_CHECK_EQUAL(query.get_point_hash(4), system::one_hash); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_key__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_tx_key(0), test::genesis.transactions_ptr()->front()->hash(false)); - BOOST_CHECK_EQUAL(query.get_tx_key(1), system::null_hash); - BOOST_CHECK(query.set(test::tx4)); - BOOST_CHECK(query.set(test::tx5)); - BOOST_CHECK_EQUAL(query.get_tx_key(1), test::tx4.hash(false)); - BOOST_CHECK_EQUAL(query.get_tx_key(2), test::tx5.hash(false)); - BOOST_CHECK_EQUAL(query.get_tx_key(3), system::null_hash); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_height1__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1, context{ 0, 1, 0 }, false, false)); - BOOST_CHECK(query.set(test::block2, context{ 0, 2, 0 }, false, false)); - BOOST_CHECK(query.set(test::block3, context{ 0, 3, 0 }, false, false)); - BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); - BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); - - size_t out{}; - BOOST_CHECK(query.get_height(out, 0)); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_height(out, 1)); - BOOST_CHECK_EQUAL(out, 1u); - BOOST_CHECK(query.get_height(out, 2)); - BOOST_CHECK_EQUAL(out, 2u); - BOOST_CHECK(query.get_height(out, 3)); - BOOST_CHECK_EQUAL(out, 3u); - BOOST_CHECK(query.get_height(out, 4)); - BOOST_CHECK_EQUAL(out, 1u); - BOOST_CHECK(query.get_height(out, 5)); - BOOST_CHECK_EQUAL(out, 2u); - BOOST_CHECK(!query.get_height(out, 6)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_height2__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1, context{ 0, 1, 0 }, false, false)); - BOOST_CHECK(query.set(test::block2, context{ 0, 2, 0 }, false, false)); - BOOST_CHECK(query.set(test::block3, context{ 0, 3, 0 }, false, false)); - BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); - BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); - - size_t out{}; - BOOST_CHECK(query.get_height(out, test::genesis.hash())); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_height(out, test::block1.hash())); - BOOST_CHECK_EQUAL(out, 1u); - BOOST_CHECK(query.get_height(out, test::block2.hash())); - BOOST_CHECK_EQUAL(out, 2u); - BOOST_CHECK(query.get_height(out, test::block3.hash())); - BOOST_CHECK_EQUAL(out, 3u); - BOOST_CHECK(query.get_height(out, test::block1a.hash())); - BOOST_CHECK_EQUAL(out, 1u); - BOOST_CHECK(query.get_height(out, test::block2a.hash())); - BOOST_CHECK_EQUAL(out, 2u); - BOOST_CHECK(!query.get_height(out, system::one_hash)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_height__not_strong__false) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::tx4)); - - size_t out{}; - BOOST_CHECK(!query.get_tx_height(out, 1)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_position__confirmed__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); - BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); - BOOST_CHECK(query.set(test::block3a, context{ 0, 3, 0 }, false, false)); - - size_t out{}; - const auto foo = query.get_tx_position(out, 0); - BOOST_CHECK(foo); - BOOST_CHECK_EQUAL(out, 0u); - - BOOST_CHECK(!query.get_tx_position(out, 1)); - BOOST_CHECK(!query.get_tx_position(out, 2)); - BOOST_CHECK(!query.get_tx_position(out, 3)); - BOOST_CHECK(!query.get_tx_position(out, 4)); - BOOST_CHECK(query.set_strong(1)); - BOOST_CHECK(query.set_strong(2)); - BOOST_CHECK(query.set_strong(3)); - - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_tx_position(out, 0)); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_tx_position(out, 1)); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_tx_position(out, 2)); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_tx_position(out, 3)); - BOOST_CHECK_EQUAL(out, 1u); - BOOST_CHECK(query.get_tx_position(out, 4)); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(!query.get_tx_position(out, 5)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_position__always__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); - BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); - BOOST_CHECK(query.set(test::block3a, context{ 0, 3, 0 }, false, false)); - BOOST_CHECK(query.set(test::tx4)); - - size_t out{}; - BOOST_CHECK(query.get_tx_position(out, 0)); - BOOST_CHECK_EQUAL(out, 0u); - - BOOST_CHECK(!query.get_tx_position(out, 1)); - BOOST_CHECK(!query.get_tx_position(out, 2)); - BOOST_CHECK(!query.get_tx_position(out, 3)); - BOOST_CHECK(!query.get_tx_position(out, 4)); - BOOST_CHECK(query.set_strong(1)); - BOOST_CHECK(query.set_strong(2)); - BOOST_CHECK(query.set_strong(3)); - - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_tx_position(out, 0)); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_tx_position(out, 1)); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_tx_position(out, 2)); - BOOST_CHECK_EQUAL(out, 0u); - BOOST_CHECK(query.get_tx_position(out, 3)); - BOOST_CHECK_EQUAL(out, 1u); - BOOST_CHECK(query.get_tx_position(out, 4)); - BOOST_CHECK_EQUAL(out, 0u); - - // tx4 is unconfirmed. - BOOST_CHECK(!query.get_tx_position(out, 5)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_sizes__coinbase__204) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - - size_t light{}; - size_t heavy{}; - BOOST_CHECK(query.get_tx_sizes(light, heavy, 0)); - BOOST_CHECK_EQUAL(light, 204u); - BOOST_CHECK_EQUAL(heavy, 204u); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_count__coinbase__1) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK_EQUAL(query.get_tx_count(0), 1u); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_input__not_found__nullptr) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(!query.get_input(query.to_tx(system::null_hash), 0u, false)); - BOOST_CHECK(!store.close(test::events_handler)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_input__genesis__expected) -{ - settings settings{}; - settings.header.buckets = 8; - settings.tx.buckets = 8; - settings.txs.buckets = 16; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.set(test::genesis, test::context, false, false)); - - const auto tx = test::genesis.transactions_ptr()->front(); - const auto input = tx->inputs_ptr()->front(); - ////BOOST_CHECK(*input == *query.get_input(query.to_tx(tx->hash(false)), 0u)); - ////BOOST_CHECK(*input == *query.get_input(0)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_inputs__tx_not_found__nullptr) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(!query.get_inputs(1, false)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_inputs__found__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::tx4)); - BOOST_CHECK_EQUAL(query.get_inputs(1, false)->size(), 2u); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_output__not_found__nullptr) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(!query.get_output(query.to_tx(system::null_hash), 0u)); - BOOST_CHECK(!query.get_output(query.to_output(system::chain::point{ system::null_hash, 0u }))); - BOOST_CHECK(!query.get_output(0)); - BOOST_CHECK(!store.close(test::events_handler)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_output__genesis__expected) -{ - settings settings{}; - settings.header.buckets = 8; - settings.tx.buckets = 8; - settings.txs.buckets = 16; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.set(test::genesis, test::context, false, false)); - - const auto tx = test::genesis.transactions_ptr()->front(); - const auto output1 = tx->outputs_ptr()->front(); - BOOST_CHECK(*output1 == *query.get_output(query.to_tx(tx->hash(false)), 0u)); - BOOST_CHECK(*output1 == *query.get_output(query.to_output(tx->hash(false), 0u))); - BOOST_CHECK(*output1 == *query.get_output(0)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_outputs__tx_not_found__nullptr) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(!query.get_outputs(1)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_outputs__found__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::tx4)); - BOOST_CHECK_EQUAL(query.get_outputs(1)->size(), 1u); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_transactions__tx_not_found__nullptr) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::tx4)); - BOOST_CHECK(!query.get_transactions(3, false)); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_transactions__found__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1a, test::context, false, false)); - BOOST_CHECK(query.set(test::block2a, test::context, false, false)); - BOOST_CHECK(query.set(test::tx4)); - BOOST_CHECK_EQUAL(query.get_transactions(0, false)->size(), 1u); - BOOST_CHECK_EQUAL(query.get_transactions(1, false)->size(), 1u); - BOOST_CHECK_EQUAL(query.get_transactions(2, false)->size(), 2u); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_spenders__unspent_or_not_found__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1, test::context, false, false)); - BOOST_CHECK(query.set(test::block2, test::context, false, false)); - BOOST_CHECK(query.set(test::block3, test::context, false, false)); - - // Caller should always test for nullptr. - BOOST_CHECK(query.get_spenders(output_link::terminal, true)->empty()); - BOOST_CHECK(query.get_spenders(query.to_output(0, 0), true)->empty()); - BOOST_CHECK(query.get_spenders(query.to_output(0, 1), true)->empty()); - BOOST_CHECK(query.get_spenders(query.to_output(1, 0), true)->empty()); - BOOST_CHECK(query.get_spenders(query.to_output(1, 1), true)->empty()); - BOOST_CHECK(query.get_spenders(query.to_output(2, 0), true)->empty()); - BOOST_CHECK(query.get_spenders(query.to_output(2, 1), true)->empty()); - BOOST_CHECK(query.get_spenders(query.to_output(3, 0), true)->empty()); - BOOST_CHECK(query.get_spenders(query.to_output(3, 1), true)->empty()); -} - -BOOST_AUTO_TEST_CASE(query_chain_writer__get_value__genesis__expected) -{ - settings settings{}; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - BOOST_CHECK(query.initialize(test::genesis)); - - uint64_t value{}; - BOOST_CHECK(query.get_value(value, query.to_output(0, 0))); - BOOST_CHECK_EQUAL(value, 5000000000u); -} - -BOOST_AUTO_TEST_SUITE_END() +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "../../test.hpp" +#include "../../mocks/blocks.hpp" +#include "../../mocks/chunk_store.hpp" + +// ensure context::flags is same size as chain_context::flags. +static_assert(is_same_type < database::context::flag_t::integer, decltype(system::chain::context{}.flags) > ); + +BOOST_FIXTURE_TEST_SUITE(query_chain_writer_tests, test::directory_setup_fixture) + +// slow test (mmap) +BOOST_AUTO_TEST_CASE(query_chain_writer__set_header__mmap_get_header__expected) +{ + constexpr auto milestone = false; + constexpr auto parent = system::null_hash; + constexpr auto merkle_root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); + constexpr auto block_hash = system::base16_array("85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2"); + const system::chain::header header + { + 0x31323334, // version + parent, // previous_block_hash + merkle_root,// merkle_root + 0x41424344, // timestamp + 0x51525354, // bits + 0x61626364 // nonce + }; + + settings settings{}; + settings.header.buckets = 16; + settings.path = TEST_DIRECTORY; + store store{ settings }; + query> query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.set(header, test::context, milestone)); + + table::header::record element1{}; + BOOST_CHECK(store.header.get(query.to_header(block_hash), element1)); + + const auto pointer = query.get_header(query.to_header(block_hash)); + BOOST_CHECK(pointer); + BOOST_CHECK(*pointer == header); + + // must open/close mmap + BOOST_CHECK(!store.close(test::events_handler)); + BOOST_CHECK_EQUAL(element1.ctx.height, system::mask_left(test::context.height, byte_bits)); + BOOST_CHECK_EQUAL(element1.ctx.flags, test::context.flags); + BOOST_CHECK_EQUAL(element1.ctx.mtp, test::context.mtp); + BOOST_CHECK_EQUAL(element1.milestone, milestone); + BOOST_CHECK_EQUAL(element1.version, header.version()); + BOOST_CHECK_EQUAL(element1.parent_fk, schema::header::link::terminal); + BOOST_CHECK_EQUAL(element1.merkle_root, header.merkle_root()); + BOOST_CHECK_EQUAL(element1.timestamp, header.timestamp()); + BOOST_CHECK_EQUAL(element1.bits, header.bits()); + BOOST_CHECK_EQUAL(element1.nonce, header.nonce()); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__set_link_header__is_header__expected) +{ + constexpr auto milestone = true; + constexpr auto merkle_root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); + constexpr auto block_hash = system::base16_array("85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2"); + const system::chain::header header + { + 0x31323334, // version + system::null_hash, // previous_block_hash + merkle_root,// merkle_root + 0x41424344, // timestamp + 0x51525354, // bits + 0x61626364 // nonce + }; + + // nosh + const auto expected_header_head = system::base16_chunk( + "010000ff" // record count + "ffffffff" // bucket[0]... + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "000080" // filter[8], pk-> + "de" // filter[0-7] + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + + const auto expected_header_body = system::base16_chunk( + "ffff7f" // next-> + "85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2" // sk (block.hash) + "04030201" // flags + "141312" // height + "24232221" // mtp + "ffffff" // previous_block_hash (header_fk - not found) (milestone true) + "34333231" // version + "44434241" // timestamp + "54535251" // bits + "64636261" // nonce + "119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); //merkle_root + + header_link link{}; + settings settings{}; + settings.header.buckets = 16; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + + // store open/close flushes record count to head. + BOOST_CHECK(!query.is_header(header.hash())); + BOOST_CHECK(!query.is_associated(0)); + BOOST_CHECK(!query.set_code(link, header, test::context, milestone)); + BOOST_CHECK(!link.is_terminal()); + BOOST_CHECK(query.is_header(header.hash())); + BOOST_CHECK(!query.is_associated(0)); + table::header::record element1{}; + BOOST_CHECK(store.header.get(query.to_header(block_hash), element1)); + BOOST_CHECK(!store.close(test::events_handler)); + BOOST_CHECK_EQUAL(store.header_head(), expected_header_head); + BOOST_CHECK_EQUAL(store.header_body(), expected_header_body); + + BOOST_CHECK_EQUAL(element1.ctx.height, system::mask_left(test::context.height, byte_bits)); + BOOST_CHECK_EQUAL(element1.ctx.flags, test::context.flags); + BOOST_CHECK_EQUAL(element1.ctx.mtp, test::context.mtp); + BOOST_CHECK_EQUAL(element1.milestone, milestone); + BOOST_CHECK_EQUAL(element1.version, header.version()); + BOOST_CHECK_EQUAL(element1.parent_fk, schema::header::link::terminal); + BOOST_CHECK_EQUAL(element1.merkle_root, header.merkle_root()); + BOOST_CHECK_EQUAL(element1.timestamp, header.timestamp()); + BOOST_CHECK_EQUAL(element1.bits, header.bits()); + BOOST_CHECK_EQUAL(element1.nonce, header.nonce()); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__set_tx__empty__expected) +{ + const system::chain::transaction tx{}; + const auto expected_head5_array = system::base16_chunk("0000000000"); + const auto expected_outs_head = system::base16_chunk( + "00000000" // record count + "ffffffff" // bucket[0] + "ffffffff");// bucket[1] + const auto expected_head4_hash = system::base16_chunk( + "01000000" // record count + "ffffffff" // bucket[0]... + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + const auto expected_head5_hash = system::base16_chunk( + "0000000000" // record count + "ffffffffff" // bucket[0]... + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff"); + + // data_chunk store. + settings settings{}; + settings.tx.buckets = 8; + settings.outs.buckets = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + + // store open/close flushes record count to heads. + BOOST_CHECK(!query.set(tx)); + BOOST_CHECK(!store.close(test::events_handler)); + BOOST_CHECK_EQUAL(store.tx_head(), expected_head4_hash); + BOOST_CHECK_EQUAL(store.input_head(), expected_head5_array); + BOOST_CHECK_EQUAL(store.output_head(), expected_head5_array); + BOOST_CHECK_EQUAL(store.outs_head(), expected_outs_head); + BOOST_CHECK_EQUAL(store.tx_body().size(), schema::transaction::minrow); + BOOST_CHECK(store.ins_body().empty()); + BOOST_CHECK(store.input_body().empty()); + BOOST_CHECK(store.output_body().empty()); + BOOST_CHECK(store.outs_body().empty()); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__set_link_tx__null_input__expected) +{ + using namespace system::chain; + const transaction tx + { + 0x01020304, // version + inputs + { + input{ point{}, script{}, witness{}, 0 } + }, + outputs + { + output{ 0, script{} } + }, + 0x11121314 // locktime + }; + const auto expected_tx_head = system::base16_chunk( + "01000000" // record count + "00000000" // bucket[0]... pk-> + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + const auto expected_tx_body = system::base16_chunk( + "ffffff7f" // next-> + "601f0fa54d6de8362c17dc883cc047e1f3ae0523d732598a05e3010fac591f62" // sk (tx.hash(false)) + "3c0080" // light (coinbase merged) + "3c0000" // heavy + "14131211" // locktime + "04030201" // version + "010000" // ins_count + "010000" // outs_count + "00000000" // point_fk-> (ins_fk) + "00000000"); // outs_fk-> + const auto expected_outs_head = system::base16_chunk( + "01000000" // record count + "00000000" // bucket[0] (link->0) + "ffffffff");// bucket[1] + const auto expected_address_body = system::base16_chunk( + "ffffffff"); // next->end + const auto expected_outs_body = system::base16_chunk( + "0000000000"); // output0_fk-> + const auto expected_output_head = system::base16_chunk("0600000000"); + const auto expected_output_body = system::base16_chunk( + "00000000" // parent_fk-> + "00" // value + "00"); // script + const auto expected_ins_body = system::base16_chunk( + "ffffffff" // next-> + "0000000000000000000000000000000000000000000000000000000000000000" + "ffffff"); // index + ////const auto expected_spend_head = system::base16_chunk( + //// "01000000" // record count + //// "00000000" // pk-> + //// "ffffffff" + //// "ffffffff" + //// "ffffffff" + //// "ffffffff"); + ////const auto expected_spend_body = system::base16_chunk( + //// "ffffffff" // terminal-> + //// "00000000" // fp: point_stub + //// "ffffff" // fp: point_index (null) + //// "ffffffff" // point_fk-> + //// "00000000" // parent_fk-> + //// "00000000" // sequence + //// "0000000000"); // input_fk-> + const auto expected_input_head = system::base16_chunk("0200000000"); + const auto expected_input_body = system::base16_chunk( + "00" // script + "00"); // witness + constexpr auto tx_hash = system::base16_array("601f0fa54d6de8362c17dc883cc047e1f3ae0523d732598a05e3010fac591f62"); + BOOST_CHECK_EQUAL(tx_hash, tx.hash(false)); + + // data_chunk store. + settings settings{}; + settings.tx.buckets = 8; + settings.ins.buckets = 8; + settings.outs.buckets = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(!query.set_code(tx)); + BOOST_CHECK(!store.close(test::events_handler)); + + BOOST_CHECK_EQUAL(store.tx_head(), expected_tx_head); + BOOST_CHECK_EQUAL(store.input_head(), expected_input_head); + BOOST_CHECK_EQUAL(store.output_head(), expected_output_head); + BOOST_CHECK_EQUAL(store.outs_head(), expected_outs_head); + ////BOOST_CHECK_EQUAL(store.spend_head(), expected_spend_head); + + BOOST_CHECK_EQUAL(store.tx_body(), expected_tx_body); + BOOST_CHECK_EQUAL(store.ins_body(), expected_ins_body); + BOOST_CHECK_EQUAL(store.input_body(), expected_input_body); + BOOST_CHECK_EQUAL(store.output_body(), expected_output_body); + BOOST_CHECK_EQUAL(store.outs_body(), expected_outs_body); + BOOST_CHECK_EQUAL(store.address_body(), expected_address_body); + ////BOOST_CHECK_EQUAL(store.spend_body(), expected_spend_body); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__set_tx__get_tx__expected) +{ + using namespace system::chain; + const transaction tx + { + 0x2a, // version + inputs + { + input + { + point{ system::one_hash, 0x18 }, + script{ { { opcode::op_return }, { opcode::pick } } }, + witness{ "[242424]" }, + 0x2a // sequence + }, + input + { + point{ system::one_hash, 0x2a }, + script{ { { opcode::op_return }, { opcode::roll } } }, + witness{ "[424242]" }, + 0x18 // sequence + } + }, + outputs + { + output + { + 0x18, // value + script{ { { opcode::pick } } } + }, + output + { + 0x2a, // value + script{ { { opcode::roll } } } + } + }, + 0x18 // locktime + }; + const auto expected_tx_head = system::base16_chunk( + "01000000" // record count + "00000000" // bucket[0]... pk-> + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + const auto expected_tx_body = system::base16_chunk( + "ffffff7f" // next-> + "d80f19b9c0f649081c0b279d9183b0fae35b41b72a34eb181001f82afe22043a" // sk (tx.hash(false)) + "740000" // light (not coinbase) + "800000" // heavy + "18000000" // locktime + "2a000000" // version + "020000" // ins_count + "020000" // outs_count + "00000000" // point_fk-> (ins_fk) + "00000000"); // outs_fk-> + const auto expected_outs_head = system::base16_chunk( + "02000000" // record count + "ffffffff" // bucket[0] + "01000000");// bucket[1] (link->1) + const auto expected_address_body = system::base16_chunk( + "ffffffff" // next->end + "00000000"); // next->0 + const auto expected_outs_body = system::base16_chunk( + "0000000000" // output0_fk-> + "0700000000"); // output1_fk-> + const auto expected_output_head = system::base16_chunk("0e00000000"); + const auto expected_output_body = system::base16_chunk( + "00000000" // parent_fk-> + "18" // value + "0179" // script + "00000000" // parent_fk-> + "2a" // value + "017a"); // script + const auto expected_ins_body = system::base16_chunk( + "ffffffff" // next-> + "0100000000000000000000000000000000000000000000000000000000000000" + "180000" // index + "ffffffff" // next-> + "0100000000000000000000000000000000000000000000000000000000000000" + "2a0000"); // index + ////const auto expected_spend_head = system::base16_chunk( + //// "02000000" // record count + //// "00000000" // spend0_fk-> + //// "ffffffff" + //// "ffffffff" + //// "01000000" // spend1_fk-> + //// "ffffffff"); + ////const auto expected_spend_body = system::base16_chunk( + //// "ffffffff" // terminal-> + //// "01000000" // fp: point_stub + //// "180000" // fp: point_index + //// "00000000" // point_fk-> + //// "00000000" // parent_fk-> + //// "2a000000" // sequence + //// "0000000000" // input_fk-> + //// + //// "ffffffff" // terminal-> + //// "01000000" // fp: point_stub + //// "2a0000" // fp: point_index + //// "01000000" // point_fk-> + //// "00000000" // parent_fk-> + //// "18000000" // sequence + //// "0800000000"); // input_fk-> + const auto expected_input_head = system::base16_chunk("1000000000"); + const auto expected_input_body = system::base16_chunk( + "026a79" // script + "0103242424" // witness + "026a7a" // script + "0103424242"); // witness + + constexpr auto tx_hash = system::base16_array("d80f19b9c0f649081c0b279d9183b0fae35b41b72a34eb181001f82afe22043a"); + BOOST_CHECK_EQUAL(tx_hash, tx.hash(false)); + + // data_chunk store. + settings settings{}; + settings.tx.buckets = 8; + settings.ins.buckets = 8; + settings.outs.buckets = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(!query.is_tx(tx.hash(false))); + BOOST_CHECK(query.set(tx)); + BOOST_CHECK(query.is_tx(tx.hash(false))); + + const auto pointer = query.get_transaction(query.to_tx(tx_hash), true); + BOOST_CHECK(pointer); + BOOST_CHECK(*pointer == tx); + BOOST_CHECK_EQUAL(pointer->hash(false), tx_hash); + BOOST_CHECK(!store.close(test::events_handler)); + + BOOST_CHECK_EQUAL(store.tx_head(), expected_tx_head); + BOOST_CHECK_EQUAL(store.input_head(), expected_input_head); + BOOST_CHECK_EQUAL(store.output_head(), expected_output_head); + BOOST_CHECK_EQUAL(store.outs_head(), expected_outs_head); + ////BOOST_CHECK_EQUAL(store.spend_head(), expected_spend_head); + + BOOST_CHECK_EQUAL(store.tx_body(), expected_tx_body); + BOOST_CHECK_EQUAL(store.ins_body(), expected_ins_body); + BOOST_CHECK_EQUAL(store.input_body(), expected_input_body); + BOOST_CHECK_EQUAL(store.output_body(), expected_output_body); + BOOST_CHECK_EQUAL(store.outs_body(), expected_outs_body); + BOOST_CHECK_EQUAL(store.address_body(), expected_address_body); + ////BOOST_CHECK_EQUAL(store.spend_body(), expected_spend_body); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__set_block__get_block__expected) +{ + constexpr auto milestone = true; + const auto genesis_header_head = system::base16_chunk( + "010000ff" // record count + "ffffffff" // bucket[0]... + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "000080" // filter[8], pk-> + "da"); // filter[0-7] + const auto genesis_header_body = system::base16_chunk( + "ffff7f" // next-> + "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" // sk (block.hash) + "04030201" // flags + "141312" // height + "24232221" // mtp + "ffffff" // previous_block_hash (header_fk - not found) (milestone true) + "01000000" // version + "29ab5f49" // timestamp + "ffff001d" // bits + "1dac2b7c" // nonce + "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"); // merkle_root + const auto genesis_tx_head = system::base16_chunk( + "01000000" // record count + "ffffffff" // bucket[0]... + "ffffffff" + "ffffffff" + "00000000" // pk-> + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + const auto genesis_tx_body = system::base16_chunk( + "ffffff7f" // next-> + "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" // sk (tx.hash(false)) + "cc0080" // light (coinbase merged) + "cc0000" // heavy + "00000000" // locktime + "01000000" // version + "010000" // ins_count + "010000" // outs_count + "00000000" // point_fk-> (ins_fk) + "00000000"); // outs_fk-> + const auto genesis_outs_head = system::base16_chunk( + "01000000" // record count + "ffffffff" // bucket[0] + "00000000");// bucket[1] (link->0) + const auto genesis_address_body = system::base16_chunk( + "ffffffff"); // next->end + const auto genesis_outs_body = system::base16_chunk( + "0000000000"); // output0_fk-> + const auto genesis_output_head = system::base16_chunk("5100000000"); + const auto genesis_output_body = system::base16_chunk( + "00000000" // parent_fk-> + "ff00f2052a01000000" // value + "434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"); // script + const auto genesis_ins_body = system::base16_chunk( + "ffffffff" // next-> + "0000000000000000000000000000000000000000000000000000000000000000" + "ffffff"); // index + const auto genesis_spend_head = system::base16_chunk( + "01000000" // record count + "00000000" // spend0_fk-> + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + const auto genesis_spend_body = system::base16_chunk( + "ffffffff" // terminal-> + "00000000" // fp: point_stub + "ffffff" // fp: point_index (null) + "ffffffff" // point_fk-> + "00000000" // parent_fk-> + "ffffffff" // sequence + "0000000000"); // input_fk-> (coinbase) + const auto genesis_input_head = system::base16_chunk("4f00000000"); + const auto genesis_input_body = system::base16_chunk( + "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script + "00"); // witness + const auto genesis_txs_head = system::base16_chunk( + "1301000000" // slab size + "0000000000" // pk-> + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff"); + const auto genesis_txs_body = system::build_chunk( + { + system::base16_chunk( + "1d0100" // size light (285) + "1d0100" // size heavy (285) + "0100" // txs count (1) + "00000000" // transaction[0] + "01" // envelope version (1) - genesis only + "ff00" // interval depth (255) + "08000000" // header buckets (8) + "08000000" // ins buckets (8) + "02000000" // outs buckets (2) + "08000000" // tx buckets (8) + "80000000" // strong_tx buckets (128) + "80000000" // duplicate buckets (128) + "80000000" // validated_tx buckets (128) + "01"), // filter (true) + system::data_chunk(231, 0x00) + }); + + settings settings{}; + settings.header.buckets = 8; + settings.tx.buckets = 8; + settings.ins.buckets = 8; + settings.txs.buckets = 16; + settings.outs.buckets = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + + // Set block (header/txs). + BOOST_CHECK(!query.is_block(test::genesis.hash())); + BOOST_CHECK(query.set(test::genesis, test::context, milestone, false)); + BOOST_CHECK(query.is_block(test::genesis.hash())); + + table::header::record element1{}; + BOOST_CHECK(store.header.get(query.to_header(test::genesis.hash()), element1)); + BOOST_CHECK(!store.close(test::events_handler)); + + BOOST_CHECK_EQUAL(store.header_head(), genesis_header_head); + BOOST_CHECK_EQUAL(store.tx_head(), genesis_tx_head); + BOOST_CHECK_EQUAL(store.input_head(), genesis_input_head); + BOOST_CHECK_EQUAL(store.output_head(), genesis_output_head); + BOOST_CHECK_EQUAL(store.outs_head(), genesis_outs_head); + ////BOOST_CHECK_EQUAL(store.spend_head(), genesis_spend_head); + BOOST_CHECK_EQUAL(store.txs_head(), genesis_txs_head); + + BOOST_CHECK_EQUAL(store.header_body(), genesis_header_body); + BOOST_CHECK_EQUAL(store.tx_body(), genesis_tx_body); + BOOST_CHECK_EQUAL(store.ins_body(), genesis_ins_body); + BOOST_CHECK_EQUAL(store.input_body(), genesis_input_body); + BOOST_CHECK_EQUAL(store.output_body(), genesis_output_body); + ////BOOST_CHECK_EQUAL(store.spend_body(), genesis_spend_body); + BOOST_CHECK_EQUAL(store.txs_body(), genesis_txs_body); + + const auto pointer = query.get_block(query.to_header(test::genesis.hash()), false); + BOOST_CHECK(pointer); + BOOST_CHECK(*pointer == test::genesis); + + const auto hashes = query.get_tx_keys(query.to_header(test::genesis.hash())); + BOOST_CHECK_EQUAL(hashes.size(), 1u); + BOOST_CHECK_EQUAL(hashes, test::genesis.transaction_hashes(false)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__set_block_txs__get_block__expected) +{ + constexpr auto milestone = true; + const auto genesis_header_head = system::base16_chunk( + "010000ff" // record count + "ffffffff" // bucket[0]... + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "000080" // filter[8], pk-> + "da"); // filter[0-7] + const auto genesis_header_body = system::base16_chunk( + "ffff7f" // next-> + "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" // sk (block.hash) + "04030201" // flags + "141312" // height + "24232221" // mtp + "ffffff" // previous_block_hash (header_fk - not found) (milestone true) + "01000000" // version + "29ab5f49" // timestamp + "ffff001d" // bits + "1dac2b7c" // nonce + "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"); // merkle_root + const auto genesis_tx_head = system::base16_chunk( + "01000000" // record count + "ffffffff" // bucket[0]... + "ffffffff" + "ffffffff" + "00000000" // pk-> + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + const auto genesis_tx_body = system::base16_chunk( + "ffffff7f" // next-> + "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" // sk (tx.hash(false)) + "cc0080" // light (coinbase merged) + "cc0000" // heavy + "00000000" // locktime + "01000000" // version + "010000" // ins_count + "010000" // outs_count + "00000000" // point_fk-> (ins_fk) + "00000000"); // outs_fk-> + const auto genesis_outs_head = system::base16_chunk( + "01000000" // record count + "ffffffff" // bucket[0] + "00000000");// bucket[1] (link->0) + const auto genesis_outs_body = system::base16_chunk( + "00000000" // spend0_fk-> + "0000000000"); // output0_fk-> + const auto genesis_output_head = system::base16_chunk("5100000000"); + const auto genesis_output_body = system::base16_chunk( + "00000000" // parent_fk-> + "ff00f2052a01000000" // value + "434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"); // script + const auto genesis_ins_body = system::base16_chunk( + "ffffffff" // next-> + "0000000000000000000000000000000000000000000000000000000000000000" + "ffffff"); // index + const auto genesis_spend_head = system::base16_chunk( + "01000000" // record count + "00000000" // spend0_fk-> + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + const auto genesis_spend_body = system::base16_chunk( + "ffffffff" // terminal-> + "00000000" // fp: point_hash + "ffffff" // fp: point_index (null) + "ffffffff" // point_fk-> + "00000000" // parent_fk-> + "ffffffff" // sequence + "0000000000"); // input_fk-> (coinbase) + const auto genesis_input_head = system::base16_chunk("4f00000000"); + const auto genesis_input_body = system::base16_chunk( + "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script + "00"); // witness + const auto genesis_txs_head = system::base16_chunk( + "1301000000" // slab size + "0000000000" // pk-> + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff" + "ffffffffff"); + const auto genesis_txs_body = system::build_chunk( + { + system::base16_chunk( + "1d0100" // size light (285) + "1d0100" // size heavy (285) + "0100" // txs count (1) + "00000000" // transaction[0] + "01" // envelope version (1) - genesis only + "ff00" // interval depth (255) + "08000000" // header buckets (8) + "08000000" // ins buckets (8) + "02000000" // outs buckets (2) + "08000000" // tx buckets (8) + "80000000" // strong_tx buckets (128) + "80000000" // duplicate buckets (128) + "80000000" // validated_tx buckets (128) + "01"), // filter (true) + system::data_chunk(231, 0x00) + }); + + settings settings{}; + settings.header.buckets = 8; + settings.tx.buckets = 8; + settings.ins.buckets = 8; + settings.txs.buckets = 16; + settings.outs.buckets = 2; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + + // Set header and then txs. + BOOST_CHECK(!query.is_block(test::genesis.hash())); + BOOST_CHECK(query.set(test::genesis.header(), test::context, milestone)); + BOOST_CHECK(!query.is_associated(0)); + BOOST_CHECK(query.set(test::genesis, false, false)); + BOOST_CHECK(query.is_block(test::genesis.hash())); + BOOST_CHECK(query.is_associated(0)); + + table::header::record element1{}; + BOOST_CHECK(store.header.get(query.to_header(test::genesis.hash()), element1)); + BOOST_CHECK(!store.close(test::events_handler)); + + BOOST_CHECK_EQUAL(store.header_head(), genesis_header_head); + BOOST_CHECK_EQUAL(store.tx_head(), genesis_tx_head); + BOOST_CHECK_EQUAL(store.input_head(), genesis_input_head); + BOOST_CHECK_EQUAL(store.output_head(), genesis_output_head); + BOOST_CHECK_EQUAL(store.outs_head(), genesis_outs_head); + ////BOOST_CHECK_EQUAL(store.spend_head(), genesis_spend_head); + BOOST_CHECK_EQUAL(store.txs_head(), genesis_txs_head); + + BOOST_CHECK_EQUAL(store.header_body(), genesis_header_body); + BOOST_CHECK_EQUAL(store.tx_body(), genesis_tx_body); + BOOST_CHECK_EQUAL(store.ins_body(), genesis_ins_body); + BOOST_CHECK_EQUAL(store.input_body(), genesis_input_body); + BOOST_CHECK_EQUAL(store.output_body(), genesis_output_body); + ////BOOST_CHECK_EQUAL(store.spend_body(), genesis_spend_body); + BOOST_CHECK_EQUAL(store.txs_body(), genesis_txs_body); + + const auto pointer = query.get_block(query.to_header(test::genesis.hash()), false); + BOOST_CHECK(pointer); + BOOST_CHECK(*pointer == test::genesis); + + const auto hashes = query.get_tx_keys(query.to_header(test::genesis.hash())); + BOOST_CHECK_EQUAL(hashes.size(), 1u); + BOOST_CHECK_EQUAL(hashes, test::genesis.transaction_hashes(false)); +} + +// populate_with_metadata +// ---------------------------------------------------------------------------- + +// METADATA IS SETTABLE ON CONST TEST OBJECTS. +// COPY CONSTRUCTION CREATES SHARED POINTER REFERENCES. +// SO POPULATE ON CONST OBJECTS HAS SIDE EFFECTS IF THE OBJECTS SPAN TESTS. +const auto& clean_(const auto& block_or_tx) NOEXCEPT +{ + const auto inputs = block_or_tx.inputs_ptr(); + for (const auto& input: *inputs) + { + input->prevout.reset(); + input->metadata = system::chain::prevout{}; + } + + return block_or_tx; +} + +// First four blocks have only coinbase txs. +BOOST_AUTO_TEST_CASE(query_chain_writer__populate_with_metadata__null_prevouts__true) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1, test::context, false, false)); + BOOST_CHECK(query.set(test::block2, test::context, false, false)); + BOOST_CHECK(query.set(test::block3, test::context, false, false)); + + const auto& copy = clean_(test::genesis); + const auto& copy1 = clean_(test::block1); + const auto& copy2 = clean_(test::block2); + const auto& copy3 = clean_(test::block3); + + BOOST_CHECK(query.populate_with_metadata(copy)); + BOOST_CHECK(query.populate_with_metadata(copy1)); + BOOST_CHECK(query.populate_with_metadata(copy2)); + BOOST_CHECK(query.populate_with_metadata(copy3)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__populate_with_metadata__partial_prevouts__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1a, test::context, false, false)); + BOOST_CHECK(query.set(test::block2a, test::context, false, false)); + BOOST_CHECK(query.set(test::tx4)); + + const auto& block1a = clean_(test::block1a); + + // Block populate treates first tx as null point. + BOOST_CHECK( query.populate_with_metadata(block1a)); + BOOST_CHECK(!query.populate_with_metadata(*block1a.transactions_ptr()->at(0))); + BOOST_CHECK(!query.populate_with_metadata(*block1a.inputs_ptr()->at(0))); + BOOST_CHECK(!query.populate_with_metadata(*block1a.inputs_ptr()->at(2))); + + // Block populate treates first tx as null point and other has missing prevouts. + const auto& block2a = clean_(test::block2a); + BOOST_CHECK(!query.populate_with_metadata(block2a)); + BOOST_CHECK( query.populate_with_metadata(*block2a.transactions_ptr()->at(0))); + BOOST_CHECK(!query.populate_with_metadata(*block2a.transactions_ptr()->at(1))); + BOOST_CHECK( query.populate_with_metadata(*block2a.inputs_ptr()->at(0))); + BOOST_CHECK(!query.populate_with_metadata(*block2a.inputs_ptr()->at(3))); + + const auto& tx4 = clean_(test::tx4); + BOOST_CHECK(query.populate_with_metadata(tx4)); + BOOST_CHECK(query.populate_with_metadata(*tx4.inputs_ptr()->at(0))); + BOOST_CHECK(query.populate_with_metadata(*tx4.inputs_ptr()->at(1))); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__populate_with_metadata__metadata__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1a, test::context, false, false)); + BOOST_CHECK(query.set(test::block2a, test::context, false, false)); + BOOST_CHECK(query.set(test::tx4)); + BOOST_CHECK(!query.is_coinbase(1)); + + // Genesis only has coinbase, which does not spend. + const auto& genesis = clean_(test::genesis); + + BOOST_CHECK(!genesis.inputs_ptr()->at(0)->prevout); + BOOST_CHECK( genesis.inputs_ptr()->at(0)->metadata.coinbase); + BOOST_CHECK_EQUAL(genesis.inputs_ptr()->at(0)->metadata.parent_tx, max_uint32); + + BOOST_CHECK(query.populate_with_metadata(genesis)); + + BOOST_CHECK(!genesis.inputs_ptr()->at(0)->prevout); + BOOST_CHECK( genesis.inputs_ptr()->at(0)->metadata.coinbase); + BOOST_CHECK_EQUAL(genesis.inputs_ptr()->at(0)->metadata.parent_tx, max_uint32); + + // Transaction population. + const auto& tx4 = clean_(test::tx4); + + BOOST_CHECK(!tx4.inputs_ptr()->at(0)->prevout); + BOOST_CHECK( tx4.inputs_ptr()->at(0)->metadata.coinbase); + BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(0)->metadata.parent_tx, max_uint32); + BOOST_CHECK(!tx4.inputs_ptr()->at(1)->prevout); + BOOST_CHECK( tx4.inputs_ptr()->at(1)->metadata.coinbase); + BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(1)->metadata.parent_tx, max_uint32); + + BOOST_CHECK(query.populate_with_metadata(tx4)); + + // TODO: test non-coinbase and other parent. + // spent/mtp are defaults, coinbase/parent are set (to non-default values). + BOOST_CHECK( tx4.inputs_ptr()->at(0)->prevout); + BOOST_CHECK(!tx4.inputs_ptr()->at(0)->metadata.coinbase); + BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(0)->metadata.parent_tx, 1u); + BOOST_CHECK( tx4.inputs_ptr()->at(1)->prevout); + BOOST_CHECK(!tx4.inputs_ptr()->at(1)->metadata.coinbase); + BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(1)->metadata.parent_tx, 1u); +} + +// populate_without_metadata +// ---------------------------------------------------------------------------- + +// First four blocks have only coinbase txs. +BOOST_AUTO_TEST_CASE(query_chain_writer__populate_without_metadata__null_prevouts__true) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1, test::context, false, false)); + BOOST_CHECK(query.set(test::block2, test::context, false, false)); + BOOST_CHECK(query.set(test::block3, test::context, false, false)); + BOOST_CHECK(query.populate_without_metadata(test::genesis)); + BOOST_CHECK(query.populate_without_metadata(test::block1)); + BOOST_CHECK(query.populate_without_metadata(test::block2)); + BOOST_CHECK(query.populate_without_metadata(test::block3)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__populate_without_metadata__partial_prevouts__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1a, test::context, false, false)); + BOOST_CHECK(query.set(test::block2a, test::context, false, false)); + BOOST_CHECK(query.set(test::tx4)); + + // Block populate treates first tx as null point. + BOOST_CHECK( query.populate_without_metadata(test::block1a)); + BOOST_CHECK(!query.populate_without_metadata(*test::block1a.transactions_ptr()->at(0))); + BOOST_CHECK(!query.populate_without_metadata(*test::block1a.inputs_ptr()->at(0))); + BOOST_CHECK(!query.populate_without_metadata(*test::block1a.inputs_ptr()->at(2))); + + // Block populate treates first tx as null point and other has missing prevouts. + BOOST_CHECK(!query.populate_without_metadata(test::block2a)); + BOOST_CHECK( query.populate_without_metadata(*test::block2a.transactions_ptr()->at(0))); + BOOST_CHECK(!query.populate_without_metadata(*test::block2a.transactions_ptr()->at(1))); + BOOST_CHECK( query.populate_without_metadata(*test::block2a.inputs_ptr()->at(0))); + BOOST_CHECK(!query.populate_without_metadata(*test::block2a.inputs_ptr()->at(3))); + + BOOST_CHECK(query.populate_without_metadata(test::tx4)); + BOOST_CHECK(query.populate_without_metadata(*test::tx4.inputs_ptr()->at(0))); + BOOST_CHECK(query.populate_without_metadata(*test::tx4.inputs_ptr()->at(1))); +} + +// ---------------------------------------------------------------------------- + +// archive_write (foreign-keyed) + +BOOST_AUTO_TEST_CASE(query_chain_writer__is_coinbase__coinbase__true) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1, context{}, false, false)); + BOOST_CHECK(query.set(test::block2, context{}, false, false)); + BOOST_CHECK(query.set(test::block3, context{}, false, false)); + BOOST_CHECK(query.is_coinbase(0)); + BOOST_CHECK(query.is_coinbase(1)); + BOOST_CHECK(query.is_coinbase(2)); + BOOST_CHECK(query.is_coinbase(3)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__is_coinbase__non_coinbase__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1a, context{}, false, false)); + BOOST_CHECK(query.set(test::block2a, context{}, false, false)); + BOOST_CHECK(!query.is_coinbase(1)); + BOOST_CHECK(!query.is_coinbase(2)); + BOOST_CHECK(!query.is_coinbase(3)); + BOOST_CHECK(!query.is_coinbase(4)); + BOOST_CHECK(!query.is_coinbase(5)); + BOOST_CHECK(!query.is_coinbase(42)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__is_milestone__genesis__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK_EQUAL(store.create(test::events_handler), error::success); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(!query.is_milestone(0)); + BOOST_CHECK(!query.is_milestone(1)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__is_milestone__set__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK_EQUAL(store.create(test::events_handler), error::success); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1, context{}, true, false)); + BOOST_CHECK(query.set(test::block2, context{}, false, false));; + BOOST_CHECK(!query.is_milestone(0)); + BOOST_CHECK(query.is_milestone(1)); + BOOST_CHECK(!query.is_milestone(2)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_header__invalid_parent__expected) +{ + constexpr auto root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); + constexpr auto block_hash = system::base16_array("85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2"); + const system::chain::header header + { + 0x31323334, // version + system::null_hash, // previous_block_hash + root, // merkle_root + 0x41424344, // timestamp + 0x51525354, // bits + 0x61626364 // nonce + }; + const auto expected_header_head = system::base16_chunk( + "010000" // record count + "ffffff" // bucket[0]... + "000000" // pk-> + "ffffff" + "ffffff" + "ffffff" + "ffffff" + "ffffff" + "ffffff" + "ffffff" + "ffffff"); + const auto expected_header_body = system::base16_chunk( + "ffffff" // next-> + "85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2" // sk (block.hash) + "14131211" // flags + "040302" // height + "24232221" // mtp + "424242" // previous_block_hash (header_fk - invalid) (milestone false) + "34333231" // version + "44434241" // timestamp + "54535251" // bits + "64636261" // nonce + "119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); // merkle_root + + settings settings{}; + settings.header.buckets = 16; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + + store.header_head() = expected_header_head; + store.header_body() = expected_header_body; + BOOST_CHECK(!query.get_header(query.to_header(block_hash))); + BOOST_CHECK(!query.get_header(header_link::terminal)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_header__default__expected) +{ + constexpr auto root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); + constexpr auto block_hash = system::base16_array("85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2"); + static_assert(0x45d6f6162ab0d085_u64 % 10u == 5u); + const system::chain::header header + { + 0x31323334, // version + system::null_hash, // previous_block_hash + root, // merkle_root + 0x41424344, // timestamp + 0x51525354, // bits + 0x61626364 // nonce + }; + + // TODO: heads may be undersized due to the change to base2 sizing. + const auto expected_header_head = system::base16_chunk( + "000000ff" // record count + "ffffffff" // bucket[0]... + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "000080" // filter[8], pk-> + "de" // filter[0-7] + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff" + "ffffffff"); + + const auto expected_header_body = system::base16_chunk( + "ffff7f" // next-> + "85d0b02a16f6d645aa865fad4a8666f5e7bb2b0c4392a5d675496d6c3defa1f2" // sk (block.hash) + "14131211" // flags + "040302" // height + "24232221" // mtp + "ffffff" // previous_block_hash (header_fk - terminal) (milestone true) + "34333231" // version + "44434241" // timestamp + "54535251" // bits + "64636261" // nonce + "119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f"); // merkle_root + + settings settings{}; + settings.header.buckets = 16; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + + ////store.header_head() = expected_header_head; + ////store.header_body() = expected_header_body; + BOOST_CHECK(query.set(header, context{ 0x11121314, 0x01020304, 0x21222324 }, true)); + BOOST_CHECK_EQUAL(store.header_head(), expected_header_head); + BOOST_CHECK_EQUAL(store.header_body(), expected_header_body); + + const auto foo = query.to_header(block_hash); + const auto pointer1 = query.get_header(foo); + BOOST_CHECK(pointer1); + BOOST_CHECK(*pointer1 == header); + + // Verify hash caching. + BOOST_CHECK_EQUAL(pointer1->hash(), block_hash); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_keys__not_found__empty) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.get_tx_keys(query.to_header(system::null_hash)).empty()); + BOOST_CHECK(!store.close(test::events_handler)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_header_key__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK_EQUAL(query.get_header_key(0), test::genesis.hash()); + BOOST_CHECK_EQUAL(query.get_header_key(1), system::null_hash); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_point_key__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK_EQUAL(query.get_point_hash(0), system::null_hash); + + // tx4/5 prevouts are all block1a.tx1. + BOOST_CHECK(query.set(test::tx4)); + BOOST_CHECK(query.set(test::tx5)); + BOOST_CHECK_EQUAL(query.get_point_hash(1), test::block1a.transactions_ptr()->front()->hash(false)); + BOOST_CHECK_EQUAL(query.get_point_hash(2), test::block1a.transactions_ptr()->front()->hash(false)); + + // block1a adds three prevouts of two txs. + BOOST_CHECK(query.set(test::block1a, context{}, false, false)); + BOOST_CHECK_EQUAL(query.get_point_hash(4), system::one_hash); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_key__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK_EQUAL(query.get_tx_key(0), test::genesis.transactions_ptr()->front()->hash(false)); + BOOST_CHECK_EQUAL(query.get_tx_key(1), system::null_hash); + BOOST_CHECK(query.set(test::tx4)); + BOOST_CHECK(query.set(test::tx5)); + BOOST_CHECK_EQUAL(query.get_tx_key(1), test::tx4.hash(false)); + BOOST_CHECK_EQUAL(query.get_tx_key(2), test::tx5.hash(false)); + BOOST_CHECK_EQUAL(query.get_tx_key(3), system::null_hash); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_height1__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1, context{ 0, 1, 0 }, false, false)); + BOOST_CHECK(query.set(test::block2, context{ 0, 2, 0 }, false, false)); + BOOST_CHECK(query.set(test::block3, context{ 0, 3, 0 }, false, false)); + BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); + BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); + + size_t out{}; + BOOST_CHECK(query.get_height(out, 0)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_height(out, 1)); + BOOST_CHECK_EQUAL(out, 1u); + BOOST_CHECK(query.get_height(out, 2)); + BOOST_CHECK_EQUAL(out, 2u); + BOOST_CHECK(query.get_height(out, 3)); + BOOST_CHECK_EQUAL(out, 3u); + BOOST_CHECK(query.get_height(out, 4)); + BOOST_CHECK_EQUAL(out, 1u); + BOOST_CHECK(query.get_height(out, 5)); + BOOST_CHECK_EQUAL(out, 2u); + BOOST_CHECK(!query.get_height(out, 6)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_height2__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1, context{ 0, 1, 0 }, false, false)); + BOOST_CHECK(query.set(test::block2, context{ 0, 2, 0 }, false, false)); + BOOST_CHECK(query.set(test::block3, context{ 0, 3, 0 }, false, false)); + BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); + BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); + + size_t out{}; + BOOST_CHECK(query.get_height(out, test::genesis.hash())); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_height(out, test::block1.hash())); + BOOST_CHECK_EQUAL(out, 1u); + BOOST_CHECK(query.get_height(out, test::block2.hash())); + BOOST_CHECK_EQUAL(out, 2u); + BOOST_CHECK(query.get_height(out, test::block3.hash())); + BOOST_CHECK_EQUAL(out, 3u); + BOOST_CHECK(query.get_height(out, test::block1a.hash())); + BOOST_CHECK_EQUAL(out, 1u); + BOOST_CHECK(query.get_height(out, test::block2a.hash())); + BOOST_CHECK_EQUAL(out, 2u); + BOOST_CHECK(!query.get_height(out, system::one_hash)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_height__not_strong__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::tx4)); + + size_t out{}; + BOOST_CHECK(!query.get_tx_height(out, 1)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_position__confirmed__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); + BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); + BOOST_CHECK(query.set(test::block3a, context{ 0, 3, 0 }, false, false)); + + size_t out{}; + const auto foo = query.get_tx_position(out, 0); + BOOST_CHECK(foo); + BOOST_CHECK_EQUAL(out, 0u); + + BOOST_CHECK(!query.get_tx_position(out, 1)); + BOOST_CHECK(!query.get_tx_position(out, 2)); + BOOST_CHECK(!query.get_tx_position(out, 3)); + BOOST_CHECK(!query.get_tx_position(out, 4)); + BOOST_CHECK(query.set_strong(1)); + BOOST_CHECK(query.set_strong(2)); + BOOST_CHECK(query.set_strong(3)); + + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_position(out, 0)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_position(out, 1)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_position(out, 2)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_position(out, 3)); + BOOST_CHECK_EQUAL(out, 1u); + BOOST_CHECK(query.get_tx_position(out, 4)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(!query.get_tx_position(out, 5)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_position__always__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); + BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); + BOOST_CHECK(query.set(test::block3a, context{ 0, 3, 0 }, false, false)); + BOOST_CHECK(query.set(test::tx4)); + + size_t out{}; + BOOST_CHECK(query.get_tx_position(out, 0)); + BOOST_CHECK_EQUAL(out, 0u); + + BOOST_CHECK(!query.get_tx_position(out, 1)); + BOOST_CHECK(!query.get_tx_position(out, 2)); + BOOST_CHECK(!query.get_tx_position(out, 3)); + BOOST_CHECK(!query.get_tx_position(out, 4)); + BOOST_CHECK(query.set_strong(1)); + BOOST_CHECK(query.set_strong(2)); + BOOST_CHECK(query.set_strong(3)); + + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_position(out, 0)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_position(out, 1)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_position(out, 2)); + BOOST_CHECK_EQUAL(out, 0u); + BOOST_CHECK(query.get_tx_position(out, 3)); + BOOST_CHECK_EQUAL(out, 1u); + BOOST_CHECK(query.get_tx_position(out, 4)); + BOOST_CHECK_EQUAL(out, 0u); + + // tx4 is unconfirmed. + BOOST_CHECK(!query.get_tx_position(out, 5)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_sizes__coinbase__204) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + + size_t light{}; + size_t heavy{}; + BOOST_CHECK(query.get_tx_sizes(light, heavy, 0)); + BOOST_CHECK_EQUAL(light, 204u); + BOOST_CHECK_EQUAL(heavy, 204u); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_tx_count__coinbase__1) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK_EQUAL(query.get_tx_count(0), 1u); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_input__not_found__nullptr) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(!query.get_input(query.to_tx(system::null_hash), 0u, false)); + BOOST_CHECK(!store.close(test::events_handler)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_input__genesis__expected) +{ + settings settings{}; + settings.header.buckets = 8; + settings.tx.buckets = 8; + settings.txs.buckets = 16; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.set(test::genesis, test::context, false, false)); + + const auto tx = test::genesis.transactions_ptr()->front(); + const auto input = tx->inputs_ptr()->front(); + ////BOOST_CHECK(*input == *query.get_input(query.to_tx(tx->hash(false)), 0u)); + ////BOOST_CHECK(*input == *query.get_input(0)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_inputs__tx_not_found__nullptr) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(!query.get_inputs(1, false)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_inputs__found__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::tx4)); + BOOST_CHECK_EQUAL(query.get_inputs(1, false)->size(), 2u); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_output__not_found__nullptr) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(!query.get_output(query.to_tx(system::null_hash), 0u)); + BOOST_CHECK(!query.get_output(query.to_output(system::chain::point{ system::null_hash, 0u }))); + BOOST_CHECK(!query.get_output(0)); + BOOST_CHECK(!store.close(test::events_handler)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_output__genesis__expected) +{ + settings settings{}; + settings.header.buckets = 8; + settings.tx.buckets = 8; + settings.txs.buckets = 16; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.set(test::genesis, test::context, false, false)); + + const auto tx = test::genesis.transactions_ptr()->front(); + const auto output1 = tx->outputs_ptr()->front(); + BOOST_CHECK(*output1 == *query.get_output(query.to_tx(tx->hash(false)), 0u)); + BOOST_CHECK(*output1 == *query.get_output(query.to_output(tx->hash(false), 0u))); + BOOST_CHECK(*output1 == *query.get_output(0)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_outputs__tx_not_found__nullptr) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(!query.get_outputs(1)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_outputs__found__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::tx4)); + BOOST_CHECK_EQUAL(query.get_outputs(1)->size(), 1u); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_transactions__tx_not_found__nullptr) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::tx4)); + BOOST_CHECK(!query.get_transactions(3, false)); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_transactions__found__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1a, test::context, false, false)); + BOOST_CHECK(query.set(test::block2a, test::context, false, false)); + BOOST_CHECK(query.set(test::tx4)); + BOOST_CHECK_EQUAL(query.get_transactions(0, false)->size(), 1u); + BOOST_CHECK_EQUAL(query.get_transactions(1, false)->size(), 1u); + BOOST_CHECK_EQUAL(query.get_transactions(2, false)->size(), 2u); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_spenders__unspent_or_not_found__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + BOOST_CHECK(query.set(test::block1, test::context, false, false)); + BOOST_CHECK(query.set(test::block2, test::context, false, false)); + BOOST_CHECK(query.set(test::block3, test::context, false, false)); + + // Caller should always test for nullptr. + BOOST_CHECK(query.get_spenders(output_link::terminal, true)->empty()); + BOOST_CHECK(query.get_spenders(query.to_output(0, 0), true)->empty()); + BOOST_CHECK(query.get_spenders(query.to_output(0, 1), true)->empty()); + BOOST_CHECK(query.get_spenders(query.to_output(1, 0), true)->empty()); + BOOST_CHECK(query.get_spenders(query.to_output(1, 1), true)->empty()); + BOOST_CHECK(query.get_spenders(query.to_output(2, 0), true)->empty()); + BOOST_CHECK(query.get_spenders(query.to_output(2, 1), true)->empty()); + BOOST_CHECK(query.get_spenders(query.to_output(3, 0), true)->empty()); + BOOST_CHECK(query.get_spenders(query.to_output(3, 1), true)->empty()); +} + +BOOST_AUTO_TEST_CASE(query_chain_writer__get_value__genesis__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + BOOST_CHECK(query.initialize(test::genesis)); + + uint64_t value{}; + BOOST_CHECK(query.get_value(value, query.to_output(0, 0))); + BOOST_CHECK_EQUAL(value, 5000000000u); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/archive/wire_writer.cpp b/test/query/archive/wire_writer.cpp index ee80619b1..1e286f9a7 100644 --- a/test/query/archive/wire_writer.cpp +++ b/test/query/archive/wire_writer.cpp @@ -1,110 +1,122 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#include "../../test.hpp" -#include "../../mocks/blocks.hpp" -#include "../../mocks/chunk_store.hpp" - -BOOST_FIXTURE_TEST_SUITE(query_wire_writer_writer_tests, test::directory_setup_fixture) - -// The view (wire) block writer must produce a store byte-identical to the -// chain block writer (see analogous chain_writer expectations). -BOOST_AUTO_TEST_CASE(query_wire_writer__set_block_view__genesis__expected) -{ - constexpr auto milestone = true; - const auto genesis_header_body = system::base16_chunk( - "ffff7f" // next-> - "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" // sk (block.hash) - "04030201" // flags - "141312" // height - "24232221" // mtp - "ffffff" // previous_block_hash (header_fk - not found) (milestone true) - "01000000" // version - "29ab5f49" // timestamp - "ffff001d" // bits - "1dac2b7c" // nonce - "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"); // merkle_root - const auto genesis_tx_body = system::base16_chunk( - "ffffff7f" // next-> - "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" // sk (tx.hash(false)) - "cc0080" // light (coinbase merged) - "cc0000" // heavy - "00000000" // locktime - "01000000" // version - "010000" // ins_count - "010000" // outs_count - "00000000" // point_fk-> (ins_fk) - "00000000"); // outs_fk-> - const auto genesis_outs_body = system::base16_chunk( - "0000000000"); // output0_fk-> - const auto genesis_output_body = system::base16_chunk( - "00000000" // parent_fk-> - "ff00f2052a01000000" // value - "434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"); // script - const auto genesis_ins_body = system::base16_chunk( - "ffffffff" // next-> - "0000000000000000000000000000000000000000000000000000000000000000" - "ffffff"); // index - const auto genesis_sequence_body = system::base16_chunk( - "ffffffff" // sequence - "0000000000" // input_fk-> (coinbase) - "00000000"); // parent_fk-> - const auto genesis_input_body = system::base16_chunk( - "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script - "00"); // witness - const auto genesis_txs_body = system::base16_chunk( - "1d0100" // size light (285) - "1d0100" // size heavy (285) - "0100" // txs count (1) - "00000000" // transaction[0] - "ff" // depth (255) - genesis only - "00000000"); // forks (0) - genesis only - - settings settings{}; - settings.header.buckets = 8; - settings.tx.buckets = 8; - settings.ins.buckets = 8; - settings.txs.buckets = 16; - settings.path = TEST_DIRECTORY; - test::chunk_store store{ settings }; - test::query_accessor query{ store }; - BOOST_CHECK(!store.create(test::events_handler)); - - // Set header, then txs from the parsed view of the serialized block. - BOOST_CHECK(query.set(test::genesis.header(), test::context, milestone)); - system::chain::block_view view{ test::genesis.to_data(true), true }; - BOOST_CHECK(view.is_valid()); - BOOST_CHECK_EQUAL(query.set_code(view, false, true), error::success); - BOOST_CHECK(query.is_block(test::genesis.hash())); - BOOST_CHECK(!store.close(test::events_handler)); - - BOOST_CHECK_EQUAL(store.header_body(), genesis_header_body); - BOOST_CHECK_EQUAL(store.tx_body(), genesis_tx_body); - BOOST_CHECK_EQUAL(store.ins_body(), genesis_ins_body); - BOOST_CHECK_EQUAL(store.ins_sequence_body(), genesis_sequence_body); - BOOST_CHECK_EQUAL(store.input_body(), genesis_input_body); - BOOST_CHECK_EQUAL(store.output_body(), genesis_output_body); - BOOST_CHECK_EQUAL(store.outs_body(), genesis_outs_body); - BOOST_CHECK_EQUAL(store.txs_body(), genesis_txs_body); - - const auto pointer = query.get_block(query.to_header(test::genesis.hash()), false); - BOOST_CHECK(pointer); - BOOST_CHECK(*pointer == test::genesis); -} - -BOOST_AUTO_TEST_SUITE_END() +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "../../test.hpp" +#include "../../mocks/blocks.hpp" +#include "../../mocks/chunk_store.hpp" + +BOOST_FIXTURE_TEST_SUITE(query_wire_writer_writer_tests, test::directory_setup_fixture) + +// The view (wire) block writer must produce a store byte-identical to the +// chain block writer (see analogous chain_writer expectations). +BOOST_AUTO_TEST_CASE(query_wire_writer__set_block_view__genesis__expected) +{ + constexpr auto milestone = true; + const auto genesis_header_body = system::base16_chunk( + "ffff7f" // next-> + "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" // sk (block.hash) + "04030201" // flags + "141312" // height + "24232221" // mtp + "ffffff" // previous_block_hash (header_fk - not found) (milestone true) + "01000000" // version + "29ab5f49" // timestamp + "ffff001d" // bits + "1dac2b7c" // nonce + "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"); // merkle_root + const auto genesis_tx_body = system::base16_chunk( + "ffffff7f" // next-> + "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" // sk (tx.hash(false)) + "cc0080" // light (coinbase merged) + "cc0000" // heavy + "00000000" // locktime + "01000000" // version + "010000" // ins_count + "010000" // outs_count + "00000000" // point_fk-> (ins_fk) + "00000000"); // outs_fk-> + const auto genesis_outs_body = system::base16_chunk( + "0000000000"); // output0_fk-> + const auto genesis_output_body = system::base16_chunk( + "00000000" // parent_fk-> + "ff00f2052a01000000" // value + "434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"); // script + const auto genesis_ins_body = system::base16_chunk( + "ffffffff" // next-> + "0000000000000000000000000000000000000000000000000000000000000000" + "ffffff"); // index + const auto genesis_sequence_body = system::base16_chunk( + "ffffffff" // sequence + "0000000000" // input_fk-> (coinbase) + "00000000"); // parent_fk-> + const auto genesis_input_body = system::base16_chunk( + "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script + "00"); // witness + const auto genesis_txs_body = system::build_chunk( + { + system::base16_chunk( + "1d0100" // size light (285) + "1d0100" // size heavy (285) + "0100" // txs count (1) + "00000000" // transaction[0] + "01" // envelope version (1) - genesis only + "ff00" // interval depth (255) + "08000000" // header buckets (8) + "08000000" // ins buckets (8) + "80000000" // outs buckets (128) + "08000000" // tx buckets (8) + "80000000" // strong_tx buckets (128) + "80000000" // duplicate buckets (128) + "80000000" // validated_tx buckets (128) + "01"), // filter (true) + system::data_chunk(231, 0x00) + }); + + settings settings{}; + settings.header.buckets = 8; + settings.tx.buckets = 8; + settings.ins.buckets = 8; + settings.txs.buckets = 16; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_CHECK(!store.create(test::events_handler)); + + // Set header, then txs from the parsed view of the serialized block. + BOOST_CHECK(query.set(test::genesis.header(), test::context, milestone)); + system::chain::block_view view{ test::genesis.to_data(true), true }; + BOOST_CHECK(view.is_valid()); + BOOST_CHECK_EQUAL(query.set_code(view, false, true), error::success); + BOOST_CHECK(query.is_block(test::genesis.hash())); + BOOST_CHECK(!store.close(test::events_handler)); + + BOOST_CHECK_EQUAL(store.header_body(), genesis_header_body); + BOOST_CHECK_EQUAL(store.tx_body(), genesis_tx_body); + BOOST_CHECK_EQUAL(store.ins_body(), genesis_ins_body); + BOOST_CHECK_EQUAL(store.ins_sequence_body(), genesis_sequence_body); + BOOST_CHECK_EQUAL(store.input_body(), genesis_input_body); + BOOST_CHECK_EQUAL(store.output_body(), genesis_output_body); + BOOST_CHECK_EQUAL(store.outs_body(), genesis_outs_body); + BOOST_CHECK_EQUAL(store.txs_body(), genesis_txs_body); + + const auto pointer = query.get_block(query.to_header(test::genesis.hash()), false); + BOOST_CHECK(pointer); + BOOST_CHECK(*pointer == test::genesis); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/extent.cpp b/test/query/extent.cpp index 5ffb9c5fe..c13efc5b7 100644 --- a/test/query/extent.cpp +++ b/test/query/extent.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(query_extent__body_sizes__genesis__expected) BOOST_REQUIRE_EQUAL(query.input_body_size(), 79u); BOOST_REQUIRE_EQUAL(query.ins_body_size(), schema::ins::minrow + schema::ins_sequence::minrow); BOOST_REQUIRE_EQUAL(query.outs_body_size(), schema::address::minrow + schema::outs::minrow); - BOOST_REQUIRE_EQUAL(query.txs_body_size(), schema::txs::minrow + one + schema::flags); + BOOST_REQUIRE_EQUAL(query.txs_body_size(), schema::txs::minrow + settings.envelope.serialized_size()); BOOST_REQUIRE_EQUAL(query.tx_body_size(), schema::transaction::minrow); BOOST_REQUIRE_EQUAL(query.candidate_body_size(), zero); diff --git a/test/settings.cpp b/test/settings.cpp index fd94ff8a3..01abe828e 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -26,7 +26,15 @@ BOOST_AUTO_TEST_CASE(settings__construct__default__expected) BOOST_REQUIRE_EQUAL(configuration.turbo, false); BOOST_REQUIRE_EQUAL(configuration.mark_unconfirmable, true); BOOST_REQUIRE_EQUAL(configuration.interval_depth, 255u); - BOOST_REQUIRE_EQUAL(configuration.fork_flags, 0u); + BOOST_REQUIRE_EQUAL(configuration.envelope.interval_depth, 255u); + BOOST_REQUIRE_EQUAL(configuration.envelope.header_buckets, 128u); + BOOST_REQUIRE_EQUAL(configuration.envelope.ins_buckets, 128u); + BOOST_REQUIRE_EQUAL(configuration.envelope.outs_buckets, 128u); + BOOST_REQUIRE_EQUAL(configuration.envelope.tx_buckets, 128u); + BOOST_REQUIRE_EQUAL(configuration.envelope.strong_tx_buckets, 128u); + BOOST_REQUIRE_EQUAL(configuration.envelope.duplicate_buckets, 128u); + BOOST_REQUIRE_EQUAL(configuration.envelope.validated_tx_buckets, 128u); + BOOST_REQUIRE_EQUAL(configuration.envelope.filter, true); BOOST_REQUIRE_EQUAL(configuration.path, "bitcoin"); // Archives. diff --git a/test/tables/archives/txs.cpp b/test/tables/archives/txs.cpp index 6547690a0..c145e04e6 100644 --- a/test/tables/archives/txs.cpp +++ b/test/tables/archives/txs.cpp @@ -30,12 +30,11 @@ const table::txs::slab slab0 0x000000, 0x000000, { - // tx fk 0 uniquely identifies genesis, resulting in depth storage. + // tx fk 0 uniquely identifies genesis, resulting in envelope storage. 0x00000000_u32 }, {}, // interval (unused due to default span) - 0x42, // depth (genesis only) - 0x12345678 // flags (genesis only) + {} // envelope (genesis only) }; const table::txs::slab slab1 { @@ -67,26 +66,29 @@ const table::txs::slab slab3 0x56341233_u32 } }; -const data_chunk expected0 +const data_chunk expected0 = build_chunk( { - // slab0 (light) [0x00] - 0x00, 0x00, 0x00, + data_chunk + { + // slab0 (light) [0x00] + 0x00, 0x00, 0x00, - // slab0 (heavy) [0x00] - 0x00, 0x00, 0x00, + // slab0 (heavy) [0x00] + 0x00, 0x00, 0x00, - // slab0 (count) [1] - 0x01, 0x00, + // slab0 (count) [1] + 0x01, 0x00, - // slab0 (txs) - 0x00, 0x00, 0x00, 0x00, + // slab0 (txs) + 0x00, 0x00, 0x00, 0x00, - // depth (genesis) - 0x42, + // envelope version (genesis) + 0x01 + }, - // flags (genesis) - 0x78, 0x56, 0x34, 0x12 -}; + // default envelope body (genesis) + data_chunk(262, 0x00) +}); const data_chunk expected1 { // slab1 (light) [0x0000ab] @@ -151,8 +153,7 @@ BOOST_AUTO_TEST_CASE(txs__put__get__expected) BOOST_CHECK(slab == slab0); BOOST_CHECK_EQUAL(body_store.buffer(), build_chunk({ expected0 })); - slab.depth = {}; - slab.forks = {}; + slab.envelope = {}; BOOST_CHECK(instance.put(key, slab1)); BOOST_CHECK(instance.exists(key)); BOOST_CHECK(instance.at(key, slab));