Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/bitcoin/database/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ enum error_t : uint8_t
/// txs archive
txs_header,
txs_empty,
txs_height,
txs_confirm,
txs_txs_put
};
Expand Down
26 changes: 11 additions & 15 deletions include/bitcoin/database/impl/query/archive_write.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,10 @@ bool CLASS::set(const transaction& tx) NOEXCEPT
}

TEMPLATE
bool CLASS::set(const block& block, bool strong, bool bypass,
size_t height) NOEXCEPT
bool CLASS::set(const block& block, bool strong, bool bypass) NOEXCEPT
{
// This sets only the txs of a block with header/context already archived.
return !set_code(block, strong, bypass, height);
return !set_code(block, strong, bypass);
}

// set transaction
Expand Down Expand Up @@ -350,27 +349,30 @@ code CLASS::set_code(header_link& out_fk, const block& block,
// releases all memory for parts of itself, due to the custom allocator.

TEMPLATE
code CLASS::set_code(const block& block, bool strong, bool bypass,
size_t height) NOEXCEPT
code CLASS::set_code(const block& block, bool strong, bool bypass) NOEXCEPT
{
header_link unused{};
return set_code(unused, block, strong, bypass, height);
return set_code(unused, block, strong, bypass);
}

TEMPLATE
code CLASS::set_code(header_link& out_fk, const block& block, bool strong,
bool bypass, size_t height) NOEXCEPT
bool bypass) NOEXCEPT
{
out_fk = to_header(block.get_hash());
if (out_fk.is_terminal())
return error::txs_header;

size_t height{};
if (!get_height(height, out_fk))
return error::txs_height;

return set_code(block, out_fk, strong, bypass, height);
}

TEMPLATE
code CLASS::set_code(const block& block, const header_link& key,
bool strong, bool bypass, size_t /* height */) NOEXCEPT
bool strong, bool bypass, size_t height) NOEXCEPT
{
using namespace system;
if (key.is_terminal())
Expand All @@ -392,16 +394,10 @@ code CLASS::set_code(const block& block, const header_link& key,
return ec;

using bytes = linkage<schema::size>::integer;
auto interval = get_interval(key, height);
const auto size = block.serialized_size(true);
const auto wire = possible_narrow_cast<bytes>(size);

// TODO: compute and set interval hash for interval blocks as configured.
// TODO: create query to walk header.parent across full interval to collect
// TODO: merkle leaves and compute intermediate merkle root. This requires
// TODO: header.parent link traversal only, with read of hash for each. The
// TODO: full interval of hashes (e.g. 2048) is preallocated to a vector.
std::optional<hash_digest> interval{};

// ========================================================================
const auto scope = store_.get_transactor();
constexpr auto positive = true;
Expand Down
35 changes: 35 additions & 0 deletions include/bitcoin/database/impl/query/optional.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,41 @@ code CLASS::get_confirmed_balance(std::atomic_bool& cancel, uint64_t& balance,
return error::success;
}

TEMPLATE
std::optional<hash_digest> CLASS::get_interval(header_link link,
size_t height) const NOEXCEPT
{
// Interval is enabled by address table.
if (!address_enabled())
return {};

// Interval is the merkle root that spans 2^depth block hashes.
const auto span = system::power2(store_.interval_depth());

// power2() overflow returns zero.
if (is_zero(span))
return {};

// One is a functional but undesirable case.
if (is_one(span))
return get_header_key(link);

// Interval ends at nth block where n is a multiple of span.
if (!system::is_multiple(add1(height), span))
return {};

// Generate the leaf nodes for the span.
hashes leaves(span);
for (auto& leaf: std::views::reverse(leaves))
{
leaf = get_header_key(link);
link = to_parent(link);
}

// Generate the interval (merkle root) for the span ending on link header.
return system::merkle_root(std::move(leaves));
}

////TEMPLATE
////bool CLASS::set_address_output(const output& output,
//// const output_link& link) NOEXCEPT
Expand Down
6 changes: 6 additions & 0 deletions include/bitcoin/database/impl/store.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ bool CLASS::turbo() const NOEXCEPT
return configuration_.turbo;
}

TEMPLATE
uint8_t CLASS::interval_depth() const NOEXCEPT
{
return configuration_.interval_depth;
}

TEMPLATE
code CLASS::create(const event_handler& handler) NOEXCEPT
{
Expand Down
16 changes: 10 additions & 6 deletions include/bitcoin/database/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <atomic>
#include <mutex>
#include <optional>
#include <utility>
#include <bitcoin/system.hpp>
#include <bitcoin/database/define.hpp>
Expand Down Expand Up @@ -412,8 +413,7 @@ class query
bool set(const block& block, const context& ctx,
bool milestone, bool strong) NOEXCEPT;
bool set(const transaction& tx) NOEXCEPT;
bool set(const block& block, bool strong, bool bypass,
size_t height) NOEXCEPT;
bool set(const block& block, bool strong, bool bypass) NOEXCEPT;

/// Set transaction.
code set_code(const transaction& tx) NOEXCEPT;
Expand All @@ -439,10 +439,9 @@ class query
const chain_context& ctx, bool milestone, bool strong) NOEXCEPT;

/// Set block.txs (headers-first).
code set_code(const block& block, bool strong, bool bypass,
size_t height) NOEXCEPT;
code set_code(const block& block, bool strong, bool bypass) NOEXCEPT;
code set_code(header_link& out_fk, const block& block, bool strong,
bool bypass, size_t height) NOEXCEPT;
bool bypass) NOEXCEPT;
code set_code(const block& block, const header_link& key, bool strong,
bool bypass, size_t height) NOEXCEPT;

