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
9 changes: 8 additions & 1 deletion include/bitcoin/database/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,14 @@ enum error_t : uint8_t
txs_empty,
txs_height,
txs_confirm,
txs_txs_put
txs_txs_put,

/// optional
merkle_proof,
merkle_interval,
merkle_hashes,
merkle_arguments,
merkle_not_found
};

// No current need for error_code equivalence mapping.
Expand Down
2 changes: 1 addition & 1 deletion include/bitcoin/database/impl/query/archive_write.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ 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);
auto interval = create_interval(key, height);
const auto size = block.serialized_size(true);
const auto wire = possible_narrow_cast<bytes>(size);

Expand Down
27 changes: 27 additions & 0 deletions include/bitcoin/database/impl/query/height.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#define LIBBITCOIN_DATABASE_QUERY_HEIGHT_IPP

#include <algorithm>
#include <ranges>
#include <bitcoin/system.hpp>
#include <bitcoin/database/define.hpp>

Expand Down Expand Up @@ -209,6 +210,32 @@ hashes CLASS::get_confirmed_hashes(const heights& heights) const NOEXCEPT
///////////////////////////////////////////////////////////////////////////
}

TEMPLATE
hashes CLASS::get_confirmed_hashes(size_t first, size_t count) const NOEXCEPT
{
using namespace system;
const auto size = is_odd(count) && count > one ? add1(count) : count;
if (is_add_overflow(count, one) || is_add_overflow(first, size))
return {};

auto link = to_confirmed(first + count);
if (link.is_terminal())
return {};

// Extra allocation for odd count optimizes for merkle root.
// Vector capacity is never reduced when resizing to smaller size.
hashes out(size);
out.resize(count);

for (auto& hash: std::views::reverse(out))
{
hash = get_header_key(link);
link = to_parent(link);
}

return out;
}

// writers
// ----------------------------------------------------------------------------

