@@ -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
236237TEMPLATE
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
252245TEMPLATE
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
274337TEMPLATE
275338error::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.
305376TEMPLATE
306377code 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
417492TEMPLATE
418493spend_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+
556645TEMPLATE
557646bool CLASS::is_strong_tx (const tx_link& link) const NOEXCEPT
558647{
0 commit comments