Expand Down Expand Up @@ -569,7 +568,12 @@ class query
code get_minimum_unspent_outputs(std::atomic_bool& cancel, outpoints& out,
const hash_digest& key, uint64_t value, bool turbo=false) const NOEXCEPT;
code get_confirmed_balance(std::atomic_bool& cancel,
uint64_t& balance, const hash_digest& key, bool turbo=false) const NOEXCEPT;
uint64_t& balance, const hash_digest& key,
bool turbo=false) const NOEXCEPT;

/// No value if header is not at configured interval.
std::optional<hash_digest> get_interval(header_link header,
size_t height) const NOEXCEPT;

bool is_filtered_body(const header_link& link) const NOEXCEPT;
bool get_filter_body(filter& out, const header_link& link) const NOEXCEPT;
Expand Down
8 changes: 8 additions & 0 deletions include/bitcoin/database/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@ struct BCD_API settings
settings(system::chain::selection context) NOEXCEPT;

/// Properties.
/// -----------------------------------------------------------------------

/// Enable concurrency in individual client-server queries.
bool turbo;

/// Depth of electrum merkle tree interval caching.
uint8_t interval_depth;

/// Path to the database directory.
std::filesystem::path path;

/// Archives.
Expand Down
3 changes: 3 additions & 0 deletions include/bitcoin/database/store.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class store
/// Allow full throttle concurrent query execution (may use 100% CPU).
bool turbo() const NOEXCEPT;

/// Depth of electrum merkle tree interval caching.
uint8_t interval_depth() const NOEXCEPT;

/// Methods.
/// -----------------------------------------------------------------------

Expand Down
5 changes: 3 additions & 2 deletions include/bitcoin/database/tables/archives/txs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct txs
return set_right(wire, offset, interval);
}

// Intervals are set only if non-zero in database.interval.
// Intervals are optional based on store configuration.
struct slab
: public schema::txs
{
Expand Down Expand Up @@ -121,7 +121,8 @@ struct txs
inline link count() const NOEXCEPT
{
return system::possible_narrow_cast<link::integer>(ct::size +
bytes::size + tx::size * number);
bytes::size + tx::size * number +
(interval.has_value() ? schema::hash : zero));
}

