Skip to content
Merged
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
2 changes: 1 addition & 1 deletion configs/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ logging:
# Global defaults - applies to all chains unless overridden
defaults:
enabled: false # chains inherit this unless they override enabled below
from_latest: true # start indexing from the latest block
from_latest: true # on cold start begin at chain head; set false per chain to start from start_block
poll_interval: "5s" # how often to poll for new blocks
reorg_rollback_window: 20 # number of blocks to roll back on reorg
two_way_indexing: false # enable two-way indexing for all chains
Expand Down
10 changes: 8 additions & 2 deletions internal/worker/regular.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ 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 we begin at the
// configured start_block when from_latest is false, otherwise at the chain head.
func (rw *RegularWorker) determineStartingBlock() uint64 {
kvLatest, kvErr := rw.blockStore.GetLatestBlock(rw.chain.GetNetworkInternalCode())

Expand Down Expand Up @@ -231,6 +231,12 @@ 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()
}
if !rw.config.IsFromLatest() && rw.config.StartBlock > 0 {
rw.logger.Info("Cold start from configured start_block",
"chain", rw.chain.GetName(), "start_block", rw.config.StartBlock)
return uint64(rw.config.StartBlock)
}
return rw.waitForChainHead()
}
Expand Down
37 changes: 37 additions & 0 deletions internal/worker/regular_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,43 @@ func TestRegularWorkerDetermineStartingBlockColdStartChainUp(t *testing.T) {
require.Equal(t, uint64(500), rw.determineStartingBlock())
}

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

chain := &stubIndexer{name: "ethereum", internalCode: "ETH", networkType: enum.NetworkTypeEVM, latest: 500}
store := &stubBlockStore{latestBlock: 0}
rw := newTestRegularWorker(chain, store, 0, 2)
fromLatest := false
rw.config.FromLatest = &fromLatest
rw.config.StartBlock = 120

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

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

chain := &stubIndexer{name: "ethereum", internalCode: "ETH", networkType: enum.NetworkTypeEVM, latest: 500}
store := &stubBlockStore{latestBlock: 0}
rw := newTestRegularWorker(chain, store, 0, 2)
rw.config.StartBlock = 120

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

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

chain := &stubIndexer{name: "ethereum", internalCode: "ETH", networkType: enum.NetworkTypeEVM, latest: 300}
store := &stubBlockStore{latestBlock: 300}
rw := newTestRegularWorker(chain, store, 0, 2)
fromLatest := false
rw.config.FromLatest = &fromLatest
rw.config.StartBlock = 120

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

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

Expand Down
17 changes: 14 additions & 3 deletions pkg/common/config/chains.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ func (c Chains) Validate(names []string) error {
func (c Chains) OverrideFromLatest(names []string) {
for _, name := range names {
if chain, ok := c[name]; ok {
chain.FromLatest = true
fromLatest := true
chain.FromLatest = &fromLatest
c[name] = chain
}
}
Expand All @@ -75,8 +76,9 @@ func (c Chains) ApplyDefaults(def Defaults) error {
enabled := *def.Enabled
chain.Enabled = &enabled
}
if !chain.FromLatest {
chain.FromLatest = def.FromLatest
if chain.FromLatest == nil && def.FromLatest != nil {
fromLatest := *def.FromLatest
chain.FromLatest = &fromLatest
}
if !chain.TwoWayIndexing {
chain.TwoWayIndexing = def.TwoWayIndexing
Expand All @@ -101,3 +103,12 @@ func (c Chains) ApplyDefaults(def Defaults) error {
}
return nil
}

// IsFromLatest reports whether a cold start begins at the chain head. Defaults to true when unset.
func (c ChainConfig) IsFromLatest() bool {
return boolOrTrue(c.FromLatest)
}

func boolOrTrue(b *bool) bool {
return b == nil || *b
}
24 changes: 24 additions & 0 deletions pkg/common/config/chains_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,27 @@ func TestEnabledNames_IncludesExplicitlyEnabledChains(t *testing.T) {

require.ElementsMatch(t, []string{"ethereum_mainnet"}, chains.EnabledNames())
}

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

f, tr := false, true
tests := []struct {
name string
def *bool
chain *bool
expected bool
}{
{"unset everywhere defaults to true", nil, nil, true},
{"inherits default false", &f, nil, false},
{"chain overrides default", &tr, &f, false},
{"chain true overrides default false", &f, &tr, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
chains := Chains{"eth": {FromLatest: tt.chain}}
require.NoError(t, chains.ApplyDefaults(Defaults{FromLatest: tt.def}))
require.Equal(t, tt.expected, chains["eth"].IsFromLatest())
})
}
}
4 changes: 2 additions & 2 deletions pkg/common/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (c *LoggingConfig) Normalize() {

type Defaults struct {
Enabled *bool `yaml:"enabled"`
FromLatest bool `yaml:"from_latest"`
FromLatest *bool `yaml:"from_latest"`
TwoWayIndexing bool `yaml:"two_way_indexing"`
PollInterval time.Duration `yaml:"poll_interval" validate:"required"`
ReorgRollbackWindow int `yaml:"reorg_rollback_window" validate:"required,min=1"`
Expand All @@ -71,7 +71,7 @@ type ChainConfig struct {
InternalCode string `yaml:"internal_code"`
NativeDenom string `yaml:"native_denom"`
Type enum.NetworkType `yaml:"type" validate:"required"`
FromLatest bool `yaml:"from_latest"`
FromLatest *bool `yaml:"from_latest"`
StartBlock int `yaml:"start_block" validate:"min=0"`
PollInterval time.Duration `yaml:"poll_interval"`
ReorgRollbackWindow int `yaml:"reorg_rollback_window"`
Expand Down
3 changes: 3 additions & 0 deletions pkg/common/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ func validateChainConfig(chain ChainConfig) error {
if chain.Type == enum.NetworkTypeCosmos && chain.NativeDenom == "" {
return fmt.Errorf("native_denom is required for cosmos chains")
}
if !chain.IsFromLatest() && chain.StartBlock == 0 {
return fmt.Errorf("start_block is required when from_latest is false")
}
statusCfg := chain.Status.Normalize()
if statusCfg.HealthyMaxPendingBlocks >= statusCfg.SlowMaxPendingBlocks {
return fmt.Errorf(
Expand Down
11 changes: 11 additions & 0 deletions pkg/common/config/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,14 @@ func TestLoggingConfigValidate_RejectsUnknownModeAndFormat(t *testing.T) {
invalid := LoggingConfig{Mode: "somewhere", Format: "plain"}
require.Error(t, validate.Struct(&invalid))
}

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

fromLatest := false
chain := ChainConfig{Type: enum.NetworkTypeEVM, FromLatest: &fromLatest}
require.Error(t, validateChainConfig(chain))

chain.StartBlock = 100
require.NoError(t, validateChainConfig(chain))
}
Loading