Skip to content

Commit f6fb6cc

Browse files
authored
Merge pull request #393 from SiaFoundation/nate/pruning
Change block pruning
2 parents ff3cac4 + 6e66275 commit f6fb6cc

File tree

6 files changed

+79
-21
lines changed

6 files changed

+79
-21
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
default: major
3+
---
4+
5+
# Changed block pruning to an active decision for integrators rather than a passive option
6+
7+
This fixes a race condition on some nodes when chain subscribers are slow where blocks would be removed from the store before they could be indexed.

chain/db_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,40 @@ func TestReorgExpiringFileContractOrder(t *testing.T) {
338338
t.Fatal("expected the chain states to be equal after reorg with new manager")
339339
}
340340
}
341+
342+
func TestPruneBlocks(t *testing.T) {
343+
n, genesisBlock := testutil.V2Network()
344+
345+
store, tipState, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock, nil)
346+
if err != nil {
347+
t.Fatal(err)
348+
}
349+
cm := chain.NewManager(store, tipState)
350+
351+
// mine a bunch of blocks
352+
testutil.MineBlocks(t, cm, types.VoidAddress, 100)
353+
354+
// prune up to height 50
355+
cm.PruneBlocks(50)
356+
357+
// ensure blocks < 50 are pruned
358+
for height := range uint64(50) {
359+
if index, ok := cm.BestIndex(height); !ok {
360+
t.Fatalf("expected header at height %d to exist", height)
361+
} else if _, exists := cm.Block(index.ID); exists {
362+
t.Fatalf("expected block at height %d to not exist", height)
363+
}
364+
}
365+
366+
// ensure blocks >= 50 exist
367+
for height := uint64(50); height <= 100; height++ {
368+
index, ok := cm.BestIndex(height)
369+
if !ok {
370+
t.Fatalf("expected block at height %d to exist", height)
371+
} else if block, exists := cm.Block(index.ID); !exists {
372+
t.Fatalf("expected block at height %d to exist", height)
373+
} else if block.ID() != index.ID {
374+
t.Fatalf("block ID mismatch at height %d: expected %s, got %s", height, index.ID, block.ID())
375+
}
376+
}
377+
}

chain/manager.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ type Manager struct {
8585
expiringFileContractOrder map[types.BlockID][]types.FileContractID
8686

8787
// configuration options
88-
log *zap.Logger
89-
pruneTarget uint64
88+
log *zap.Logger
9089

9190
txpool struct {
9291
txns []types.Transaction
@@ -430,12 +429,6 @@ func (m *Manager) applyTip(index types.ChainIndex) error {
430429
m.store.ApplyBlock(cs, cau)
431430
m.applyPoolUpdate(cau, cs)
432431
m.tipState = cs
433-
434-
if m.pruneTarget != 0 && cs.Index.Height > m.pruneTarget {
435-
if index, ok := m.store.BestIndex(cs.Index.Height - m.pruneTarget); ok {
436-
m.store.PruneBlock(index.ID)
437-
}
438-
}
439432
return nil
440433
}
441434

@@ -527,6 +520,31 @@ func (m *Manager) reorgTo(index types.ChainIndex) error {
527520
return nil
528521
}
529522

523+
// PruneBlocks prunes any blocks below the specified height
524+
// from the store. This should only be called after all
525+
// subscribers have processed blocks up to the specified height.
526+
//
527+
// Once the blocks are removed, they cannot be re-added without
528+
// resyncing from genesis.
529+
//
530+
// This can take a while depending on the number of blocks
531+
// it is recommended to call this frequently to avoid
532+
// a large backlog.
533+
func (m *Manager) PruneBlocks(height uint64) {
534+
m.mu.Lock()
535+
defer m.mu.Unlock()
536+
537+
for h := height; h > 0; h-- {
538+
index, ok := m.store.BestIndex(h - 1)
539+
if !ok {
540+
break // block does not exist
541+
} else if _, _, ok := m.store.Block(index.ID); !ok {
542+
break // block does not exist
543+
}
544+
m.store.PruneBlock(index.ID)
545+
}
546+
}
547+
530548
// UpdatesSince returns at most max updates on the path between index and the
531549
// Manager's current tip.
532550
func (m *Manager) UpdatesSince(index types.ChainIndex, maxBlocks int) (rus []RevertUpdate, aus []ApplyUpdate, err error) {

chain/options.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@ func WithLog(l *zap.Logger) ManagerOption {
1515
}
1616
}
1717

18-
// WithPruneTarget sets the target number of blocks to store.
19-
func WithPruneTarget(n uint64) ManagerOption {
20-
return func(m *Manager) {
21-
m.pruneTarget = n
22-
}
23-
}
24-
2518
// WithExpiringContractOrder sets the order of file contracts that are expiring
2619
// at a given height. This is used to work around a bug in the chain db
2720
// where the order of expiring file contracts is not preserved across

internal/cmd/calcswaps/main.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/cmd/sync/main.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func main() {
151151
if err != nil {
152152
log.Panic("failed to create store", zap.Error(err))
153153
}
154-
cm := chain.NewManager(store, tipState, chain.WithLog(log.Named("chain")), chain.WithPruneTarget(pruneTarget))
154+
cm := chain.NewManager(store, tipState, chain.WithLog(log.Named("chain")))
155155
log = log.With(zap.Stringer("start", cm.Tip()))
156156

157157
l, err := net.Listen("tcp", ":0")
@@ -186,21 +186,24 @@ func main() {
186186

187187
log.Info("starting sync", zap.String("network", network), zap.String("dir", dir))
188188

189-
reorgCh := make(chan struct{}, 1)
189+
reorgCh := make(chan types.ChainIndex, 1)
190190
var lastLog time.Time
191191
cm.OnReorg(func(ci types.ChainIndex) {
192192
if time.Since(lastLog) > 5*time.Second {
193193
// debounce
194194
log.Info("synced to", zap.Stringer("tip", ci))
195195
lastLog = time.Now()
196196
}
197-
reorgCh <- struct{}{}
197+
reorgCh <- ci
198198
})
199199

200200
for {
201201
select {
202-
case <-reorgCh:
203-
// still syncing
202+
case tip := <-reorgCh:
203+
// still syncing, prune blocks
204+
if tip.Height > 144 {
205+
cm.PruneBlocks(tip.Height - 144)
206+
}
204207
continue
205208
case <-ctx.Done():
206209
log.Info("shutting down")

0 commit comments

Comments
 (0)