Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor confirm and comments. #440

Merged
merged 2 commits into from
Apr 20, 2024
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
48 changes: 9 additions & 39 deletions include/bitcoin/database/impl/query/confirm.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,6 @@ 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, 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 @@ -256,14 +254,6 @@ TEMPLATE
inline error::error_t CLASS::unspendable_prevout(const point_link& link,
uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT
{
// 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 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 :
Expand Down Expand Up @@ -293,45 +283,25 @@ inline error::error_t CLASS::unspent_duplicates(const tx_link& link,
if (!ctx.is_enabled(system::chain::flags::bip30_rule))
return error::success;

// Self is not a spender in this case.
constexpr auto self = tx_link::terminal;
// Self should be strong but was not identified.
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).
// 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{};
// All but one (self) must be confirmed spent or coinbase is unspent.
size_t strong_unspent{};
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;
}
}
}
if (!spent_prevout(spend::compose(coinbase.tx, out)) &&
is_one(strong_unspent++))
return error::unspent_coinbase_collision;

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;
}
// Only self should/must be unspent.
return is_zero(strong_unspent) ? error::integrity : error::success;
}

TEMPLATE
Expand Down
58 changes: 31 additions & 27 deletions include/bitcoin/database/impl/query/translate.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ output_link CLASS::to_prevout(const spend_link& link) const NOEXCEPT

// block/tx to block (reverse navigation)
// ----------------------------------------------------------------------------
// Required for confirmation processing.

TEMPLATE
header_link CLASS::to_parent(const header_link& link) const NOEXCEPT
Expand All @@ -186,22 +187,21 @@ header_link CLASS::to_parent(const header_link& link) const NOEXCEPT
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 {};

// Terminal implies not strong.
// Terminal implies not strong (false).
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.
// Return the first block-tx tuple where the tx is strong by the block.
TEMPLATE
inline strong_pair CLASS::to_strong(const hash_digest& tx_hash) const NOEXCEPT
{
Expand All @@ -218,15 +218,15 @@ inline strong_pair CLASS::to_strong(const hash_digest& tx_hash) const NOEXCEPT
}

// protected
// This is required for bip30 processing.
// The distinct set of block-tx tuples where the tx is strong by the block.
// Required for bip30 processing.
// 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.
// Return the distinct set of block-tx tuples where tx is strong by 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
Expand All @@ -240,42 +240,46 @@ inline strong_pairs CLASS::to_strongs(const hash_digest& tx_hash) const NOEXCEPT
}

// protected
// This is required for bip30 processing.
// 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.
// Return the distinct set of block/header links where tx is strong by block.
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 {};

records strongs{};
block_tx strong{};
block_txs 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 strong_only(strongs);
}

// Return just the block links of the strong associations.
// private/static
TEMPLATE
inline bool CLASS::contains(const block_txs& blocks,
const block_tx& block) NOEXCEPT
{
return std::any_of(blocks.begin(), blocks.end(),
[&block](const auto& it) NOEXCEPT
{
return it.header_fk == block.header_fk;
});
}

// private/static
TEMPLATE
inline header_links CLASS::strong_only(const block_txs& strongs) NOEXCEPT
{
header_links blocks{};
for (const auto& strong: strongs)
if (strong.positive)
Expand Down
17 changes: 13 additions & 4 deletions include/bitcoin/database/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,6 @@ 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,
Expand All @@ -389,6 +386,11 @@ class query
spend_links to_tx_spends(uint32_t& version,
const tx_link& link) const NOEXCEPT;

// Critical path
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;

/// Archival
/// -----------------------------------------------------------------------
point_link set_link_(const hash_digest& point_hash) NOEXCEPT;
Expand All @@ -412,7 +414,7 @@ class query

// Critical path
inline error::error_t spent_prevout(const foreign_point& point,
const tx_link& self) const NOEXCEPT;
const tx_link& self = tx_link::terminal) const NOEXCEPT;
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,
Expand Down Expand Up @@ -454,6 +456,13 @@ class query
const header_link& link, size_t height) const NOEXCEPT;

private:
// for to_blocks
using block_tx = table::strong_tx::record;
using block_txs = std::vector<block_tx>;
static inline header_links strong_only(const block_txs& strongs) NOEXCEPT;
static inline bool contains(const block_txs& blocks,
const block_tx& block) NOEXCEPT;

Store& store_;
};

Expand Down