Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
40e8335
feat(data): wire BlockDB into State — persist QCs and blocks across r…
wen-coding Jul 13, 2026
039c935
fix(data): ignore BlockDB iterator Close errors for errcheck
wen-coding Jul 13, 2026
9bf41f8
fix(data): fall back to BlockDB when entries were evicted from memory
wen-coding Jul 13, 2026
64a2608
test(data): cover BlockDB fallback after memory eviction
wen-coding Jul 13, 2026
c179ece
feat(autobahn): expose optional BlockDB overrides in autobahn.json
wen-coding Jul 13, 2026
cd75fbd
fix(node): move BlockDB lifecycle ownership to nodeImpl
wen-coding Jul 14, 2026
18c3c3c
test(autobahn): panic in BlockDB test helpers instead of t.Fatalf
wen-coding Jul 14, 2026
f7ff617
refactor(data): drop firstBlock flag in BlockDB replay
wen-coding Jul 14, 2026
a2c2efd
chore(data): address remaining pompon0 nits on BlockDB replay
wen-coding Jul 14, 2026
e4b398f
refactor(data): prune BlockDB under the inner lock
wen-coding Jul 14, 2026
2ab8c62
refactor(data): seed runPersist from BlockDB write high-water marks
wen-coding Jul 14, 2026
048ae1e
fix(data): tolerate cache eviction in pruneFirst; close BlockDB on fa…
wen-coding Jul 14, 2026
36fb633
fix(data): evict recovered BlockDB window after restart catch-up
wen-coding Jul 14, 2026
c1de32b
fix(data): align prune semantics for hash and TryBlock reads
wen-coding Jul 14, 2026
50ff709
docs(config): document AutobahnBlockDBConfig defaults and fsync safety
wen-coding Jul 15, 2026
61280b3
docs(config): avoid hardcoding LittDB defaults in AutobahnBlockDBConfig
wen-coding Jul 15, 2026
d519368
fix(node): treat empty persistent_state_dir as disabled; fix makeCloser
wen-coding Jul 15, 2026
6ba6dd7
fix(config): always enable BlockDB fsync; drop config knob
wen-coding Jul 15, 2026
1838665
test(data): drop redundant eviction wait-loop tests
wen-coding Jul 15, 2026
e2bb015
refactor(config): make BlockDB a plain struct with omitzero
wen-coding Jul 16, 2026
ec6bbcb
refactor(config): resolve BlockDB overrides into LittBlockConfig
wen-coding Jul 16, 2026
2ab6f38
fix(data): error on BlockDB inconsistencies; clean up review nits
wen-coding Jul 16, 2026
0a29226
refactor(data): drop unnecessary breaks in GlobalBlockByHash
wen-coding Jul 16, 2026
f3775e2
refactor(data): prune via BlockDB only; Read* returns ErrPruned/ErrNo…
wen-coding Jul 16, 2026
8803170
refactor(blockdb): move setup ownership out of giga
wen-coding Jul 16, 2026
7ffd047
refactor(data): drop unreachable insertBlock nil-QC check
wen-coding Jul 16, 2026
3abb764
fix(data): prune stale in-memory cache entries
wen-coding Jul 16, 2026
fa50942
fix(data,node): typed block-gap error; simplify OnStart BlockDB close
wen-coding Jul 16, 2026
674d934
fix(data): adapt to main BlockDB Option/ErrPruned prune clamp
wen-coding Jul 17, 2026
88a10c4
fix(node): spawn giga after genesis wait, before router
wen-coding Jul 17, 2026
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
83 changes: 83 additions & 0 deletions sei-db/ledger_db/block/block_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func TestBlockDB(t *testing.T) {
t.Run("PruneQCOnlyThenWriteBlock", func(t *testing.T) {
testPruneQCOnlyThenWriteBlock(t, impl.build)
})
t.Run("Status", func(t *testing.T) { testStatus(t, impl.build) })
t.Run("WriteOrderRejected", func(t *testing.T) { testWriteOrderRejected(t, impl.build) })
t.Run("WriteOrderRejectedAfterRestart", func(t *testing.T) {
testWriteOrderRejectedAfterRestart(t, impl.build)
Expand Down Expand Up @@ -130,6 +131,88 @@ func testEmptyDB(t *testing.T, build builder) {
require.NoError(t, err)
require.False(t, ok, "empty db should yield no QCs")
require.NoError(t, qcIt.Close())

tips := db.Status()
require.False(t, tips.LastBlockNumber.IsPresent(), "empty db has no block write tip")
require.False(t, tips.LastQCNext.IsPresent(), "empty db has no QC write tip")
}

// testStatus asserts Status matches the highest block/QC still present
// (via reverse iterators), including after prune and restart, and that a QC
// written ahead of its blocks advances only LastQCNext.
func testStatus(t *testing.T, build builder) {
committee, keys := buildCommittee()
batches := generateBatches(committee, keys)
db, o := openFresh(t, build)
defer func() { _ = db.Close() }()

require.NoError(t, db.WriteQC(batches[0].first, batches[0].next, batches[0].qc))
tips := db.Status()
gotQC, ok := tips.LastQCNext.Get()
require.True(t, ok)
require.Equal(t, batches[0].next, gotQC)
require.False(t, tips.LastBlockNumber.IsPresent(), "QC-only store has no block tip")
assertTipsMatchPresent(t, db)

for i, blk := range batches[0].blocks {
require.NoError(t, db.WriteBlock(batches[0].first+gbn(i), blk))
}
writeAll(t, db, batches[1:])
last := batches[len(batches)-1]
tips = db.Status()
gotBlock, ok := tips.LastBlockNumber.Get()
require.True(t, ok)
require.Equal(t, last.next-1, gotBlock)
gotQC, ok = tips.LastQCNext.Get()
require.True(t, ok)
require.Equal(t, last.next, gotQC)
assertTipsMatchPresent(t, db)

// Prune away an early cohort; the write tip must still equal the highest
// present records (newest cohort is never removed).
require.Greater(t, len(batches), 1)
require.NoError(t, db.PruneBefore(batches[1].first))
assertTipsMatchPresent(t, db)
tips = db.Status()
gotBlock, ok = tips.LastBlockNumber.Get()
require.True(t, ok)
require.Equal(t, last.next-1, gotBlock, "prune must not move the block write tip")
gotQC, ok = tips.LastQCNext.Get()
require.True(t, ok)
require.Equal(t, last.next, gotQC, "prune must not move the QC write tip")

db = restart(t, o, db)
assertTipsMatchPresent(t, db)
tips = db.Status()
gotBlock, ok = tips.LastBlockNumber.Get()
require.True(t, ok)
require.Equal(t, last.next-1, gotBlock, "block tip must survive restart")
gotQC, ok = tips.LastQCNext.Get()
require.True(t, ok)
require.Equal(t, last.next, gotQC, "QC tip must survive restart")
}

// assertTipsMatchPresent checks Status against one reverse-iterator
// step for blocks and QCs (the highest records the public read API still serves).
func assertTipsMatchPresent(t *testing.T, db types.BlockDB) {
t.Helper()
tips := db.Status()

highest, hasBlock := recoverHighestBlock(t, db)
if n, ok := tips.LastBlockNumber.Get(); ok {
require.True(t, hasBlock, "Status has a block tip but Blocks is empty")
require.Equal(t, highest, n, "LastBlockNumber must be the highest present block")
} else {
require.False(t, hasBlock, "Blocks has data but Status has no block tip")
}

lastQC, hasQC := recoverLastQC(t, db)
if n, ok := tips.LastQCNext.Get(); ok {
require.True(t, hasQC, "Status has a QC tip but QCs is empty")
require.Equal(t, lastQC.GlobalRange().Next, n, "LastQCNext must be Next of the highest present QC")
} else {
require.False(t, hasQC, "QCs has data but Status has no QC tip")
}
}

func testReadRoundTrip(t *testing.T, build builder) {
Expand Down
13 changes: 13 additions & 0 deletions sei-db/ledger_db/block/littblock/litt_block_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@ func (s *blockDB) Flush() error {
return nil
}

func (s *blockDB) Status() types.DBStatus {
s.mu.Lock()
defer s.mu.Unlock()
var tips types.DBStatus
if s.hasBlocks {
tips.LastBlockNumber = utils.Some(s.lastBlockNumber)
}
if s.hasQC {
tips.LastQCNext = utils.Some(s.lastQCNext)
}
return tips
}

func (s *blockDB) Blocks(reverse bool) (types.BlockIterator, error) {
it, err := s.table.Iterator(reverse)
if err != nil {
Expand Down
10 changes: 4 additions & 6 deletions sei-db/ledger_db/block/littblock_crash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ func TestLittblockNoBlockWithoutQCAfterTornTail(t *testing.T) {
break
}
n := it.Number()
qc, err := db2.ReadQCByBlockNumber(n)
require.NoError(t, err)
require.True(t, qc.IsPresent(), "block %d survived but its covering QC was lost", n)
_, err = db2.ReadQCByBlockNumber(n)
require.NoError(t, err, "block %d survived but its covering QC was lost", n)
present++
}
Comment on lines 82 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 TestLittblockNoBlockWithoutQCAfterTornTail and TestLittblockFlushSurvivesHardKill dropped their require.True(t, qc.IsPresent(), ...) assertion, leaving only require.NoError on the result of ReadQCByBlockNumber. Per the BlockDB contract, a block with no covering QC returns (None, nil) — a nil error — so both tests now pass even if a block survives crash recovery without its covering QC, which is exactly the invariant they exist to verify.

Extended reasoning...

What changed. In sei-db/ledger_db/block/littblock_crash_test.go, both TestLittblockNoBlockWithoutQCAfterTornTail (torn-tail recovery) and TestLittblockFlushSurvivesHardKill (hard-kill recovery) previously did:

qc, err := db2.ReadQCByBlockNumber(n)
require.NoError(t, err)
require.True(t, qc.IsPresent(), "block %d survived but its covering QC was lost", n)

and now do:

_, err = db2.ReadQCByBlockNumber(n)
require.NoError(t, err, "block %d survived but its covering QC was lost", n)

Why this is vacuous. Per the BlockDB.ReadQCByBlockNumber contract (sei-tendermint/autobahn/types/block_db.go:262-277), when a block number is at or above the retention watermark but has no covering QC, the method returns utils.None with a nil error — err is only non-nil for genuine failures (ErrPruned, I/O/decode errors). The littblock implementation (litt_block_db.go around line 410-416) matches this contract exactly: when s.table.Get(qcKey(n)) reports !exists, it returns (utils.None, nil).

In both of these crash-recovery tests, the watermark stays at genesis (no pruning occurs in either torn-tail or hard-kill scenarios), so every block number iterated is at or above the watermark. That means a block which survives crash recovery without its covering QC — exactly the failure mode these tests are named to catch — now produces (None, nil) from ReadQCByBlockNumber, and require.NoError(t, err, ...) passes without complaint. The dropped require.True(t, qc.IsPresent(), ...) was the only assertion that actually checked for QC presence.

Why nothing else catches this. These are the only two assertions in the codebase directly verifying the 'every persisted block is still covered by a persisted QC' invariant after a simulated crash (torn WAL tail / hard kill). No other test path exercises this specific recovery scenario, so weakening these two removes the only regression guard for this invariant.

Step-by-step proof:

  1. Suppose littblock's recovery logic regresses and, after a torn-tail or hard-kill event, block n survives on disk but its covering QC does not.
  2. db2.ReadQCByBlockNumber(n) is called. Since n is at or above the (genesis) watermark and no QC covers it, the method returns (utils.None, nil) per contract.
  3. The test executes _, err = db2.ReadQCByBlockNumber(n) then require.NoError(t, err, ...). Since err is nil, this assertion passes.
  4. The loop increments present++ and continues; the test function returns without failing.
  5. The regression is silently swallowed — the test suite reports green despite a real violation of the crash-safety invariant the test's own doc comment claims to guarantee ('every persisted block is still covered by a persisted QC ... never the reverse').

Impact and fix. This is test-only: no production/runtime/consensus code path is affected, and the underlying littblock recovery logic itself is unchanged by this PR (all three verifiers independently confirmed the current implementation still satisfies the invariant in practice). The regression is purely in the tests' ability to detect a future violation. The fix is to restore the dropped assertion, e.g.:

qc, err := db2.ReadQCByBlockNumber(n)
require.NoError(t, err)
require.True(t, qc.IsPresent(), "block %d survived but its covering QC was lost", n)

at both call sites (lines ~85 and ~232 pre-diff).


Expand Down Expand Up @@ -230,9 +229,8 @@ func TestLittblockFlushSurvivesHardKill(t *testing.T) {
break
}
n := it.Number()
qc, err := db.ReadQCByBlockNumber(n)
require.NoError(t, err)
require.True(t, qc.IsPresent(), "block %d lost its covering QC after hard kill", n)
_, err = db.ReadQCByBlockNumber(n)
require.NoError(t, err, "block %d lost its covering QC after hard kill", n)
present++
}

Expand Down
13 changes: 13 additions & 0 deletions sei-db/ledger_db/block/memblock/mem_block_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@ func (s *blockDB) PruneBefore(n types.GlobalBlockNumber) error {

func (s *blockDB) Flush() error { return nil }

func (s *blockDB) Status() types.DBStatus {
s.mu.RLock()
defer s.mu.RUnlock()
var tips types.DBStatus
if s.hasBlocks {
tips.LastBlockNumber = utils.Some(s.lastBlockNumber)
}
if s.hasQC {
tips.LastQCNext = utils.Some(s.lastQCNext)
}
return tips
}

func (s *blockDB) Blocks(reverse bool) (types.BlockIterator, error) {
s.mu.RLock()
defer s.mu.RUnlock()
Expand Down
5 changes: 2 additions & 3 deletions sei-tendermint/autobahn/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ type BlockNumber uint64
// GlobalBlockNumber is the number of a block in the global chain.
type GlobalBlockNumber uint64

// BlockWithNumber pairs a block with its GlobalBlockNumber. It is used as the
// payload of the utils.Option returned by ReadBlockByHash so that the block
// number is only present when the block itself is present.
// BlockWithNumber pairs a block with its GlobalBlockNumber. It is returned by
// ReadBlockByHash so that the block number is available alongside the block.
type BlockWithNumber struct {
Block *Block
Number GlobalBlockNumber
Expand Down
20 changes: 20 additions & 0 deletions sei-tendermint/autobahn/types/block_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,20 @@ type BlockDB interface {
// issuance).
Flush() error

// Status returns the block and QC write tips as one consistent snapshot.
// LastBlockNumber is the highest GlobalBlockNumber accepted by WriteBlock
// (utils.None if none). LastQCNext is the exclusive upper bound of the last
// WriteQC — the lowerBound the next contiguous WriteQC must use (utils.None
// if none).
//
// These are the in-memory write cursors used to enforce Write ordering
// (recovered at open); the call does not perform I/O. Because PruneBefore
// never removes the newest written cohort, the tips always describe records
// that are still present and readable: LastBlockNumber equals the highest
// number a reverse Blocks iterator would yield, and LastQCNext equals
// GlobalRange().Next of the QC a reverse QCs iterator would yield.
Status() DBStatus

// Blocks returns an iterator over every persisted block not yet
// pruned, for startup replay. Intended to be called once at
// construction by data.State.NewState.
Expand Down Expand Up @@ -269,6 +283,12 @@ type BlockDB interface {
Close() error
}

// DBStatus is the in-memory block and QC write tips returned by BlockDB.Status.
type DBStatus struct {
LastBlockNumber utils.Option[GlobalBlockNumber]
LastQCNext utils.Option[GlobalBlockNumber]
}

// BlockIterator iterates over persisted blocks in GlobalBlockNumber order —
// ascending, or descending if the iterator was created with reverse=true. It
// is created via BlockDB.Blocks and captures a snapshot of the blocks present
Expand Down
38 changes: 37 additions & 1 deletion sei-tendermint/cmd/tendermint/commands/gen_autobahn_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Output is written to the file specified by --output.`,
return fmt.Errorf("--output flag is required")
}
persistentStateDir, _ := cmd.Flags().GetString("persistent-state-dir")
blockDBRetention, _ := cmd.Flags().GetString("blockdb-retention")
blockDBGCPeriod, _ := cmd.Flags().GetString("blockdb-gc-period")

var validators []config.AutobahnValidator
for _, dir := range args {
Expand Down Expand Up @@ -97,6 +99,11 @@ Output is written to the file specified by --output.`,
if persistentStateDir != "" {
cfg.PersistentStateDir = utils.Some(persistentStateDir)
}
blockDB, err := buildGenBlockDBConfig(blockDBRetention, blockDBGCPeriod)
if err != nil {
return err
}
cfg.BlockDB = blockDB

data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
Expand All @@ -110,6 +117,35 @@ Output is written to the file specified by --output.`,
},
}
cmd.Flags().StringP("output", "o", "", "output file path for the autobahn config")
cmd.Flags().String("persistent-state-dir", "data/autobahn", "directory to persist autobahn consensus and data WALs across restarts; relative paths are resolved against the node's --home dir; pass --persistent-state-dir= (empty) to disable persistence and run in-memory only")
cmd.Flags().String("persistent-state-dir", "data/autobahn", "directory to persist autobahn consensus state and BlockDB across restarts; relative paths are resolved against the node's --home dir; pass --persistent-state-dir= (empty) to disable persistence and run in-memory only (memblock)")
// Default 30s: this helper is used by docker/local clusters, not production
// node bring-up. Pass --blockdb-retention= (empty) to omit block_db and keep
// littblock's production default (24h).
cmd.Flags().String("blockdb-retention", "30s", "BlockDB retention TTL written into block_db (default 30s for local/docker); pass empty to omit and keep littblock's 24h default")
cmd.Flags().String("blockdb-gc-period", "", "optional BlockDB GC period (e.g. 10s); omit to keep littblock default")
return cmd
}

// buildGenBlockDBConfig builds optional block_db overrides from gen-autobahn-config flags.
// Empty duration strings leave BlockDB as the zero value (omitted from JSON).
func buildGenBlockDBConfig(retention, gcPeriod string) (config.AutobahnBlockDBConfig, error) {
var bdb config.AutobahnBlockDBConfig
if retention != "" {
d, err := time.ParseDuration(retention)
if err != nil {
return config.AutobahnBlockDBConfig{}, fmt.Errorf("--blockdb-retention: %w", err)
}
bdb.Retention = utils.Some(utils.Duration(d))
}
if gcPeriod != "" {
d, err := time.ParseDuration(gcPeriod)
if err != nil {
return config.AutobahnBlockDBConfig{}, fmt.Errorf("--blockdb-gc-period: %w", err)
}
bdb.GCPeriod = utils.Some(utils.Duration(d))
}
if err := bdb.Validate(); err != nil {
return config.AutobahnBlockDBConfig{}, fmt.Errorf("block_db: %w", err)
}
return bdb, nil
}
61 changes: 61 additions & 0 deletions sei-tendermint/config/autobahn.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/url"

"github.com/sei-protocol/sei-chain/sei-db/ledger_db/block/littblock"
atypes "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
Expand Down Expand Up @@ -34,6 +35,26 @@ type AutobahnValidator struct {
EVMRPC URL `json:"evmrpc"`
}

// AutobahnBlockDBConfig holds optional overrides for the LittDB-backed BlockDB
// opened under persistent_state_dir/blockdb. Paths are never taken from here —
// Autobahn always roots BlockDB at <persistent_state_dir>/blockdb.
//
// Each field is independently optional. Absent fields keep whatever
// littblock.DefaultConfig currently uses (do not duplicate those values here —
// they live in the littblock / LittDB packages and may change).
//
// Disk impact (most → least useful for bounding usage): Retention, then
// GCPeriod. Segment sizing is intentionally not exposed (engine-internal).
type AutobahnBlockDBConfig struct {
// Retention is the failsafe minimum age before pruned records may be
// reclaimed. Primary knob for worst-case disk after PruneBefore advances.
// Absent ⇒ littblock.DefaultConfig Retention.
Retention utils.Option[utils.Duration] `json:"retention"`
// GCPeriod is how often GC runs once data is eligible (reclaim latency).
// Absent ⇒ littblock.DefaultConfig / LittDB GCPeriod.
GCPeriod utils.Option[utils.Duration] `json:"gc_period"`
}

// AutobahnFileConfig is the JSON structure of the autobahn config file.
type AutobahnFileConfig struct {
Validators []AutobahnValidator `json:"validators"`
Expand All @@ -49,6 +70,11 @@ type AutobahnFileConfig struct {
// fullnodes serving downstream block-sync are subject to the same
// cap). Absent ⇒ DefaultMaxInboundFullnodePeers. Some(0) ⇒ reject all.
MaxInboundFullnodePeers utils.Option[uint64] `json:"max_inbound_fullnode_peers"`
// BlockDB optionally overlays AutobahnBlockDBConfig onto littblock.DefaultConfig

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if all fields are optional, then does it really make sense to make it optional as a whole? You can just add omitzero to the json spec to avoid having it serialized.

// when PersistentStateDir is set. Zero value ⇒ littblock.DefaultConfig unchanged
// (see AutobahnBlockDBConfig for field semantics). Ignored when
// PersistentStateDir is absent (memblock). Omitted from JSON when empty.
BlockDB AutobahnBlockDBConfig `json:"block_db,omitzero"`
}

// DefaultMaxInboundFullnodePeers is the built-in cap used when
Expand Down Expand Up @@ -80,5 +106,40 @@ func (fc *AutobahnFileConfig) Validate() error {
if fc.DialInterval <= 0 {
return errors.New("dial_interval must be > 0")
}
if err := fc.BlockDB.Validate(); err != nil {
return fmt.Errorf("block_db: %w", err)
}
return nil
}

// Validate checks optional BlockDB overrides. Absent fields are fine.
func (c AutobahnBlockDBConfig) Validate() error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] Validate() intentionally does not reject Fsync: false. Since durability before AppQC voting depends on Flush() (runPersist flushes before advancing nextBlockToPersist), a production config with fsync: false can silently lose acknowledged blocks/QCs on crash. Consider rejecting fsync=false here for non-test construction, or at minimum logging a loud unsafe-mode warning at startup (mirroring HashVaultDisabledUnsafe), since the field is silently accepted today. (Codex rated this High; the safe default of true keeps it non-blocking.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about making Validate() return an instantiated littDB config (with the defaults applied)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or add a separate method which resolves the littDB config)

if r, ok := c.Retention.Get(); ok && r <= 0 {
return errors.New("retention must be > 0 when set")
}
if p, ok := c.GCPeriod.Get(); ok && p <= 0 {
return errors.New("gc_period must be > 0 when set")
}
return nil
}

// LittBlockConfig returns littblock.DefaultConfig(dir) with this config's
// optional overrides applied. Fsync is always forced on.
func (c AutobahnBlockDBConfig) LittBlockConfig(dir string) (littblock.LittBlockConfig, error) {
if err := c.Validate(); err != nil {
return littblock.LittBlockConfig{}, err
}
cfg, err := littblock.DefaultConfig(dir)
if err != nil {
return littblock.LittBlockConfig{}, fmt.Errorf("littblock.DefaultConfig: %w", err)
}
if r, ok := c.Retention.Get(); ok {
cfg.Retention = r.Duration()
}
if p, ok := c.GCPeriod.Get(); ok {
cfg.Litt.GCPeriod = p.Duration()
}
// NOT SAFE to set false: crash can lose acknowledged BlockDB writes.
cfg.Litt.Fsync = true
return *cfg, nil
}
18 changes: 18 additions & 0 deletions sei-tendermint/config/autobahn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"encoding/json"
"net/url"
"testing"
"time"

"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
"github.com/stretchr/testify/require"
)

Expand All @@ -24,3 +26,19 @@ func TestURLJSONReencode(t *testing.T) {
require.NotNil(t, got.URL)
require.Equal(t, want.String(), got.String())
}

func TestAutobahnBlockDBConfig_LittBlockConfig(t *testing.T) {
dir := t.TempDir()
const (
wantRetention = 2 * time.Hour
wantGCPeriod = 3 * time.Second
)
cfg, err := (AutobahnBlockDBConfig{
Retention: utils.Some(utils.Duration(wantRetention)),
GCPeriod: utils.Some(utils.Duration(wantGCPeriod)),
}).LittBlockConfig(dir)
require.NoError(t, err)
require.Equal(t, wantRetention, cfg.Retention)
require.Equal(t, wantGCPeriod, cfg.Litt.GCPeriod)
require.True(t, cfg.Litt.Fsync)
}
Loading
Loading