Skip to content

feat(taiko-client): lookahead sliding window #19322

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

Merged
merged 45 commits into from
Apr 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
c1e2580
lookahead sliding window
cyberhorsey Apr 22, 2025
91ff0fd
handover + split ranges
cyberhorsey Apr 22, 2025
e8ce751
lint
cyberhorsey Apr 22, 2025
6b7ad01
lint
cyberhorsey Apr 22, 2025
943d39d
lint + comment
cyberhorsey Apr 22, 2025
bda4978
lint
cyberhorsey Apr 22, 2025
f39fa48
skip if cant fetch current/next
cyberhorsey Apr 22, 2025
7cad33b
.
cyberhorsey Apr 22, 2025
045fa9e
miss continue
cyberhorsey Apr 22, 2025
1d6dc5a
push next epoch
cyberhorsey Apr 22, 2025
875173e
test for being next op
cyberhorsey Apr 22, 2025
8752bf6
comment
cyberhorsey Apr 22, 2025
e8edf18
add skip slots/slotsPerEpoch to the window
cyberhorsey Apr 22, 2025
3471d16
first slot should only be processed by the next operator bercause the…
cyberhorsey Apr 22, 2025
67e9a85
Trim
cyberhorsey Apr 22, 2025
fbeb8b4
use ToECDSA
cyberhorsey Apr 22, 2025
1599962
update tests
cyberhorsey Apr 22, 2025
bce7b9e
nob uild
cyberhorsey Apr 22, 2025
bb7f423
early return bug
cyberhorsey Apr 22, 2025
05f1ad5
remove specialc ase handling
cyberhorsey Apr 22, 2025
342f6d5
err
cyberhorsey Apr 22, 2025
0abe8f2
rm build
cyberhorsey Apr 22, 2025
75811b1
Tests
cyberhorsey Apr 22, 2025
4307c2a
bugfix + build
cyberhorsey Apr 22, 2025
ff7869f
remove error returns
cyberhorsey Apr 22, 2025
9c7b581
update tests
cyberhorsey Apr 22, 2025
b6cbbfc
err
cyberhorsey Apr 22, 2025
4ca5824
use masaya + fix test
cyberhorsey Apr 22, 2025
1ebf898
test: remove an unused var and use TestSuite
davidtaikocha Apr 23, 2025
6b44204
feat: fix two logs
davidtaikocha Apr 23, 2025
f764db6
Update packages/taiko-client/driver/driver.go
cyberhorsey Apr 23, 2025
d028eba
Update packages/taiko-client/driver/driver.go
cyberhorsey Apr 23, 2025
e8e63b5
Merge branch 'main' into lookahead_sliding_window
cyberhorsey Apr 23, 2025
0809de5
Update packages/taiko-client/driver/driver.go
cyberhorsey Apr 23, 2025
ea850a9
build
cyberhorsey Apr 23, 2025
c3996e5
fork height logs
cyberhorsey Apr 23, 2025
8e2f87e
Merge branch 'main' into lookahead_sliding_window
davidtaikocha Apr 24, 2025
c4f7e62
add some addtl logs
cyberhorsey Apr 24, 2025
c47465b
add some logs
cyberhorsey Apr 24, 2025
8ea6944
Merge branch 'lookahead_sliding_window' of github.com:taikoxyz/taiko-…
cyberhorsey Apr 24, 2025
046e479
no need tocheck fee recipient
cyberhorsey Apr 24, 2025
61436b3
fix tests
cyberhorsey Apr 24, 2025
9f57d1c
Merge branch 'main' into lookahead_sliding_window
davidtaikocha Apr 25, 2025
94aebd6
rm build
cyberhorsey Apr 25, 2025
d921918
Merge branch 'lookahead_sliding_window' of github.com:taikoxyz/taiko-…
cyberhorsey Apr 25, 2025
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 go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)

