Skip to content

Commit d89f5b9

Browse files
authored
Merge pull request #526 from evoskuil/master
Confirmation optimizations, comments, dup tx fix.
2 parents fb87d38 + 6e8a6a5 commit d89f5b9

File tree

5 files changed

+214
-94
lines changed

5 files changed

+214
-94
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,14 @@ bool CLASS::populate(const input& input) const NOEXCEPT
142142
if (input.prevout)
143143
return true;
144144

145+
const auto tx = to_tx(input.point().hash());
146+
input.prevout = get_output(tx, input.point().index());
147+
input.metadata.coinbase = is_coinbase(tx);
148+
input.metadata.parent = tx;
149+
145150
// input.metadata is not populated.
146151
// Null point would return nullptr and be interpreted as missing.
147-
input.prevout = get_output(input.point());
152+
////input.prevout = get_output(input.point());
148153
return !is_null(input.prevout);
149154
}
150155

@@ -213,8 +218,11 @@ bool CLASS::populate_with_metadata(const input& input,
213218
if (!get_context(ctx, block))
214219
return false;
215220

221+
const auto point_fk = to_point(input.point().hash());
222+
const auto point_index = input.point().index();
223+
216224
input.metadata.coinbase = is_coinbase(tx);
217-
input.metadata.spent = is_spent_prevout(input.point(), link);
225+
input.metadata.spent = is_spent_prevout(point_fk, point_index, link);
218226
input.metadata.median_time_past = ctx.mtp;
219227
input.metadata.height = ctx.height;
220228
return true;

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

Lines changed: 137 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ bool CLASS::is_spent(const spend_link& link) const NOEXCEPT
135135
if (spend.is_null())
136136
return false;
137137

138-
return is_spent_prevout(spend.prevout(), spend.parent_fk);
138+
return is_spent_prevout(spend.point_fk, spend.point_index,
139+
spend.parent_fk);
139140
}
140141

