Cleanup sector root cache - #1009
Conversation
039a40a to
ce88512
Compare
There was a problem hiding this comment.
🟡 Changes recommended
A critical stale-cache issue remains when rejected contracts are reset, along with a migration-test comment nit.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Fixes a memory leak by evicting cached sector roots after contract lifecycle transitions.
Changes:
- Adds lifecycle tracking and migration support.
- Implements synchronized cache expiration.
- Updates persistence, tests, and release notes.
File summaries
| File | Summary |
|---|---|
persist/sqlite/migrations.go |
Adds schema migration. |
persist/sqlite/migrations_test.go |
Tests migration backfill. Nit (1 vote): Correct misplaced migration test comment. |
persist/sqlite/init.sql |
Updates the current schema. |
persist/sqlite/contracts.go |
Adds update tracking and expiration queries. |
persist/sqlite/consensus.go |
Tracks lifecycle transition indices. |
host/contracts/update.go |
Triggers cache expiration. |
host/contracts/persist.go |
Extends the persistence interface. |
host/contracts/manager.go |
Integrates the roots cache. |
host/contracts/manager_test.go |
Tests cache lifecycle behavior. |
host/contracts/lock.go |
Uses the cached roots. |
host/contracts/integrity.go |
Uses the cached roots. |
host/contracts/cache.go |
Implements synchronized root cleanup. Critical (1 vote): Resetting rejected contracts can leave stale cached roots attached to the replacement contract. |
.changeset/release_cached_sector_roots.md |
Documents the release change. |
Review details
Suppressed comments (1)
persist/sqlite/migrations_test.go:770
- The comment immediately above this newly inserted test still describes
TestMigrateV55, soTestMigrateV56is documented as testing the previous migration and the actual v55 test below has no header. Please move the v55 comment below this test and add a v56 description here.
func TestMigrateV56(t *testing.T) {
- Files reviewed: 13/13 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
Two critical lifecycle and reorg-buffer issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
persist/sqlite/migrations_test.go:770
- The existing
TestMigrateV55doc comment now immediately precedesTestMigrateV56, so it documents the wrong test and leaves the v55 test undocumented. Move this new test belowTestMigrateV55, or update/add the comments so each test describes its own migration.
func TestMigrateV56(t *testing.T) {
- Files reviewed: 13/13 changed files
- Comments generated: 2
- Review effort level: Lite
750e460 to
eeb750c
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Terminal roots are not restored after restart/reorg, and failed-resolution reverts can leave stale failed state.
Review details
Suppressed comments (2)
host/contracts/manager.go:386
- Because
rootscomes fromV2SectorRoots, which excludes resolved and rejected contracts, this cache is empty for terminal contracts after every restart. If a restart is followed by a reorg within the reorg buffer, the chain update can restore such a contract to pending/active, butSectorRootsstill returns nil, so validation or renewal loses the contract's sectors. Recent terminal roots need to be persisted or otherwise restored across startup.
cm.roots = &rootsCache{
store: store,
contractSectors: roots,
lastExpiredHeight: expireHeight,
persist/sqlite/consensus.go:1718
- The added
last_updated_*update is unreachable for failed-resolution reverts:revertFailedV2Contractshits thecontinuein itsstate.Status == Failedbranch before executing this statement. A failed proof/expiration that is reorged therefore remains marked failed with its old resolution height, soExpireContractscan delete its cached roots even though the contract should be active again. This path needs to perform the active-state update (and timestamp it withindex) before the cache expiry logic runs.
updateStmt, err := tx.Prepare(`UPDATE contracts_v2 SET resolution_block_id=NULL, resolution_height=NULL, contract_status=?, last_updated_height=?, last_updated_block_id=? WHERE id=?`)
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Lite
02d9a3d to
a6b7a69
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Shallow-tip expiry can remove roots before the reorg buffer has elapsed.
Review details
Suppressed comments (1)
host/contracts/manager.go:373
- When the stored tip is at or below
ReorgBuffer, this leavesexpireHeightequal to the tip instead of zero. A restart at a shallow height therefore callsV2SectorRootswith a minimum that excludes resolved contracts from the current reorg window (and height exactlyReorgBufferexcludes every resolution below that height), so their roots disappear from the in-memory cache before the buffer has elapsed. Initialize the expiry height to zero and only subtract the buffer when the tip is higher than it.
expireHeight := tip.Height
if expireHeight > ReorgBuffer {
expireHeight -= ReorgBuffer
}
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
Fixes a memory leak where the in-memory sector root cache kept the roots of every resolved, renewed, and rejected contract for the lifetime of the process.