Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f0c3418
Port sei-v3 PR #512: persist AppQC and blocks to disk
wen-coding Feb 16, 2026
7071384
Refactor: extract loadPersistedState from NewState
wen-coding Feb 18, 2026
5444e65
Refactor: move block sorting and gap handling into persist layer
wen-coding Feb 18, 2026
91078d8
Fix goimports formatting in avail/state.go
wen-coding Feb 18, 2026
284d29b
Async block persistence: move fsync off the critical path
wen-coding Feb 18, 2026
7edd471
Derive persistEnabled from loaded instead of extra arg
wen-coding Feb 18, 2026
2f0bbad
Move blockPersisted init next to block restoration in newInner
wen-coding Feb 19, 2026
869066f
Fix NewState call sites after rebase: add persistence dir arg
wen-coding Feb 20, 2026
4157de8
Merge branch 'main' into wen/persist_appqc_and_blocks
wen-coding Feb 20, 2026
933afaa
Merge remote-tracking branch 'origin/main' into wen/persist_appqc_and…
wen-coding Feb 20, 2026
bed6824
Encapsulate async block persistence in BlockPersister
wen-coding Feb 20, 2026
a7b9650
Merge branch 'wen/persist_appqc_and_blocks' of github.com:sei-protoco…
wen-coding Feb 20, 2026
588b294
Merge remote-tracking branch 'origin/main' into wen/persist_appqc_and…
wen-coding Feb 20, 2026
fbac226
simplify blockPersisted initialization in newInner
wen-coding Feb 22, 2026
b08698d
improve persistence tests and comments
wen-coding Feb 22, 2026
fafc051
remove stringly-typed error assertions in avail tests
wen-coding Feb 22, 2026
2dd5306
move persistence watermark tracking into BlockPersister
wen-coding Feb 22, 2026
a658e5b
clarify Queue comment: stall is until restart, not permanent
wen-coding Feb 22, 2026
ec33acb
clean up orphaned block files from previous committees
wen-coding Feb 22, 2026
7d71dac
remove stringly-typed assertion in consensus test
wen-coding Feb 22, 2026
de0a944
Merge remote-tracking branch 'origin/main' into wen/persist_appqc_and…
wen-coding Feb 22, 2026
a04ef1c
document WAL migration plan in blocks.go
wen-coding Feb 22, 2026
d5f3f40
address reviewer feedback: rename persistence cursors, use Option, us…
wen-coding Feb 23, 2026
9b4f9c3
make Persister generic: Persister[T proto.Message] interface
wen-coding Feb 23, 2026
a61ab55
address reviewer feedback: use Option for blockPersist, log persisten…
wen-coding Feb 23, 2026
5da8458
simplify PersistBlock/Queue API: extract lane and block number from p…
wen-coding Feb 23, 2026
6d7ab36
replace onPersisted callback with AtomicSend tips in BlockPersister
wen-coding Feb 23, 2026
d1ed789
unexport A/B suffixes; extract newState constructor for test injection
wen-coding Feb 23, 2026
ce04946
make DeleteBefore async: route cleanup through Run() via the persist …
wen-coding Feb 23, 2026
05110d6
return lane firsts map from prune() instead of bool
wen-coding Feb 23, 2026
1581831
remove unnecessary IgnoreAfterCancel around bp.Queue()
wen-coding Feb 24, 2026
00b68e8
move AppQC persistence out of inner lock into async goroutine
wen-coding Feb 24, 2026
2133804
replace blocking queue with goroutine that reads inner.blocks directly
wen-coding Feb 24, 2026
da11eb0
persist CommitQCs to disk for fast restart recovery
wen-coding Feb 24, 2026
f95bcc1
use inner.prune() for init; newInner returns error
wen-coding Feb 24, 2026
796faf9
Merge branch 'main' into wen/persist_appqc_and_blocks
wen-coding Feb 24, 2026
72fcbf5
fix persist goroutine race: clamp cursors past pruned entries
wen-coding Feb 24, 2026
27ec1ec
add queue.reset(); use it instead of prune() on fresh queues in newInner
wen-coding Feb 24, 2026
4bdd6f6
refactor persist goroutine into persistLoop and collectPersistBatch
wen-coding Feb 24, 2026
c2caac4
Merge branch 'main' into wen/persist_appqc_and_blocks
wen-coding Feb 24, 2026
f3fb718
group persisters into single Option; error on non-consecutive commitQCs
wen-coding Feb 25, 2026
d8664ef
encapsulate persistence into persisters.run(); inline persistLoop
wen-coding Feb 25, 2026
9e97d47
make persisters a pure I/O struct; move orchestration to State.Run()
wen-coding Feb 25, 2026
1f753f1
simplify prune() to return (bool, error) instead of laneFirsts map
wen-coding Feb 25, 2026
1efb09e
persist AppQC after CommitQCs in the same goroutine
wen-coding Feb 25, 2026
960a3ce
gate consensus on CommitQC persistence before advancing view
wen-coding Feb 25, 2026
99458d7
Merge branch 'main' into wen/persist_appqc_and_blocks
wen-coding Feb 25, 2026
14e1421
fix stale comment: AppQC is now persisted in the same goroutine
wen-coding Feb 25, 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
86 changes: 84 additions & 2 deletions sei-tendermint/internal/autobahn/avail/inner.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import (
"fmt"

"github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/consensus/persist"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/types"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
)
Expand All @@ -14,23 +15,102 @@
commitQCs *queue[types.RoadIndex, *types.CommitQC]
blocks map[types.LaneID]*queue[types.BlockNumber, *types.Signed[*types.LaneProposal]]
votes map[types.LaneID]*queue[types.BlockNumber, blockVotes]
// nextBlockToPersist tracks per-lane how far block persistence has progressed.
// RecvBatch only yields blocks below this cursor for voting.
// nil when persistence is disabled (testing); RecvBatch then uses q.next.
// nextBlockToPersist itself is not persisted to disk: on restart it is
// reconstructed from the blocks already on disk (see newInner).
nextBlockToPersist map[types.LaneID]types.BlockNumber
}

