batchcanon: VTXO lineage availability derivation (C5 core) - #796
batchcanon: VTXO lineage availability derivation (C5 core)#796ellemouton wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the Availability type and its associated logic to derive and manage VTXO-lineage spendability based on parent batch states, along with comprehensive unit tests. The review feedback suggests optimizing LineageAvailability by deduplicating input transaction IDs to avoid redundant store queries and adding a defensive check for nil records. Additionally, it recommends caching the rank of the worst availability in CombineAvailability to prevent redundant function calls during iteration.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| func LineageAvailability(ctx context.Context, store Store, | ||
| batchTxids ...chainhash.Hash) (Availability, error) { | ||
|
|
||
| if len(batchTxids) == 0 { | ||
| return AvailabilityUnknown, nil | ||
| } | ||
|
|
||
| avails := make([]Availability, 0, len(batchTxids)) | ||
| for _, txid := range batchTxids { | ||
| record, err := store.GetBatch(ctx, txid) | ||
| switch { | ||
| case errors.Is(err, ErrBatchNotFound): | ||
| avails = append(avails, AvailabilityUnknown) | ||
|
|
||
| case err != nil: | ||
| return AvailabilityUnknown, err | ||
|
|
||
| default: | ||
| avails = append( | ||
| avails, AvailabilityForState(record.State), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| return CombineAvailability(avails...), nil | ||
| } |
There was a problem hiding this comment.
There are two potential issues in LineageAvailability:
- Redundant Store Queries: If
batchTxidscontains duplicate transaction IDs (e.g., if multiple inputs from the same batch are spent), the function will perform redundant store queries. Deduplicating the transaction IDs beforehand avoids unnecessary database I/O. - Defensive Programming: If a store implementation returns a
nilrecord with anilerror (e.g., in mock/test environments or due to an unexpected bug), accessingrecord.Statewill cause a panic. Adding a defensivenilcheck prevents this.
func LineageAvailability(ctx context.Context, store Store,
batchTxids ...chainhash.Hash) (Availability, error) {
if len(batchTxids) == 0 {
return AvailabilityUnknown, nil
}
seen := make(map[chainhash.Hash]struct{}, len(batchTxids))
avails := make([]Availability, 0, len(batchTxids))
for _, txid := range batchTxids {
if _, ok := seen[txid]; ok {
continue
}
seen[txid] = struct{}{}
record, err := store.GetBatch(ctx, txid)
switch {
case errors.Is(err, ErrBatchNotFound):
avails = append(avails, AvailabilityUnknown)
case err != nil:
return AvailabilityUnknown, err
case record == nil:
return AvailabilityUnknown, fmt.Errorf("store returned nil record for txid %s", txid)
default:
avails = append(
avails, AvailabilityForState(record.State),
)
}
}
return CombineAvailability(avails...), nil
}| func CombineAvailability(parents ...Availability) Availability { | ||
| if len(parents) == 0 { | ||
| return AvailabilityUnknown | ||
| } | ||
|
|
||
| worst := parents[0] | ||
| for _, p := range parents[1:] { | ||
| if availabilityRank(p) > availabilityRank(worst) { | ||
| worst = p | ||
| } | ||
| } | ||
|
|
||
| return worst | ||
| } |
There was a problem hiding this comment.
In CombineAvailability, availabilityRank(worst) is evaluated in every iteration of the loop, even though worst only changes occasionally. Since this function is called in performance-sensitive paths (such as VTXO selection), caching the rank of worst in a local variable avoids redundant function calls and improves efficiency.
| func CombineAvailability(parents ...Availability) Availability { | |
| if len(parents) == 0 { | |
| return AvailabilityUnknown | |
| } | |
| worst := parents[0] | |
| for _, p := range parents[1:] { | |
| if availabilityRank(p) > availabilityRank(worst) { | |
| worst = p | |
| } | |
| } | |
| return worst | |
| } | |
| func CombineAvailability(parents ...Availability) Availability { | |
| if len(parents) == 0 { | |
| return AvailabilityUnknown | |
| } | |
| worst := parents[0] | |
| worstRank := availabilityRank(worst) | |
| for _, p := range parents[1:] { | |
| pRank := availabilityRank(p) | |
| if pRank > worstRank { | |
| worst = p | |
| worstRank = pRank | |
| } | |
| } | |
| return worst | |
| } |
7389b71 to
2afb589
Compare
1de8612 to
9a99bae
Compare
9a99bae to
e3a9a64
Compare
95c13ec to
007743b
Compare
e3a9a64 to
3efe711
Compare
|
@ellemouton, remember to re-request review from reviewers when ready |
Squashed for the btcd v2 port. The batchcanon.Manager actor: one reorg-aware conf watch per batch + one spend watch per consumed input via chainsource, derives State by priority, recomputes effective expiry on reconfirm, and reconciles non-final watches on restart. No admission (that is C5).
007743b to
4f0707f
Compare
3efe711 to
a18c0a4
Compare
Squashed for the btcd v2 port. batchcanon Availability vocab (available_final/provisional/unknown, limbo_reorg/conflict, invalidated) + CombineAvailability + store-driven LineageBlocked, and the vtxo.Manager coin-selection/forfeit admission gate that drops candidates whose batch lineage is limbo/invalidated. Permissive for unseen/unregistered; no-op when the store is nil.
4f0707f to
212e4d0
Compare
a18c0a4 to
b0e7182
Compare
Summary
The reusable, fully-tested core of task C5 (darepo#454): derive per-VTXO lineage availability from batch canonicality
State, and the store-driven gate logic the VTXO manager's admission path will call. Stacked on #795 (C3/C4).What's here (pure, unit-tested)
Availabilityvocabulary (the arkrpc: validate ancestry tree_depth at indexer trust boundary (#370) #454 VTXO-lineage states, never persisted):available_final,available_provisional,available_unknown,limbo_reorg,limbo_conflict,invalidated.AvailabilityForStatemaps one batch'sState;CombineAvailabilitytakes the worst across a multi-parent lineage (a VTXO is only as available as its least-available parent — the AND a multi-input OOR VTXO needs);Usable()is true only for confirmed lineage.LineageAvailability/LineageBlockedload each parent batch from theStoreand produce the combined availability / block decision the manager calls per candidate. Deliberately permissive: unseen / not-yet-registered lineage does not block — onlylimbo_reorg/limbo_conflict/invalidateddoes — so the gate is safe to enable before every producer registers its batches.Why just the core (and what's next)
The actual wiring into
vtxo.Manager.selectAndReserveVTXOsis an invasive change to a performance-sensitive selection path (the selection projection deliberately omits ancestry, so it needs a projection/query change + multi-parent lineage resolution) and the expiry-as-terminal rewire touches the per-VTXO FSM transitions — both are best validated with the daemon/itest harness. This PR lands the correctness-critical derivation logic with full unit tests; the manager/FSM wiring is the next step (precise plan in the branch's.context/reorg-safety-remaining-execplan.md).🤖 Generated with Claude Code