Skip to content

Commit 828c2dc

Browse files
committed
op-batcher: propagate clock.Clock to gossip builder check. refactor MaxChannelDuration
1 parent b64ead9 commit 828c2dc

File tree

24 files changed

+581
-127
lines changed

24 files changed

+581
-127
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package batcher
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/davecgh/go-spew/spew"
8+
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
9+
"github.com/ethereum-optimism/optimism/op-devstack/dsl"
10+
"github.com/ethereum-optimism/optimism/op-devstack/presets"
11+
"github.com/ethereum-optimism/optimism/op-devstack/stack"
12+
"github.com/ethereum-optimism/optimism/op-devstack/stack/match"
13+
"github.com/ethereum-optimism/optimism/op-service/apis"
14+
"github.com/ethereum-optimism/optimism/op-service/eth"
15+
"github.com/ethereum-optimism/optimism/op-service/txplan"
16+
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
17+
"github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes"
18+
"github.com/ethereum/go-ethereum/common"
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
func TestBatcherFullChannelsAfterDowntime(gt *testing.T) {
23+
t := devtest.SerialT(gt)
24+
sys := presets.NewSingleChainMultiNodeWithTestSeq(t)
25+
l := t.Logger()
26+
ts_L2 := sys.TestSequencer.Escape().ControlAPI(sys.L2EL.ChainID())
27+
ts_L1 := sys.TestSequencer.Escape().ControlAPI(sys.L1Network.ChainID())
28+
29+
alice := sys.FunderL2.NewFundedEOA(eth.OneWei)
30+
cathrine := sys.FunderL2.NewFundedEOA(eth.OneTenthEther)
31+
32+
cl := sys.L1Network.Escape().L1CLNode(match.FirstL1CL)
33+
34+
sys.ControlPlane.FakePoSState(cl.ID(), stack.Stop)
35+
36+
latestUnsafe_A := sys.L2CL.StopSequencer()
37+
l.Info("Latest unsafe block after stopping the L2 sequencer", "latestUnsafe", latestUnsafe_A)
38+
39+
parent := latestUnsafe_A
40+
for j := 0; j < 200; j++ {
41+
l1Origin := sys.L1EL.BlockRefByLabel(eth.Unsafe).Hash
42+
43+
for i := 0; i < 5; i++ {
44+
l.Debug("Sequencing L2 block", "iteration", i, "parent", parent)
45+
sequenceBlockWithL1Origin(t, ts_L2, parent, l1Origin, alice, cathrine)
46+
47+
parent = sys.L2CL.HeadBlockRef(types.LocalUnsafe).Hash
48+
49+
sys.AdvanceTime(time.Second * 2)
50+
}
51+
52+
l.Debug("Sequencing L1 block", "iteration_j", j)
53+
sequenceBlock(t, ts_L1, common.Hash{})
54+
}
55+
56+
sys.L2CL.StartSequencer()
57+
58+
l.Info("Current L1 unsafe block", "currentL1Unsafe", sys.L1EL.BlockRefByLabel(eth.Unsafe))
59+
sys.ControlPlane.FakePoSState(cl.ID(), stack.Start)
60+
61+
sys.L2Batcher.Start()
62+
63+
channels, channelFrames, l2Txs := sys.L2Chain.DeriveData(4) // over the next 4 blocks, collect batches/channels/frames submitted by the batcher on the L1 network, and parse them
64+
{
65+
for _, c := range channels {
66+
l.Info("Channel details", "channelID", c.String(), "frameCount", len(channelFrames[c]), "dataLength_frame0", len(channelFrames[c][0].Data))
67+
}
68+
69+
require.Equal(t, 10, len(channels)) // we expect a total of 10 channels, due to existing MaxChannelDuration bug filed at: https://github.com/ethereum-optimism/optimism/issues/18092
70+
71+
// values are dependent on:
72+
// - MaxPendingTransactions
73+
// - number of blocks and transactions sent in the test - 1000 L2 blocks with 1 transaction from cathrine to alice
74+
// - MaxL1TxSize (this is set to 40_000 bytes for test purposes)
75+
sizeRanges := []struct {
76+
min int
77+
max int
78+
note string
79+
}{
80+
{min: 7_000, max: 10_000, note: "channel 0 - the first 100 blocks"},
81+
{min: 100, max: 1000, note: "channel 1 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
82+
{min: 100, max: 1000, note: "channel 2 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
83+
{min: 100, max: 1000, note: "channel 3 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
84+
{min: 100, max: 1000, note: "channel 4 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
85+
{min: 100, max: 1000, note: "channel 5 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
86+
{min: 100, max: 1000, note: "channel 6 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
87+
{min: 100, max: 1000, note: "channel 7 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
88+
{min: 20_000, max: 40_000, note: "channel 8 - filled to the max capacity, due to blocking behavior because of MaxPendingTransactions limit reached"},
89+
{min: 20_000, max: 40_000, note: "channel 9 - filled to the max capacity, due to blocking behavior because of MaxPendingTransactions limit reached"},
90+
}
91+
92+
for i, entry := range sizeRanges {
93+
require.LessOrEqual(t, len(channelFrames[channels[i]][0].Data), entry.max, entry.note)
94+
require.GreaterOrEqual(t, len(channelFrames[channels[i]][0].Data), entry.min, entry.note)
95+
}
96+
97+
require.Equal(t, len(l2Txs[cathrine.Address()]), 1000) // we expect 1000 transactions total sent from cathrine to alice
98+
}
99+
100+
//sys.L2Chain.PrintChain()
101+
//sys.L1Network.PrintChain()
102+
103+
status := sys.L2CL.SyncStatus()
104+
spew.Dump(status)
105+
}
106+
107+
func sequenceBlock(t devtest.T, ts apis.TestSequencerControlAPI, parent common.Hash) {
108+
require.NoError(t, ts.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent}))
109+
require.NoError(t, ts.Next(t.Ctx()))
110+
}
111+
112+
func sequenceBlockWithL1Origin(t devtest.T, ts apis.TestSequencerControlAPI, parent common.Hash, l1Origin common.Hash, alice *dsl.EOA, cathrine *dsl.EOA) {
113+
require.NoError(t, ts.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent, L1Origin: &l1Origin}))
114+
115+
// include simple transfer tx in opened block
116+
{
117+
to := cathrine.PlanTransfer(alice.Address(), eth.OneWei)
118+
opt := txplan.Combine(to)
119+
ptx := txplan.NewPlannedTx(opt)
120+
signed_tx, err := ptx.Signed.Eval(t.Ctx())
121+
require.NoError(t, err, "Expected to be able to evaluate a planned transaction on op-test-sequencer, but got error")
122+
txdata, err := signed_tx.MarshalBinary()
123+
require.NoError(t, err, "Expected to be able to marshal a signed transaction on op-test-sequencer, but got error")
124+
125+
err = ts.IncludeTx(t.Ctx(), txdata)
126+
require.NoError(t, err, "Expected to be able to include a signed transaction on op-test-sequencer, but got error")
127+
}
128+
129+
require.NoError(t, ts.Next(t.Ctx()))
130+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package batcher
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
bss "github.com/ethereum-optimism/optimism/op-batcher/batcher"
8+
"github.com/ethereum-optimism/optimism/op-devstack/compat"
9+
"github.com/ethereum-optimism/optimism/op-devstack/presets"
10+
"github.com/ethereum-optimism/optimism/op-devstack/stack"
11+
"github.com/ethereum-optimism/optimism/op-devstack/sysgo"
12+
)
13+
14+
func TestMain(m *testing.M) {
15+
presets.DoMain(m, presets.WithSingleChainMultiNode(),
16+
presets.WithExecutionLayerSyncOnVerifiers(),
17+
presets.WithCompatibleTypes(compat.SysGo),
18+
presets.WithNoDiscovery(),
19+
presets.WithTimeTravel(),
20+
stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) {
21+
cfg.Stopped = true
22+
23+
// set the blob max size to 40_000 bytes for test purposes
24+
cfg.MaxL1TxSize = 40_000
25+
cfg.TestUseMaxTxSizeForBlobs = true
26+
27+
cfg.PollInterval = 1000 * time.Millisecond
28+
29+
cfg.MaxChannelDuration = 50 // not sure if this is reasonable, or should be larger?
30+
cfg.MaxPendingTransactions = 7
31+
})),
32+
)
33+
}

op-batcher/batcher/channel.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type channel struct {
2727
}
2828

2929
func newChannel(log log.Logger, metr metrics.Metricer, cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64, channelOut derive.ChannelOut) *channel {
30-
cb := NewChannelBuilderWithChannelOut(cfg, rollupCfg, latestL1OriginBlockNum, channelOut)
30+
cb := NewChannelBuilderWithChannelOut(log, cfg, rollupCfg, latestL1OriginBlockNum, channelOut)
3131
return &channel{
3232
ChannelBuilder: cb,
3333
log: log,

op-batcher/batcher/channel_builder.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
1212
"github.com/ethereum-optimism/optimism/op-service/eth"
1313
"github.com/ethereum-optimism/optimism/op-service/queue"
14+
"github.com/ethereum/go-ethereum/log"
1415
)
1516

1617
var (
@@ -47,6 +48,7 @@ type frameData struct {
4748
// ChannelBuilder uses a ChannelOut to create a channel with output frame
4849
// size approximation.
4950
type ChannelBuilder struct {
51+
log log.Logger
5052
cfg ChannelConfig
5153
rollupCfg *rollup.Config
5254

@@ -87,8 +89,9 @@ type ChannelBuilder struct {
8789
outputBytes int
8890
}
8991

90-
func NewChannelBuilderWithChannelOut(cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64, channelOut derive.ChannelOut) *ChannelBuilder {
92+
func NewChannelBuilderWithChannelOut(log log.Logger, cfg ChannelConfig, rollupCfg *rollup.Config, latestL1OriginBlockNum uint64, channelOut derive.ChannelOut) *ChannelBuilder {
9193
cb := &ChannelBuilder{
94+
log: log.With("channel_id", channelOut.ID()),
9295
cfg: cfg,
9396
rollupCfg: rollupCfg,
9497
co: channelOut,
@@ -224,8 +227,7 @@ func (c *ChannelBuilder) updateDurationTimeout(l1BlockNum uint64) {
224227
if c.cfg.MaxChannelDuration == 0 {
225228
return
226229
}
227-
timeout := l1BlockNum + c.cfg.MaxChannelDuration
228-
c.updateTimeout(timeout, ErrMaxDurationReached)
230+
c.updateTimeout(l1BlockNum+c.cfg.MaxChannelDuration, ErrMaxDurationReached)
229231
}
230232

231233
// updateSwTimeout updates the block timeout with the sequencer window timeout
@@ -244,6 +246,7 @@ func (c *ChannelBuilder) updateSwTimeout(l1InfoNumber uint64) {
244246
// full error reason in case the timeout is hit in the future.
245247
func (c *ChannelBuilder) updateTimeout(timeoutBlockNum uint64, reason error) {
246248
if c.timeout == 0 || c.timeout > timeoutBlockNum {
249+
c.log.Debug("setting timeout", "number", timeoutBlockNum, "timeout", c.timeout)
247250
c.timeout = timeoutBlockNum
248251
c.timeoutReason = reason
249252
}
@@ -252,17 +255,12 @@ func (c *ChannelBuilder) updateTimeout(timeoutBlockNum uint64, reason error) {
252255
// CheckTimeout checks if the channel is timed out at the given block number and
253256
// in this case marks the channel as full, if it wasn't full already.
254257
func (c *ChannelBuilder) CheckTimeout(l1BlockNum uint64) {
255-
if !c.IsFull() && c.TimedOut(l1BlockNum) {
258+
if c.timeout != 0 && !c.IsFull() && l1BlockNum >= c.timeout {
259+
c.log.Debug("checking timeout", "l1blockNum", l1BlockNum, "timeout", c.timeout)
256260
c.setFullErr(c.timeoutReason)
257261
}
258262
}
259263

260-
// TimedOut returns whether the passed block number is after the timeout block
261-
// number. If no block timeout is set yet, it returns false.
262-
func (c *ChannelBuilder) TimedOut(blockNum uint64) bool {
263-
return c.timeout != 0 && blockNum >= c.timeout
264-
}
265-
266264
// IsFull returns whether the channel is full.
267265
// FullErr returns the reason for the channel being full.
268266
func (c *ChannelBuilder) IsFull() bool {

0 commit comments

Comments
 (0)