Expand Down
151 changes: 130 additions & 21 deletions include/bitcoin/database/impl/query/optional.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ code CLASS::get_confirmed_balance(std::atomic_bool& cancel, uint64_t& balance,
const hash_digest& key, bool turbo) const NOEXCEPT
{
outpoints outs{};
if (code ec = get_confirmed_unspent_outputs(cancel, outs, key, turbo))
if (const auto ec = get_confirmed_unspent_outputs(cancel, outs, key, turbo))
{
balance = zero;
return ec;
Expand All @@ -259,42 +259,151 @@ code CLASS::get_confirmed_balance(std::atomic_bool& cancel, uint64_t& balance,
return error::success;
}

// merkle
// ----------------------------------------------------------------------------

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

// Interval is the merkle root that spans 2^depth block hashes.
// span of zero (overflow) is disallowed (division by zero).
// span of one (2^0) caches every block (no optimization, wasted storage).
// span greater than top height eliminates caching (no optimization).
const auto span = system::power2(store_.interval_depth());
return is_zero(span) ? max_size_t : span;
}

// 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);

// protected
TEMPLATE
CLASS::hash_option CLASS::create_interval(header_link link,
size_t height) const NOEXCEPT
{
// Interval ends at nth block where n is a multiple of span.
if (!system::is_multiple(add1(height), span))
// One is a functional but undesirable case (no optimization).
const auto span = interval_span();
BC_ASSERT(!is_zero(span));

// If valid link is provided then empty return implies non-interval height.
if (link.is_terminal() || !system::is_multiple(add1(height), span))
return {};

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

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

// protected
TEMPLATE
CLASS::hash_option CLASS::get_confirmed_interval(size_t height) const NOEXCEPT
{
const auto span = interval_span();
BC_ASSERT(!is_zero(span));

if (!system::is_multiple(add1(height), span))
return {};

table::txs::get_interval txs{};
if (!store_.txs.get(to_confirmed(height), txs))
return {};

return txs.interval;
}

// protected
TEMPLATE
void CLASS::push_merkle(hashes& to, hashes&& from, size_t first) NOEXCEPT
{
using namespace system;
for (const auto& row: block::merkle_branch(first, from.size()))
{
BC_ASSERT(row.sibling * add1(row.width) <= from.size());
const auto it = std::next(from.begin(), row.sibling * row.width);
const auto mover = std::make_move_iterator(it);
to.push_back(merkle_root({ mover, std::next(mover, row.width) }));
}
}

// protected
TEMPLATE
code CLASS::get_merkle_proof(hashes& proof, hashes roots, size_t target,
size_t waypoint) NOEXCEPT
{
const auto span = interval_span();
BC_ASSERT(!is_zero(span));

const auto first = (target / span) * span;
const auto last = std::min(sub1(first + span), waypoint);
auto other = get_confirmed_hashes(first, add1(last - first));
if (other.empty())
return error::merkle_proof;

using namespace system;
proof.reserve(ceilinged_log2(other.size()) + ceilinged_log2(roots.size()));
push_merkle(proof, std::move(other), target % span);
push_merkle(proof, std::move(roots), target / span);
return error::success;
}

// protected
TEMPLATE
code CLASS::get_merkle_tree(hashes& tree, size_t waypoint) NOEXCEPT
{
const auto span = interval_span();
BC_ASSERT(!is_zero(span));

const auto range = add1(waypoint);
tree.reserve(system::ceilinged_divide(range, span));
for (size_t first{}; first < range; first += span)
{
const auto last = std::min(sub1(first + span), waypoint);
const auto size = add1(last - first);

if (size == span)
{
auto interval = get_confirmed_interval(first);
if (!interval.has_value()) return error::merkle_interval;
tree.push_back(std::move(interval.value()));
}
else
{
auto confirmed = get_confirmed_hashes(first, size);
if (confirmed.empty()) return error::merkle_hashes;
tree.push_back(system::merkle_root(std::move(confirmed)));
}
}

return error::success;
}

TEMPLATE
code CLASS::get_merkle_root_and_proof(hash_digest& root, hashes& proof,
size_t target, size_t waypoint) NOEXCEPT
{
if (target > waypoint)
return error::merkle_arguments;

if (waypoint > get_top_confirmed())
return error::merkle_not_found;

hashes tree{};
if (const auto ec = get_merkle_tree(tree, waypoint))
return ec;

proof.clear();
if (const auto ec = get_merkle_proof(proof, tree, target, waypoint))
return ec;

root = system::merkle_root(std::move(tree));
return {};
}

////TEMPLATE
////bool CLASS::set_address_output(const output& output,
//// const output_link& link) NOEXCEPT
Expand Down
24 changes: 17 additions & 7 deletions include/bitcoin/database/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,6 @@ class query
size_t get_unassociated_count_above(size_t height) const NOEXCEPT;
size_t get_unassociated_count_above(size_t height,
size_t last) const NOEXCEPT;
hashes get_candidate_hashes(const heights& heights) const NOEXCEPT;
hashes get_confirmed_hashes(const heights& heights) const NOEXCEPT;

/// Translation (key/link to link/s).
/// -----------------------------------------------------------------------
Expand Down Expand Up @@ -537,6 +535,10 @@ class query
size_t get_confirmed_size() const NOEXCEPT;
size_t get_confirmed_size(size_t top) const NOEXCEPT;

hashes get_candidate_hashes(const heights& heights) const NOEXCEPT;
hashes get_confirmed_hashes(const heights& heights) const NOEXCEPT;
hashes get_confirmed_hashes(size_t first, size_t count) const NOEXCEPT;

header_links get_confirmed_fork(const header_link& fork) const NOEXCEPT;
header_links get_candidate_fork(size_t& fork_point) const NOEXCEPT;
header_states get_validated_fork(size_t& fork_point,
Expand Down Expand Up @@ -570,10 +572,8 @@ class query
code get_confirmed_balance(std::atomic_bool& cancel,
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(const header_link& link,
size_t height) const NOEXCEPT;
code get_merkle_root_and_proof(hash_digest& root, hashes& proof,
size_t target, size_t checkpoint) NOEXCEPT;

bool is_filtered_body(const header_link& link) const NOEXCEPT;
bool get_filter_body(filter& out, const header_link& link) const NOEXCEPT;
Expand All @@ -593,6 +593,7 @@ class query
const hash_digest& hash) NOEXCEPT;

protected:
using hash_option = std::optional<hash_digest>;
struct span
{
size_t size() const NOEXCEPT { return end - begin; }
Expand Down Expand Up @@ -707,7 +708,6 @@ class query

/// address
/// -----------------------------------------------------------------------

code get_address_outputs_turbo(std::atomic_bool& cancel,
outpoints& out, const hash_digest& key) const NOEXCEPT;
code get_confirmed_unspent_outputs_turbo(std::atomic_bool& cancel,
Expand All @@ -716,6 +716,16 @@ class query
outpoints& out, const hash_digest& key,
uint64_t minimum) const NOEXCEPT;

/// merkle
/// -----------------------------------------------------------------------
size_t interval_span() const NOEXCEPT;
hash_option get_confirmed_interval(size_t height) const NOEXCEPT;
hash_option create_interval(header_link link, size_t height) const NOEXCEPT;
void push_merkle(hashes& branch, hashes&& hashes, size_t first) NOEXCEPT;
code get_merkle_tree(hashes& roots, size_t waypoint) NOEXCEPT;
code get_merkle_proof(hashes& proof, hashes roots, size_t target,
size_t waypoint) NOEXCEPT;

/// tx_fk must be allocated.
/// -----------------------------------------------------------------------
code set_code(const tx_link& tx_fk, const transaction& tx,
Expand Down
32 changes: 27 additions & 5 deletions include/bitcoin/database/tables/archives/txs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ struct txs
{
using namespace system;
tx_fks.resize(source.read_little_endian<ct::integer, ct::size>());
const auto flaged = source.read_little_endian<bytes::integer, bytes::size>();
wire = set_right(flaged, offset, false);
const auto flagged = source.read_little_endian<bytes::integer, bytes::size>();
wire = set_right(flagged, offset, false);
std::for_each(tx_fks.begin(), tx_fks.end(), [&](auto& fk) NOEXCEPT
{
fk = source.read_little_endian<tx::integer, tx::size>();
});

if (get_right(flaged, offset)) interval = source.read_hash();
if (get_right(flagged, offset)) interval = source.read_hash();
BC_ASSERT(!source || source.get_read_position() == count());
return source;
}
Expand All @@ -86,11 +86,11 @@ struct txs
BC_ASSERT(tx_fks.size() < power2<uint64_t>(to_bits(ct::size)));

const auto flag = interval.has_value();
const auto flaged = merge(flag, wire);
const auto flagged = merge(flag, wire);
const auto fks = possible_narrow_cast<ct::integer>(tx_fks.size());

sink.write_little_endian<ct::integer, ct::size>(fks);
sink.write_little_endian<bytes::integer, bytes::size>(flaged);
sink.write_little_endian<bytes::integer, bytes::size>(flagged);
std::for_each(tx_fks.begin(), tx_fks.end(),
[&](const auto& fk) NOEXCEPT
{
Expand Down Expand Up @@ -150,6 +150,28 @@ struct txs
hash interval{};
};

struct get_interval
: public schema::txs
{
inline link count() const NOEXCEPT
{
BC_ASSERT(false);
return {};
}

inline bool from_data(reader& source) NOEXCEPT
{
using namespace system;
const auto number = source.read_little_endian<ct::integer, ct::size>();
const auto flagged = source.read_little_endian<bytes::integer, bytes::size>();
source.skip_bytes(tx::size * number);
if (get_right(flagged, offset)) interval = source.read_hash();
return source;
}

hash interval{};
};

struct get_position
: public schema::txs
{
Expand Down
9 changes: 8 additions & 1 deletion src/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,14 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
{ txs_empty, "txs_empty" },
{ txs_height, "txs_height" },
{ txs_confirm, "txs_confirm" },
{ txs_txs_put, "txs_txs_put" }
{ txs_txs_put, "txs_txs_put" },

// optional
{ merkle_proof, "merkle_proof" },
{ merkle_interval, "merkle_interval" },
{ merkle_hashes, "merkle_hashes" },
{ merkle_arguments, "merkle_arguments" },
{ merkle_not_found, "merkle_not_found" }
};

DEFINE_ERROR_T_CATEGORY(error, "database", "database code")
Expand Down
Loading
Loading