fix(state): preserve shared cached UTXOs - #797
Conversation
evan-forbes
left a comment
There was a problem hiding this comment.
The provider-aware cache design looks correct. It preserves provider-specific UTXO data, removes one provider at a time, and keeps duplicate sent-block registration idempotent.
This PR now conflicts with main because #831 changed the same queued-block test file. Please restack the PR and preserve both sets of tests. The production changes from #831 merge automatically, but the test conflict needs a deliberate resolution. The retained #831 test also calls queue.known_utxos.is_empty(), while UtxoProviderCache currently exposes len() but not is_empty().
Please also add one regression that exercises both changes together. The test should remove a failed descendant subtree that contains one provider for an outpoint while a live block outside that subtree provides the same outpoint. It should verify the following behavior:
- Cleanup removes the providers from the failed subtree.
- Cleanup preserves the live provider's exact UTXO metadata.
- Cleanup removes the outpoint after the final provider leaves.
5d11308 to
fe1c20a
Compare
|
@evan-forbes, the requested changes are addressed in the restacked branch through
Issue #493 and the earlier review findings are also covered:
Verification on the current branch:
Could you please re-review? A maintainer also needs to add the |
4ae8983 to
d59351a
Compare
d59351a to
0af90da
Compare
Motivation
Queued and sent block caches stored one UTXO value per outpoint but removed entries per block. When competing blocks contained the same non-coinbase transaction, removing either provider deleted the shared outpoint even while another live block still provided it. AwaitUtxo could then wait and retry unnecessarily.
Restored sent-block forks were also flattened into one pruning batch, so height pruning could stop at an ineligible block from one fork and leave eligible blocks from another fork behind.
Solution
Track each cached UTXO by outpoint and provider block hash. Storage preserves every live provider value so removing one provider exposes the exact metadata supplied by a survivor. Lookup intentionally returns an arbitrary live provider while a collision remains; it does not select a branch.
Use an inline single-provider representation for the common case, promote to a provider map only on collisions, and collapse it after removals. Missing ownership is asserted in debug builds and remains a no-op in release builds.
Make sent-block registration idempotent and reconstruct one ordered pruning batch per non-finalized chain so every restored fork is pruned independently.
Integrate queued-body replacement with provider ownership: replacing one block removes its old outputs without deleting outputs still supplied by a competing block. Preserve the admission handling and queue bound introduced by #748.
Performance tradeoffs
The common case keeps one provider hash and UTXO inline, avoiding a separate allocation but adding one 32-byte block hash per distinct cached outpoint. Shared outpoints allocate a provider map and retain each provider-specific UTXO until the collision is resolved; the entry collapses back to inline storage when only one provider remains. A reference count would use less memory, but it could not recover the surviving provider's metadata when the currently exposed provider is removed.
Testing
Verified at
0af90da75, rebased ontoorigin/mainat9b83b94fd.SingleandMultiple, preserves both provider-specific values after an unknown-provider removal, and verifies subsequent cleanup.Changelog
Added docs/changelog/unreleased/797.md for shared-provider retention and complete height pruning across restored competing chains. Validated it with the changelog and Markdown checks above.
Specifications & References
Closes #493.