Skip to content

Commit dd69913

Browse files
committed
op-batcher: propagate clock.Clock to gossip builder check. refactor MaxChannelDuration
1 parent 87d406d commit dd69913

File tree

24 files changed

+586
-129
lines changed

24 files changed

+586
-129
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
nonce := uint64(0)
41+
for j := 0; j < 200; j++ {
42+
l1Origin := sys.L1EL.BlockRefByLabel(eth.Unsafe).Hash
43+
44+
for i := 0; i < 5; i++ {
45+
l.Debug("Sequencing L2 block", "iteration", i, "parent", parent)
46+
sequenceBlockWithL1Origin(t, ts_L2, parent, l1Origin, alice, cathrine, nonce)
47+
nonce++
48+
49+
parent = sys.L2CL.HeadBlockRef(types.LocalUnsafe).Hash
50+
51+
sys.AdvanceTime(time.Second * 2)
52+
}
53+
54+
l.Debug("Sequencing L1 block", "iteration_j", j)
55+
sequenceBlock(t, ts_L1, common.Hash{})
56+
}
57+
58+
sys.L2CL.StartSequencer()
59+
60+
l.Info("Current L1 unsafe block", "currentL1Unsafe", sys.L1EL.BlockRefByLabel(eth.Unsafe))
61+
sys.ControlPlane.FakePoSState(cl.ID(), stack.Start)
62+
63+
sys.L2Batcher.Start()
64+
65+
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
66+
{
67+
for _, c := range channels {
68+
l.Info("Channel details", "channelID", c.String(), "frameCount", len(channelFrames[c]), "dataLength_frame0", len(channelFrames[c][0].Data))
69+
}
70+
71+
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
72+
73+
// values are dependent on:
74+
// - MaxPendingTransactions
75+
// - number of blocks and transactions sent in the test - 1000 L2 blocks with 1 transaction from cathrine to alice
76+
// - MaxL1TxSize (this is set to 40_000 bytes for test purposes)
77+
sizeRanges := []struct {
78+
min int
79+
max int
80+
note string
81+
}{
82+
{min: 7_000, max: 10_000, note: "channel 0 - the first 100 blocks"},
83+
{min: 100, max: 1000, note: "channel 1 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
84+
{min: 100, max: 1000, note: "channel 2 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
85+
{min: 100, max: 1000, note: "channel 3 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
86+
{min: 100, max: 1000, note: "channel 4 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
87+
{min: 100, max: 1000, note: "channel 5 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
88+
{min: 100, max: 1000, note: "channel 6 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
89+
{min: 100, max: 1000, note: "channel 7 - only a few blocks due to racy behavior, nowhere near the channel capacity"},
90+
{min: 20_000, max: 40_000, note: "channel 8 - filled to the max capacity, due to blocking behavior because of MaxPendingTransactions limit reached"},
91+
{min: 20_000, max: 40_000, note: "channel 9 - filled to the max capacity, due to blocking behavior because of MaxPendingTransactions limit reached"},
92+
}
93+
94+
for i, entry := range sizeRanges {
95+
require.LessOrEqual(t, len(channelFrames[channels[i]][0].Data), entry.max, entry.note)
96+
require.GreaterOrEqual(t, len(channelFrames[channels[i]][0].Data), entry.min, entry.note)
97+
}
98+
99+
require.Equal(t, len(l2Txs[cathrine.Address()]), 1000) // we expect 1000 transactions total sent from cathrine to alice
100+
}
101+
102+
//sys.L2Chain.PrintChain()
103+
//sys.L1Network.PrintChain()
104+
105+
status := sys.L2CL.SyncStatus()
106+
spew.Dump(status)
107+
}
108+
109+
func sequenceBlock(t devtest.T, ts apis.TestSequencerControlAPI, parent common.Hash) {
110+
require.NoError(t, ts.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent}))
111+
require.NoError(t, ts.Next(t.Ctx()))
112+
}
113+
114+
func sequenceBlockWithL1Origin(t devtest.T, ts apis.TestSequencerControlAPI, parent common.Hash, l1Origin common.Hash, alice *dsl.EOA, cathrine *dsl.EOA, nonce uint64) {
115+
require.NoError(t, ts.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent, L1Origin: &l1Origin}))
116+
117+
// include simple transfer tx in opened block
118+
{
119+
to := cathrine.PlanTransfer(alice.Address(), eth.OneWei)
120+
opt := txplan.Combine(to, txplan.WithStaticNonce(nonce))
121+
ptx := txplan.NewPlannedTx(opt)
122+
signed_tx, err := ptx.Signed.Eval(t.Ctx())
123+
require.NoError(t, err, "Expected to be able to evaluate a planned transaction on op-test-sequencer, but got error")
124+
txdata, err := signed_tx.MarshalBinary()
125+
require.NoError(t, err, "Expected to be able to marshal a signed transaction on op-test-sequencer, but got error")
126+
127+
err = ts.IncludeTx(t.Ctx(), txdata)
128+
require.NoError(t, err, "Expected to be able to include a signed transaction on op-test-sequencer, but got error")
129+
}
130+
131+
require.NoError(t, ts.Next(t.Ctx()))
132+
}
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)