141142
// unused
@@ -234,63 +235,130 @@ code CLASS::locked_prevout(const point_link& link, uint32_t sequence,
234235

235236
// protected
236237
TEMPLATE
237-
bool CLASS::is_spent_prevout(const tx_link& link, index index) const NOEXCEPT
238-
{
239-
const auto fp = table::spend::compose(link, index);
240-
return is_spent_prevout(fp, tx_link::terminal);
241-
}
242-
243-
// protected
244-
TEMPLATE
245-
bool CLASS::is_spent_prevout(const foreign_point& point,
238+
bool CLASS::is_spent_prevout(const point_link& link, index index,
246239
const tx_link& self) const NOEXCEPT
247240
{
248-
return spent_prevout(point, self) != error::success;
241+
return spent_prevout(link, index, self) != error::success;
249242
}
250243

251244
// protected
252245
TEMPLATE
253-
error::error_t CLASS::spent_prevout(const foreign_point& point,
246+
error::error_t CLASS::spent_prevout(const point_link& link, index index,
254247
const tx_link& self) const NOEXCEPT
255248
{
256-
auto it = store_.spend.it(point);
257-
if (!it)
258-
return error::success;
249+
// TODO: get_point_key(link) is redundant with unspendable_prevout().
250+
// searches [point.iterate {new} x (spend.iterate + strong_tx.find)].
251+
252+
// The search for spenders must be exhaustive.
253+
// This is walking the full conflict list for the hash, but there is only
254+
// one match possible (self) unless there are duplicates/conflicts.
255+
// Conflicts here are both likely tx pool conflicts and rare duplicate txs,
256+
// since the points table is written for each spend (unless compressed and
257+
// that is still not a guarantee. So all must be checked. This holds one
258+
// instance of a tx for ***all spends of all outputs*** of that tx.
259+
260+
// TODO: evaluate.
261+
// If point hash was in spend table key there would be just as many but the
262+
// key would be the hash and index combined, resulting in no unnecessary
263+
// point hash searches over irrelevant point indexes. Would save some space
264+
// in table compression, and simplify some code, but would eliminate store
265+
// compression and might increase paging cost due to spend table increase.
266+
// There would be only one search unless duplicates, and this would be self
267+
// so would not result in calling the is_strong_tx search. Spenders of outs
268+
// of the same prevout.tx would not result in search hits. Table no-hash
269+
// algorithm would require definition. This would eliminate spend.point_fk
270+
// and a point.pk link per record, or 8 bytes per spend. This is the amount
271+
// to be added by the new array cache table, maybe just repurpose point.
272+
// Because cache can be removed this is a 19GB reduction, for the loss of
273+
// ability to reduce 50GB, which we don't generally do. So also a win on
274+
// nominal store size. All we need from the cache is the spend.pk/index.
275+
// spend.pk size does not change because it's an array (count unchanged).
276+
// So this is a reduction from plan, 4+3 bytes per cache row vs. 5+3.
277+
// But if we hold the spend.pk/prevout.tx we can just read the
278+
// spend.hash/index, so we don't need to store the index, and we need to
279+
// read the spend.hash anyway, so index is free (no paging). So that's now
280+
// just spend[4] + tx[4], back to 8 bytes (19GB).
281+
282+
// Iterate points by point hash (of output tx) because may be conflicts.
283+
auto point = store_.point.it(get_point_key(link));
284+
if (!point)
285+
return error::integrity;
259286

260-
table::spend::get_parent spend{};
261287
do
262288
{
263-
if (!store_.spend.get(it, spend))
264-
return error::integrity;
289+
// Iterate all spends of the point to find double spends.
290+
auto it = store_.spend.it(table::spend::compose(point.self(), index));
291+
if (!it)
292+
return error::success;
265293

266-
if ((spend.parent_fk != self) && is_strong_tx(spend.parent_fk))
267-
return error::confirmed_double_spend;
294+
table::spend::get_parent spend{};
295+
do
296+
{
297+
if (!store_.spend.get(it, spend))
298+
return error::integrity;
299+
300+
// is_strong_tx (search) only called in the case of duplicate.
301+
// Other parent tx of spend is strong (confirmed spent prevout).
302+
if ((spend.parent_fk != self) && is_strong_tx(spend.parent_fk))
303+
return error::confirmed_double_spend;
304+
}
305+
while (it.advance());
268306
}
269-
while (it.advance());
307+
while (point.advance());
270308
return error::success;
271309
}
272310

311+
// Low cost.
312+
// header_link
313+
// header_link.ctx.mtp
314+
// header_link.ctx.flags
315+
// header_link.ctx.height
316+
// header_link:txs.tx.pk
317+
// header_link:txs.tx.version
318+
319+
// unspendable_prevout
320+
// Given that these use the same txs association, there is no way for the
321+
// header.txs.tx to change, and it is only ever this pk that is set strong.
322+
// If unconfirmed_spend is encountered, perform a search (free). It's not
323+
// possible for a confirmed spend to be the wrong tx instance.
324+
//
325+
// There is no strong (prevout->tx->block) association at this point in validation.
326+
// strong_tx is interrogated for each spend except self (0) and each prevout (2.6B).
327+
// to_tx(get_point_key(header_link:txs.tx.puts.spend.point_fk)):block.ctx.height|mtp
328+
// This is done in populate, except for to_strong, ***so save prevout tx [4]***
329+
//
330+
// is_coinbase_mature(is_coinbase(header_link:txs.tx), ...block.ctx.height), is_locked
331+
// is_locked(header_link:txs.tx.puts.spend.sequence, ...block.ctx.height|mtp)
332+
333+
// spent_prevout (see notes in fn).
334+
// header_link:txs.tx.puts.spend.point_index
335+
273336
// protected
274337
TEMPLATE
275338
error::error_t CLASS::unspendable_prevout(const point_link& link,
276339
uint32_t sequence, uint32_t version, const context& ctx) const NOEXCEPT
277340
{
278-
// utxo.find(spend.prevout()) no iteration or hash conversion.
279-
// Read utxo => is_coinbase, header_link => ctx (height/mtp).
280-
const auto strong = to_strong(get_point_key(link));
341+
// TODO: If unconfirmed_spend is encountered, perform a search (free).
342+
// It's not possible for a confirmed spend to be the wrong tx instance.
343+
// This eliminates the hash lookup and to_strong(hash) iteration.
344+
345+
// TODO: don't need to return tx link here, just the block (for strong/context).
346+
// MOOT: get_point_key(link) is redundant with spent_prevout().
347+
// to_strong has the only searches [tx.iterate, strong.find].
348+
const auto strong_prevout = to_strong(get_point_key(link));
281349

282-
// utxo is strong if present.
283-
if (strong.block.is_terminal())
284-
return strong.tx.is_terminal() ? error::missing_previous_output :
285-
error::unconfirmed_spend;
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;
286354

287-
// utxo get context is still required.
288355
context out{};
289-
if (!get_context(out, strong.block))
356+
if (!get_context(out, strong_prevout.block))
290357
return error::integrity;
291358

292-
// utxo.is_coinbase (is known).
293-
if (is_coinbase(strong.tx) &&
359+
// All txs with same hash must be coinbase or not, so this query is redundant.
360+
// TODO: Just use the cached value for the prevout, obtained in validation.
361+
if (is_coinbase(strong_prevout.tx) &&
294362
!transaction::is_coinbase_mature(out.height, ctx.height))
295363
return error::coinbase_maturity;
296364

@@ -302,30 +370,38 @@ error::error_t CLASS::unspendable_prevout(const point_link& link,
302370
return error::success;
303371
}
304372

373+
// Duplicate tx instances (with the same hash) may result from a write race.
374+
// Duplicate cb tx instances are allowed by consensus. Apart from two bip30
375+
// exceptions, duplicate cb txs are allowed only if previous are fully spent.
305376
TEMPLATE
306377
code CLASS::unspent_duplicates(const header_link& link,
307378
const context& ctx) const NOEXCEPT
308379
{
380+
// This is generally going to be disabled.
309381
if (!ctx.is_enabled(system::chain::flags::bip30_rule))
310382
return error::success;
311383

312-
// This will be empty if current block is not set_strong.
313-
const auto coinbases = to_strong_txs(get_tx_key(to_coinbase(link)));
384+
// [txs.find, {tx.iterate}, strong_tx.it]
385+
auto coinbases = to_strong_txs(get_tx_key(to_coinbase(link)));
314386

387+
// Found only this block's coinbase instance, no duplicates.
315388
if (is_one(coinbases.size()))
316389
return error::success;
317390

318-
if (coinbases.empty())
391+
// Remove self (will be not found if current block is not set_strong).
392+
const auto self = std::find(coinbases.begin(), coinbases.end(), link);
393+
if (self == coinbases.end() || coinbases.erase(self) == coinbases.end())
319394
return error::integrity;
320395

321-
// bip30: all (but self) must be confirmed spent or dup invalid (cb only).
322-
size_t unspent{};
323-
for (const auto& tx: coinbases)
324-
for (index out{}; out < output_count(tx); ++out)
325-
if (!is_spent_prevout(tx, out) && is_one(unspent++))
326-
return error::unspent_coinbase_collision;
396+
// [point.first, is_spent_prevout()]
397+
const auto spent = [this](const auto& tx) NOEXCEPT
398+
{
399+
return is_spent_coinbase(tx);
400+
};
327401

328-
return is_zero(unspent) ? error::integrity : error::success;
402+
// bip30: all outputs of all previous duplicate coinbases must be spent.
403+
return std::all_of(coinbases.begin(), coinbases.end(), spent) ?
404+
error::success : error::unspent_coinbase_collision;
329405
}
330406

331407
#if defined(UNDEFINED)
@@ -379,7 +455,7 @@ code CLASS::tx_confirmable(const tx_link& link,
379455

380456
// This query goes away.
381457
// If utxo exists then it is not spent (push own block first).
382-
if (is_spent_prevout(spend.prevout(), link))
458+
if (is_spent_prevout(spend.point_fk, spend.point_index, link))
383459
return error::confirmed_double_spend;
384460
}
385461

@@ -412,12 +488,12 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
412488

413489
#endif
414490

415-
416491
// protected
417492
TEMPLATE
418493
spend_sets CLASS::to_spend_sets(const header_link& link) const NOEXCEPT
419494
{
420-
// Coinbase tx does not spend.
495+
// This is the only search [txs.find].
496+
// Coinbase tx does not spend so is not retrieved.
421497
const auto txs = to_spending_transactions(link);
422498

423499
if (txs.empty())
@@ -443,10 +519,12 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
443519
if (!get_context(ctx, link))
444520
return error::integrity;
445521

522+
// This is never invoked (bip30).
446523
code ec{};
447524
if ((ec = unspent_duplicates(link, ctx)))
448525
return ec;
449526

527+
// This is eliminated by caching, since each non-internal spend is cached.
450528
const auto sets = to_spend_sets(link);
451529
if (sets.empty())
452530
return ec;
@@ -471,7 +549,7 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
471549
{
472550
error::error_t ec{};
473551
for (const auto& spend: set.spends)
474-
if ((ec = spent_prevout(spend.prevout(), set.tx)))
552+
if ((ec = spent_prevout(spend.point_fk, spend.point_index, set.tx)))
475553
{
476554
fault.store(ec);
477555
return true;
@@ -514,7 +592,7 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
514592
set.version, ctx)))
515593
return ec;
516594

517-
if (is_spent_prevout(spend.prevout(), set.tx))
595+
if (is_spent_prevout(spend.point_fk, spend.point_index, set.tx))
518596
return error::confirmed_double_spend;
519597
}
520598
}
@@ -545,14 +623,25 @@ code CLASS::block_confirmable(const header_link& link) const NOEXCEPT
545623

546624
for (const auto& set: sets)
547625
for (const auto& spend: set.spends)
548-
if (is_spent_prevout(spend.prevout(), set.tx))
626+
if (is_spent_prevout(spend.point_fk, spend.point_index, set.tx))
549627
return error::confirmed_double_spend;
550628

551629
return ec;
552630
}
553631

554632
#endif // DISABLED
555633

634+
TEMPLATE
635+
bool CLASS::is_spent_coinbase(const tx_link& link) const NOEXCEPT
636+
{
637+
const auto point_fk = to_point(get_tx_key(link));
638+
for (index index{}; index < output_count(link); ++index)
639+
if (!is_spent_prevout(point_fk, index))
640+
return false;
641+
642+
return true;
643+
}
644+
556645
TEMPLATE
557646
bool CLASS::is_strong_tx(const tx_link& link) const NOEXCEPT
558647
{

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

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -218,30 +218,28 @@ TEMPLATE
218218
inline strong_pair CLASS::to_strong(const hash_digest& tx_hash) const NOEXCEPT
219219
{
220220
// Iteration of tx is necessary because there may be duplicates.
221-
// Only top block (strong) association for given tx instance is considered.
222221
auto it = store_.tx.it(tx_hash);
223222
strong_pair strong{ {}, it.self() };
224223
if (!it)
225224
return strong;
226225

227226
do
228227
{
229-
strong.tx = it.self();
228+
// Only top block (strong) association for given tx is considered.
230229
strong.block = to_block(strong.tx);
231230
if (!strong.block.is_terminal())
231+
{
232+
strong.tx = it.self();
232233
return strong;
234+
}
233235
}
234236
while (it.advance());
235237
return strong;
236238
}
237239

238240
// protected
239241
// Required for bip30 processing.
240-
// Each it.self() is a unique link to a tx instance with tx_hash.
241-
// Duplicate tx instances with the same hash result from a write race.
242-
// It is possible that one tx instance is strong by distinct blocks, but it
243-
// is not possible that two tx instances are both strong by the same block.
244-
// Return the distinct set of block-tx tuples where tx is strong by block.
242+
// Return distinct set of txs by link for hash where each is strong by block.
245243
TEMPLATE
246244
inline tx_links CLASS::to_strong_txs(const hash_digest& tx_hash) const NOEXCEPT
247245
{
@@ -261,19 +259,18 @@ inline tx_links CLASS::to_strong_txs(const hash_digest& tx_hash) const NOEXCEPT
261259

262260
// protected
263261
// Required for bip30 processing.
264-
// A single tx.link may be associated to multiple blocks (see bip30). But the
265-
// top of the strong_tx table will reflect the current state of only one block
266-
// association. This scans the multimap for the first instance of each block
267-
// that is associated by the tx.link and returns that set of block links.
268-
// Return the distinct set of tx links where each tx is strong by block.
262+
// The top of the strong_tx table will reflect the current state of only one
263+
// block association. This scans the multimap for the first instance of each
264+
// block that is associated by the tx.link and returns that set of block links.
265+
// Return distinct set of txs by link where each is strong by block.
269266
TEMPLATE
270267
inline tx_links CLASS::to_strong_txs(const tx_link& link) const NOEXCEPT
271268
{
272269
auto it = store_.strong_tx.it(link);
273270
if (!it)
274271
return {};
275272

276-
// Obtain all first (by block) duplicate (by hash) tx records.
273+
// Obtain all first (by block) duplicate (by link) tx records.
277274
maybe_strongs pairs{};
278275
do
279276
{

0 commit comments

Comments
 (0)