Skip to content

Commit

Permalink
Merge pull request #507 from evoskuil/master
Browse files Browse the repository at this point in the history
Update populate() and complete populate_with_metadata().
  • Loading branch information
evoskuil committed Jul 2, 2024
2 parents 9083a2d + 243faa3 commit cd3eb15
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 35 deletions.
89 changes: 69 additions & 20 deletions include/bitcoin/database/impl/query/archive.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,13 @@ bool CLASS::set(const block& block, bool strong) NOEXCEPT
return !set_code(block, strong);
}

// populate

TEMPLATE
bool CLASS::populate(const input& input) const NOEXCEPT
{
if (input.prevout || input.point().is_null())
BC_ASSERT(!input.point().is_null());
if (input.prevout)
return true;

// input.metadata is not populated.
Expand All @@ -148,6 +151,8 @@ bool CLASS::populate(const input& input) const NOEXCEPT
TEMPLATE
bool CLASS::populate(const transaction& tx) const NOEXCEPT
{
BC_ASSERT(!tx->is_coinbase());

auto result = true;
const auto& ins = *tx.inputs_ptr();
std::for_each(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT
Expand All @@ -161,31 +166,45 @@ bool CLASS::populate(const transaction& tx) const NOEXCEPT
TEMPLATE
bool CLASS::populate(const block& block) const NOEXCEPT
{
const auto& txs = *block.transactions_ptr();
if (txs.empty())
return false;

auto result = true;
const auto ins = block.inputs_ptr();
std::for_each(ins->begin(), ins->end(), [&](const auto& in) NOEXCEPT
{
result &= populate(*in);
});
std::for_each(std::next(txs.begin()), txs.end(),
[&](const auto& tx) NOEXCEPT
{
const auto& ins = *tx->inputs_ptr();
std::for_each(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT
{
result &= populate(*in);
});
});

return result;
}

// populate_with_metadata

TEMPLATE
bool CLASS::populate_with_metadata(const input& input) const NOEXCEPT
bool CLASS::populate_with_metadata(const input& input,
const tx_link& link) const NOEXCEPT
{
if (input.prevout || input.point().is_null())
BC_ASSERT(!input.point().is_null());
if (input.prevout)
return true;

// Null point would return nullptr and be interpreted as missing.
// null point returns nullptr and is therefore interpreted as missing.
input.prevout = get_output(input.point());
if (is_null(input.prevout))
return false;

// tx of input point exists.
const auto tx = to_tx(input.point().hash());
if (tx.is_terminal())
return false;

// tx of input point is strong.
const auto block = to_block(tx);
if (block.is_terminal())
return false;
Expand All @@ -194,38 +213,68 @@ bool CLASS::populate_with_metadata(const input& input) const NOEXCEPT
if (!get_context(ctx, block))
return false;

// For testing only.
// Assumes previous coinbase is spent and prevouts of others are not.
input.metadata.coinbase = is_coinbase(tx);
input.metadata.spent = input.metadata.coinbase;
input.metadata.spent = is_spent_prevout(input.point(), link);
input.metadata.median_time_past = ctx.mtp;
input.metadata.height = ctx.height;
return true;
}


TEMPLATE
bool CLASS::populate_with_metadata(const transaction& tx) const NOEXCEPT
bool CLASS::populate_with_metadata(const transaction& tx,
const tx_link& link) const NOEXCEPT
{
auto result = true;
const auto& ins = *tx.inputs_ptr();
std::for_each(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT
{
result &= populate_with_metadata(*in);
result &= populate_with_metadata(*in, link);
});

return result;
}

TEMPLATE
bool CLASS::populate_with_metadata(const transaction& tx) const NOEXCEPT
{
BC_ASSERT(is_coinbase(tx));

// A coinbase tx is allowed only one input.
const auto& input = *tx.inputs_ptr()->front();

// Find any confirmed unspent duplicates of tx (unspent_coinbase_collision).
const auto ec = unspent_duplicates(tx);
if (ec == error::integrity)
return false;

// The prevout of a coinbase is null (not an output of a coinbase tx).
input.metadata.coinbase = false;
input.metadata.spent = (ec != error::unspent_coinbase_collision);
input.metadata.median_time_past = max_uint32;
input.metadata.height = zero;
return true;
}

TEMPLATE
bool CLASS::populate_with_metadata(const block& block) const NOEXCEPT
{
const auto& txs = *block.transactions_ptr();
if (txs.empty())
return false;

auto result = true;
const auto ins = block.inputs_ptr();
std::for_each(ins->begin(), ins->end(), [&](const auto& in) NOEXCEPT
{
result &= populate_with_metadata(*in);
});
const auto coinbase = populate_with_metadata(txs.front());
std::for_each(std::next(txs.begin()), txs.end(),
[&](const auto& tx) NOEXCEPT
{
const auto link = to_tx(tx.get_hash());
result &= !link.is_terminal();
const auto& ins = *tx->inputs_ptr();
std::for_each(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT
{
result &= populate_with_metadata(*in, link);
});
});

return result;
}
Expand Down
21 changes: 20 additions & 1 deletion include/bitcoin/database/impl/query/confirm.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,21 @@ TEMPLATE
error::error_t CLASS::unspendable_prevout(const point_link& link,
uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT
{
// utxo.find(spend.prevout()) no iteration or hash conversion.
// Read utxo => is_coinbase, header_link => ctx (height/mtp).
const auto strong = to_strong(get_point_key(link));

// utxo is strong if present.
if (strong.block.is_terminal())
return strong.tx.is_terminal() ? error::missing_previous_output :
error::unconfirmed_spend;

// utxo get context is still required.
context out{};
if (!get_context(out, strong.block))
return error::integrity;

// utxo.is_coinbase (is known).
if (is_coinbase(strong.tx) &&
!transaction::is_coinbase_mature(out.height, ctx.height))
return error::coinbase_maturity;
Expand Down Expand Up @@ -349,14 +354,28 @@ TEMPLATE
code CLASS::tx_confirmable(const tx_link& link,
const context& ctx) const NOEXCEPT
{
code ec{};
// utxo needs spend set for sequence and spend.prevout(), which is the non-
// iterating key to utxo table. This is the identifier of the point:index
// which is used to obtain the output for validation by
// tx.find(get_point_key(link)). However we don't need the output for
// confirmation, just a unique identifier for it. Yet the point:index (fp)
// is not unique, because here are many fps for any given output due to tx
// and therefore point table duplication. So the spend sets identify all of
// the spends of a given tx, but one tx may have a different point for the
// same output. So all outputs of the point must be searched for existence,
// which requires point and tx table traversal just as before. :<
const auto set = to_spend_set(link);

code ec{};
for (const auto& spend: set.spends)
{
// If utxo exists then it is strong (push own block first).
if ((ec = unspendable_prevout(spend.point_fk, spend.sequence,
set.version, ctx)))
return ec;

// This query goes away.
// If utxo exists then it is not spent (push own block first).
if (is_spent_prevout(spend.prevout(), link))
return error::confirmed_double_spend;
}
Expand Down
9 changes: 6 additions & 3 deletions include/bitcoin/database/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,17 @@ class query

/// False implies not fully populated, input.metadata is not populated.
bool populate(const input& input) const NOEXCEPT;
bool populate(const transaction& tx) const NOEXCEPT;
bool populate(const block& block) const NOEXCEPT;
bool populate(const transaction& tx) const NOEXCEPT;

/// For testing only.
/// False implies not fully populated, input.metadata is populated.
bool populate_with_metadata(const input& input) const NOEXCEPT;
bool populate_with_metadata(const transaction& tx) const NOEXCEPT;
bool populate_with_metadata(const block& block) const NOEXCEPT;
bool populate_with_metadata(const transaction& tx) const NOEXCEPT;
bool populate_with_metadata(const transaction& tx,
const tx_link& link) const NOEXCEPT;
bool populate_with_metadata(const input& input,
const tx_link& link) const NOEXCEPT;

/// Archival (surrogate-keyed).
/// -----------------------------------------------------------------------
Expand Down
27 changes: 16 additions & 11 deletions test/query/archive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,7 @@ BOOST_AUTO_TEST_CASE(query_archive__set_block_txs__get_block__expected)
BOOST_REQUIRE_EQUAL(hashes, test::genesis.transaction_hashes(false));
}

// First four blocks have only coinbase txs.
BOOST_AUTO_TEST_CASE(query_archive__populate__null_prevouts__true)
{
settings settings{};
Expand All @@ -773,23 +774,23 @@ BOOST_AUTO_TEST_CASE(query_archive__populate__null_prevouts__true)

system::chain::block copy{ test::genesis };
BOOST_REQUIRE(query.populate(copy));
BOOST_REQUIRE(query.populate(*test::genesis.transactions_ptr()->front()));
BOOST_REQUIRE(query.populate(*test::genesis.inputs_ptr()->front()));
////BOOST_REQUIRE(query.populate(*test::genesis.transactions_ptr()->front()));
////BOOST_REQUIRE(query.populate(*test::genesis.inputs_ptr()->front()));

system::chain::block copy1{ test::block1 };
BOOST_REQUIRE(query.populate(copy1));
BOOST_REQUIRE(query.populate(*test::block1.transactions_ptr()->front()));
BOOST_REQUIRE(query.populate(*test::block1.inputs_ptr()->front()));
////BOOST_REQUIRE(query.populate(*test::block1.transactions_ptr()->front()));
////BOOST_REQUIRE(query.populate(*test::block1.inputs_ptr()->front()));

system::chain::block copy2{ test::block2 };
BOOST_REQUIRE(query.populate(copy2));
BOOST_REQUIRE(query.populate(*test::block2.transactions_ptr()->front()));
BOOST_REQUIRE(query.populate(*test::block2.inputs_ptr()->front()));
////BOOST_REQUIRE(query.populate(*test::block2.transactions_ptr()->front()));
///BOOST_REQUIRE(query.populate(*test::block2.inputs_ptr()->front()));

system::chain::block copy3{ test::block3 };
BOOST_REQUIRE(query.populate(copy3));
BOOST_REQUIRE(query.populate(*test::block3.transactions_ptr()->front()));
BOOST_REQUIRE(query.populate(*test::block3.inputs_ptr()->front()));
////BOOST_REQUIRE(query.populate(*test::block3.transactions_ptr()->front()));
////BOOST_REQUIRE(query.populate(*test::block3.inputs_ptr()->front()));
}

BOOST_AUTO_TEST_CASE(query_archive__populate__partial_prevouts__false)
Expand All @@ -800,23 +801,27 @@ BOOST_AUTO_TEST_CASE(query_archive__populate__partial_prevouts__false)
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(events_handler));
BOOST_REQUIRE(query.initialize(test::genesis));
BOOST_REQUIRE(!query.set_link(test::block1a, test::context, false, false).is_terminal());
BOOST_REQUIRE(!query.set_link(test::block2a, test::context, false, false).is_terminal());
BOOST_REQUIRE(query.set(test::block1a, test::context, false, false));
BOOST_REQUIRE(query.set(test::block2a, test::context, false, false));
BOOST_REQUIRE(query.set(test::tx4));

system::chain::block copy1{ test::block1a };
BOOST_REQUIRE(!query.populate(copy1));

// Block populate treates first tx as null point.
BOOST_REQUIRE( query.populate(copy1));
BOOST_REQUIRE(!query.populate(*test::block1a.transactions_ptr()->front()));
BOOST_REQUIRE(!query.populate(*test::block1a.inputs_ptr()->front()));
BOOST_REQUIRE(!query.populate(*test::block1a.inputs_ptr()->back()));

// Block populate treates first tx as null point and other has missing prevouts.
system::chain::block copy2{ test::block2a };
BOOST_REQUIRE(!query.populate(copy2));
BOOST_REQUIRE( query.populate(*test::block2a.transactions_ptr()->front()));
BOOST_REQUIRE(!query.populate(*test::block2a.transactions_ptr()->back()));
BOOST_REQUIRE( query.populate(*test::block2a.inputs_ptr()->front()));
BOOST_REQUIRE(!query.populate(*test::block2a.inputs_ptr()->back()));

// Block populate treates first tx as null point and other has found prevouts.
BOOST_REQUIRE(query.populate(test::tx4));
BOOST_REQUIRE(query.populate(*test::tx4.inputs_ptr()->front()));
BOOST_REQUIRE(query.populate(*test::tx4.inputs_ptr()->back()));
Expand Down

0 comments on commit cd3eb15

Please sign in to comment.