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
2dd2a627
Unverified
Commit
2dd2a627
authored
Mar 16, 2023
by
mergify[bot]
Committed by
GitHub
Mar 16, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5123 from ethereum-optimism/refcell/batcher/tests
feat(op-batcher): Channel Builder Unit Tests
parents
bf9146b2
49a5414c
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
737 additions
and
16 deletions
+737
-16
Makefile
op-batcher/Makefile
+11
-1
channel_builder.go
op-batcher/batcher/channel_builder.go
+3
-1
channel_builder_test.go
op-batcher/batcher/channel_builder_test.go
+594
-14
channel_config_test.go
op-batcher/batcher/channel_config_test.go
+129
-0
No files found.
op-batcher/Makefile
View file @
2dd2a627
...
...
@@ -19,8 +19,18 @@ test:
lint
:
golangci-lint run
-E
goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint
-e
"errors.As"
-e
"errors.Is"
fuzz
:
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzDurationZero ./batcher
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzDurationTimeoutMaxChannelDuration ./batcher
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzDurationTimeoutZeroMaxChannelDuration ./batcher
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzChannelCloseTimeout ./batcher
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzChannelZeroCloseTimeout ./batcher
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzSeqWindowClose ./batcher
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzSeqWindowZeroTimeoutClose ./batcher
.PHONY
:
\
op-batcher
\
clean
\
test
\
lint
lint
\
fuzz
op-batcher/batcher/channel_builder.go
View file @
2dd2a627
...
...
@@ -129,6 +129,8 @@ type channelBuilder struct {
frames
[]
frameData
}
// newChannelBuilder creates a new channel builder or returns an error if the
// channel out could not be created.
func
newChannelBuilder
(
cfg
ChannelConfig
)
(
*
channelBuilder
,
error
)
{
co
,
err
:=
derive
.
NewChannelOut
()
if
err
!=
nil
{
...
...
@@ -255,7 +257,7 @@ func (c *channelBuilder) updateTimeout(timeoutBlockNum uint64, reason error) {
}
// 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 alredy.
// in this case marks the channel as full, if it wasn't full alre
a
dy.
func
(
c
*
channelBuilder
)
checkTimeout
(
blockNum
uint64
)
{
if
!
c
.
IsFull
()
&&
c
.
TimedOut
(
blockNum
)
{
c
.
setFullErr
(
c
.
timeoutReason
)
...
...
op-batcher/batcher/channel_builder_test.go
View file @
2dd2a627
package
batcher
import
(
"bytes"
"crypto/rand"
"math"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/require"
)
// defaultChannelConfig returns a valid, default [ChannelConfig] struct.
func
defaultChannelConfig
()
ChannelConfig
{
return
ChannelConfig
{
SeqWindowSize
:
15
,
ChannelTimeout
:
40
,
MaxChannelDuration
:
1
,
SubSafetyMargin
:
4
,
MaxFrameSize
:
120000
,
TargetFrameSize
:
100000
,
TargetNumFrames
:
1
,
ApproxComprRatio
:
0.4
,
}
var
defaultTestChannelConfig
=
ChannelConfig
{
SeqWindowSize
:
15
,
ChannelTimeout
:
40
,
MaxChannelDuration
:
1
,
SubSafetyMargin
:
4
,
MaxFrameSize
:
120000
,
TargetFrameSize
:
100000
,
TargetNumFrames
:
1
,
ApproxComprRatio
:
0.4
,
}
// TestConfigValidation tests the validation of the [ChannelConfig] struct.
func
TestConfigValidation
(
t
*
testing
.
T
)
{
// Construct a valid config.
validChannelConfig
:=
default
ChannelConfig
()
validChannelConfig
:=
default
TestChannelConfig
require
.
NoError
(
t
,
validChannelConfig
.
Check
())
// Set the config to have a zero max frame size.
...
...
@@ -33,8 +42,579 @@ func TestConfigValidation(t *testing.T) {
// Reset the config and test the Timeout error.
// NOTE: We should be fuzzing these values with the constraint that
// SubSafetyMargin > ChannelTimeout to ensure validation.
validChannelConfig
=
default
ChannelConfig
()
validChannelConfig
=
default
TestChannelConfig
validChannelConfig
.
ChannelTimeout
=
0
validChannelConfig
.
SubSafetyMargin
=
1
require
.
ErrorIs
(
t
,
validChannelConfig
.
Check
(),
ErrInvalidChannelTimeout
)
}
// addNonsenseBlock is a helper function that adds a nonsense block
// to the channel builder using the [channelBuilder.AddBlock] method.
func
addNonsenseBlock
(
cb
*
channelBuilder
)
error
{
lBlock
:=
types
.
NewBlock
(
&
types
.
Header
{
BaseFee
:
big
.
NewInt
(
10
),
Difficulty
:
common
.
Big0
,
Number
:
big
.
NewInt
(
100
),
},
nil
,
nil
,
nil
,
trie
.
NewStackTrie
(
nil
))
l1InfoTx
,
err
:=
derive
.
L1InfoDeposit
(
0
,
lBlock
,
eth
.
SystemConfig
{},
false
)
if
err
!=
nil
{
return
err
}
txs
:=
[]
*
types
.
Transaction
{
types
.
NewTx
(
l1InfoTx
)}
a
:=
types
.
NewBlock
(
&
types
.
Header
{
Number
:
big
.
NewInt
(
0
),
},
txs
,
nil
,
nil
,
trie
.
NewStackTrie
(
nil
))
err
=
cb
.
AddBlock
(
a
)
return
err
}
// buildTooLargeRlpEncodedBlockBatch is a helper function that builds a batch
// of blocks that are too large to be added to a channel.
func
buildTooLargeRlpEncodedBlockBatch
(
cb
*
channelBuilder
)
error
{
// Construct a block with way too many txs
lBlock
:=
types
.
NewBlock
(
&
types
.
Header
{
BaseFee
:
big
.
NewInt
(
10
),
Difficulty
:
common
.
Big0
,
Number
:
big
.
NewInt
(
100
),
},
nil
,
nil
,
nil
,
trie
.
NewStackTrie
(
nil
))
l1InfoTx
,
_
:=
derive
.
L1InfoDeposit
(
0
,
lBlock
,
eth
.
SystemConfig
{},
false
)
txs
:=
[]
*
types
.
Transaction
{
types
.
NewTx
(
l1InfoTx
)}
for
i
:=
0
;
i
<
500
_000
;
i
++
{
txData
:=
make
([]
byte
,
32
)
_
,
_
=
rand
.
Read
(
txData
)
tx
:=
types
.
NewTransaction
(
0
,
common
.
Address
{},
big
.
NewInt
(
0
),
0
,
big
.
NewInt
(
0
),
txData
)
txs
=
append
(
txs
,
tx
)
}
block
:=
types
.
NewBlock
(
&
types
.
Header
{
Number
:
big
.
NewInt
(
0
),
},
txs
,
nil
,
nil
,
trie
.
NewStackTrie
(
nil
))
// Try to add the block to the channel builder
// This should fail since the block is too large
// When a batch is constructed from the block and
// then rlp encoded in the channel out, the size
// will exceed [derive.MaxRLPBytesPerChannel]
err
:=
cb
.
AddBlock
(
block
)
return
err
}
// FuzzDurationTimeoutZeroMaxChannelDuration ensures that when whenever the MaxChannelDuration
// is set to 0, the channel builder cannot have a duration timeout.
func
FuzzDurationTimeoutZeroMaxChannelDuration
(
f
*
testing
.
F
)
{
for
i
:=
range
[
10
]
int
{}
{
f
.
Add
(
uint64
(
i
))
}
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
l1BlockNum
uint64
)
{
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxChannelDuration
=
0
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
cb
.
timeout
=
0
cb
.
updateDurationTimeout
(
l1BlockNum
)
require
.
False
(
t
,
cb
.
TimedOut
(
l1BlockNum
))
})
}
// FuzzDurationZero ensures that when whenever the MaxChannelDuration
// is not set to 0, the channel builder will always have a duration timeout
// as long as the channel builder's timeout is set to 0.
func
FuzzDurationZero
(
f
*
testing
.
F
)
{
for
i
:=
range
[
10
]
int
{}
{
f
.
Add
(
uint64
(
i
),
uint64
(
i
))
}
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
l1BlockNum
uint64
,
maxChannelDuration
uint64
)
{
if
maxChannelDuration
==
0
{
t
.
Skip
(
"Max channel duration cannot be 0"
)
}
// Create the channel builder
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxChannelDuration
=
maxChannelDuration
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Whenever the timeout is set to 0, the channel builder should have a duration timeout
cb
.
timeout
=
0
cb
.
updateDurationTimeout
(
l1BlockNum
)
cb
.
checkTimeout
(
l1BlockNum
+
maxChannelDuration
)
require
.
ErrorIs
(
t
,
cb
.
FullErr
(),
ErrMaxDurationReached
)
})
}
// FuzzDurationTimeoutMaxChannelDuration ensures that when whenever the MaxChannelDuration
// is not set to 0, the channel builder will always have a duration timeout
// as long as the channel builder's timeout is greater than the target block number.
func
FuzzDurationTimeoutMaxChannelDuration
(
f
*
testing
.
F
)
{
// Set multiple seeds in case fuzzing isn't explicitly used
for
i
:=
range
[
10
]
int
{}
{
f
.
Add
(
uint64
(
i
),
uint64
(
i
),
uint64
(
i
))
}
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
l1BlockNum
uint64
,
maxChannelDuration
uint64
,
timeout
uint64
)
{
if
maxChannelDuration
==
0
{
t
.
Skip
(
"Max channel duration cannot be 0"
)
}
// Create the channel builder
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxChannelDuration
=
maxChannelDuration
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Whenever the timeout is greater than the l1BlockNum,
// the channel builder should have a duration timeout
cb
.
timeout
=
timeout
cb
.
updateDurationTimeout
(
l1BlockNum
)
if
timeout
>
l1BlockNum
+
maxChannelDuration
{
// Notice: we cannot call this outside of the if statement
// because it would put the channel builder in an invalid state.
// That is, where the channel builder has a value set for the timeout
// with no timeoutReason. This subsequently causes a panic when
// a nil timeoutReason is used as an error (eg when calling FullErr).
cb
.
checkTimeout
(
l1BlockNum
+
maxChannelDuration
)
require
.
ErrorIs
(
t
,
cb
.
FullErr
(),
ErrMaxDurationReached
)
}
else
{
require
.
NoError
(
t
,
cb
.
FullErr
())
}
})
}
// FuzzChannelCloseTimeout ensures that the channel builder has a [ErrChannelTimeoutClose]
// as long as the timeout constraint is met and the builder's timeout is greater than
// the calculated timeout
func
FuzzChannelCloseTimeout
(
f
*
testing
.
F
)
{
// Set multiple seeds in case fuzzing isn't explicitly used
for
i
:=
range
[
10
]
int
{}
{
f
.
Add
(
uint64
(
i
),
uint64
(
i
),
uint64
(
i
),
uint64
(
i
*
5
))
}
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
l1BlockNum
uint64
,
channelTimeout
uint64
,
subSafetyMargin
uint64
,
timeout
uint64
)
{
// Create the channel builder
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
ChannelTimeout
=
channelTimeout
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Check the timeout
cb
.
timeout
=
timeout
cb
.
FramePublished
(
l1BlockNum
)
calculatedTimeout
:=
l1BlockNum
+
channelTimeout
-
subSafetyMargin
if
timeout
>
calculatedTimeout
&&
calculatedTimeout
!=
0
{
cb
.
checkTimeout
(
calculatedTimeout
)
require
.
ErrorIs
(
t
,
cb
.
FullErr
(),
ErrChannelTimeoutClose
)
}
else
{
require
.
NoError
(
t
,
cb
.
FullErr
())
}
})
}
// FuzzChannelZeroCloseTimeout ensures that the channel builder has a [ErrChannelTimeoutClose]
// as long as the timeout constraint is met and the builder's timeout is set to zero.
func
FuzzChannelZeroCloseTimeout
(
f
*
testing
.
F
)
{
// Set multiple seeds in case fuzzing isn't explicitly used
for
i
:=
range
[
10
]
int
{}
{
f
.
Add
(
uint64
(
i
),
uint64
(
i
),
uint64
(
i
))
}
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
l1BlockNum
uint64
,
channelTimeout
uint64
,
subSafetyMargin
uint64
)
{
// Create the channel builder
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
ChannelTimeout
=
channelTimeout
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Check the timeout
cb
.
timeout
=
0
cb
.
FramePublished
(
l1BlockNum
)
calculatedTimeout
:=
l1BlockNum
+
channelTimeout
-
subSafetyMargin
cb
.
checkTimeout
(
calculatedTimeout
)
if
cb
.
timeout
!=
0
{
require
.
ErrorIs
(
t
,
cb
.
FullErr
(),
ErrChannelTimeoutClose
)
}
})
}
// FuzzSeqWindowClose ensures that the channel builder has a [ErrSeqWindowClose]
// as long as the timeout constraint is met and the builder's timeout is greater than
// the calculated timeout
func
FuzzSeqWindowClose
(
f
*
testing
.
F
)
{
// Set multiple seeds in case fuzzing isn't explicitly used
for
i
:=
range
[
10
]
int
{}
{
f
.
Add
(
uint64
(
i
),
uint64
(
i
),
uint64
(
i
),
uint64
(
i
*
5
))
}
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
epochNum
uint64
,
seqWindowSize
uint64
,
subSafetyMargin
uint64
,
timeout
uint64
)
{
// Create the channel builder
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
SeqWindowSize
=
seqWindowSize
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Check the timeout
cb
.
timeout
=
timeout
cb
.
updateSwTimeout
(
&
derive
.
BatchData
{
BatchV1
:
derive
.
BatchV1
{
EpochNum
:
rollup
.
Epoch
(
epochNum
),
},
})
calculatedTimeout
:=
epochNum
+
seqWindowSize
-
subSafetyMargin
if
timeout
>
calculatedTimeout
&&
calculatedTimeout
!=
0
{
cb
.
checkTimeout
(
calculatedTimeout
)
require
.
ErrorIs
(
t
,
cb
.
FullErr
(),
ErrSeqWindowClose
)
}
else
{
require
.
NoError
(
t
,
cb
.
FullErr
())
}
})
}
// FuzzSeqWindowZeroTimeoutClose ensures that the channel builder has a [ErrSeqWindowClose]
// as long as the timeout constraint is met and the builder's timeout is set to zero.
func
FuzzSeqWindowZeroTimeoutClose
(
f
*
testing
.
F
)
{
// Set multiple seeds in case fuzzing isn't explicitly used
for
i
:=
range
[
10
]
int
{}
{
f
.
Add
(
uint64
(
i
),
uint64
(
i
),
uint64
(
i
))
}
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
epochNum
uint64
,
seqWindowSize
uint64
,
subSafetyMargin
uint64
)
{
// Create the channel builder
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
SeqWindowSize
=
seqWindowSize
channelConfig
.
SubSafetyMargin
=
subSafetyMargin
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Check the timeout
cb
.
timeout
=
0
cb
.
updateSwTimeout
(
&
derive
.
BatchData
{
BatchV1
:
derive
.
BatchV1
{
EpochNum
:
rollup
.
Epoch
(
epochNum
),
},
})
calculatedTimeout
:=
epochNum
+
seqWindowSize
-
subSafetyMargin
cb
.
checkTimeout
(
calculatedTimeout
)
if
cb
.
timeout
!=
0
{
require
.
ErrorIs
(
t
,
cb
.
FullErr
(),
ErrSeqWindowClose
,
"Sequence window close should be reached"
)
}
})
}
// TestBuilderNextFrame tests calling NextFrame on a ChannelBuilder with only one frame
func
TestBuilderNextFrame
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
// Create a new channel builder
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Mock the internals of `channelBuilder.outputFrame`
// to construct a single frame
co
:=
cb
.
co
var
buf
bytes
.
Buffer
fn
,
err
:=
co
.
OutputFrame
(
&
buf
,
channelConfig
.
MaxFrameSize
)
require
.
NoError
(
t
,
err
)
// Push one frame into to the channel builder
expectedTx
:=
txID
{
chID
:
co
.
ID
(),
frameNumber
:
fn
}
expectedBytes
:=
buf
.
Bytes
()
frameData
:=
frameData
{
id
:
frameID
{
chID
:
co
.
ID
(),
frameNumber
:
fn
,
},
data
:
expectedBytes
,
}
cb
.
PushFrame
(
frameData
)
// There should only be 1 frame in the channel builder
require
.
Equal
(
t
,
1
,
cb
.
NumFrames
())
// We should be able to increment to the next frame
constructedFrame
:=
cb
.
NextFrame
()
require
.
Equal
(
t
,
expectedTx
,
constructedFrame
.
id
)
require
.
Equal
(
t
,
expectedBytes
,
constructedFrame
.
data
)
require
.
Equal
(
t
,
0
,
cb
.
NumFrames
())
// The next call should panic since the length of frames is 0
require
.
PanicsWithValue
(
t
,
"no next frame"
,
func
()
{
cb
.
NextFrame
()
})
}
// TestBuilderInvalidFrameId tests that a panic is thrown when a frame is pushed with an invalid frame id
func
TestBuilderWrongFramePanic
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
// Construct a channel builder
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Mock the internals of `channelBuilder.outputFrame`
// to construct a single frame
co
,
err
:=
derive
.
NewChannelOut
()
require
.
NoError
(
t
,
err
)
var
buf
bytes
.
Buffer
fn
,
err
:=
co
.
OutputFrame
(
&
buf
,
channelConfig
.
MaxFrameSize
)
require
.
NoError
(
t
,
err
)
// The frame push should panic since we constructed a new channel out
// so the channel out id won't match
require
.
PanicsWithValue
(
t
,
"wrong channel"
,
func
()
{
frame
:=
frameData
{
id
:
frameID
{
chID
:
co
.
ID
(),
frameNumber
:
fn
,
},
data
:
buf
.
Bytes
(),
}
cb
.
PushFrame
(
frame
)
})
}
// TestOutputFrames tests the OutputFrames function
func
TestOutputFrames
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxFrameSize
=
2
// Construct the channel builder
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
require
.
False
(
t
,
cb
.
IsFull
())
require
.
Equal
(
t
,
0
,
cb
.
NumFrames
())
// Calling OutputFrames without having called [AddBlock]
// should return no error
require
.
NoError
(
t
,
cb
.
OutputFrames
())
// There should be no ready bytes yet
readyBytes
:=
cb
.
co
.
ReadyBytes
()
require
.
Equal
(
t
,
0
,
readyBytes
)
// Let's add a block
err
=
addNonsenseBlock
(
cb
)
require
.
NoError
(
t
,
err
)
// Check how many ready bytes
readyBytes
=
cb
.
co
.
ReadyBytes
()
require
.
Equal
(
t
,
2
,
readyBytes
)
require
.
Equal
(
t
,
0
,
cb
.
NumFrames
())
// The channel should not be full
// but we want to output the frames for testing anyways
isFull
:=
cb
.
IsFull
()
require
.
False
(
t
,
isFull
)
// Since we manually set the max frame size to 2,
// we should be able to compress the two frames now
err
=
cb
.
OutputFrames
()
require
.
NoError
(
t
,
err
)
// There should be one frame in the channel builder now
require
.
Equal
(
t
,
1
,
cb
.
NumFrames
())
// There should no longer be any ready bytes
readyBytes
=
cb
.
co
.
ReadyBytes
()
require
.
Equal
(
t
,
0
,
readyBytes
)
}
// TestMaxRLPBytesPerChannel tests the [channelBuilder.OutputFrames]
// function errors when the max RLP bytes per channel is reached.
func
TestMaxRLPBytesPerChannel
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxFrameSize
=
2
// Construct the channel builder
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
require
.
False
(
t
,
cb
.
IsFull
())
require
.
Equal
(
t
,
0
,
cb
.
NumFrames
())
// Add a block that overflows the [ChannelOut]
err
=
buildTooLargeRlpEncodedBlockBatch
(
cb
)
require
.
ErrorIs
(
t
,
err
,
derive
.
ErrTooManyRLPBytes
)
}
// TestOutputFramesMaxFrameIndex tests the [channelBuilder.OutputFrames]
// function errors when the max frame index is reached.
func
TestOutputFramesMaxFrameIndex
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
channelConfig
.
MaxFrameSize
=
1
channelConfig
.
TargetNumFrames
=
math
.
MaxInt
channelConfig
.
TargetFrameSize
=
1
channelConfig
.
ApproxComprRatio
=
0
// Continuously add blocks until the max frame index is reached
// This should cause the [channelBuilder.OutputFrames] function
// to error
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
require
.
False
(
t
,
cb
.
IsFull
())
require
.
Equal
(
t
,
0
,
cb
.
NumFrames
())
for
{
lBlock
:=
types
.
NewBlock
(
&
types
.
Header
{
BaseFee
:
common
.
Big0
,
Difficulty
:
common
.
Big0
,
Number
:
common
.
Big0
,
},
nil
,
nil
,
nil
,
trie
.
NewStackTrie
(
nil
))
l1InfoTx
,
_
:=
derive
.
L1InfoDeposit
(
0
,
lBlock
,
eth
.
SystemConfig
{},
false
)
txs
:=
[]
*
types
.
Transaction
{
types
.
NewTx
(
l1InfoTx
)}
a
:=
types
.
NewBlock
(
&
types
.
Header
{
Number
:
big
.
NewInt
(
0
),
},
txs
,
nil
,
nil
,
trie
.
NewStackTrie
(
nil
))
err
=
cb
.
AddBlock
(
a
)
if
cb
.
IsFull
()
{
fullErr
:=
cb
.
FullErr
()
require
.
ErrorIs
(
t
,
fullErr
,
ErrMaxFrameIndex
)
break
}
require
.
NoError
(
t
,
err
)
_
=
cb
.
OutputFrames
()
// Flushing so we can construct new frames
_
=
cb
.
co
.
Flush
()
}
}
// TestBuilderAddBlock tests the AddBlock function
func
TestBuilderAddBlock
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
// Lower the max frame size so that we can batch
channelConfig
.
MaxFrameSize
=
2
// Configure the Input Threshold params so we observe a full channel
// In reality, we only need the input bytes (74) below to be greater than
// or equal to the input threshold (3 * 2) / 1 = 6
channelConfig
.
TargetFrameSize
=
3
channelConfig
.
TargetNumFrames
=
2
channelConfig
.
ApproxComprRatio
=
1
// Construct the channel builder
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Add a nonsense block to the channel builder
err
=
addNonsenseBlock
(
cb
)
require
.
NoError
(
t
,
err
)
// Check the fields reset in the AddBlock function
require
.
Equal
(
t
,
74
,
cb
.
co
.
InputBytes
())
require
.
Equal
(
t
,
1
,
len
(
cb
.
blocks
))
require
.
Equal
(
t
,
0
,
len
(
cb
.
frames
))
require
.
True
(
t
,
cb
.
IsFull
())
// Since the channel output is full, the next call to AddBlock
// should return the channel out full error
err
=
addNonsenseBlock
(
cb
)
require
.
ErrorIs
(
t
,
err
,
ErrInputTargetReached
)
}
// TestBuilderReset tests the Reset function
func
TestBuilderReset
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
// Lower the max frame size so that we can batch
channelConfig
.
MaxFrameSize
=
2
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Add a nonsense block to the channel builder
err
=
addNonsenseBlock
(
cb
)
require
.
NoError
(
t
,
err
)
// Check the fields reset in the Reset function
require
.
Equal
(
t
,
1
,
len
(
cb
.
blocks
))
require
.
Equal
(
t
,
0
,
len
(
cb
.
frames
))
// Timeout should be updated in the AddBlock internal call to `updateSwTimeout`
timeout
:=
uint64
(
100
)
+
cb
.
cfg
.
SeqWindowSize
-
cb
.
cfg
.
SubSafetyMargin
require
.
Equal
(
t
,
timeout
,
cb
.
timeout
)
require
.
NoError
(
t
,
cb
.
fullErr
)
// Output frames so we can set the channel builder frames
err
=
cb
.
OutputFrames
()
require
.
NoError
(
t
,
err
)
// Add another block to increment the block count
err
=
addNonsenseBlock
(
cb
)
require
.
NoError
(
t
,
err
)
// Check the fields reset in the Reset function
require
.
Equal
(
t
,
2
,
len
(
cb
.
blocks
))
require
.
Equal
(
t
,
1
,
len
(
cb
.
frames
))
require
.
Equal
(
t
,
timeout
,
cb
.
timeout
)
require
.
NoError
(
t
,
cb
.
fullErr
)
// Reset the channel builder
err
=
cb
.
Reset
()
require
.
NoError
(
t
,
err
)
// Check the fields reset in the Reset function
require
.
Equal
(
t
,
0
,
len
(
cb
.
blocks
))
require
.
Equal
(
t
,
0
,
len
(
cb
.
frames
))
require
.
Equal
(
t
,
uint64
(
0
),
cb
.
timeout
)
require
.
NoError
(
t
,
cb
.
fullErr
)
require
.
Equal
(
t
,
0
,
cb
.
co
.
InputBytes
())
require
.
Equal
(
t
,
0
,
cb
.
co
.
ReadyBytes
())
}
// TestBuilderRegisterL1Block tests the RegisterL1Block function
func
TestBuilderRegisterL1Block
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
// Construct the channel builder
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Assert params modified in RegisterL1Block
require
.
Equal
(
t
,
uint64
(
1
),
channelConfig
.
MaxChannelDuration
)
require
.
Equal
(
t
,
uint64
(
0
),
cb
.
timeout
)
// Register a new L1 block
cb
.
RegisterL1Block
(
uint64
(
100
))
// Assert params modified in RegisterL1Block
require
.
Equal
(
t
,
uint64
(
1
),
channelConfig
.
MaxChannelDuration
)
require
.
Equal
(
t
,
uint64
(
101
),
cb
.
timeout
)
}
// TestBuilderRegisterL1BlockZeroMaxChannelDuration tests the RegisterL1Block function
func
TestBuilderRegisterL1BlockZeroMaxChannelDuration
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
// Set the max channel duration to 0
channelConfig
.
MaxChannelDuration
=
0
// Construct the channel builder
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Assert params modified in RegisterL1Block
require
.
Equal
(
t
,
uint64
(
0
),
channelConfig
.
MaxChannelDuration
)
require
.
Equal
(
t
,
uint64
(
0
),
cb
.
timeout
)
// Register a new L1 block
cb
.
RegisterL1Block
(
uint64
(
100
))
// Since the max channel duration is set to 0,
// the L1 block register should not update the timeout
require
.
Equal
(
t
,
uint64
(
0
),
channelConfig
.
MaxChannelDuration
)
require
.
Equal
(
t
,
uint64
(
0
),
cb
.
timeout
)
}
// TestFramePublished tests the FramePublished function
func
TestFramePublished
(
t
*
testing
.
T
)
{
channelConfig
:=
defaultTestChannelConfig
// Construct the channel builder
cb
,
err
:=
newChannelBuilder
(
channelConfig
)
require
.
NoError
(
t
,
err
)
// Let's say the block number is fed in as 100
// and the channel timeout is 1000
l1BlockNum
:=
uint64
(
100
)
cb
.
cfg
.
ChannelTimeout
=
uint64
(
1000
)
cb
.
cfg
.
SubSafetyMargin
=
100
// Then the frame published will update the timeout
cb
.
FramePublished
(
l1BlockNum
)
// Now the timeout will be 1000
require
.
Equal
(
t
,
uint64
(
1000
),
cb
.
timeout
)
}
op-batcher/batcher/channel_config_test.go
0 → 100644
View file @
2dd2a627
package
batcher_test
import
(
"math"
"testing"
"github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/stretchr/testify/require"
)
// TestInputThreshold tests the [ChannelConfig.InputThreshold]
// function using a table-driven testing approach.
func
TestInputThreshold
(
t
*
testing
.
T
)
{
type
testInput
struct
{
TargetFrameSize
uint64
TargetNumFrames
int
ApproxComprRatio
float64
}
type
test
struct
{
input
testInput
assertion
func
(
uint64
)
}
// Construct test cases that test the boundary conditions
tests
:=
[]
test
{
{
input
:
testInput
{
TargetFrameSize
:
1
,
TargetNumFrames
:
1
,
ApproxComprRatio
:
0.4
,
},
assertion
:
func
(
output
uint64
)
{
require
.
Equal
(
t
,
uint64
(
2
),
output
)
},
},
{
input
:
testInput
{
TargetFrameSize
:
1
,
TargetNumFrames
:
100000
,
ApproxComprRatio
:
0.4
,
},
assertion
:
func
(
output
uint64
)
{
require
.
Equal
(
t
,
uint64
(
250
_000
),
output
)
},
},
{
input
:
testInput
{
TargetFrameSize
:
1
,
TargetNumFrames
:
1
,
ApproxComprRatio
:
1
,
},
assertion
:
func
(
output
uint64
)
{
require
.
Equal
(
t
,
uint64
(
1
),
output
)
},
},
{
input
:
testInput
{
TargetFrameSize
:
1
,
TargetNumFrames
:
1
,
ApproxComprRatio
:
2
,
},
assertion
:
func
(
output
uint64
)
{
require
.
Equal
(
t
,
uint64
(
0
),
output
)
},
},
{
input
:
testInput
{
TargetFrameSize
:
100000
,
TargetNumFrames
:
1
,
ApproxComprRatio
:
0.4
,
},
assertion
:
func
(
output
uint64
)
{
require
.
Equal
(
t
,
uint64
(
250
_000
),
output
)
},
},
{
input
:
testInput
{
TargetFrameSize
:
1
,
TargetNumFrames
:
100000
,
ApproxComprRatio
:
0.4
,
},
assertion
:
func
(
output
uint64
)
{
require
.
Equal
(
t
,
uint64
(
250
_000
),
output
)
},
},
{
input
:
testInput
{
TargetFrameSize
:
100000
,
TargetNumFrames
:
100000
,
ApproxComprRatio
:
0.4
,
},
assertion
:
func
(
output
uint64
)
{
require
.
Equal
(
t
,
uint64
(
25
_000_000_000
),
output
)
},
},
{
input
:
testInput
{
TargetFrameSize
:
1
,
TargetNumFrames
:
1
,
ApproxComprRatio
:
0.000001
,
},
assertion
:
func
(
output
uint64
)
{
require
.
Equal
(
t
,
uint64
(
1
_000_000
),
output
)
},
},
{
input
:
testInput
{
TargetFrameSize
:
0
,
TargetNumFrames
:
0
,
ApproxComprRatio
:
0
,
},
assertion
:
func
(
output
uint64
)
{
// Need to allow for NaN depending on the machine architecture
require
.
True
(
t
,
output
==
uint64
(
0
)
||
output
==
uint64
(
math
.
NaN
()))
},
},
}
// Validate each test case
for
_
,
tt
:=
range
tests
{
config
:=
batcher
.
ChannelConfig
{
TargetFrameSize
:
tt
.
input
.
TargetFrameSize
,
TargetNumFrames
:
tt
.
input
.
TargetNumFrames
,
ApproxComprRatio
:
tt
.
input
.
ApproxComprRatio
,
}
got
:=
config
.
InputThreshold
()
tt
.
assertion
(
got
)
}
}
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