-
Notifications
You must be signed in to change notification settings - Fork 21
Delete at height safely #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Delete at height safely #187
Conversation
|
Claude Code Review - Status: Complete This PR implements comprehensive child transaction verification before parent deletion in UTXO pruning. The documentation is thorough and the implementation is generally sound with proper fail-safe behavior. Issue Found:
Strengths:
History:
|
| AND ( | ||
| -- Extract child TX hash from spending_data (first 32 bytes) | ||
| -- Check if this child is NOT stable | ||
| NOT EXISTS ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SQL Logic Issue: Confusing double-negative could mask bugs
The nested NOT EXISTS creates complex logic that's hard to verify. The current structure translates to: Delete if ALL outputs with spending_data have stable children.
This appears functionally correct for fully-spent parents, but has concerns:
- Readability: Double-negative makes code review and maintenance difficult
- Edge case: If a parent has delete_at_height set but still has unspent outputs (spending_data = NULL), those are filtered out by IS NOT NULL, and parent would be deleted despite being partially unspent
Recommendation:
Verify that parents with delete_at_height are always fully spent. Consider flattening to single NOT EXISTS for clarity - reading as "No output exists that has spending_data but lacks a stable child"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This concern is addressed by the implementation. Confirmed that delete_at_height is only set when all UTXOs are fully spent (see setDeleteAtHeight in teranode.lua:977-982). The condition allSpent ensures parents with unspent outputs never have delete_at_height set.
The double-negative SQL logic is functionally correct for this use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed - the logic is functionally correct. The double-negative structure (NOT EXISTS with AND NOT EXISTS) is complex but properly implements: "Delete only if no output exists with an unstable child."
While the readability concern remains (nested negatives are harder to verify), the implementation correctly ensures all spending children are stable before parent deletion. The allSpent precondition provides additional safety as noted.
…o stu/delete-at-height-safely
…o stu/delete-at-height-safely
|
🤖 Claude Code Review Status: Complete Current ReviewDocumentation Accuracy Issue: The documentation in docs/utxo-safe-deletion.md states that PreserveParentsOfOldUnminedTransactions() was "deleted" and is "no longer needed." However, this function is still actively called in services/pruner/worker.go:82 and remains a critical part of the pruning process. Reality: The code implements a defense-in-depth approach with TWO independent safety mechanisms:
Both mechanisms work together, which is good design. The documentation should be updated to accurately reflect this dual-layer protection rather than claiming the preservation mechanism was removed. Minor Issues Resolved:
Overall Assessment: History:
|
…o stu/delete-at-height-safely
| # Enable defensive checks before deleting UTXO transactions | ||
| # When enabled, verifies that all spending children are mined > BlockHeightRetention blocks ago | ||
| # Default: false | ||
| pruner_utxoDefensiveEnabled = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Root cause identified:
The SQL pruner service does not have a defensiveEnabled field in its struct (stores/utxo/sql/pruner/pruner_service.go:26-34) and does not read the UTXODefensiveEnabled setting during initialization (pruner_service.go:88-94).
The defensive verification is hardcoded into the SQL DELETE query (lines 230-258) and always runs regardless of the flag value.
Fix needed:
Add defensiveEnabled field to SQL Service struct and conditionally build the query based on this flag, similar to Aerospike implementation at line 483.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Resolved - This issue has been fixed in the current code.
Both SQL and Aerospike pruners now properly read the defensiveEnabled flag from settings (SQL: line 91, Aerospike: similar) and conditionally apply verification logic (SQL: lines 234-242 for disabled mode, 243+ for enabled mode).
No description provided.