inline bool to_data(finalizer& sink) const NOEXCEPT
Expand Down
1 change: 1 addition & 0 deletions src/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
// txs archive
{ txs_header, "txs_header" },
{ txs_empty, "txs_empty" },
{ txs_height, "txs_height" },
{ txs_confirm, "txs_confirm" },
{ txs_txs_put, "txs_txs_put" }
};
Expand Down
1 change: 1 addition & 0 deletions src/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ using namespace bc::system;

settings::settings() NOEXCEPT
: turbo{ false },
interval_depth{ max_uint8 },
path{ "bitcoin" },

// Archives.
Expand Down
9 changes: 9 additions & 0 deletions test/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__txs_empty__true_exected_message)
BOOST_REQUIRE_EQUAL(ec.message(), "txs_empty");
}

BOOST_AUTO_TEST_CASE(error_t__code__txs_height__true_exected_message)
{
constexpr auto value = error::txs_height;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "txs_height");
}

BOOST_AUTO_TEST_CASE(error_t__code__txs_confirm__true_exected_message)
{
constexpr auto value = error::txs_confirm;
Expand Down
2 changes: 1 addition & 1 deletion test/query/archive_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ BOOST_AUTO_TEST_CASE(query_archive_write__set_block_txs__get_block__expected)
BOOST_REQUIRE(!query.is_block(test::genesis.hash()));
BOOST_REQUIRE(query.set(test::genesis.header(), test::context, milestone));
BOOST_REQUIRE(!query.is_associated(0));
BOOST_REQUIRE(query.set(test::genesis, false, false, zero));
BOOST_REQUIRE(query.set(test::genesis, false, false));
BOOST_REQUIRE(query.is_block(test::genesis.hash()));
BOOST_REQUIRE(query.is_associated(0));

Expand Down
10 changes: 5 additions & 5 deletions test/query/initialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,17 +415,17 @@ BOOST_AUTO_TEST_CASE(query_initialize__get_unassociated_above__gapped_candidate_
BOOST_REQUIRE_EQUAL(unassociated3.size(), 0u);

// There are two unassociated blocks above block 1 (new fork point).
BOOST_REQUIRE(query.set(test::block1, false, false, zero));
BOOST_REQUIRE(query.set(test::block1, false, false));
BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false));
BOOST_REQUIRE_EQUAL(query.get_all_unassociated().size(), 2u);

// There is one unassociated block above block 2 (new fork point).
BOOST_REQUIRE(query.set(test::block2, false, false, zero));
BOOST_REQUIRE(query.set(test::block2, false, false));
BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false));
BOOST_REQUIRE_EQUAL(query.get_all_unassociated().size(), 1u);

// There are no unassociated blocks above block 3 (new fork point).
BOOST_REQUIRE(query.set(test::block3, false, false, zero));
BOOST_REQUIRE(query.set(test::block3, false, false));
BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false));
BOOST_REQUIRE_EQUAL(query.get_all_unassociated().size(), 0u);
}
Expand Down Expand Up @@ -482,15 +482,15 @@ BOOST_AUTO_TEST_CASE(query_initialize__get_unassociated_count_above__gapped_cand
BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(3, 1), 0u);

// There is one unassociated block at block 2.
BOOST_REQUIRE(query.set(test::block3, false, false, zero)); // associated
BOOST_REQUIRE(query.set(test::block3, false, false)); // associated
BOOST_REQUIRE_EQUAL(query.get_unassociated_count(), 1u);
BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(0), 1u);
BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(1), 1u);
BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(2), 0u);
BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(3), 0u);

// There are no unassociated blocks.
BOOST_REQUIRE(query.set(test::block2, false, false, zero)); // associated
BOOST_REQUIRE(query.set(test::block2, false, false)); // associated
BOOST_REQUIRE_EQUAL(query.get_unassociated_count(), 0u);
BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(0), 0u);
BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(1), 0u);
Expand Down
88 changes: 88 additions & 0 deletions test/query/optional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,94 @@ BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_balance__genesis__expected)
BOOST_REQUIRE_EQUAL(out, 5000000000u);
}

// Merkle root of test blocks [0..1]
constexpr auto root01 = system::base16_hash("abdc2227d02d114b77be15085c1257709252a7a103f9ac0ab3c85d67e12bc0b8");

