Skip to content

Commit

Permalink
Merge pull request #439 from evoskuil/master
Browse files Browse the repository at this point in the history
Add bip30 queries, add strong.tx.positive field (bit).
  • Loading branch information
evoskuil committed Apr 19, 2024
2 parents db5cd35 + f4065e2 commit cef9d36
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 165 deletions.
98 changes: 61 additions & 37 deletions include/bitcoin/database/impl/query/confirm.ipp
Expand Up @@ -242,8 +242,8 @@ inline error::error_t CLASS::spent_prevout(const foreign_point& point,
continue;

// If strong spender exists then prevout is confirmed double spent.
// Since all spends are traversed, this is safe for duplicate txs.
// For each parent_fk (tx) a unique set of ordered strong_txs exist.
// Since all spends are traversed, and strength is distinct by tx-block
// link association, this is safe for duplicate txs.
if (!to_block(spend.parent_fk).is_terminal())
return error::confirmed_double_spend;
}
Expand All @@ -253,35 +253,27 @@ inline error::error_t CLASS::spent_prevout(const foreign_point& point,

// protected
TEMPLATE
inline error::error_t CLASS::spendable_prevout(const point_link& link,
inline error::error_t CLASS::unspendable_prevout(const point_link& link,
uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT
{
auto it = store_.tx.it(get_point_key(link));
if (it.self().is_terminal())
return error::missing_previous_output;

// Due to the natural key coupling between tx-spend and spent-tx, it is
// possible to associate a not strong tx when a strong tx exists. So must
// iterate over the tx set associated by the point hash. set/unset strong
// are always applied via the block-txs-tx links, so are consistent for any
// confirmed/unconfirmed block. to_block(tx) returns most recent archived.
// There is no material cost to this unless there are duplicates.
header_link block{};
do { block = to_block(it.self()); }
while (block.is_terminal() && it.advance());

// Because of this check (only) all txs in the block under evaluation (and
// all prior) must be set to strong. Otherwise txs in the same block will
// result in spend of an unconfirmed prevout, and short of scanning the
// current block txs there is no other way to know link's block context.
if (block.is_terminal())
return error::unconfirmed_spend;
// iterate over the tx set associated by the transaction hash (vs. link).
// A strong/not-strong association only affects the associated instances
// and is there definitive for a given tx-block tuple. Therefore if one
// instance is negative and another is positive, it implies a reorganized
// block (negative) and a strong block (positive). There may be multiple
// positive and/or negative, but one positive is sufficient here.
const auto strong = to_strong(get_point_key(link));
if (strong.block.is_terminal())
return strong.tx.is_terminal() ? error::missing_previous_output :
error::unconfirmed_spend;

context out{};
if (!get_context(out, block))
if (!get_context(out, strong.block))
return error::integrity;

if (is_coinbase(it.self()) &&
if (is_coinbase(strong.tx) &&
!transaction::is_coinbase_mature(out.height, ctx.height))
return error::coinbase_maturity;

Expand All @@ -294,35 +286,67 @@ inline error::error_t CLASS::spendable_prevout(const point_link& link,
}

TEMPLATE
inline error::error_t CLASS::unspent_coinbase(const tx_link&,
inline error::error_t CLASS::unspent_duplicates(const tx_link& link,
const context& ctx) const NOEXCEPT
{
using namespace table;
if (!ctx.is_enabled(system::chain::flags::bip30_rule))
return error::success;

// TODO: iterate over all distinct-block associated instances by tx.hash.
// TODO: sum net of confirmed and unconfirmed associations by block.
// TODO: this results in a per-block state for the transaction, independent
// TODO: of whether the tx instance is repeated or unique for any block.
// TODO: note that normally the top association *by tx.hash* is taken.
return error::success;
// Self is not a spender in this case.
constexpr auto self = tx_link::terminal;
const auto coinbases = to_strongs(get_tx_key(link));

// self should be strong but was not found (fault).
if (coinbases.empty())
return error::integrity;

// assuming it is strong only self was found (optimization).
if (is_one(coinbases.size()))
return error::success;

// All that are found must be confirmed spent except self.
size_t strong_unspent_coinbase_count{};
for (const auto& coinbase: coinbases)
{
// All outputs must be spent or the coinbase is unspent.
for (spend::pt::integer out{}; out < output_count(coinbase.tx); ++out)
{
// Could stop at two but very rare so condition is more costly.
if (!spent_prevout(spend::compose(coinbase.tx, out), self))
{
++strong_unspent_coinbase_count;
continue;
}
}
}

switch (strong_unspent_coinbase_count)
{
// self should be unspent (fault).
case zero: return error::integrity;

// only self is unspent.
case one: return error::success;

// at least one instance other than self is unspent.
default: return error::unspent_coinbase_collision;
}
}

TEMPLATE
code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
{
// header(rd).
context ctx{};
if (!get_context(ctx, link))
return error::integrity;

// txs(srch/rd).
const auto txs = to_txs(link);
if (txs.empty())
return error::success;

code ec{};
if ((ec = unspent_coinbase(txs.front(), ctx)))
if ((ec = unspent_duplicates(txs.front(), ctx)))
return ec;

uint32_t version{};
Expand All @@ -334,7 +358,7 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
if (!store_.spend.get(spend_fk, spend))
return error::integrity;

if ((ec = spendable_prevout(spend.point_fk, spend.sequence,
if ((ec = unspendable_prevout(spend.point_fk, spend.sequence,
version, ctx)))
return ec;

Expand All @@ -353,7 +377,7 @@ bool CLASS::set_strong(const header_link& link) NOEXCEPT
if (txs.empty())
return false;

const table::strong_tx::record strong{ {}, link };
const table::strong_tx::record strong{ {}, link, true };

// ========================================================================
const auto scope = store_.get_transactor();
Expand All @@ -372,7 +396,7 @@ bool CLASS::set_unstrong(const header_link& link) NOEXCEPT
if (txs.empty())
return false;

const table::strong_tx::record strong{ {}, header_link::terminal };
const table::strong_tx::record strong{ {}, link, false };

// ========================================================================
const auto scope = store_.get_transactor();
Expand Down Expand Up @@ -489,7 +513,7 @@ bool CLASS::pop_confirmed() NOEXCEPT
//// // sequence value tells which is needed, height or mtp (bip68).
//// // but both may apply, so need to allow for two (no flags at prevout).
//// const context ctx{ 0, point.height, point.mtp };
//// if ((ec = spendable_prevout(point.tx, point.coinbase, point.sequence, ctx)))
//// if ((ec = unspendable_prevout(point.tx, point.coinbase, point.sequence, ctx)))
//// return ec;
////
//// // may only be strong-spent by self (and must be but is not checked).
Expand Down
103 changes: 96 additions & 7 deletions include/bitcoin/database/impl/query/translate.ipp
Expand Up @@ -21,6 +21,7 @@

#include <algorithm>
#include <iterator>
#include <unordered_map>
#include <utility>
#include <bitcoin/system.hpp>
#include <bitcoin/database/define.hpp>
Expand Down Expand Up @@ -174,25 +175,113 @@ output_link CLASS::to_prevout(const spend_link& link) const NOEXCEPT
// block/tx to block (reverse navigation)
// ----------------------------------------------------------------------------

TEMPLATE
header_link CLASS::to_parent(const header_link& link) const NOEXCEPT
{
table::header::get_parent_fk header{};
if (!store_.header.get(link, header))
return {};

// Terminal implies genesis (no parent).
return header.parent_fk;
}

// The block of a strong block-tx association.
TEMPLATE
header_link CLASS::to_block(const tx_link& link) const NOEXCEPT
{
table::strong_tx::record strong{};
if (!store_.strong_tx.get(store_.strong_tx.first(link), strong))
return {};

return strong.header_fk;
// Terminal implies not strong.
return strong.positive ? strong.header_fk : header_link::terminal;
}

// protected
// The first block-tx tuple where the tx is strong by the block.
// If there are no associations the link of the first tx by hash is returned,
// which is an optimization to prevent requery to determine tx existence.
TEMPLATE
header_link CLASS::to_parent(const header_link& link) const NOEXCEPT
inline strong_pair CLASS::to_strong(const hash_digest& tx_hash) const NOEXCEPT
{
table::header::get_parent_fk header{};
if (!store_.header.get(link, header))
auto it = store_.tx.it(tx_hash);
strong_pair strong{ {}, it.self() };
do
{
const auto link = to_block(it.self());
if (!link.is_terminal())
return { link, it.self() };
}
while (it.advance());
return strong;
}

// protected
// This is required for bip30 processing.
// The distinct set of block-tx tuples where the tx is strong by the block.
TEMPLATE
inline strong_pairs CLASS::to_strongs(const hash_digest& tx_hash) const NOEXCEPT
{
// Each it.self() is a unique link to a tx instance with tx_hash.
// Duplicate tx instances with the same hash result from a write race.
// It is possible that one tx instance is strong by distinct blocks, but it
// is not possible that two tx instances are both strong by the same block.
auto it = store_.tx.it(tx_hash);
strong_pairs strongs{};
do
{
// clang emplace_back bug (no matching constructor), using push_back.
for (const auto& link: to_blocks(it.self()))
strongs.push_back({ link, it.self() });
}
while (it.advance());
return strongs;
}

// protected
// This is required for bip30 processing.
// A single tx.link may be associated to multiple blocks (see bip30). But the
// top of the strong_tx table will reflect the current state of only one block
// association. This scans the multimap for the first instance of each block
// that is associated by the tx.link and returns that set of block links.
TEMPLATE
inline header_links CLASS::to_blocks(const tx_link& link) const NOEXCEPT
{
using record = table::strong_tx::record;
using records = std::vector<record>;
const auto contains = [](const records& items, const record& item) NOEXCEPT
{
return std::any_of(items.begin(), items.end(), [&](const record& it)
{
return it.header_fk == item.header_fk;
});
};

auto it = store_.strong_tx.it(link);
if (it.self().is_terminal())
return {};

// Terminal implies genesis (no parent).
return header.parent_fk;
records strongs{};
do
{
record strong{};
if (!store_.strong_tx.get(it.self(), strong))
return {};

// Retain only the first record for each block, strong or weak.
if (!contains(strongs, strong))
strongs.push_back(strong);
}
while(it.advance());

// Return just the block links of the strong associations.
header_links blocks{};
for (const auto& strong: strongs)
if (strong.positive)
blocks.push_back(strong.header_fk);

return blocks;
}

// output to spenders (reverse navigation)
Expand Down Expand Up @@ -321,7 +410,7 @@ spend_links CLASS::to_tx_spends(const tx_link& link) const NOEXCEPT
return std::move(puts.spend_fks);
}

// used in optimized block_confirmable
// protected
TEMPLATE
spend_links CLASS::to_tx_spends(uint32_t& version,
const tx_link& link) const NOEXCEPT
Expand Down
23 changes: 15 additions & 8 deletions include/bitcoin/database/query.hpp
Expand Up @@ -40,12 +40,17 @@ using point_link = table::point::link;
using spend_link = table::spend::link;
using txs_link = table::txs::link;
using tx_link = table::transaction::link;

using header_links = std_vector<header_link::integer>;
using tx_links = std_vector<tx_link::integer>;
using spend_links = std_vector<spend_link::integer>;
using input_links = std_vector<input_link::integer>;
using output_links = std_vector<output_link::integer>;

using foreign_point = table::spend::search_key;
using two_counts = std::pair<size_t, size_t>;
struct strong_pair { header_link block; tx_link tx; };
using strong_pairs = std_vector<strong_pair>;

template <typename Store>
class query
Expand Down Expand Up @@ -163,9 +168,9 @@ class query
output_link to_output(const tx_link& link, uint32_t output_index) const NOEXCEPT;
output_link to_prevout(const spend_link& link) const NOEXCEPT;

/// block/tx to block (reverse navigation)
header_link to_block(const tx_link& link) const NOEXCEPT;
/// block/tx to block/s (reverse navigation)
header_link to_parent(const header_link& link) const NOEXCEPT;
header_link to_block(const tx_link& link) const NOEXCEPT;

/// output to spenders (reverse navigation)
spend_links to_spenders(const point& prevout) const NOEXCEPT;
Expand All @@ -177,8 +182,6 @@ class query
/// tx to puts (forward navigation)
output_links to_tx_outputs(const tx_link& link) const NOEXCEPT;
spend_links to_tx_spends(const tx_link& link) const NOEXCEPT;
spend_links to_tx_spends(uint32_t& version,
const tx_link& link) const NOEXCEPT;

/// block to txs/puts (forward navigation)
tx_links to_txs(const header_link& link) const NOEXCEPT;
Expand Down Expand Up @@ -374,12 +377,17 @@ class query
protected:
/// Translate.
/// -----------------------------------------------------------------------
inline header_links to_blocks(const tx_link& link) const NOEXCEPT;
inline strong_pair to_strong(const hash_digest& tx_hash) const NOEXCEPT;
inline strong_pairs to_strongs(const hash_digest& tx_hash) const NOEXCEPT;
uint32_t to_spend_index(const tx_link& parent_fk,
const spend_link& input_fk) const NOEXCEPT;
uint32_t to_output_index(const tx_link& parent_fk,
const output_link& output_fk) const NOEXCEPT;
spend_link to_spender(const tx_link& link,
const foreign_point& point) const NOEXCEPT;
spend_links to_tx_spends(uint32_t& version,
const tx_link& link) const NOEXCEPT;

/// Archival
/// -----------------------------------------------------------------------
Expand All @@ -403,13 +411,12 @@ class query
const context& ctx) const NOEXCEPT;

// Critical path

inline error::error_t unspent_coinbase(const tx_link& link,
const context& ctx) const NOEXCEPT;
inline error::error_t spent_prevout(const foreign_point& point,
const tx_link& self) const NOEXCEPT;
inline error::error_t spendable_prevout(const point_link& link,
inline error::error_t unspendable_prevout(const point_link& link,
uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT;
inline error::error_t unspent_duplicates(const tx_link& link,
const context& ctx) const NOEXCEPT;

/// context
/// -----------------------------------------------------------------------
Expand Down

0 comments on commit cef9d36

Please sign in to comment.