func newInner(c *types.Committee) *inner {
// loadedAvailState holds data loaded from disk on restart.
// blocks are sorted by number and contiguous (gaps already resolved by loader).
// commitQCs are sorted by road index and contiguous (gaps already resolved by loader).
type loadedAvailState struct {
appQC utils.Option[*types.AppQC]
commitQCs []persist.LoadedCommitQC
blocks map[types.LaneID][]persist.LoadedBlock
}

func newInner(c *types.Committee, loaded utils.Option[*loadedAvailState]) (*inner, error) {
votes := map[types.LaneID]*queue[types.BlockNumber, blockVotes]{}
blocks := map[types.LaneID]*queue[types.BlockNumber, *types.Signed[*types.LaneProposal]]{}
for _, lane := range c.Lanes().All() {
votes[lane] = newQueue[types.BlockNumber, blockVotes]()
blocks[lane] = newQueue[types.BlockNumber, *types.Signed[*types.LaneProposal]]()
}
return &inner{

i := &inner{
latestAppQC: utils.None[*types.AppQC](),
latestCommitQC: utils.NewAtomicSend(utils.None[*types.CommitQC]()),
appVotes: newQueue[types.GlobalBlockNumber, appVotes](),
commitQCs: newQueue[types.RoadIndex, *types.CommitQC](),
blocks: blocks,
votes: votes,
}

l, ok := loaded.Get()
if !ok {
return i, nil
}

// Restore persisted CommitQCs into the queue.
if len(l.commitQCs) > 0 {
i.commitQCs.reset(l.commitQCs[0].Index)
for _, lqc := range l.commitQCs {
if lqc.Index != i.commitQCs.next {
return nil, fmt.Errorf("non-consecutive commitQC: got index %d, want %d", lqc.Index, i.commitQCs.next)
}
i.commitQCs.pushBack(lqc.QC)
}
i.latestCommitQC.Store(utils.Some(i.commitQCs.q[i.commitQCs.next-1]))
}

// nextBlockToPersist gates RecvBatch: only blocks below this cursor are
// eligible for voting. Lanes without loaded blocks start at 0, which
// is safe — an empty queue has nothing to vote on. New blocks must
// arrive in order via PushBlock, get persisted, and the callback will
// advance nextBlockToPersist accordingly.
i.nextBlockToPersist = make(map[types.LaneID]types.BlockNumber, c.Lanes().Len())

// Restore persisted blocks into their lane queues.
for lane, bs := range l.blocks {
q, ok := i.blocks[lane]
if !ok || len(bs) == 0 {
continue
}
first := bs[0].Number
q.reset(first)
for _, b := range bs {
q.pushBack(b.Proposal)
}
// Loaded blocks are already on disk, so immediately consider them persisted.
i.nextBlockToPersist[lane] = q.next
// Votes are not persisted (cheap to re-gossip, short-lived, and
// high-volume per block × validator). Advance the votes queue past
// loaded blocks so that headers() returns ErrPruned for blocks
// before `first` instead of blocking forever waiting for votes
// that will never arrive.
i.votes[lane].reset(first)
}

// Prune all queues based on the persisted AppQC. Called after loading
// so that prune() operates on populated queues (same path as runtime).
if aq, ok := l.appQC.Get(); ok {
idx := aq.Proposal().RoadIndex()
qc, ok := i.commitQCs.q[idx]
if !ok {
// AppQC is persisted after CommitQCs in the same goroutine,
// so the matching CommitQC is always on disk before the AppQC.
// If we get here, the persisted state is corrupt.
return nil, fmt.Errorf("persisted AppQC at road index %d but no matching commitQC on disk", idx)
}
if _, err := i.prune(aq, qc); err != nil {
return nil, fmt.Errorf("prune: %w", err)
}
}

return i, nil
}

func (i *inner) laneQC(c *types.Committee, lane types.LaneID, n types.BlockNumber) (*types.LaneQC, bool) {
Expand All @@ -42,6 +122,8 @@
return nil, false
}

// prune advances the state to account for a new AppQC/CommitQC pair.
// Returns true if pruning occurred, false if the QC was stale.
func (i *inner) prune(appQC *types.AppQC, commitQC *types.CommitQC) (bool, error) {
idx := appQC.Proposal().RoadIndex()
if idx != commitQC.Proposal().Index() {
Expand Down
Loading
Loading