Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
65 changes: 20 additions & 45 deletions sei-db/ledger_db/block/block_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func testPruneStraddleRetainsQC(t *testing.T, build builder) {
require.NoError(t, err)
got, ok := opt.Get()
require.True(t, ok, "straddling QC must be retained")
require.Equal(t, straddled.first, got.QC().GlobalRange(committee).First)
require.Equal(t, straddled.first, got.QC().GlobalRange().First)
}

// testPruneIdempotentMonotonic asserts PruneBefore is idempotent and the
Expand Down Expand Up @@ -485,7 +485,7 @@ func testReverseIteratorOrdering(t *testing.T, build builder) {
}
qc, err := qcIt.QC()
require.NoError(t, err)
first := qc.QC().GlobalRange(committee).First
first := qc.QC().GlobalRange().First
if qcCount == 0 {
require.Equal(t, lastFirst, first, "reverse QCs must surface the last QC first")
}
Expand Down Expand Up @@ -525,8 +525,8 @@ func testResumeAfterRestart(t *testing.T, build builder) {

prevQC, ok := recoverLastQC(t, db)
require.True(t, ok)
require.Equal(t, last.first, prevQC.GlobalRange(committee).First, "recovered QC must be the last persisted QC")
require.Equal(t, last.next, prevQC.GlobalRange(committee).Next)
require.Equal(t, last.first, prevQC.GlobalRange().First, "recovered QC must be the last persisted QC")
require.Equal(t, last.next, prevQC.GlobalRange().Next)

// The recovered QC's upper bound is exactly where the continuation begins;
// writing the next contiguous batch must be accepted.
Expand Down Expand Up @@ -713,7 +713,7 @@ func TestMemblockPruneRemovesBelowWatermark(t *testing.T) {
}
fqc, err := qcIt.QC()
require.NoError(t, err)
require.GreaterOrEqual(t, fqc.QC().GlobalRange(committee).First, watermark,
require.GreaterOrEqual(t, fqc.QC().GlobalRange().First, watermark,
"QC iterator must not surface pruned QCs")
}
require.NoError(t, qcIt.Close())
Expand Down Expand Up @@ -844,13 +844,13 @@ func assertBlocksReadable(t *testing.T, db types.BlockDB, batches []batch) {

func assertQCsReadable(t *testing.T, db types.BlockDB, committee *types.Committee, batches []batch) {
for _, b := range batches {
r := b.qc.QC().GlobalRange(committee)
r := b.qc.QC().GlobalRange()
for n := r.First; n < r.Next; n++ {
opt, err := db.ReadQCByBlockNumber(n)
require.NoError(t, err)
got, ok := opt.Get()
require.True(t, ok, "QC covering %d should exist", n)
gr := got.QC().GlobalRange(committee)
gr := got.QC().GlobalRange()
require.Equal(t, r.First, gr.First)
require.Equal(t, r.Next, gr.Next)
require.Len(t, got.Headers(), len(b.qc.Headers()), "QC must round-trip its full header set")
Expand Down Expand Up @@ -904,7 +904,7 @@ func assertIterators(t *testing.T, db types.BlockDB, committee *types.Committee,
}
qc, err := qcIt.QC()
require.NoError(t, err)
first := qc.QC().GlobalRange(committee).First
first := qc.QC().GlobalRange().First
if haveQC {
require.Greater(t, first, prevFirst, "QCs must iterate ascending by First")
}
Expand Down Expand Up @@ -962,7 +962,7 @@ func buildCommittee() (*types.Committee, []types.SecretKey) {
keys[i] = types.GenSecretKey(rng)
replicas[i] = keys[i].Public()
}
committee := utils.OrPanic1(types.NewRoundRobinElection(replicas, 0, genesisTime))
committee := utils.OrPanic1(types.NewRoundRobinElection(replicas))
return committee, keys
}

Expand All @@ -974,7 +974,7 @@ func generateBatches(committee *types.Committee, keys []types.SecretKey) []batch
batches := make([]batch, 0, numBatches)
for range numBatches {
fqc, blocks := buildFullCommitQC(rng, committee, keys, prev)
r := fqc.QC().GlobalRange(committee)
r := fqc.QC().GlobalRange()
batches = append(batches, batch{first: r.First, next: r.Next, blocks: blocks, qc: fqc})
prev = utils.Some(fqc.QC())
}
Expand All @@ -993,18 +993,12 @@ func buildFullCommitQC(
parent := bs[len(bs)-1]
return types.NewBlock(producer, parent.Header().Next(), parent.Header().Hash(), types.GenPayload(rng))
}
return types.NewBlock(
producer,
types.LaneRangeOpt(prev, producer).Next(),
types.GenBlockHeaderHash(rng),
types.GenPayload(rng),
)
return types.NewBlock(producer, types.LaneRangeOpt(prev, producer).Next(), types.GenBlockHeaderHash(rng), types.GenPayload(rng))
}
for range blocksPerQC {
producer := committee.Lanes().At(rng.Intn(committee.Lanes().Len()))
blocks[producer] = append(blocks[producer], makeBlock(producer))
}

laneQCs := map[types.LaneID]*types.LaneQC{}
var headers []*types.BlockHeader
var blockList []*types.Block
Expand All @@ -1017,35 +1011,16 @@ func buildFullCommitQC(
}
}
}

viewSpec := types.ViewSpec{CommitQC: prev}
leader := committee.Leader(viewSpec.View())
var leaderKey types.SecretKey
for _, k := range keys {
if k.Public() == leader {
leaderKey = k
break
}
}
proposal := utils.OrPanic1(types.NewProposal(
leaderKey,
committee,
viewSpec,
genesisTime,
laneQCs,
func() utils.Option[*types.AppQC] {
if n := types.GlobalRangeOpt(prev, committee).Next; n > 0 {
p := types.NewAppProposal(n-1, viewSpec.View().Index, types.GenAppHash(rng))
return utils.Some(testAppQC(keys, p))
}
return utils.None[*types.AppQC]()
}(),
))
votes := make([]*types.Signed[*types.CommitVote], 0, len(keys))
for _, k := range keys {
votes = append(votes, types.Sign(k, types.NewCommitVote(proposal.Proposal().Msg())))
var appQC utils.Option[*types.AppQC]
if cqc, ok := prev.Get(); ok {
p := types.NewAppProposal(cqc.GlobalRange().Next-1, types.NextIndexOpt(prev), types.GenAppHash(rng), cqc.Proposal().EpochIndex())
appQC = utils.Some(testAppQC(keys, p))
} else {
appQC = utils.None[*types.AppQC]()
}
return types.NewFullCommitQC(types.NewCommitQC(votes), headers), blockList
ep := types.NewEpoch(0, types.OpenRoadRange(), genesisTime, committee, 0)
cqc := types.BuildCommitQC(ep, keys, prev, laneQCs, appQC)
return types.NewFullCommitQC(cqc, headers), blockList
}

func testLaneQC(keys []types.SecretKey, header *types.BlockHeader) *types.LaneQC {
Expand Down
8 changes: 4 additions & 4 deletions sei-db/ledger_db/block/blocksim/block_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (g *BlockGenerator) mainLoop() {

func (g *BlockGenerator) buildBatch() *generatedBatch {
fqc, blocks := g.buildFullCommitQC()
r := fqc.QC().GlobalRange(g.committee)
r := fqc.QC().GlobalRange()
g.prev = utils.Some(fqc.QC())
return &generatedBatch{first: r.First, next: r.Next, blocks: blocks, qc: fqc}
}
Expand Down Expand Up @@ -168,11 +168,11 @@ func (g *BlockGenerator) buildFullCommitQC() (*types.FullCommitQC, []*types.Bloc
}
}

viewSpec := types.ViewSpec{CommitQC: prev}
viewSpec := types.ViewSpec{CommitQC: prev, Epoch: types.NewEpoch(0, types.OpenRoadRange(), genesisTime, committee, 0)}
leader := committee.Leader(viewSpec.View())
appQC := func() utils.Option[*types.AppQC] {
if n := types.GlobalRangeOpt(prev, committee).Next; n > 0 {
p := types.NewAppProposal(n-1, viewSpec.View().Index, types.AppHash(g.rand.Bytes(hashSizeBytes)))
if n := viewSpec.NextGlobalBlock(); n > 0 {
p := types.NewAppProposal(n-1, viewSpec.View().Index, types.AppHash(g.rand.Bytes(hashSizeBytes)), viewSpec.Epoch.EpochIndex())
return utils.Some(g.fakeAppQC(p))
}
return utils.None[*types.AppQC]()
Expand Down
4 changes: 2 additions & 2 deletions sei-db/ledger_db/block/blocksim/blocksim.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func NewBlockSim(
// last QC's range — the next batch then appends contiguously. Block bytes
// are irrelevant here (this is a DB stress test), so the backfill writes
// freshly generated blocks under the already-persisted QC.
qcRange := prevQC.GlobalRange(committee)
qcRange := prevQC.GlobalRange()
lastQCNext := uint64(qcRange.Next)
firstMissing := uint64(qcRange.First)
if h, ok := highestOpt.Get(); ok {
Expand Down Expand Up @@ -268,7 +268,7 @@ func buildCommittee(rng tmutils.Rng, size int) (*types.Committee, []types.Secret
keys[i] = types.GenSecretKey(rng)
replicas[i] = keys[i].Public()
}
committee, err := types.NewRoundRobinElection(replicas, 0, genesisTime)
committee, err := types.NewRoundRobinElection(replicas)
if err != nil {
return nil, nil, fmt.Errorf("failed to build committee: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions sei-db/ledger_db/block/blocksim/resume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ func TestRecoverResumeState(t *testing.T) {

prevQC, ok := prev.Get()
require.True(t, ok, "recovered prev QC must be present")
require.Equal(t, last.first, prevQC.GlobalRange(committee).First, "recovered QC must be the last persisted QC")
require.Equal(t, last.next, prevQC.GlobalRange(committee).Next)
require.Equal(t, last.first, prevQC.GlobalRange().First, "recovered QC must be the last persisted QC")
require.Equal(t, last.next, prevQC.GlobalRange().Next)

// Empty-store sanity: a fresh dir recovers nothing.
empty, err := openBlockDB(&BlocksimConfig{Backend: "litt", DataDir: t.TempDir(), LittRetentionSeconds: 1})
Expand Down
7 changes: 7 additions & 0 deletions sei-tendermint/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ Within sei-tendermint subdirectory
* Avoid checking human readable error messages in tests. If programatic error type check is requested, use errors.Is/As/AsType instead.
* After introducing changes, you may Use `go test --count=0` to quickly check if tests compile. You may run `go test` to actually run the tests
afterwards. Prefer running modified tests selectively and run the whole test suite only if requested or looking for failures.
* Proto fields: use `optional` for all scalar and message fields (required for protobuf3 presence
detection and wireguard bounds checking). Annotate semantically required fields with `// required`
and truly optional fields with `// optional`. Example:
optional uint64 index = 1; // required
optional AppProposal app = 4; // optional
Required fields must be nil-checked at the boundary (constructor and proto Decode) and trusted
everywhere else — do not add defensive nil-checks in internal logic.
* Avoid removing comments and logs which are not obviously obsolete. Keep the original wording, only fixing mistakes or obsolete parts.
* TestRng instance should be one per test, constructed directly in the test function. In case of nested/table tests, each nested test should create its own instance.
Use TestRng.Split() (before spawning) if you need to pass entropy source to a spawned goroutine
Expand Down
24 changes: 18 additions & 6 deletions sei-tendermint/autobahn/types/app_proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ type AppProposal struct {
globalNumber GlobalBlockNumber
roadIndex RoadIndex
appHash AppHash
epochIndex EpochIndex
}

// NewAppProposal creates a new AppProposal.
func NewAppProposal(globalNumber GlobalBlockNumber, roadIndex RoadIndex, appHash AppHash) *AppProposal {
return &AppProposal{globalNumber: globalNumber, roadIndex: roadIndex, appHash: appHash}
func NewAppProposal(globalNumber GlobalBlockNumber, roadIndex RoadIndex, appHash AppHash, epochIndex EpochIndex) *AppProposal {
return &AppProposal{globalNumber: globalNumber, roadIndex: roadIndex, appHash: appHash, epochIndex: epochIndex}
}

// GlobalNumber .
Expand All @@ -34,19 +35,25 @@ func (m *AppProposal) RoadIndex() RoadIndex { return m.roadIndex }
// AppHash .
func (m *AppProposal) AppHash() AppHash { return m.appHash }

// EpochIndex returns the epoch this proposal belongs to.
func (m *AppProposal) EpochIndex() EpochIndex { return m.epochIndex }

// Next is the next global block number to compute AppHash for.
func (m *AppProposal) Next() RoadIndex {
return m.RoadIndex() + 1
}

// Verify verifies that the AppProposal is consistent with the CommitQC.
func (m *AppProposal) Verify(c *Committee, qc *CommitQC) error {
func (m *AppProposal) Verify(qc *CommitQC) error {
if got, want := m.RoadIndex(), qc.Proposal().Index(); got != want {
return fmt.Errorf("roadIndex() = %v, want %v", got, want)
}
if got, want := m.GlobalNumber(), qc.GlobalRange(c); got < want.First || got >= want.Next {
if got, want := m.GlobalNumber(), qc.GlobalRange(); got < want.First || got >= want.Next {
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
wen-coding marked this conversation as resolved.
Comment thread
wen-coding marked this conversation as resolved.
Comment thread
wen-coding marked this conversation as resolved.
return fmt.Errorf("globalNumber() = %v, want in range [%v,%v)", got, want.First, want.Next)
}
if got, want := m.EpochIndex(), qc.Proposal().EpochIndex(); got != want {
return fmt.Errorf("epoch_index = %d, want %d", got, want)
}
return nil
}

Expand All @@ -57,19 +64,24 @@ var AppProposalConv = protoutils.Conv[*AppProposal, *pb.AppProposal]{
GlobalNumber: utils.Alloc(uint64(m.globalNumber)),
RoadIndex: utils.Alloc(uint64(m.roadIndex)),
AppHash: m.appHash,
EpochIndex: utils.Alloc(uint64(m.epochIndex)),
}
},
Decode: func(m *pb.AppProposal) (*AppProposal, error) {
if m.GlobalNumber == nil {
return nil, fmt.Errorf("GlobalNumber: missing")
return nil, fmt.Errorf("global_number: missing")
}
if m.RoadIndex == nil {
return nil, fmt.Errorf("RoadIndex: missing")
return nil, fmt.Errorf("road_index: missing")
}
if m.EpochIndex == nil {
return nil, fmt.Errorf("epoch_index: missing")
}
return &AppProposal{
globalNumber: GlobalBlockNumber(*m.GlobalNumber),
roadIndex: RoadIndex(*m.RoadIndex),
appHash: AppHash(m.AppHash),
epochIndex: EpochIndex(*m.EpochIndex),
}, nil
},
}
25 changes: 14 additions & 11 deletions sei-tendermint/autobahn/types/commit_qc.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ func (m *CommitQC) LaneRange(lane LaneID) *LaneRange {
}

// GlobalRange returns the finalized global block range.
func (m *CommitQC) GlobalRange(c *Committee) GlobalRange {
return m.Proposal().GlobalRange(c)
func (m *CommitQC) GlobalRange() GlobalRange {
return m.Proposal().GlobalRange()
}

// Verify verifies the CommitQC against the committee.
// Currently it doesn't require the previous CommitQC.
func (m *CommitQC) Verify(c *Committee) error {
// Verify verifies the CommitQC against the epoch.
func (m *CommitQC) Verify(ep *Epoch) error {
if err := m.Proposal().Verify(ep); err != nil {
return err
}
c := ep.Committee()
Comment thread
cursor[bot] marked this conversation as resolved.
return m.vote.verifyQC(c, c.CommitQuorum(), m.sigs)
Comment thread
pompon0 marked this conversation as resolved.
}

Expand All @@ -60,7 +63,7 @@ type FullCommitQC struct {

// NewFullCommitQC constructs a new FullCommitQC.
func NewFullCommitQC(qc *CommitQC, headers []*BlockHeader) *FullCommitQC {
if got, want := len(headers), int(qc.Proposal().globalRangeWithoutOffset.Len()); got != want { //nolint:gosec // total lane range len is a small bounded value representing block count in a QC
if got, want := len(headers), int(qc.GlobalRange().Len()); got != want { //nolint:gosec // total lane range len is a small bounded value representing block count in a QC
panic(fmt.Sprintf("headers length %d != finalized blocks %d", got, want))
}
return &FullCommitQC{qc: qc, headers: headers}
Expand All @@ -77,16 +80,16 @@ func (m *FullCommitQC) Index() RoadIndex {
return m.qc.Index()
}

// Verify verifies the FullCommitQC against the committee.
func (m *FullCommitQC) Verify(c *Committee) error {
if err := m.qc.Verify(c); err != nil {
// Verify verifies the FullCommitQC against the epoch.
func (m *FullCommitQC) Verify(ep *Epoch) error {
if err := m.qc.Verify(ep); err != nil {
return fmt.Errorf("qC: %w", err)
}
n := uint64(0)
if want, got := int(m.qc.GlobalRange(c).Len()), len(m.headers); want != got { //nolint:gosec // global range len is a small bounded value representing block count in a QC
if want, got := int(m.qc.GlobalRange().Len()), len(m.headers); want != got { //nolint:gosec // global range len is a small bounded value representing block count in a QC
return fmt.Errorf("len(headers) = %d, want %d", got, want)
}
for lane := range c.Lanes().All() {
for lane := range ep.Committee().Lanes().All() {
Comment thread
wen-coding marked this conversation as resolved.
lr := m.qc.LaneRange(lane)
if lr.Len() == 0 {
continue
Expand Down
Loading
Loading