// Merkle root of test blocks [2..4]
constexpr auto root02 = system::base16_hash("f2a2a2907abb326726a2d6500fe494f63772a941b414236c302e920bc1aa9caf");

// Merkle root of test blocks [0..4]
constexpr auto root04 = system::sha256::double_hash(root01, root02);

BOOST_AUTO_TEST_CASE(query_optional__get_interval__depth_0__block_hash)
{
settings settings{};
settings.interval_depth = 0;
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success);
BOOST_REQUIRE(query.initialize(test::genesis));
BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, false, false));
BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, false, false));
BOOST_REQUIRE(query.set(test::block3, context{ 0, 3, 0 }, false, false));

const auto header0 = query.to_header(test::genesis.hash());
const auto header1 = query.to_header(test::block1.hash());
const auto header2 = query.to_header(test::block2.hash());
const auto header3 = query.to_header(test::block3.hash());
BOOST_REQUIRE(!header0.is_terminal());
BOOST_REQUIRE(!header1.is_terminal());
BOOST_REQUIRE(!header2.is_terminal());
BOOST_REQUIRE(!header3.is_terminal());
BOOST_REQUIRE(query.get_interval(header0, 0).has_value());
BOOST_REQUIRE(query.get_interval(header1, 1).has_value());
BOOST_REQUIRE(query.get_interval(header2, 2).has_value());
BOOST_REQUIRE(query.get_interval(header3, 3).has_value());
BOOST_REQUIRE_EQUAL(query.get_interval(header0, 0).value(), test::genesis.hash());
BOOST_REQUIRE_EQUAL(query.get_interval(header1, 1).value(), test::block1.hash());
BOOST_REQUIRE_EQUAL(query.get_interval(header2, 2).value(), test::block2.hash());
BOOST_REQUIRE_EQUAL(query.get_interval(header3, 3).value(), test::block3.hash());
}

BOOST_AUTO_TEST_CASE(query_optional__get_interval__depth_1__expected)
{
settings settings{};
settings.interval_depth = 1;
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success);
BOOST_REQUIRE(query.initialize(test::genesis));
BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, false, false));
BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, false, false));
BOOST_REQUIRE(query.set(test::block3, context{ 0, 3, 0 }, false, false));

const auto header0 = query.to_header(test::genesis.hash());
const auto header1 = query.to_header(test::block1.hash());
const auto header2 = query.to_header(test::block2.hash());
const auto header3 = query.to_header(test::block3.hash());
BOOST_REQUIRE(!header0.is_terminal());
BOOST_REQUIRE(!header1.is_terminal());
BOOST_REQUIRE(!header2.is_terminal());
BOOST_REQUIRE(!header3.is_terminal());
BOOST_REQUIRE(!query.get_interval(header0, 0).has_value());
BOOST_REQUIRE( query.get_interval(header1, 1).has_value());
BOOST_REQUIRE(!query.get_interval(header2, 2).has_value());
BOOST_REQUIRE( query.get_interval(header3, 3).has_value());
BOOST_REQUIRE_EQUAL(query.get_interval(header1, 1).value(), root01);
BOOST_REQUIRE_EQUAL(query.get_interval(header3, 3).value(), root02);
}

BOOST_AUTO_TEST_CASE(query_optional__get_interval__depth_2__expected)
{
settings settings{};
settings.interval_depth = 2;
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success);
BOOST_REQUIRE(query.initialize(test::genesis));
BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, false, false));
BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, false, false));
BOOST_REQUIRE(query.set(test::block3, context{ 0, 3, 0 }, false, false));

const auto header3 = query.to_header(test::block3.hash());
BOOST_REQUIRE(!header3.is_terminal());
BOOST_REQUIRE(query.get_interval(header3, 3).has_value());
BOOST_REQUIRE_EQUAL(query.get_interval(header3, 3).value(), root04);
}

////BOOST_AUTO_TEST_CASE(query_optional__set_filter__get_filter_and_head__expected)
////{
//// const auto& filter_head0 = system::null_hash;
Expand Down
Loading
Loading