Skip to content

Commit c9d990d

Browse files
authored
Merge pull request #541 from evoskuil/master
Updates to prevouts table/queries, impl set_filter_head(link).
2 parents e576fe3 + f65372b commit c9d990d

File tree

8 files changed

+124
-62
lines changed

8 files changed

+124
-62
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ build
2929

3030
*.vsidx
3131
*.lock
32+
/.vs

include/bitcoin/database/impl/query/confirm.ipp

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -338,31 +338,31 @@ error::error_t CLASS::spent_prevout(const point_link& link, index index,
338338

339339
// protected
340340
TEMPLATE
341-
error::error_t CLASS::unspendable_prevout(const point_link& link,
342-
uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT
341+
error::error_t CLASS::unspendable_prevout(uint32_t sequence, bool coinbase,
342+
const tx_link& prevout_tx, uint32_t version,
343+
const context& ctx) const NOEXCEPT
343344
{
344-
// TODO: If unconfirmed_spend is encountered, perform a search (free).
345-
// It's not possible for a confirmed spend to be the wrong tx instance.
346-
// This eliminates the hash lookup and to_strong(hash) iteration.
347-
348345
// TODO: don't need to return tx link here, just the block (for strong/context).
349346
// MOOT: get_point_key(link) is redundant with spent_prevout().
350347
// to_strong has the only searches [tx.iterate, strong.find].
351-
const auto strong_prevout = to_strong(get_point_key(link));
348+
////const auto strong_prevout = to_strong(get_point_key(link));
349+
////
350+
////// prevout is strong if present.
351+
////if (strong_prevout.block.is_terminal())
352+
//// return strong_prevout.tx.is_terminal() ?
353+
//// error::missing_previous_output : error::unconfirmed_spend;
352354

353-
// prevout is strong if present.
354-
if (strong_prevout.block.is_terminal())
355-
return strong_prevout.tx.is_terminal() ?
356-
error::missing_previous_output : error::unconfirmed_spend;
355+
// TODO: If unconfirmed_spend is encountered, perform a search (free).
356+
// It's not possible for a confirmed spend to be the wrong tx instance.
357+
// This eliminates the hash lookup and to_strong(hash) iteration.
358+
const auto block = to_block(prevout_tx);
357359

358360
context out{};
359-
if (!get_context(out, strong_prevout.block))
361+
if (!get_context(out, block))
360362
return error::integrity;
361363

362-
// All txs with same hash must be coinbase or not, so this query is redundant.
363-
// TODO: Just use the cached value for the prevout, obtained in validation.
364-
if (is_coinbase(strong_prevout.tx) &&
365-
!transaction::is_coinbase_mature(out.height, ctx.height))
364+
// All txs with same hash must be coinbase or not.
365+
if (coinbase && !transaction::is_coinbase_mature(out.height, ctx.height))
366366
return error::coinbase_maturity;
367367

368368
if (ctx.is_enabled(system::chain::flags::bip68_rule) &&
@@ -387,15 +387,10 @@ code CLASS::unspent_duplicates(const header_link& link,
387387
// [txs.find, {tx.iterate}, strong_tx.it]
388388
auto coinbases = to_strong_txs(get_tx_key(to_coinbase(link)));
389389

390-
// Found only this block's coinbase instance, no duplicates.
391-
if (is_one(coinbases.size()))
390+
// Current block is not set strong.
391+
if (is_zero(coinbases.size()))
392392
return error::success;
393393

394-
// Remove self (will be not found if current block is not set_strong).
395-
const auto self = std::find(coinbases.begin(), coinbases.end(), link);
396-
if (self == coinbases.end() || coinbases.erase(self) == coinbases.end())
397-
return error::integrity;
398-
399394
// [point.first, is_spent_prevout()]
400395
const auto spent = [this](const auto& tx) NOEXCEPT
401396
{
@@ -502,16 +497,34 @@ spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT
502497
if (txs.empty())
503498
return {};
504499

505-
spend_sets out{ txs.size() };
500+
spend_sets sets{ txs.size() };
506501
const auto to_set = [this](const auto& tx) NOEXCEPT
507502
{
508503
return to_spend_set(tx);
509504
};
510505

511506
// C++17 incomplete on GCC/CLang, so presently parallel only on MSVC++.
512-
std_transform(bc::par_unseq, txs.begin(), txs.end(), out.begin(), to_set);
507+
std_transform(bc::par_unseq, txs.begin(), txs.end(), sets.begin(), to_set);
508+
509+
const auto count = [](size_t total, const auto& set) NOEXCEPT
510+
{
511+
return system::ceilinged_add(total, set.spends.size());
512+
};
513+
const auto spends = std::accumulate(sets.begin(), sets.end(), zero, count);
514+
515+
table::prevout::record_get prevouts{};
516+
prevouts.values.resize(spends);
517+
store_.prevout.at(link, prevouts);
513518

514-
return out;
519+
size_t index{};
520+
for (auto& set: sets)
521+
for (auto& spend: set.spends)
522+
{
523+
spend.coinbase = prevouts.coinbase(index);
524+
spend.prevout_tx_fk = prevouts.output_tx_fk(index++);
525+
}
526+
527+
return sets;
515528
}
516529

517530
// split(3) 219 secs for 400k-410k; split(2) 255 and split(0) 456 (not shown).
@@ -526,8 +539,7 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
526539
code ec{};
527540
if ((ec = unspent_duplicates(link, ctx)))
528541
return ec;
529-
530-
// This is eliminated by caching, since each non-internal spend is cached.
542+
531543
const auto sets = to_spend_sets(link);
532544
if (sets.empty())
533545
return ec;
@@ -536,27 +548,39 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
536548

537549
const auto is_unspendable = [this, &ctx, &fault](const auto& set) NOEXCEPT
538550
{
551+
// TODO: prevout table optimized, evaluate.
539552
error::error_t ec{};
540553
for (const auto& spend: set.spends)
541-
if ((ec = unspendable_prevout(spend.point_fk, spend.sequence,
542-
set.version, ctx)))
554+
{
555+
if (spend.prevout_tx_fk == table::prevout::tx::terminal)
556+
continue;
557+
558+
if ((ec = unspendable_prevout(spend.sequence, spend.coinbase,
559+
spend.prevout_tx_fk, set.version, ctx)))
543560
{
544561
fault.store(ec);
545562
return true;
546563
}
564+
}
547565

548566
return false;
549567
};
550568

551569
const auto is_spent = [this, &fault](const auto& set) NOEXCEPT
552570
{
571+
// TODO: point table optimize via consolidation with spend table.
553572
error::error_t ec{};
554-
for (const auto& spend: set.spends)
573+
for (const spend_set::spend& spend: set.spends)
574+
{
575+
if (spend.prevout_tx_fk == table::prevout::tx::terminal)
576+
continue;
577+
555578
if ((ec = spent_prevout(spend.point_fk, spend.point_index, set.tx)))
556579
{
557580
fault.store(ec);
558581
return true;
559582
}
583+
}
560584

561585
return false;
562586
};
@@ -708,7 +732,7 @@ bool CLASS::set_unstrong(const header_link& link) NOEXCEPT
708732
}
709733

710734
TEMPLATE
711-
bool CLASS::set_prevouts(size_t height, const block& block) NOEXCEPT
735+
bool CLASS::set_prevouts(const header_link& link, const block& block) NOEXCEPT
712736
{
713737
// Empty or coinbase only implies no spends.
714738
if (block.transactions() <= one)
@@ -719,7 +743,7 @@ bool CLASS::set_prevouts(size_t height, const block& block) NOEXCEPT
719743

720744
// Clean single allocation failure (e.g. disk full).
721745
const table::prevout::record_put_ref prevouts{ {}, block };
722-
return store_.prevout.put(height, prevouts);
746+
return store_.prevout.put(link, prevouts);
723747
// ========================================================================
724748
}
725749

include/bitcoin/database/impl/query/optional.ipp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,38 @@ bool CLASS::get_filter_head(hash_digest& out,
198198
return true;
199199
}
200200

201+
TEMPLATE
202+
bool CLASS::set_filter_head(const header_link& link) NOEXCEPT
203+
{
204+
if (!neutrino_enabled())
205+
return true;
206+
207+
// The filter body must have been previously stored under the block link.
208+
filter bytes{};
209+
if (!get_filter_body(bytes, link))
210+
return false;
211+
212+
// If genesis then previous is null_hash otherwise get by confirmed height.
213+
hash_digest previous{};
214+
const auto height = get_height(link);
215+
if (!is_zero(height))
216+
if (!get_filter_head(previous, to_confirmed(sub1(height))))
217+
return false;
218+
219+
// Use the previous head and current body to compute the current head.
220+
return set_filter_head(link,
221+
system::neutrino::compute_filter_header(previous, bytes));
222+
}
223+
201224
TEMPLATE
202225
bool CLASS::set_filter_body(const header_link& link,
203226
const block& block) NOEXCEPT
204227
{
205-
filter bytes{};
228+
if (!neutrino_enabled())
229+
return true;
206230

207-
// compute_filter is only false if prevouts are missing.
231+
// Compute the current filter from the block and store under the link.
232+
filter bytes{};
208233
return system::neutrino::compute_filter(bytes, block) &&
209234
set_filter_body(link, bytes);
210235
}

include/bitcoin/database/impl/query/translate.ipp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,9 +506,11 @@ spend_set CLASS::to_spend_set(const tx_link& link) const NOEXCEPT
506506
// Translate query to public struct.
507507
#if defined(HAVE_CLANG)
508508
// emplace_back aggregate initialization requires clang 16.
509-
set.spends.push_back({ get.point_fk, get.point_index, get.sequence });
509+
set.spends.push_back({ get.point_fk, get.point_index, get.sequence,
510+
table::prevout::tx::integer{}, bool{} });
510511
#else
511-
set.spends.emplace_back(get.point_fk, get.point_index, get.sequence);
512+
set.spends.emplace_back(get.point_fk, get.point_index, get.sequence,
513+
table::prevout::tx::integer{}, bool{});
512514
#endif
513515
}
514516

include/bitcoin/database/impl/query/validate.ipp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ code CLASS::get_block_state(const header_link& link) const NOEXCEPT
196196
if (!store_.validated_bk.find(link, valid))
197197
return is_associated(link) ? error::unvalidated : error::unassociated;
198198

199-
// Fees only valid if block_confirmable.
200199
return to_block_code(valid.code);
201200
}
202201

@@ -208,8 +207,9 @@ code CLASS::get_block_state(uint64_t& fees,
208207
if (!store_.validated_bk.find(link, valid))
209208
return is_associated(link) ? error::unvalidated : error::unassociated;
210209

211-
// Fees only valid if block_confirmable.
210+
// TODO: Fees only valid if block_valid is the current state (iterate for valid).
212211
fees = valid.fees;
212+
213213
return to_block_code(valid.code);
214214
}
215215

include/bitcoin/database/query.hpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,14 @@ struct spend_set
6767
return point_fk == table::spend::pt::terminal;
6868
}
6969

70+
// From tx input.
7071
table::spend::pt::integer point_fk{};
7172
table::spend::ix::integer point_index{};
7273
uint32_t sequence{};
74+
75+
// From prevouts table.
76+
table::prevout::tx::integer prevout_tx_fk{};
77+
bool coinbase{};
7378
};
7479

