Skip to content

Commit

Permalink
Refactor block_confirmable, add comments regarding bip30 check.
Browse files Browse the repository at this point in the history
  • Loading branch information
evoskuil committed Apr 17, 2024
1 parent c71b802 commit a72c50b
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 73 deletions.
196 changes: 124 additions & 72 deletions include/bitcoin/database/impl/query/confirm.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -112,58 +112,7 @@ bool CLASS::is_spent_output(const output_link& link) const NOEXCEPT

// Confirmation.
// ----------------------------------------------------------------------------
// Block confirmed by height is not considered for confirmation (just strong).
// Transactions must be set strong before executing confirmation queries.

// protected
TEMPLATE
inline error::error_t CLASS::spent_prevout(const foreign_point& point,
const tx_link& self) const NOEXCEPT
{
auto it = store_.spend.it(point);
if (it.self().is_terminal())
return error::success;

table::spend::get_parent spend{};
do
{
// Iterated element must be found, otherwise fault.
if (!store_.spend.get(it.self(), spend))
return error::integrity;

// Skip the tx spend, which is the only one if not double spent.
if (spend.parent_fk == self)
continue;

// If strong spender exists then prevout is confirmed double spent.
if (!to_block(spend.parent_fk).is_terminal())
return error::confirmed_double_spend;
}
while (it.advance());
return error::success;
}

// protected
TEMPLATE
inline error::error_t CLASS::spendable_prevout(const tx_link& link,
uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT
{
context out{};
if (!get_context(out, to_block(link)))
return error::unconfirmed_spend;

// spend of a coinbase
if (is_coinbase(link) &&
!transaction::is_coinbase_mature(out.height, ctx.height))
return error::coinbase_maturity;

if (ctx.is_enabled(system::chain::flags::bip68_rule) &&
(version >= system::chain::relative_locktime_min_version) &&
input::is_locked(sequence, ctx.height, ctx.mtp, out.height, out.mtp))
return error::relative_time_locked;

return error::success;
}
// Block confirmed by height is not used for confirmation (just strong tx).

// unused
TEMPLATE
Expand Down Expand Up @@ -273,49 +222,152 @@ error::error_t CLASS::locked_prevout(const point_link& link, uint32_t sequence,
return error::success;
}

// protected
TEMPLATE
inline error::error_t CLASS::spent_prevout(const foreign_point& point,
const tx_link& self) const NOEXCEPT
{
auto it = store_.spend.it(point);
if (it.self().is_terminal())
return error::success;

table::spend::get_parent spend{};
do
{
if (!store_.spend.get(it.self(), spend))
return error::integrity;

// Skip current spend, which is the only one if not double spent.
if (spend.parent_fk == self)
continue;

// If strong spender exists then prevout is confirmed double spent.
if (!to_block(spend.parent_fk).is_terminal())
return error::confirmed_double_spend;
}
while (it.advance());
return error::success;
}

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

// 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.
context out{};
if (!get_context(out, to_block(spent_fk)))
return error::unconfirmed_spend;

if (is_coinbase(spent_fk) &&
!transaction::is_coinbase_mature(out.height, ctx.height))
return error::coinbase_maturity;

if (ctx.is_enabled(system::chain::flags::bip68_rule) &&
(version >= system::chain::relative_locktime_min_version) &&
input::is_locked(sequence, ctx.height, ctx.mtp, out.height, out.mtp))
return error::relative_time_locked;

return error::success;
}

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

auto cb = store_.tx.it(get_tx_key(link));
if (cb.self().is_terminal())
return error::integrity;

// Multiple may or may not share the same tx record (race condition),
// so must test each instance for confirmation and confirmed spend.
do
{
// is coinbase confirmed?
auto st = store_.strong_tx.it(cb.self());
if (st.self().is_terminal())
continue;

do
{
table::strong_tx::record strong{};
if (!store_.strong_tx.get(st.self(), strong))
return error::integrity;

// TODO: foreign_point will always be ambiguous. However spenders
// TODO: cannot be duplicates - only coinbases can be duplicated.
// TODO: So distinct spend records must have a unique parent_fk. So
// TODO: while the number of coinbases is arbitrary, the number of
// TODO: spenders of them with unique parent_fk alway reflects
// TODO: actual spends. So we can know how many unique spends of
// TODO: the duplicates exist - even if unique spending txs are
// TODO: duplicated (due to race), by collapsing on parent_fk. And
// TODO: since each block association includes a header_fk, we
// TODO: we can know how many unique confirmed coinbases exist by
// TODO: collapsing on strong header_fk. We don't care about
// TODO: unconfirmed coinbases or unconfirmed spenders. So, if
// TODO: there are as many unique confirmed spends of a cb as there
// TODO: are unique confirmed cb's, then they are all spent. If
// TODO: there are fewer there is an unspent duplicate, and if
// TODO: there are more it implies an integrity fault.
// TODO: So create methods to get unique confirmed spenders of
// TODO: tx_link and unique instances of block associations from
// TODO: tx_link. Then simply compare the counts.

// is non-self confirmed coinbase confirmed spent?
const auto ec = spent_prevout(foreign_point{}, link);
if (ec == error::confirmed_double_spend)
continue;

// success means confirmed coinbase is not confirmed spent.
return !ec ? error::unspent_coinbase_collision : ec;
}
while (st.advance());
}
while (cb.advance());
return error::success;
}

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

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

// TODO: incorporate bip30 check.
// TODO: this is complicated by possibility of redundant writes.
// TODO: so must associate all instances of the tx to (confirmed) blocks,
// TODO: and if not this block (!= link) must be spent by confirmed block.
// TODO: maturity ensures it's not spent in this block (or <= 100 blocks).
////if (ctx.is_enabled(system::chain::flags::bip30_rule))
////{
//// const auto cb = txs.front();
////}

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

uint32_t version{};
table::spend::get_prevout_parent_sequence spend{};
for (auto tx = std::next(txs.begin()); tx != txs.end(); ++tx)
{
// spender-tx(read) & puts(read).
for (const auto& spend_fk: to_tx_spends(version, *tx))
{
// spend(read).
if (!store_.spend.get(spend_fk, spend))
return error::integrity;

// point(read) & spent-tx(search)
const auto spent_fk = to_tx(get_point_key(spend.point_fk));

// spent-tx(read) & strong_tx(search/read) & spent-header(read).
if ((ec = spendable_prevout(spent_fk, spend.sequence, version, ctx)))
if ((ec = spendable_prevout(spend.point_fk, spend.sequence,
version, ctx)))
return ec;

// spend(search/read) & strong_tx(search/read).
if ((ec = spent_prevout(spend.prevout(), spend.parent_fk)))
return ec;
}
Expand Down
5 changes: 4 additions & 1 deletion include/bitcoin/database/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,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 tx_link& link,
inline error::error_t spendable_prevout(const point_link& link,
uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT;

/// context
Expand Down

0 comments on commit a72c50b

Please sign in to comment.