Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions internal/worker/regular.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,9 @@ func (rw *RegularWorker) commitProgress(lastSuccess uint64, lastSuccessHash stri

// determineStartingBlock picks a start block from the last indexed block and the
// chain head. GetLatestBlock returns (0, nil) for a cold start, so a non-nil
// kvErr means the store is genuinely down. When there is no prior block to
// resume from, the chain head is the only safe anchor and we wait for it.
// kvErr means the store is genuinely down. On a cold start with a healthy store,
// a configured start_block (> 0) is used as the anchor; otherwise the chain head
// is the only safe anchor and we wait for it.
func (rw *RegularWorker) determineStartingBlock() uint64 {
kvLatest, kvErr := rw.blockStore.GetLatestBlock(rw.chain.GetNetworkInternalCode())

Expand All @@ -217,7 +218,21 @@ func (rw *RegularWorker) determineStartingBlock() uint64 {
if kvErr != nil {
rw.logger.Error("Block store unavailable, starting from chain head",
"chain", rw.chain.GetName(), "error", kvErr)
return rw.waitForChainHead()
}

// Cold start with a healthy store and no prior block: honor the configured
// start_block so operators can backfill from a chosen height. The regular
// loop then walks forward from here to the chain head. A down store falls
// through to the chain head above, so a broken store never triggers a
// re-backfill from start_block on every restart.
if rw.config.StartBlock > 0 {
start := uint64(rw.config.StartBlock)
rw.logger.Info("Cold start from configured start_block",
"chain", rw.chain.GetName(), "start_block", start)
return start
}

return rw.waitForChainHead()
}

Expand Down
39 changes: 39 additions & 0 deletions internal/worker/regular_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,45 @@ func TestRegularWorkerDetermineStartingBlockColdStartChainUp(t *testing.T) {
require.Equal(t, uint64(500), rw.determineStartingBlock())
}

func TestRegularWorkerDetermineStartingBlockColdStartUsesConfiguredStartBlock(t *testing.T) {
t.Parallel()

// Cold start with a healthy store and a configured start_block: anchor on it
// rather than the chain head, so a backfill begins from the chosen height.
chain := &stubIndexer{name: "ethereum", internalCode: "ETH", networkType: enum.NetworkTypeEVM, latest: 500}
store := &stubBlockStore{latestBlock: 0}
rw := newTestRegularWorker(chain, store, 0, 2)
rw.config.StartBlock = 100

require.Equal(t, uint64(100), rw.determineStartingBlock())
}

func TestRegularWorkerDetermineStartingBlockResumeIgnoresStartBlock(t *testing.T) {
t.Parallel()

// A prior KV checkpoint always wins over start_block: resume from the head
// (queuing catchup for the gap), never rewind to the configured start_block.
chain := &stubIndexer{name: "ethereum", internalCode: "ETH", networkType: enum.NetworkTypeEVM, latest: 500}
store := &stubBlockStore{latestBlock: 300}
rw := newTestRegularWorker(chain, store, 300, 2)
rw.config.StartBlock = 100

require.Equal(t, uint64(500), rw.determineStartingBlock())
}

func TestRegularWorkerDetermineStartingBlockStoreDownIgnoresStartBlock(t *testing.T) {
t.Parallel()

// Store down: fall back to the chain head even when start_block is set, so a
// broken store never re-triggers a backfill from start_block on each restart.
chain := &stubIndexer{name: "ethereum", internalCode: "ETH", networkType: enum.NetworkTypeEVM, latest: 500}
store := &stubBlockStore{getLatestBlockErr: errors.New("redis down")}
rw := newTestRegularWorker(chain, store, 0, 2)
rw.config.StartBlock = 100

require.Equal(t, uint64(500), rw.determineStartingBlock())
}

func TestRegularWorkerDetermineStartingBlockChainCancelledReturnsZero(t *testing.T) {
t.Parallel()

Expand Down