replace github.com/ethereum/go-ethereum v1.15.5 => github.com/taikoxyz/taiko-geth v1.12.1-0.20250311074847-364acd00d1f2
replace github.com/ethereum/go-ethereum v1.15.5 => github.com/taikoxyz/taiko-geth v0.0.0-20250422211912-3a4f3775cc30

replace github.com/ethereum-optimism/optimism v1.7.4 => github.com/taikoxyz/optimism v0.0.0-20250407113505-a4338a4857e6

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -878,8 +878,8 @@ github.com/taikoxyz/hive v0.0.0-20240827015317-405b241dd082 h1:ymZR+Y88LOnA8i3Ke
github.com/taikoxyz/hive v0.0.0-20240827015317-405b241dd082/go.mod h1:RHnIu3EFehrWX3JhFAMQSXD5uz7l0xaNroTzXrap7EQ=
github.com/taikoxyz/optimism v0.0.0-20250407113505-a4338a4857e6 h1:TZqB0xhP6eqTbuPSnMYB6md6EtGoYoYCMAK/v9HV1YM=
github.com/taikoxyz/optimism v0.0.0-20250407113505-a4338a4857e6/go.mod h1:V0VCkKtCzuaJH6qcL75SRcbdlakM9LhurMEJUhO6VXA=
github.com/taikoxyz/taiko-geth v1.12.1-0.20250311074847-364acd00d1f2 h1:MnjxihB0mYW3MMUXkDiwWPJVyfHKtYY+4S+Dmaz2hew=
github.com/taikoxyz/taiko-geth v1.12.1-0.20250311074847-364acd00d1f2/go.mod h1:1LG2LnMOx2yPRHR/S+xuipXH29vPr6BIH6GElD8N/fo=
github.com/taikoxyz/taiko-geth v0.0.0-20250422211912-3a4f3775cc30 h1:zduJWfvVFUMoSiBQ2SjlmhckQY3X+XkdtHitooaHpKg=
github.com/taikoxyz/taiko-geth v0.0.0-20250422211912-3a4f3775cc30/go.mod h1:1LG2LnMOx2yPRHR/S+xuipXH29vPr6BIH6GElD8N/fo=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
Expand Down
14 changes: 14 additions & 0 deletions packages/taiko-client/driver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"net/url"
"time"

p2pFlags "github.com/ethereum-optimism/optimism/op-node/flags"
"github.com/ethereum-optimism/optimism/op-node/p2p"
p2pCli "github.com/ethereum-optimism/optimism/op-node/p2p/cli"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"

Expand All @@ -32,6 +34,7 @@ type Config struct {
P2PConfigs *p2p.Config
P2PSignerConfigs p2p.SignerSetup
PreconfHandoverSkipSlots uint64
PreconfOperatorAddress common.Address
}

// NewConfigFromCliContext creates a new config instance from
Expand Down Expand Up @@ -124,6 +127,16 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) {
)
}

var preconfOperatorAddress common.Address
if c.IsSet(p2pFlags.SequencerP2PKeyName) {
sequencerP2PKey, err := crypto.ToECDSA(common.FromHex(c.String(p2pFlags.SequencerP2PKeyName)))
if err != nil {
return nil, err
}

preconfOperatorAddress = crypto.PubkeyToAddress(sequencerP2PKey.PublicKey)
}

return &Config{
ClientConfig: clientConfig,
RetryInterval: c.Duration(flags.BackOffRetryInterval.Name),
Expand All @@ -136,5 +149,6 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) {
P2PConfigs: p2pConfigs,
P2PSignerConfigs: signerConfigs,
PreconfHandoverSkipSlots: preconfHandoverSkipSlots,
PreconfOperatorAddress: preconfOperatorAddress,
}, nil
}
62 changes: 40 additions & 22 deletions packages/taiko-client/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,23 +379,30 @@ func (d *Driver) cacheLookaheadLoop() {
var (
seenBlockNumber uint64 = 0
lastSlot uint64 = 0
opWin = preconfBlocks.NewOpWindow(
d.PreconfHandoverSkipSlots,
d.rpc.L1Beacon.SlotsPerEpoch,
)
)

for {
select {
case <-d.ctx.Done():
return
case <-ticker.C:
currentSlot := d.rpc.L1Beacon.CurrentSlot()
var (
currentEpoch = d.rpc.L1Beacon.CurrentEpoch()
currentSlot = d.rpc.L1Beacon.CurrentSlot()
slotInEpoch = d.rpc.L1Beacon.SlotInEpoch()
slotsLeftInEpoch = d.rpc.L1Beacon.SlotsPerEpoch - d.rpc.L1Beacon.SlotInEpoch()
)

latestSeenBlockNumber, err := d.rpc.L1.BlockNumber(d.ctx)
if err != nil {
log.Error("Failed to fetch the latest L1 head for lookahead", "error", err)
continue
}

// Avoid fetching on missed slots, otherwise the previous "current" can get tagged as
// the new "current" for this epoch.
if latestSeenBlockNumber == seenBlockNumber {
// Leave some grace period for the block to arrive.
if lastSlot != currentSlot &&
Expand All @@ -412,39 +419,50 @@ func (d *Driver) cacheLookaheadLoop() {
continue
}

seenBlockNumber = latestSeenBlockNumber
lastSlot = currentSlot
seenBlockNumber = latestSeenBlockNumber

var (
currentEpoch = d.rpc.L1Beacon.CurrentEpoch()
remainingSlots = d.rpc.L1Beacon.SlotsPerEpoch - d.rpc.L1Beacon.SlotInEpoch()
)

currentOperatorAddress, err := d.rpc.GetPreconfWhiteListOperator(nil)
currOp, err := d.rpc.GetPreconfWhiteListOperator(nil)
if err != nil {
log.Warn("Failed to get current preconf whitelist operator address", "error", err)
log.Warn("Could not fetch current operator", "err", err)
continue
}

nextOperatorAddress, err := d.rpc.GetNextPreconfWhiteListOperator(nil)
nextOp, err := d.rpc.GetNextPreconfWhiteListOperator(nil)
if err != nil {
log.Warn("Failed to get next preconf whitelist operator address", "error", err)
nextOperatorAddress = rpc.ZeroAddress
log.Warn("Could not fetch next operator", "err", err)
continue
}

// Update the lookahead information for the preconfirmation
// block server.
// push into our 3‑epoch ring
opWin.Push(currentEpoch, currOp, nextOp)

// Push next epoch (nextOp becomes currOp at next epoch)
opWin.Push(currentEpoch+1, nextOp, common.Address{}) // we don't know next-next-op, safe to leave zero

var (
currRanges = opWin.SequencingWindowSplit(d.PreconfOperatorAddress, true)
nextRanges = opWin.SequencingWindowSplit(d.PreconfOperatorAddress, false)
)

d.preconfBlockServer.UpdateLookahead(&preconfBlocks.Lookahead{
CurrOperator: currentOperatorAddress,
NextOperator: nextOperatorAddress,
CurrOperator: currOp,
NextOperator: nextOp,
CurrRanges: currRanges,
NextRanges: nextRanges,
UpdatedAt: time.Now().UTC(),
})

log.Debug("Lookahead information",
"remainingSlots", remainingSlots,
log.Info(
"Lookahead information refreshed",
"currentSlot", currentSlot,
"currentEpoch", currentEpoch,
"currentOperator", currentOperatorAddress.Hex(),
"nextOperator", nextOperatorAddress.Hex(),
"slotsLeftInEpoch", slotsLeftInEpoch,
"slotInEpoch", slotInEpoch,
"currOp", currOp.Hex(),
"nextOp", nextOp.Hex(),
"currRanges", currRanges,
"nextRanges", nextRanges,
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/taiko-client/driver/preconf_blocks/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (s *PreconfBlockAPIServer) BuildPreconfBlock(c echo.Context) error {

// Check if the fee recipient the current operator or the next operator if its in handover window.
if s.rpc.L1Beacon != nil {
if err := s.checkLookaheadHandover(reqBody.ExecutableData.FeeRecipient, s.rpc.L1Beacon.SlotInEpoch()); err != nil {
if err := s.checkLookaheadHandover(reqBody.ExecutableData.FeeRecipient, s.rpc.L1Beacon.CurrentSlot()); err != nil {
return s.returnError(c, http.StatusBadRequest, err)
}
}
Expand Down
98 changes: 97 additions & 1 deletion packages/taiko-client/driver/preconf_blocks/lookahead.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,110 @@
package preconfblocks

import (
"sort"
"time"

"github.com/ethereum/go-ethereum/common"
)

// Lookahead represents the lookahead information in the current beacon consensus.
// SlotRange represents a half‑open [Start,End) range of L1 slots.
type SlotRange struct {
Start uint64
End uint64
}

// mergeRanges coalesces overlapping or adjacent SlotRanges.
func mergeRanges(r []SlotRange) []SlotRange {
if len(r) == 0 {
return r
}

sort.Slice(r, func(i, j int) bool {
return r[i].Start < r[j].Start
})

out := []SlotRange{r[0]}

for _, rng := range r[1:] {
last := &out[len(out)-1]

if rng.Start <= last.End {
if rng.End > last.End {
last.End = rng.End
}
} else {
out = append(out, rng)
}
}
return out
}

// opWindow holds the last three epochs’ operator addresses.
type opWindow struct {
epochs [3]uint64
currOps [3]common.Address
nextOps [3]common.Address
valid [3]bool
handoverSkipSlots uint64
slotsPerEpoch uint64
}

// NewOpWindow creates a new opWindow instance.
func NewOpWindow(handoverSkipSlots, slotsPerEpoch uint64) *opWindow {
return &opWindow{handoverSkipSlots: handoverSkipSlots, slotsPerEpoch: slotsPerEpoch}
}

// Push records the operator pair for one epoch into the ring.
func (w *opWindow) Push(epoch uint64, curr, next common.Address) {
idx := int(epoch % 3)

w.epochs[idx] = epoch

w.currOps[idx] = curr

w.nextOps[idx] = next

w.valid[idx] = true
}

// SequencingWindowSplit creates a slot range for either the current or the next operator
func (w *opWindow) SequencingWindowSplit(operator common.Address, curr bool) []SlotRange {
var ranges []SlotRange
threshold := w.slotsPerEpoch - w.handoverSkipSlots

for i := 0; i < 3; i++ {
if !w.valid[i] {
continue
}

epoch := w.epochs[i]
startEpoch := epoch * w.slotsPerEpoch

if curr {
if w.currOps[i] == operator {
ranges = append(ranges, SlotRange{
Start: startEpoch,
End: startEpoch + threshold,
})
}
} else {
if w.nextOps[i] == operator {
ranges = append(ranges, SlotRange{
Start: startEpoch + threshold,
End: (epoch + 1) * w.slotsPerEpoch,
})
}
}
}

return mergeRanges(ranges)
}

// Lookahead holds the up‑to‑date sequencing window and operator addrs.
type Lookahead struct {
CurrOperator common.Address
NextOperator common.Address
CurrRanges []SlotRange // slots allowed for CurrOperator (0..threshold-1)
NextRanges []SlotRange // slots allowed for NextOperator (threshold..slotsPerEpoch-1)
UpdatedAt time.Time
}
94 changes: 94 additions & 0 deletions packages/taiko-client/driver/preconf_blocks/lookahead_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package preconfblocks

import (
"reflect"

"github.com/ethereum/go-ethereum/common"
)

func (s *PreconfBlockAPIServerTestSuite) TestLookheadMergeRangesEmpty() {
s.Len(mergeRanges([]SlotRange{}), 0, "expected empty slice")
}

func (s *PreconfBlockAPIServerTestSuite) TestLookheadMergeRangesNonOverlapping() {
input := []SlotRange{{Start: 0, End: 5}, {Start: 10, End: 15}}
got := mergeRanges(input)
s.True(reflect.DeepEqual(got, input), "expected %v, got %v", input, got)
}

func (s *PreconfBlockAPIServerTestSuite) TestLookheadMergeRangesOverlapping() {
input := []SlotRange{{Start: 0, End: 5}, {Start: 3, End: 10}, {Start: 8, End: 12}}
want := []SlotRange{{Start: 0, End: 12}}
got := mergeRanges(input)
s.True(reflect.DeepEqual(got, want), "expected %v, got %v", want, got)
}

func (s *PreconfBlockAPIServerTestSuite) TestLookheadMergeRangesAdjacent() {
input := []SlotRange{{Start: 0, End: 5}, {Start: 5, End: 8}}
want := []SlotRange{{Start: 0, End: 8}}
got := mergeRanges(input)
s.True(reflect.DeepEqual(got, want), "expected %v, got %v", want, got)
}

func (s *PreconfBlockAPIServerTestSuite) TestLookheadSequencingWindowSplit() {
handoverSlots := uint64(4)
slotsPerEpoch := uint64(32)

w := NewOpWindow(handoverSlots, slotsPerEpoch)
addr := common.HexToAddress("0xabc")
other := common.HexToAddress("0xdef")
w.Push(0, addr, other) // addr is curr at epoch 0
w.Push(1, other, addr) // addr is next at epoch 1

currRanges := w.SequencingWindowSplit(addr, true)
nextRanges := w.SequencingWindowSplit(addr, false)

s.True(reflect.DeepEqual(currRanges, []SlotRange{{Start: 0, End: 28}}), "currRanges = %v", currRanges)
s.True(reflect.DeepEqual(nextRanges, []SlotRange{{Start: 60, End: 64}}), "nextRanges = %v", nextRanges)
}

func (s *PreconfBlockAPIServerTestSuite) TestLookheadSequencingWindowSplitWithDualEpochPush() {
handoverSlots := uint64(4)
slotsPerEpoch := uint64(32)

w := NewOpWindow(handoverSlots, slotsPerEpoch)
addr := common.HexToAddress("0xabc")
other := common.HexToAddress("0xdef")
w.Push(0, addr, other) // addr is curr at epoch 0
w.Push(1, other, addr) // addr is next at epoch 1
w.Push(2, addr, other) // addr is curr again at epoch 2

currRanges := w.SequencingWindowSplit(addr, true)
nextRanges := w.SequencingWindowSplit(addr, false)

s.True(reflect.DeepEqual(currRanges, []SlotRange{
{Start: 0, End: 28},
{Start: 64, End: 92},
}), "currRanges = %v", currRanges)

s.True(reflect.DeepEqual(nextRanges, []SlotRange{
{Start: 60, End: 64},
}), "nextRanges = %v", nextRanges)
}

func (s *PreconfBlockAPIServerTestSuite) TestLookheadSequencingWindowSplitCurrRange() {
handoverSlots := uint64(4)
slotsPerEpoch := uint64(32)

w := NewOpWindow(handoverSlots, slotsPerEpoch)
addr := common.HexToAddress("0xabc")
w.Push(0, addr, addr)
w.Push(1, addr, common.Address{})

currRanges := w.SequencingWindowSplit(addr, true)
nextRanges := w.SequencingWindowSplit(addr, false)

s.True(reflect.DeepEqual(currRanges, []SlotRange{
{Start: 0, End: 28},
{Start: 32, End: 60},
}), "currRanges = %v", currRanges)

s.True(reflect.DeepEqual(nextRanges, []SlotRange{
{Start: 28, End: 32},
}), "nextRanges = %v", nextRanges)
}
Loading