Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
00950152
Unverified
Commit
00950152
authored
Mar 07, 2024
by
Roberto Bayardo
Committed by
GitHub
Mar 08, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
export ChannelBuilder so we can use it in external analysis scripts (#9784)
parent
1786bce8
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
64 additions
and
64 deletions
+64
-64
channel.go
op-batcher/batcher/channel.go
+3
-3
channel_builder.go
op-batcher/batcher/channel_builder.go
+32
-32
channel_builder_test.go
op-batcher/batcher/channel_builder_test.go
+29
-29
No files found.
op-batcher/batcher/channel.go
View file @
00950152
...
@@ -12,7 +12,7 @@ import (
...
@@ -12,7 +12,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
)
)
// channel is a lightweight wrapper around a
c
hannelBuilder which keeps track of pending
// channel is a lightweight wrapper around a
C
hannelBuilder which keeps track of pending
// and confirmed transactions for a single channel.
// and confirmed transactions for a single channel.
type
channel
struct
{
type
channel
struct
{
log
log
.
Logger
log
log
.
Logger
...
@@ -20,7 +20,7 @@ type channel struct {
...
@@ -20,7 +20,7 @@ type channel struct {
cfg
ChannelConfig
cfg
ChannelConfig
// pending channel builder
// pending channel builder
channelBuilder
*
c
hannelBuilder
channelBuilder
*
C
hannelBuilder
// Set of unconfirmed txID -> frame data. For tx resubmission
// Set of unconfirmed txID -> frame data. For tx resubmission
pendingTransactions
map
[
txID
]
txData
pendingTransactions
map
[
txID
]
txData
// Set of confirmed txID -> inclusion block. For determining if the channel is timed out
// Set of confirmed txID -> inclusion block. For determining if the channel is timed out
...
@@ -35,7 +35,7 @@ type channel struct {
...
@@ -35,7 +35,7 @@ type channel struct {
}
}
func
newChannel
(
log
log
.
Logger
,
metr
metrics
.
Metricer
,
cfg
ChannelConfig
,
rollupCfg
*
rollup
.
Config
)
(
*
channel
,
error
)
{
func
newChannel
(
log
log
.
Logger
,
metr
metrics
.
Metricer
,
cfg
ChannelConfig
,
rollupCfg
*
rollup
.
Config
)
(
*
channel
,
error
)
{
cb
,
err
:=
n
ewChannelBuilder
(
cfg
,
*
rollupCfg
)
cb
,
err
:=
N
ewChannelBuilder
(
cfg
,
*
rollupCfg
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"creating new channel: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"creating new channel: %w"
,
err
)
}
}
...
...
op-batcher/batcher/channel_builder.go
View file @
00950152
...
@@ -74,7 +74,7 @@ func (cc *ChannelConfig) Check() error {
...
@@ -74,7 +74,7 @@ func (cc *ChannelConfig) Check() error {
// If the [MaxFrameSize] is set to 0, the channel builder
// If the [MaxFrameSize] is set to 0, the channel builder
// will infinitely loop when trying to create frames in the
// will infinitely loop when trying to create frames in the
// [
c
hannelBuilder.OutputFrames] function.
// [
C
hannelBuilder.OutputFrames] function.
if
cc
.
MaxFrameSize
==
0
{
if
cc
.
MaxFrameSize
==
0
{
return
errors
.
New
(
"max frame size cannot be zero"
)
return
errors
.
New
(
"max frame size cannot be zero"
)
}
}
...
@@ -104,9 +104,9 @@ type frameData struct {
...
@@ -104,9 +104,9 @@ type frameData struct {
id
frameID
id
frameID
}
}
//
c
hannelBuilder uses a ChannelOut to create a channel with output frame
//
C
hannelBuilder uses a ChannelOut to create a channel with output frame
// size approximation.
// size approximation.
type
c
hannelBuilder
struct
{
type
C
hannelBuilder
struct
{
cfg
ChannelConfig
cfg
ChannelConfig
rollupCfg
rollup
.
Config
rollupCfg
rollup
.
Config
...
@@ -136,7 +136,7 @@ type channelBuilder struct {
...
@@ -136,7 +136,7 @@ type channelBuilder struct {
// newChannelBuilder creates a new channel builder or returns an error if the
// newChannelBuilder creates a new channel builder or returns an error if the
// channel out could not be created.
// channel out could not be created.
func
newChannelBuilder
(
cfg
ChannelConfig
,
rollupCfg
rollup
.
Config
)
(
*
c
hannelBuilder
,
error
)
{
func
NewChannelBuilder
(
cfg
ChannelConfig
,
rollupCfg
rollup
.
Config
)
(
*
C
hannelBuilder
,
error
)
{
c
,
err
:=
cfg
.
CompressorConfig
.
NewCompressor
()
c
,
err
:=
cfg
.
CompressorConfig
.
NewCompressor
()
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
...
@@ -150,41 +150,41 @@ func newChannelBuilder(cfg ChannelConfig, rollupCfg rollup.Config) (*channelBuil
...
@@ -150,41 +150,41 @@ func newChannelBuilder(cfg ChannelConfig, rollupCfg rollup.Config) (*channelBuil
return
nil
,
err
return
nil
,
err
}
}
return
&
c
hannelBuilder
{
return
&
C
hannelBuilder
{
cfg
:
cfg
,
cfg
:
cfg
,
rollupCfg
:
rollupCfg
,
rollupCfg
:
rollupCfg
,
co
:
co
,
co
:
co
,
},
nil
},
nil
}
}
func
(
c
*
c
hannelBuilder
)
ID
()
derive
.
ChannelID
{
func
(
c
*
C
hannelBuilder
)
ID
()
derive
.
ChannelID
{
return
c
.
co
.
ID
()
return
c
.
co
.
ID
()
}
}
// InputBytes returns the total amount of input bytes added to the channel.
// InputBytes returns the total amount of input bytes added to the channel.
func
(
c
*
c
hannelBuilder
)
InputBytes
()
int
{
func
(
c
*
C
hannelBuilder
)
InputBytes
()
int
{
return
c
.
co
.
InputBytes
()
return
c
.
co
.
InputBytes
()
}
}
// ReadyBytes returns the amount of bytes ready in the compression pipeline to
// ReadyBytes returns the amount of bytes ready in the compression pipeline to
// output into a frame.
// output into a frame.
func
(
c
*
c
hannelBuilder
)
ReadyBytes
()
int
{
func
(
c
*
C
hannelBuilder
)
ReadyBytes
()
int
{
return
c
.
co
.
ReadyBytes
()
return
c
.
co
.
ReadyBytes
()
}
}
func
(
c
*
c
hannelBuilder
)
OutputBytes
()
int
{
func
(
c
*
C
hannelBuilder
)
OutputBytes
()
int
{
return
c
.
outputBytes
return
c
.
outputBytes
}
}
// Blocks returns a backup list of all blocks that were added to the channel. It
// Blocks returns a backup list of all blocks that were added to the channel. It
// can be used in case the channel needs to be rebuilt.
// can be used in case the channel needs to be rebuilt.
func
(
c
*
c
hannelBuilder
)
Blocks
()
[]
*
types
.
Block
{
func
(
c
*
C
hannelBuilder
)
Blocks
()
[]
*
types
.
Block
{
return
c
.
blocks
return
c
.
blocks
}
}
// Reset resets the internal state of the channel builder so that it can be
// Reset resets the internal state of the channel builder so that it can be
// reused. Note that a new channel id is also generated by Reset.
// reused. Note that a new channel id is also generated by Reset.
func
(
c
*
c
hannelBuilder
)
Reset
()
error
{
func
(
c
*
C
hannelBuilder
)
Reset
()
error
{
c
.
blocks
=
c
.
blocks
[
:
0
]
c
.
blocks
=
c
.
blocks
[
:
0
]
c
.
frames
=
c
.
frames
[
:
0
]
c
.
frames
=
c
.
frames
[
:
0
]
c
.
timeout
=
0
c
.
timeout
=
0
...
@@ -203,7 +203,7 @@ func (c *channelBuilder) Reset() error {
...
@@ -203,7 +203,7 @@ func (c *channelBuilder) Reset() error {
// first transaction for subsequent use by the caller.
// first transaction for subsequent use by the caller.
//
//
// Call OutputFrames() afterwards to create frames.
// Call OutputFrames() afterwards to create frames.
func
(
c
*
c
hannelBuilder
)
AddBlock
(
block
*
types
.
Block
)
(
*
derive
.
L1BlockInfo
,
error
)
{
func
(
c
*
C
hannelBuilder
)
AddBlock
(
block
*
types
.
Block
)
(
*
derive
.
L1BlockInfo
,
error
)
{
if
c
.
IsFull
()
{
if
c
.
IsFull
()
{
return
nil
,
c
.
FullErr
()
return
nil
,
c
.
FullErr
()
}
}
...
@@ -236,7 +236,7 @@ func (c *channelBuilder) AddBlock(block *types.Block) (*derive.L1BlockInfo, erro
...
@@ -236,7 +236,7 @@ func (c *channelBuilder) AddBlock(block *types.Block) (*derive.L1BlockInfo, erro
//
//
// It ensures proper tracking of all possible timeouts (max channel duration,
// It ensures proper tracking of all possible timeouts (max channel duration,
// close to consensus channel timeout, close to end of sequencing window).
// close to consensus channel timeout, close to end of sequencing window).
func
(
c
*
c
hannelBuilder
)
RegisterL1Block
(
l1BlockNum
uint64
)
{
func
(
c
*
C
hannelBuilder
)
RegisterL1Block
(
l1BlockNum
uint64
)
{
c
.
updateDurationTimeout
(
l1BlockNum
)
c
.
updateDurationTimeout
(
l1BlockNum
)
c
.
checkTimeout
(
l1BlockNum
)
c
.
checkTimeout
(
l1BlockNum
)
}
}
...
@@ -244,7 +244,7 @@ func (c *channelBuilder) RegisterL1Block(l1BlockNum uint64) {
...
@@ -244,7 +244,7 @@ func (c *channelBuilder) RegisterL1Block(l1BlockNum uint64) {
// FramePublished should be called whenever a frame of this channel got
// FramePublished should be called whenever a frame of this channel got
// published with the L1-block number of the block that the frame got included
// published with the L1-block number of the block that the frame got included
// in.
// in.
func
(
c
*
c
hannelBuilder
)
FramePublished
(
l1BlockNum
uint64
)
{
func
(
c
*
C
hannelBuilder
)
FramePublished
(
l1BlockNum
uint64
)
{
timeout
:=
l1BlockNum
+
c
.
cfg
.
ChannelTimeout
-
c
.
cfg
.
SubSafetyMargin
timeout
:=
l1BlockNum
+
c
.
cfg
.
ChannelTimeout
-
c
.
cfg
.
SubSafetyMargin
c
.
updateTimeout
(
timeout
,
ErrChannelTimeoutClose
)
c
.
updateTimeout
(
timeout
,
ErrChannelTimeoutClose
)
}
}
...
@@ -254,7 +254,7 @@ func (c *channelBuilder) FramePublished(l1BlockNum uint64) {
...
@@ -254,7 +254,7 @@ func (c *channelBuilder) FramePublished(l1BlockNum uint64) {
// forward if the derived timeout is earlier than the currently set timeout.
// forward if the derived timeout is earlier than the currently set timeout.
//
//
// It does nothing if the max channel duration is set to 0.
// It does nothing if the max channel duration is set to 0.
func
(
c
*
c
hannelBuilder
)
updateDurationTimeout
(
l1BlockNum
uint64
)
{
func
(
c
*
C
hannelBuilder
)
updateDurationTimeout
(
l1BlockNum
uint64
)
{
if
c
.
cfg
.
MaxChannelDuration
==
0
{
if
c
.
cfg
.
MaxChannelDuration
==
0
{
return
return
}
}
...
@@ -266,7 +266,7 @@ func (c *channelBuilder) updateDurationTimeout(l1BlockNum uint64) {
...
@@ -266,7 +266,7 @@ func (c *channelBuilder) updateDurationTimeout(l1BlockNum uint64) {
// derived from the batch's origin L1 block. The timeout is only moved forward
// derived from the batch's origin L1 block. The timeout is only moved forward
// if the derived sequencer window timeout is earlier than the currently set
// if the derived sequencer window timeout is earlier than the currently set
// timeout.
// timeout.
func
(
c
*
c
hannelBuilder
)
updateSwTimeout
(
batch
*
derive
.
SingularBatch
)
{
func
(
c
*
C
hannelBuilder
)
updateSwTimeout
(
batch
*
derive
.
SingularBatch
)
{
timeout
:=
uint64
(
batch
.
EpochNum
)
+
c
.
cfg
.
SeqWindowSize
-
c
.
cfg
.
SubSafetyMargin
timeout
:=
uint64
(
batch
.
EpochNum
)
+
c
.
cfg
.
SeqWindowSize
-
c
.
cfg
.
SubSafetyMargin
c
.
updateTimeout
(
timeout
,
ErrSeqWindowClose
)
c
.
updateTimeout
(
timeout
,
ErrSeqWindowClose
)
}
}
...
@@ -276,7 +276,7 @@ func (c *channelBuilder) updateSwTimeout(batch *derive.SingularBatch) {
...
@@ -276,7 +276,7 @@ func (c *channelBuilder) updateSwTimeout(batch *derive.SingularBatch) {
//
//
// If the timeout is updated, the provided reason will be set as the channel
// If the timeout is updated, the provided reason will be set as the channel
// full error reason in case the timeout is hit in the future.
// full error reason in case the timeout is hit in the future.
func
(
c
*
c
hannelBuilder
)
updateTimeout
(
timeoutBlockNum
uint64
,
reason
error
)
{
func
(
c
*
C
hannelBuilder
)
updateTimeout
(
timeoutBlockNum
uint64
,
reason
error
)
{
if
c
.
timeout
==
0
||
c
.
timeout
>
timeoutBlockNum
{
if
c
.
timeout
==
0
||
c
.
timeout
>
timeoutBlockNum
{
c
.
timeout
=
timeoutBlockNum
c
.
timeout
=
timeoutBlockNum
c
.
timeoutReason
=
reason
c
.
timeoutReason
=
reason
...
@@ -285,7 +285,7 @@ func (c *channelBuilder) updateTimeout(timeoutBlockNum uint64, reason error) {
...
@@ -285,7 +285,7 @@ func (c *channelBuilder) updateTimeout(timeoutBlockNum uint64, reason error) {
// checkTimeout checks if the channel is timed out at the given block number and
// checkTimeout checks if the channel is timed out at the given block number and
// in this case marks the channel as full, if it wasn't full already.
// in this case marks the channel as full, if it wasn't full already.
func
(
c
*
c
hannelBuilder
)
checkTimeout
(
blockNum
uint64
)
{
func
(
c
*
C
hannelBuilder
)
checkTimeout
(
blockNum
uint64
)
{
if
!
c
.
IsFull
()
&&
c
.
TimedOut
(
blockNum
)
{
if
!
c
.
IsFull
()
&&
c
.
TimedOut
(
blockNum
)
{
c
.
setFullErr
(
c
.
timeoutReason
)
c
.
setFullErr
(
c
.
timeoutReason
)
}
}
...
@@ -293,13 +293,13 @@ func (c *channelBuilder) checkTimeout(blockNum uint64) {
...
@@ -293,13 +293,13 @@ func (c *channelBuilder) checkTimeout(blockNum uint64) {
// TimedOut returns whether the passed block number is after the timeout block
// TimedOut returns whether the passed block number is after the timeout block
// number. If no block timeout is set yet, it returns false.
// number. If no block timeout is set yet, it returns false.
func
(
c
*
c
hannelBuilder
)
TimedOut
(
blockNum
uint64
)
bool
{
func
(
c
*
C
hannelBuilder
)
TimedOut
(
blockNum
uint64
)
bool
{
return
c
.
timeout
!=
0
&&
blockNum
>=
c
.
timeout
return
c
.
timeout
!=
0
&&
blockNum
>=
c
.
timeout
}
}
// IsFull returns whether the channel is full.
// IsFull returns whether the channel is full.
// FullErr returns the reason for the channel being full.
// FullErr returns the reason for the channel being full.
func
(
c
*
c
hannelBuilder
)
IsFull
()
bool
{
func
(
c
*
C
hannelBuilder
)
IsFull
()
bool
{
return
c
.
fullErr
!=
nil
return
c
.
fullErr
!=
nil
}
}
...
@@ -317,11 +317,11 @@ func (c *channelBuilder) IsFull() bool {
...
@@ -317,11 +317,11 @@ func (c *channelBuilder) IsFull() bool {
// - ErrChannelTimeoutClose if the consensus channel timeout got too close,
// - ErrChannelTimeoutClose if the consensus channel timeout got too close,
// - ErrSeqWindowClose if the end of the sequencer window got too close,
// - ErrSeqWindowClose if the end of the sequencer window got too close,
// - ErrTerminated if the channel was explicitly terminated.
// - ErrTerminated if the channel was explicitly terminated.
func
(
c
*
c
hannelBuilder
)
FullErr
()
error
{
func
(
c
*
C
hannelBuilder
)
FullErr
()
error
{
return
c
.
fullErr
return
c
.
fullErr
}
}
func
(
c
*
c
hannelBuilder
)
setFullErr
(
err
error
)
{
func
(
c
*
C
hannelBuilder
)
setFullErr
(
err
error
)
{
c
.
fullErr
=
&
ChannelFullError
{
Err
:
err
}
c
.
fullErr
=
&
ChannelFullError
{
Err
:
err
}
}
}
...
@@ -333,7 +333,7 @@ func (c *channelBuilder) setFullErr(err error) {
...
@@ -333,7 +333,7 @@ func (c *channelBuilder) setFullErr(err error) {
// pull readily available frames from the compression output.
// pull readily available frames from the compression output.
// If it is full, the channel is closed and all remaining
// If it is full, the channel is closed and all remaining
// frames will be created, possibly with a small leftover frame.
// frames will be created, possibly with a small leftover frame.
func
(
c
*
c
hannelBuilder
)
OutputFrames
()
error
{
func
(
c
*
C
hannelBuilder
)
OutputFrames
()
error
{
if
c
.
IsFull
()
{
if
c
.
IsFull
()
{
err
:=
c
.
closeAndOutputAllFrames
()
err
:=
c
.
closeAndOutputAllFrames
()
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -349,7 +349,7 @@ func (c *channelBuilder) OutputFrames() error {
...
@@ -349,7 +349,7 @@ func (c *channelBuilder) OutputFrames() error {
//
//
// This is part of an optimization to already generate frames and send them off
// This is part of an optimization to already generate frames and send them off
// as txs while still collecting blocks in the channel builder.
// as txs while still collecting blocks in the channel builder.
func
(
c
*
c
hannelBuilder
)
outputReadyFrames
()
error
{
func
(
c
*
C
hannelBuilder
)
outputReadyFrames
()
error
{
// TODO: Decide whether we want to fill frames to max size and use target
// TODO: Decide whether we want to fill frames to max size and use target
// only for estimation, or use target size.
// only for estimation, or use target size.
for
c
.
co
.
ReadyBytes
()
>=
int
(
c
.
cfg
.
MaxFrameSize
)
{
for
c
.
co
.
ReadyBytes
()
>=
int
(
c
.
cfg
.
MaxFrameSize
)
{
...
@@ -362,7 +362,7 @@ func (c *channelBuilder) outputReadyFrames() error {
...
@@ -362,7 +362,7 @@ func (c *channelBuilder) outputReadyFrames() error {
return
nil
return
nil
}
}
func
(
c
*
c
hannelBuilder
)
closeAndOutputAllFrames
()
error
{
func
(
c
*
C
hannelBuilder
)
closeAndOutputAllFrames
()
error
{
if
err
:=
c
.
co
.
Close
();
err
!=
nil
{
if
err
:=
c
.
co
.
Close
();
err
!=
nil
{
return
fmt
.
Errorf
(
"closing channel out: %w"
,
err
)
return
fmt
.
Errorf
(
"closing channel out: %w"
,
err
)
}
}
...
@@ -379,7 +379,7 @@ func (c *channelBuilder) closeAndOutputAllFrames() error {
...
@@ -379,7 +379,7 @@ func (c *channelBuilder) closeAndOutputAllFrames() error {
// outputFrame creates one new frame and adds it to the frames queue.
// outputFrame creates one new frame and adds it to the frames queue.
// Note that compressed output data must be available on the underlying
// Note that compressed output data must be available on the underlying
// ChannelOut, or an empty frame will be produced.
// ChannelOut, or an empty frame will be produced.
func
(
c
*
c
hannelBuilder
)
outputFrame
()
error
{
func
(
c
*
C
hannelBuilder
)
outputFrame
()
error
{
var
buf
bytes
.
Buffer
var
buf
bytes
.
Buffer
fn
,
err
:=
c
.
co
.
OutputFrame
(
&
buf
,
c
.
cfg
.
MaxFrameSize
)
fn
,
err
:=
c
.
co
.
OutputFrame
(
&
buf
,
c
.
cfg
.
MaxFrameSize
)
if
err
!=
io
.
EOF
&&
err
!=
nil
{
if
err
!=
io
.
EOF
&&
err
!=
nil
{
...
@@ -409,7 +409,7 @@ func (c *channelBuilder) outputFrame() error {
...
@@ -409,7 +409,7 @@ func (c *channelBuilder) outputFrame() error {
// Close immediately marks the channel as full with an ErrTerminated
// Close immediately marks the channel as full with an ErrTerminated
// if the channel is not already full.
// if the channel is not already full.
func
(
c
*
c
hannelBuilder
)
Close
()
{
func
(
c
*
C
hannelBuilder
)
Close
()
{
if
!
c
.
IsFull
()
{
if
!
c
.
IsFull
()
{
c
.
setFullErr
(
ErrTerminated
)
c
.
setFullErr
(
ErrTerminated
)
}
}
...
@@ -417,7 +417,7 @@ func (c *channelBuilder) Close() {
...
@@ -417,7 +417,7 @@ func (c *channelBuilder) Close() {
// TotalFrames returns the total number of frames that were created in this channel so far.
// TotalFrames returns the total number of frames that were created in this channel so far.
// It does not decrease when the frames queue is being emptied.
// It does not decrease when the frames queue is being emptied.
func
(
c
*
c
hannelBuilder
)
TotalFrames
()
int
{
func
(
c
*
C
hannelBuilder
)
TotalFrames
()
int
{
return
c
.
numFrames
return
c
.
numFrames
}
}
...
@@ -426,20 +426,20 @@ func (c *channelBuilder) TotalFrames() int {
...
@@ -426,20 +426,20 @@ func (c *channelBuilder) TotalFrames() int {
//
//
// Call OutputFrames before to create new frames from the channel out
// Call OutputFrames before to create new frames from the channel out
// compression pipeline.
// compression pipeline.
func
(
c
*
c
hannelBuilder
)
HasFrame
()
bool
{
func
(
c
*
C
hannelBuilder
)
HasFrame
()
bool
{
return
len
(
c
.
frames
)
>
0
return
len
(
c
.
frames
)
>
0
}
}
// PendingFrames returns the number of pending frames in the frames queue.
// PendingFrames returns the number of pending frames in the frames queue.
// It is larger zero iff HasFrames() returns true.
// It is larger zero iff HasFrames() returns true.
func
(
c
*
c
hannelBuilder
)
PendingFrames
()
int
{
func
(
c
*
C
hannelBuilder
)
PendingFrames
()
int
{
return
len
(
c
.
frames
)
return
len
(
c
.
frames
)
}
}
// NextFrame returns the next available frame.
// NextFrame returns the next available frame.
// HasFrame must be called prior to check if there's a next frame available.
// HasFrame must be called prior to check if there's a next frame available.
// Panics if called when there's no next frame.
// Panics if called when there's no next frame.
func
(
c
*
c
hannelBuilder
)
NextFrame
()
frameData
{
func
(
c
*
C
hannelBuilder
)
NextFrame
()
frameData
{
if
len
(
c
.
frames
)
==
0
{
if
len
(
c
.
frames
)
==
0
{
panic
(
"no next frame"
)
panic
(
"no next frame"
)
}
}
...
@@ -451,7 +451,7 @@ func (c *channelBuilder) NextFrame() frameData {
...
@@ -451,7 +451,7 @@ func (c *channelBuilder) NextFrame() frameData {
// PushFrame adds the frame back to the internal frames queue. Panics if not of
// PushFrame adds the frame back to the internal frames queue. Panics if not of
// the same channel.
// the same channel.
func
(
c
*
c
hannelBuilder
)
PushFrame
(
frame
frameData
)
{
func
(
c
*
C
hannelBuilder
)
PushFrame
(
frame
frameData
)
{
if
frame
.
id
.
chID
!=
c
.
ID
()
{
if
frame
.
id
.
chID
!=
c
.
ID
()
{
panic
(
"wrong channel"
)
panic
(
"wrong channel"
)
}
}
...
...
op-batcher/batcher/channel_builder_test.go
View file @
00950152
...
@@ -116,8 +116,8 @@ func FuzzChannelConfig_CheckTimeout(f *testing.F) {
...
@@ -116,8 +116,8 @@ func FuzzChannelConfig_CheckTimeout(f *testing.F) {
}
}
// addMiniBlock adds a minimal valid L2 block to the channel builder using the
// addMiniBlock adds a minimal valid L2 block to the channel builder using the
//
c
hannelBuilder.AddBlock method.
//
C
hannelBuilder.AddBlock method.
func
addMiniBlock
(
cb
*
c
hannelBuilder
)
error
{
func
addMiniBlock
(
cb
*
C
hannelBuilder
)
error
{
a
:=
newMiniL2Block
(
0
)
a
:=
newMiniL2Block
(
0
)
_
,
err
:=
cb
.
AddBlock
(
a
)
_
,
err
:=
cb
.
AddBlock
(
a
)
return
err
return
err
...
@@ -163,7 +163,7 @@ func newMiniL2BlockWithNumberParent(numTx int, number *big.Int, parent common.Ha
...
@@ -163,7 +163,7 @@ func newMiniL2BlockWithNumberParent(numTx int, number *big.Int, parent common.Ha
// addTooManyBlocks adds blocks to the channel until it hits an error,
// addTooManyBlocks adds blocks to the channel until it hits an error,
// which is presumably ErrTooManyRLPBytes.
// which is presumably ErrTooManyRLPBytes.
func
addTooManyBlocks
(
cb
*
c
hannelBuilder
)
error
{
func
addTooManyBlocks
(
cb
*
C
hannelBuilder
)
error
{
rng
:=
rand
.
New
(
rand
.
NewSource
(
1234
))
rng
:=
rand
.
New
(
rand
.
NewSource
(
1234
))
for
i
:=
0
;
i
<
10
_000
;
i
++
{
for
i
:=
0
;
i
<
10
_000
;
i
++
{
block
:=
dtest
.
RandomL2BlockWithChainId
(
rng
,
1000
,
defaultTestRollupConfig
.
L2ChainID
)
block
:=
dtest
.
RandomL2BlockWithChainId
(
rng
,
1000
,
defaultTestRollupConfig
.
L2ChainID
)
...
@@ -185,7 +185,7 @@ func FuzzDurationTimeoutZeroMaxChannelDuration(f *testing.F) {
...
@@ -185,7 +185,7 @@ func FuzzDurationTimeoutZeroMaxChannelDuration(f *testing.F) {
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
l1BlockNum
uint64
)
{
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
l1BlockNum
uint64
)
{
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxChannelDuration
=
0
channelConfig
.
MaxChannelDuration
=
0
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
cb
.
timeout
=
0
cb
.
timeout
=
0
cb
.
updateDurationTimeout
(
l1BlockNum
)
cb
.
updateDurationTimeout
(
l1BlockNum
)
...
@@ -208,7 +208,7 @@ func FuzzChannelBuilder_DurationZero(f *testing.F) {
...
@@ -208,7 +208,7 @@ func FuzzChannelBuilder_DurationZero(f *testing.F) {
// Create the channel builder
// Create the channel builder
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxChannelDuration
=
maxChannelDuration
channelConfig
.
MaxChannelDuration
=
maxChannelDuration
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Whenever the timeout is set to 0, the channel builder should have a duration timeout
// Whenever the timeout is set to 0, the channel builder should have a duration timeout
...
@@ -235,7 +235,7 @@ func FuzzDurationTimeoutMaxChannelDuration(f *testing.F) {
...
@@ -235,7 +235,7 @@ func FuzzDurationTimeoutMaxChannelDuration(f *testing.F) {
// Create the channel builder
// Create the channel builder
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxChannelDuration
=
maxChannelDuration
channelConfig
.
MaxChannelDuration
=
maxChannelDuration
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Whenever the timeout is greater than the l1BlockNum,
// Whenever the timeout is greater than the l1BlockNum,
...
@@ -269,7 +269,7 @@ func FuzzChannelCloseTimeout(f *testing.F) {
...
@@ -269,7 +269,7 @@ func FuzzChannelCloseTimeout(f *testing.F) {
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
ChannelTimeout
=
channelTimeout
channelConfig
.
ChannelTimeout
=
channelTimeout
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Check the timeout
// Check the timeout
...
@@ -297,7 +297,7 @@ func FuzzChannelZeroCloseTimeout(f *testing.F) {
...
@@ -297,7 +297,7 @@ func FuzzChannelZeroCloseTimeout(f *testing.F) {
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
ChannelTimeout
=
channelTimeout
channelConfig
.
ChannelTimeout
=
channelTimeout
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Check the timeout
// Check the timeout
...
@@ -324,7 +324,7 @@ func FuzzSeqWindowClose(f *testing.F) {
...
@@ -324,7 +324,7 @@ func FuzzSeqWindowClose(f *testing.F) {
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
SeqWindowSize
=
seqWindowSize
channelConfig
.
SeqWindowSize
=
seqWindowSize
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Check the timeout
// Check the timeout
...
@@ -352,7 +352,7 @@ func FuzzSeqWindowZeroTimeoutClose(f *testing.F) {
...
@@ -352,7 +352,7 @@ func FuzzSeqWindowZeroTimeoutClose(f *testing.F) {
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
SeqWindowSize
=
seqWindowSize
channelConfig
.
SeqWindowSize
=
seqWindowSize
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Check the timeout
// Check the timeout
...
@@ -399,10 +399,10 @@ func TestChannelBuilder_NextFrame(t *testing.T) {
...
@@ -399,10 +399,10 @@ func TestChannelBuilder_NextFrame(t *testing.T) {
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
// Create a new channel builder
// Create a new channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Mock the internals of `
c
hannelBuilder.outputFrame`
// Mock the internals of `
C
hannelBuilder.outputFrame`
// to construct a single frame
// to construct a single frame
co
:=
cb
.
co
co
:=
cb
.
co
var
buf
bytes
.
Buffer
var
buf
bytes
.
Buffer
...
@@ -439,10 +439,10 @@ func TestChannelBuilder_OutputWrongFramePanic(t *testing.T) {
...
@@ -439,10 +439,10 @@ func TestChannelBuilder_OutputWrongFramePanic(t *testing.T) {
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
// Construct a channel builder
// Construct a channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Mock the internals of `
c
hannelBuilder.outputFrame`
// Mock the internals of `
C
hannelBuilder.outputFrame`
// to construct a single frame
// to construct a single frame
c
,
err
:=
channelConfig
.
CompressorConfig
.
NewCompressor
()
c
,
err
:=
channelConfig
.
CompressorConfig
.
NewCompressor
()
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
...
@@ -472,7 +472,7 @@ func TestChannelBuilder_OutputFramesWorks(t *testing.T) {
...
@@ -472,7 +472,7 @@ func TestChannelBuilder_OutputFramesWorks(t *testing.T) {
channelConfig
.
MaxFrameSize
=
24
channelConfig
.
MaxFrameSize
=
24
// Construct the channel builder
// Construct the channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
False
(
t
,
cb
.
IsFull
())
require
.
False
(
t
,
cb
.
IsFull
())
require
.
Equal
(
t
,
0
,
cb
.
PendingFrames
())
require
.
Equal
(
t
,
0
,
cb
.
PendingFrames
())
...
@@ -515,7 +515,7 @@ func TestChannelBuilder_OutputFramesWorks_SpanBatch(t *testing.T) {
...
@@ -515,7 +515,7 @@ func TestChannelBuilder_OutputFramesWorks_SpanBatch(t *testing.T) {
channelConfig
.
BatchType
=
derive
.
SpanBatchType
channelConfig
.
BatchType
=
derive
.
SpanBatchType
// Construct the channel builder
// Construct the channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
False
(
t
,
cb
.
IsFull
())
require
.
False
(
t
,
cb
.
IsFull
())
require
.
Equal
(
t
,
0
,
cb
.
PendingFrames
())
require
.
Equal
(
t
,
0
,
cb
.
PendingFrames
())
...
@@ -557,7 +557,7 @@ func TestChannelBuilder_OutputFramesWorks_SpanBatch(t *testing.T) {
...
@@ -557,7 +557,7 @@ func TestChannelBuilder_OutputFramesWorks_SpanBatch(t *testing.T) {
require
.
LessOrEqual
(
t
,
len
(
cb
.
frames
[
len
(
cb
.
frames
)
-
1
]
.
data
),
int
(
channelConfig
.
MaxFrameSize
))
require
.
LessOrEqual
(
t
,
len
(
cb
.
frames
[
len
(
cb
.
frames
)
-
1
]
.
data
),
int
(
channelConfig
.
MaxFrameSize
))
}
}
// ChannelBuilder_MaxRLPBytesPerChannel tests the [
c
hannelBuilder.OutputFrames]
// ChannelBuilder_MaxRLPBytesPerChannel tests the [
C
hannelBuilder.OutputFrames]
// function errors when the max RLP bytes per channel is reached.
// function errors when the max RLP bytes per channel is reached.
func
ChannelBuilder_MaxRLPBytesPerChannel
(
t
*
testing
.
T
,
batchType
uint
)
{
func
ChannelBuilder_MaxRLPBytesPerChannel
(
t
*
testing
.
T
,
batchType
uint
)
{
t
.
Parallel
()
t
.
Parallel
()
...
@@ -568,7 +568,7 @@ func ChannelBuilder_MaxRLPBytesPerChannel(t *testing.T, batchType uint) {
...
@@ -568,7 +568,7 @@ func ChannelBuilder_MaxRLPBytesPerChannel(t *testing.T, batchType uint) {
channelConfig
.
BatchType
=
batchType
channelConfig
.
BatchType
=
batchType
// Construct the channel builder
// Construct the channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Add a block that overflows the [ChannelOut]
// Add a block that overflows the [ChannelOut]
...
@@ -589,9 +589,9 @@ func ChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T, batchType uint) {
...
@@ -589,9 +589,9 @@ func ChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T, batchType uint) {
rng
:=
rand
.
New
(
rand
.
NewSource
(
123
))
rng
:=
rand
.
New
(
rand
.
NewSource
(
123
))
// Continuously add blocks until the max frame index is reached
// Continuously add blocks until the max frame index is reached
// This should cause the [
c
hannelBuilder.OutputFrames] function
// This should cause the [
C
hannelBuilder.OutputFrames] function
// to error
// to error
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
False
(
t
,
cb
.
IsFull
())
require
.
False
(
t
,
cb
.
IsFull
())
require
.
Equal
(
t
,
0
,
cb
.
PendingFrames
())
require
.
Equal
(
t
,
0
,
cb
.
PendingFrames
())
...
@@ -624,7 +624,7 @@ func ChannelBuilder_AddBlock(t *testing.T, batchType uint) {
...
@@ -624,7 +624,7 @@ func ChannelBuilder_AddBlock(t *testing.T, batchType uint) {
channelConfig
.
CompressorConfig
.
ApproxComprRatio
=
1
channelConfig
.
CompressorConfig
.
ApproxComprRatio
=
1
// Construct the channel builder
// Construct the channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Add a nonsense block to the channel builder
// Add a nonsense block to the channel builder
...
@@ -657,7 +657,7 @@ func ChannelBuilder_Reset(t *testing.T, batchType uint) {
...
@@ -657,7 +657,7 @@ func ChannelBuilder_Reset(t *testing.T, batchType uint) {
channelConfig
.
CompressorConfig
.
TargetFrameSize
=
24
channelConfig
.
CompressorConfig
.
TargetFrameSize
=
24
channelConfig
.
CompressorConfig
.
ApproxComprRatio
=
1
channelConfig
.
CompressorConfig
.
ApproxComprRatio
=
1
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Add a nonsense block to the channel builder
// Add a nonsense block to the channel builder
...
@@ -698,7 +698,7 @@ func TestBuilderRegisterL1Block(t *testing.T) {
...
@@ -698,7 +698,7 @@ func TestBuilderRegisterL1Block(t *testing.T) {
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
// Construct the channel builder
// Construct the channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Assert params modified in RegisterL1Block
// Assert params modified in RegisterL1Block
...
@@ -721,7 +721,7 @@ func TestBuilderRegisterL1BlockZeroMaxChannelDuration(t *testing.T) {
...
@@ -721,7 +721,7 @@ func TestBuilderRegisterL1BlockZeroMaxChannelDuration(t *testing.T) {
channelConfig
.
MaxChannelDuration
=
0
channelConfig
.
MaxChannelDuration
=
0
// Construct the channel builder
// Construct the channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Assert params modified in RegisterL1Block
// Assert params modified in RegisterL1Block
...
@@ -742,7 +742,7 @@ func TestFramePublished(t *testing.T) {
...
@@ -742,7 +742,7 @@ func TestFramePublished(t *testing.T) {
channelConfig
:=
defaultTestChannelConfig
channelConfig
:=
defaultTestChannelConfig
// Construct the channel builder
// Construct the channel builder
cb
,
err
:=
n
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
channelConfig
,
defaultTestRollupConfig
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
// Let's say the block number is fed in as 100
// Let's say the block number is fed in as 100
...
@@ -768,7 +768,7 @@ func ChannelBuilder_PendingFrames_TotalFrames(t *testing.T, batchType uint) {
...
@@ -768,7 +768,7 @@ func ChannelBuilder_PendingFrames_TotalFrames(t *testing.T, batchType uint) {
cfg
.
CompressorConfig
.
TargetNumFrames
=
tnf
cfg
.
CompressorConfig
.
TargetNumFrames
=
tnf
cfg
.
CompressorConfig
.
Kind
=
"shadow"
cfg
.
CompressorConfig
.
Kind
=
"shadow"
cfg
.
BatchType
=
batchType
cfg
.
BatchType
=
batchType
cb
,
err
:=
n
ewChannelBuilder
(
cfg
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
cfg
,
defaultTestRollupConfig
)
require
.
NoError
(
err
)
require
.
NoError
(
err
)
// initial builder should be empty
// initial builder should be empty
...
@@ -812,7 +812,7 @@ func ChannelBuilder_InputBytes(t *testing.T, batchType uint) {
...
@@ -812,7 +812,7 @@ func ChannelBuilder_InputBytes(t *testing.T, batchType uint) {
chainId
:=
big
.
NewInt
(
1234
)
chainId
:=
big
.
NewInt
(
1234
)
spanBatchBuilder
=
derive
.
NewSpanBatchBuilder
(
uint64
(
0
),
chainId
)
spanBatchBuilder
=
derive
.
NewSpanBatchBuilder
(
uint64
(
0
),
chainId
)
}
}
cb
,
err
:=
n
ewChannelBuilder
(
cfg
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
cfg
,
defaultTestRollupConfig
)
require
.
NoError
(
err
)
require
.
NoError
(
err
)
require
.
Zero
(
cb
.
InputBytes
())
require
.
Zero
(
cb
.
InputBytes
())
...
@@ -848,8 +848,8 @@ func ChannelBuilder_OutputBytes(t *testing.T, batchType uint) {
...
@@ -848,8 +848,8 @@ func ChannelBuilder_OutputBytes(t *testing.T, batchType uint) {
cfg
.
CompressorConfig
.
TargetNumFrames
=
16
cfg
.
CompressorConfig
.
TargetNumFrames
=
16
cfg
.
CompressorConfig
.
ApproxComprRatio
=
1.0
cfg
.
CompressorConfig
.
ApproxComprRatio
=
1.0
cfg
.
BatchType
=
batchType
cfg
.
BatchType
=
batchType
cb
,
err
:=
n
ewChannelBuilder
(
cfg
,
defaultTestRollupConfig
)
cb
,
err
:=
N
ewChannelBuilder
(
cfg
,
defaultTestRollupConfig
)
require
.
NoError
(
err
,
"
n
ewChannelBuilder"
)
require
.
NoError
(
err
,
"
N
ewChannelBuilder"
)
require
.
Zero
(
cb
.
OutputBytes
())
require
.
Zero
(
cb
.
OutputBytes
())
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment