-
Notifications
You must be signed in to change notification settings - Fork 887
feat(autobahn): multi-epoch window for CommitQC/AppQC flow (CON-358) #3736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
937ae20
2f4c37f
b383ba5
ccb2bb4
8e8d4b3
9fca133
428fe23
91801ca
c953b49
1e5cfd1
32b3dec
2595936
9bdcedc
ef1e401
eedf9d4
c8eb573
78864bb
ea9db9f
d1b3298
72b82df
f761b3c
2ca409f
55d8c2e
30422be
289134d
05e97b2
f85014c
018bc0c
f319c4c
a646f4e
16f6558
8646e34
e2c87f2
eb64d08
a2827c2
7f22c11
3c0ad6e
8b2d661
d0f5388
89077db
483b9f4
d32df83
c5ad50c
7126aee
41fe6f6
6b11740
883d9a9
d3d4e8b
50f5ea1
0448f7b
3438dc1
49d2e5c
afcd76a
285ff7c
f8f4832
edeaec7
c82809f
0299d04
1c71d0d
ade98c6
a46ffa0
0d9e87b
8228350
c19c75b
5ead0ee
99a4ee8
efcc631
1d7e82f
a44607e
f16bb0e
0db0b60
3f7edd5
a05f2da
424d5d6
b759b0e
53a81b8
8215ee9
cbdeb36
61cc882
dd6d4a8
c986e78
ab8d98e
d6bfd73
4ca12c3
6e08874
50c9088
f43df2f
5e1db10
c091122
edcad5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ Within sei-tendermint subdirectory | |
| 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. | ||
| * Prefer concise comments that state invariants, contracts, and observable behavior (plus brief correctness reasoning when non-obvious). Avoid narrating how the implementation works step-by-step. | ||
|
wen-coding marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Codex flagged this line as a possible prompt-injection attempt ("reviewer-directed instructions inside the untrusted PR diff"). I disagree: this is a normal contributor-guide edit — a comment-style guideline consistent with the surrounding bullets — not an instruction directed at a reviewer to change a verdict or bypass process. No action needed; noting for the record since it was raised by the second-opinion pass. |
||
| * 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 | ||
| to ensure deterministic entropy across the goroutines. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| package types | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" | ||
| ) | ||
|
|
||
| // EpochDuo is a sliding window of up to two consecutive epochs. | ||
| // Current is always set; Prev is absent only for epoch 0. | ||
| // | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This is the crux of Codex's P1: the PR title/description describe an |
||
| // Next is intentionally not held: new-committee lane traffic is admitted only | ||
| // after CommitQC advances Current into the next epoch. | ||
| type EpochDuo struct { | ||
| Prev utils.Option[*Epoch] // absent if Current is epoch 0 | ||
| Current *Epoch | ||
| } | ||
|
|
||
| // ErrRoadBeforeWindow is returned by EpochForRoad when the road is older than | ||
| // WindowFirst (behind the duo). | ||
| var ErrRoadBeforeWindow = errors.New("road before epoch duo window") | ||
|
|
||
| // ErrRoadAfterWindow is returned by EpochForRoad when the road is newer than | ||
| // Current (at or past Current.Next). | ||
| var ErrRoadAfterWindow = errors.New("road after epoch duo window") | ||
|
|
||
| func (w EpochDuo) all() [2]utils.Option[*Epoch] { | ||
| return [2]utils.Option[*Epoch]{utils.Some(w.Current), w.Prev} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] |
||
| } | ||
|
|
||
| // EpochForRoad returns the epoch whose road range contains roadIdx. | ||
| // Prefers Current so an open-range Prev cannot shadow later epochs. | ||
| // Returns ErrRoadBeforeWindow or ErrRoadAfterWindow when out of window. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] The doc promises "Prefers Current so an open-range Prev cannot shadow later epochs" and to return |
||
| func (w EpochDuo) EpochForRoad(roadIdx RoadIndex) (*Epoch, error) { | ||
| for _, oep := range w.all() { | ||
| if ep, ok := oep.Get(); ok && ep.RoadRange().Has(roadIdx) { | ||
| return ep, nil | ||
| } | ||
| } | ||
| if roadIdx < w.WindowFirst() { | ||
| return nil, fmt.Errorf("road %d before window %v: %w", roadIdx, w, ErrRoadBeforeWindow) | ||
| } | ||
| return nil, fmt.Errorf("road %d after window %v: %w", roadIdx, w, ErrRoadAfterWindow) | ||
| } | ||
|
|
||
| // EpochOptForRoad is EpochForRoad as an Option (None when out of window). | ||
| func (w EpochDuo) EpochOptForRoad(roadIdx RoadIndex) utils.Option[*Epoch] { | ||
| if ep, err := w.EpochForRoad(roadIdx); err == nil { | ||
| return utils.Some(ep) | ||
| } | ||
| return utils.None[*Epoch]() | ||
| } | ||
|
|
||
| // CurrentForRoad returns Current when roadIdx is in Current's range; else None. | ||
| // Unlike EpochOptForRoad, Prev is never admitted. | ||
| func (w EpochDuo) CurrentForRoad(roadIdx RoadIndex) utils.Option[*Epoch] { | ||
| if w.Current != nil && w.Current.RoadRange().Has(roadIdx) { | ||
| return utils.Some(w.Current) | ||
| } | ||
| return utils.None[*Epoch]() | ||
| } | ||
|
|
||
| // WindowFirst is the earliest road still in Prev|Current. | ||
| func (w EpochDuo) WindowFirst() RoadIndex { | ||
| if prev, ok := w.Prev.Get(); ok { | ||
| return prev.RoadRange().First | ||
| } | ||
| return w.Current.RoadRange().First | ||
| } | ||
|
|
||
| // EpochForIndex returns Current or Prev by epoch index. | ||
| func (w EpochDuo) EpochForIndex(idx EpochIndex) (*Epoch, error) { | ||
| if w.Current != nil && w.Current.EpochIndex() == idx { | ||
| return w.Current, nil | ||
| } | ||
| if prev, ok := w.Prev.Get(); ok && prev.EpochIndex() == idx { | ||
| return prev, nil | ||
| } | ||
| return nil, fmt.Errorf("epoch %d not in window %v", idx, w) | ||
| } | ||
|
Comment on lines
+57
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 In Extended reasoning...
Despite that, No code path can actually produce an The Step-by-step: (1) Fix: drop |
||
|
|
||
| // String returns a compact description of the epoch indices in the window. | ||
| func (w EpochDuo) String() string { | ||
| s := "epochs [" | ||
| sep := "" | ||
| for _, oep := range w.all() { | ||
| if ep, ok := oep.Get(); ok { | ||
| s += fmt.Sprintf("%s%d", sep, ep.EpochIndex()) | ||
| sep = ", " | ||
| } | ||
| } | ||
| return s + "]" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| package types_test | ||
|
|
||
| import ( | ||
| "errors" | ||
| "testing" | ||
|
|
||
| "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types" | ||
| "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" | ||
| ) | ||
|
|
||
| func testDuoEpochs(t *testing.T) (prev, current *types.Epoch) { | ||
| t.Helper() | ||
| rng := utils.TestRng() | ||
| weights := map[types.PublicKey]uint64{} | ||
| for range 3 { | ||
| weights[types.GenSecretKey(rng).Public()] = 1 | ||
| } | ||
| committee := utils.OrPanic1(types.NewCommittee(weights)) | ||
| prev = types.NewEpoch(0, types.RoadRange{First: 0, Next: 100}, utils.GenTimestamp(rng), committee, 1) | ||
| current = types.NewEpoch(1, types.RoadRange{First: 100, Next: 200}, utils.GenTimestamp(rng), committee, 101) | ||
| return prev, current | ||
| } | ||
|
|
||
| func TestEpochForRoad_HitsCurrentEpoch(t *testing.T) { | ||
| _, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Current: current} | ||
| ep, err := w.EpochForRoad(150) | ||
| if err != nil { | ||
| t.Fatalf("EpochForRoad(150): %v", err) | ||
| } | ||
| if ep != current { | ||
| t.Fatalf("got %v, want current", ep) | ||
| } | ||
| } | ||
|
|
||
| func TestEpochForRoad_HitsPrevEpoch(t *testing.T) { | ||
| prev, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Prev: utils.Some(prev), Current: current} | ||
| ep, err := w.EpochForRoad(50) | ||
| if err != nil { | ||
| t.Fatalf("EpochForRoad(50): %v", err) | ||
| } | ||
| if ep != prev { | ||
| t.Fatalf("got %v, want prev", ep) | ||
| } | ||
| } | ||
|
|
||
| func TestEpochForRoad_OutsideWindowReturnsError(t *testing.T) { | ||
| _, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Current: current} | ||
| _, err := w.EpochForRoad(999) | ||
| if !errors.Is(err, types.ErrRoadAfterWindow) { | ||
| t.Fatalf("EpochForRoad(999) = %v, want ErrRoadAfterWindow", err) | ||
| } | ||
| _, err = w.EpochForRoad(50) | ||
| if !errors.Is(err, types.ErrRoadBeforeWindow) { | ||
| t.Fatalf("EpochForRoad(50) current-only = %v, want ErrRoadBeforeWindow", err) | ||
| } | ||
| } | ||
|
|
||
| func TestEpochForRoad_OpenRangePrevDoesNotMaskCurrent(t *testing.T) { | ||
| rng := utils.TestRng() | ||
| weights := map[types.PublicKey]uint64{types.GenSecretKey(rng).Public(): 1} | ||
| committee := utils.OrPanic1(types.NewCommittee(weights)) | ||
| openEpoch := types.NewEpoch(0, types.OpenRoadRange(), utils.GenTimestamp(rng), committee, 1) | ||
| current := types.NewEpoch(1, types.RoadRange{First: 100, Next: 200}, utils.GenTimestamp(rng), committee, 101) | ||
| w := types.EpochDuo{Prev: utils.Some(openEpoch), Current: current} | ||
| ep, err := w.EpochForRoad(150) | ||
| if err != nil { | ||
| t.Fatalf("EpochForRoad(150): %v", err) | ||
| } | ||
| if ep.EpochIndex() != current.EpochIndex() { | ||
| t.Fatalf("got epoch %d (Prev with OpenRoadRange masked Current), want current (%d)", | ||
| ep.EpochIndex(), current.EpochIndex()) | ||
| } | ||
| } | ||
|
|
||
| func TestEpochForRoad_AbsentPrevSkipped(t *testing.T) { | ||
| _, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Current: current} | ||
| _, err := w.EpochForRoad(50) | ||
| if !errors.Is(err, types.ErrRoadBeforeWindow) { | ||
| t.Fatalf("EpochForRoad(50) with absent Prev = %v, want ErrRoadBeforeWindow", err) | ||
| } | ||
| } | ||
|
|
||
| func TestEpochForRoad_BeforeAndAfterWithPrev(t *testing.T) { | ||
| prev, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Prev: utils.Some(prev), Current: current} | ||
| if _, err := w.EpochForRoad(50); err != nil { | ||
| t.Fatalf("EpochForRoad(50) in prev: %v", err) | ||
| } | ||
| if _, err := w.EpochForRoad(150); err != nil { | ||
| t.Fatalf("EpochForRoad(150) in current: %v", err) | ||
| } | ||
| _, err := w.EpochForRoad(200) | ||
| if !errors.Is(err, types.ErrRoadAfterWindow) { | ||
| t.Fatalf("EpochForRoad(200) = %v, want ErrRoadAfterWindow", err) | ||
| } | ||
| } | ||
|
|
||
| func TestWindowFirst_WithPrev(t *testing.T) { | ||
| prev, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Prev: utils.Some(prev), Current: current} | ||
| if got, want := w.WindowFirst(), prev.RoadRange().First; got != want { | ||
| t.Fatalf("WindowFirst() = %d, want %d", got, want) | ||
| } | ||
| } | ||
|
|
||
| func TestWindowFirst_CurrentOnly(t *testing.T) { | ||
| _, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Current: current} | ||
| if got, want := w.WindowFirst(), current.RoadRange().First; got != want { | ||
| t.Fatalf("WindowFirst() = %d, want %d", got, want) | ||
| } | ||
| } | ||
|
|
||
| func TestEpochOptForRoad(t *testing.T) { | ||
| prev, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Prev: utils.Some(prev), Current: current} | ||
| if ep, ok := w.EpochOptForRoad(50).Get(); !ok || ep != prev { | ||
| t.Fatalf("EpochOptForRoad(50) = %v, want prev", ep) | ||
| } | ||
| if w.EpochOptForRoad(999).IsPresent() { | ||
| t.Fatal("EpochOptForRoad(999) should be None") | ||
| } | ||
| } | ||
|
|
||
| func TestCurrentForRoad(t *testing.T) { | ||
| prev, current := testDuoEpochs(t) | ||
| w := types.EpochDuo{Prev: utils.Some(prev), Current: current} | ||
| if ep, ok := w.CurrentForRoad(150).Get(); !ok || ep != current { | ||
| t.Fatalf("CurrentForRoad(150) = %v, want current", ep) | ||
| } | ||
| if w.CurrentForRoad(50).IsPresent() { | ||
| t.Fatal("CurrentForRoad(50) must not admit Prev") | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Codex flagged this line as a prompt-injection vector to be removed. I disagree: it's a benign, on-topic style guideline consistent with the comment cleanups elsewhere in this PR, with no instruction directed at a reviewer/agent. Recommend keeping it. (Flagging here only to record the resolution of Codex's point.)