7580
tx_link tx{};
@@ -507,7 +512,7 @@ class query
507512
/// Block association relies on strong (confirmed or pending).
508513
bool set_strong(const header_link& link) NOEXCEPT;
509514
bool set_unstrong(const header_link& link) NOEXCEPT;
510-
bool set_prevouts(size_t height, const block& block) NOEXCEPT;
515+
bool set_prevouts(const header_link& link, const block& block) NOEXCEPT;
511516
code block_confirmable(const header_link& link) const NOEXCEPT;
512517
////code tx_confirmable(const tx_link& link, const context& ctx) const NOEXCEPT;
513518
code unspent_duplicates(const header_link& coinbase,
@@ -538,6 +543,7 @@ class query
538543
bool get_filter_head(hash_digest& out, const header_link& link) const NOEXCEPT;
539544
bool set_filter_body(const header_link& link, const block& block) NOEXCEPT;
540545
bool set_filter_body(const header_link& link, const filter& body) NOEXCEPT;
546+
bool set_filter_head(const header_link& link) NOEXCEPT;
541547
bool set_filter_head(const header_link& link,
542548
const hash_digest& head) NOEXCEPT;
543549

@@ -581,8 +587,8 @@ class query
581587
const tx_link& self=tx_link::terminal) const NOEXCEPT;
582588
error::error_t spent_prevout(const point_link& link, index index,
583589
const tx_link& self=tx_link::terminal) const NOEXCEPT;
584-
error::error_t unspendable_prevout(const point_link& link,
585-
uint32_t sequence, uint32_t version,
590+
error::error_t unspendable_prevout(uint32_t sequence, bool coinbase,
591+
const tx_link& prevout_tx, uint32_t version,
586592
const context& ctx) const NOEXCEPT;
587593
bool set_strong(const header_link& link, const tx_links& txs,
588594
bool positive) NOEXCEPT;

include/bitcoin/database/tables/caches/prevout.hpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,18 @@ namespace libbitcoin {
2828
namespace database {
2929
namespace table {
3030

31-
/// prevout is an array map index of previous outputs by block.
31+
/// prevout is an array map index of previous outputs by block link.
3232
/// The coinbase flag is merged into the tx field, reducing it's domain.
3333
/// Masking is from the right in order to accomodate non-integral domain.
3434
struct prevout
3535
: public array_map<schema::prevout>
3636
{
3737
using tx = linkage<schema::tx>;
38+
using header = linkage<schema::block>;
3839
using array_map<schema::prevout>::arraymap;
3940
static constexpr size_t offset = sub1(to_bits(tx::size));
4041

42+
// This supports only a single record (not too useful).
4143
struct record
4244
: public schema::prevout
4345
{
@@ -61,14 +63,14 @@ struct prevout
6163
inline bool from_data(reader& source) NOEXCEPT
6264
{
6365
value = source.read_little_endian<tx::integer, tx::size>();
64-
BC_ASSERT(!source || source.get_read_position() == minrow);
66+
BC_ASSERT(!source || source.get_read_position() == count() * minrow);
6567
return source;
6668
}
6769

6870
inline bool to_data(finalizer& sink) const NOEXCEPT
6971
{
7072
sink.write_little_endian<tx::integer, tx::size>(value);
71-
BC_ASSERT(!sink || sink.get_write_position() == minrow);
73+
BC_ASSERT(!sink || sink.get_write_position() == count() * minrow);
7274
return sink;
7375
}
7476

@@ -140,6 +142,10 @@ struct prevout
140142

141143
inline bool from_data(reader& source) NOEXCEPT
142144
{
145+
// TODO: Can be optimized using unsafe_array_cast copy, as long as
146+
// TODO: endianness lines up. Should have writer methods for native
147+
// TODO: endianness so that both big and little are optimal. But
148+
// TODO: this would prevent store portability across endianness.
143149
// Values must be set to read size (i.e. using knowledge of spends).
144150
std::for_each(values.begin(), values.end(), [&](auto& value) NOEXCEPT
145151
{

0 commit comments

Comments
 (0)