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
4005ebb1
Unverified
Commit
4005ebb1
authored
Oct 05, 2022
by
protolambda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
op-e2e: action testing L2 sequencer, use L1State in verifier
parent
b660cfbe
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
232 additions
and
8 deletions
+232
-8
l2_sequencer.go
op-e2e/actions/l2_sequencer.go
+94
-0
l2_sequencer_test.go
op-e2e/actions/l2_sequencer_test.go
+102
-0
l2_verifier.go
op-e2e/actions/l2_verifier.go
+29
-8
metrics.go
op-node/testutils/metrics.go
+7
-0
No files found.
op-e2e/actions/l2_sequencer.go
0 → 100644
View file @
4005ebb1
package
actions
import
(
"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-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
// L2Sequencer is an actor that functions like a rollup node,
// without the full P2P/API/Node stack, but just the derivation state, and simplified driver with sequencing ability.
type
L2Sequencer
struct
{
L2Verifier
sequencer
*
driver
.
Sequencer
l1OriginSelector
*
driver
.
L1OriginSelector
seqOldOrigin
bool
// stay on current L1 origin when sequencing a block, unless forced to adopt the next origin
failL2GossipUnsafeBlock
error
// mock error
}
func
NewL2Sequencer
(
log
log
.
Logger
,
l1
derive
.
L1Fetcher
,
eng
derive
.
Engine
,
cfg
*
rollup
.
Config
,
seqConfDepth
uint64
)
*
L2Sequencer
{
ver
:=
NewL2Verifier
(
log
,
l1
,
eng
,
cfg
)
return
&
L2Sequencer
{
L2Verifier
:
*
ver
,
sequencer
:
driver
.
NewSequencer
(
log
,
cfg
,
l1
,
eng
),
l1OriginSelector
:
driver
.
NewL1OriginSelector
(
log
,
cfg
,
l1
,
seqConfDepth
),
seqOldOrigin
:
false
,
failL2GossipUnsafeBlock
:
nil
,
}
}
// ActL2StartBlock starts building of a new L2 block on top of the head
func
(
s
*
L2Sequencer
)
ActL2StartBlock
(
t
Testing
)
{
if
!
s
.
l2PipelineIdle
{
t
.
InvalidAction
(
"cannot start L2 build when derivation is not idle"
)
return
}
if
s
.
l2Building
{
t
.
InvalidAction
(
"already started building L2 block"
)
return
}
parent
:=
s
.
derivation
.
UnsafeL2Head
()
var
origin
eth
.
L1BlockRef
if
s
.
seqOldOrigin
{
// force old origin, for testing purposes
oldOrigin
,
err
:=
s
.
l1
.
L1BlockRefByHash
(
t
.
Ctx
(),
parent
.
L1Origin
.
Hash
)
require
.
NoError
(
t
,
err
,
"failed to get current origin: %s"
,
parent
.
L1Origin
)
origin
=
oldOrigin
s
.
seqOldOrigin
=
false
// don't repeat this
}
else
{
// select origin the real way
l1Origin
,
err
:=
s
.
l1OriginSelector
.
FindL1Origin
(
t
.
Ctx
(),
s
.
l1State
.
L1Head
(),
parent
)
require
.
NoError
(
t
,
err
)
origin
=
l1Origin
}
err
:=
s
.
sequencer
.
StartBuildingBlock
(
t
.
Ctx
(),
parent
,
s
.
derivation
.
SafeL2Head
()
.
ID
(),
s
.
derivation
.
Finalized
()
.
ID
(),
origin
)
require
.
NoError
(
t
,
err
,
"failed to start block building"
)
s
.
l2Building
=
true
}
// ActL2EndBlock completes a new L2 block and applies it to the L2 chain as new canonical unsafe head
func
(
s
*
L2Sequencer
)
ActL2EndBlock
(
t
Testing
)
{
if
!
s
.
l2Building
{
t
.
InvalidAction
(
"cannot end L2 block building when no block is being built"
)
return
}
s
.
l2Building
=
false
payload
,
err
:=
s
.
sequencer
.
CompleteBuildingBlock
(
t
.
Ctx
())
// TODO: there may be legitimate temporary errors here, if we mock engine API RPC-failure.
// For advanced tests we can catch those and print a warning instead.
require
.
NoError
(
t
,
err
)
ref
,
err
:=
derive
.
PayloadToBlockRef
(
payload
,
&
s
.
rollupCfg
.
Genesis
)
require
.
NoError
(
t
,
err
,
"payload must convert to block ref"
)
s
.
derivation
.
SetUnsafeHead
(
ref
)
// TODO: action-test publishing of payload on p2p
}
// ActL2KeepL1Origin makes the sequencer use the current L1 origin, even if the next origin is available.
func
(
s
*
L2Sequencer
)
ActL2KeepL1Origin
(
t
Testing
)
{
if
s
.
seqOldOrigin
{
// don't do this twice
t
.
InvalidAction
(
"already decided to keep old L1 origin"
)
return
}
s
.
seqOldOrigin
=
true
}
op-e2e/actions/l2_sequencer_test.go
0 → 100644
View file @
4005ebb1
package
actions
import
(
"math/big"
"testing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog"
)
func
setupSequencerTest
(
t
Testing
,
sd
*
e2eutils
.
SetupData
,
log
log
.
Logger
)
(
*
L1Miner
,
*
L2Engine
,
*
L2Sequencer
)
{
jwtPath
:=
e2eutils
.
WriteDefaultJWT
(
t
)
miner
:=
NewL1Miner
(
log
,
sd
.
L1Cfg
)
l1F
,
err
:=
sources
.
NewL1Client
(
miner
.
RPCClient
(),
log
,
nil
,
sources
.
L1ClientDefaultConfig
(
sd
.
RollupCfg
,
false
))
require
.
NoError
(
t
,
err
)
engine
:=
NewL2Engine
(
log
,
sd
.
L2Cfg
,
sd
.
RollupCfg
.
Genesis
.
L1
,
jwtPath
)
l2Cl
,
err
:=
sources
.
NewEngineClient
(
engine
.
RPCClient
(),
log
,
nil
,
sources
.
EngineClientDefaultConfig
(
sd
.
RollupCfg
))
require
.
NoError
(
t
,
err
)
sequencer
:=
NewL2Sequencer
(
log
,
l1F
,
l2Cl
,
sd
.
RollupCfg
,
0
)
return
miner
,
engine
,
sequencer
}
func
TestL2Sequencer_SequencerDrift
(
gt
*
testing
.
T
)
{
t
:=
NewDefaultTesting
(
gt
)
p
:=
&
e2eutils
.
TestParams
{
MaxSequencerDrift
:
20
,
// larger than L1 block time we simulate in this test (12)
SequencerWindowSize
:
24
,
ChannelTimeout
:
20
,
}
dp
:=
e2eutils
.
MakeDeployParams
(
t
,
p
)
sd
:=
e2eutils
.
Setup
(
t
,
dp
,
defaultAlloc
)
log
:=
testlog
.
Logger
(
t
,
log
.
LvlDebug
)
miner
,
engine
,
sequencer
:=
setupSequencerTest
(
t
,
sd
,
log
)
miner
.
ActL1SetFeeRecipient
(
common
.
Address
{
'A'
})
sequencer
.
ActL2PipelineFull
(
t
)
signer
:=
types
.
LatestSigner
(
sd
.
L2Cfg
.
Config
)
cl
:=
engine
.
EthClient
()
aliceTx
:=
func
()
{
n
,
err
:=
cl
.
PendingNonceAt
(
t
.
Ctx
(),
dp
.
Addresses
.
Alice
)
require
.
NoError
(
t
,
err
)
tx
:=
types
.
MustSignNewTx
(
dp
.
Secrets
.
Alice
,
signer
,
&
types
.
DynamicFeeTx
{
ChainID
:
sd
.
L2Cfg
.
Config
.
ChainID
,
Nonce
:
n
,
GasTipCap
:
big
.
NewInt
(
2
*
params
.
GWei
),
GasFeeCap
:
new
(
big
.
Int
)
.
Add
(
miner
.
l1Chain
.
CurrentBlock
()
.
BaseFee
(),
big
.
NewInt
(
2
*
params
.
GWei
)),
Gas
:
params
.
TxGas
,
To
:
&
dp
.
Addresses
.
Bob
,
Value
:
e2eutils
.
Ether
(
2
),
})
require
.
NoError
(
gt
,
cl
.
SendTransaction
(
t
.
Ctx
(),
tx
))
}
makeL2BlockWithAliceTx
:=
func
()
{
aliceTx
()
sequencer
.
ActL2StartBlock
(
t
)
engine
.
ActL2IncludeTx
(
dp
.
Addresses
.
Alice
)(
t
)
// include a test tx from alice
sequencer
.
ActL2EndBlock
(
t
)
}
// L1 makes a block
miner
.
ActL1StartBlock
(
12
)(
t
)
miner
.
ActL1EndBlock
(
t
)
sequencer
.
ActL1HeadSignal
(
t
)
origin
:=
miner
.
l1Chain
.
CurrentBlock
()
// L2 makes blocks to catch up
for
sequencer
.
SyncStatus
()
.
UnsafeL2
.
Time
+
sd
.
RollupCfg
.
BlockTime
<
origin
.
Time
()
{
makeL2BlockWithAliceTx
()
require
.
Equal
(
t
,
uint64
(
0
),
sequencer
.
SyncStatus
()
.
UnsafeL2
.
L1Origin
.
Number
,
"no L1 origin change before time matches"
)
}
// Check that we adopted the origin as soon as we could (conf depth is 0)
makeL2BlockWithAliceTx
()
require
.
Equal
(
t
,
uint64
(
1
),
sequencer
.
SyncStatus
()
.
UnsafeL2
.
L1Origin
.
Number
,
"L1 origin changes as soon as L2 time equals or exceeds L1 time"
)
miner
.
ActL1StartBlock
(
12
)(
t
)
miner
.
ActL1EndBlock
(
t
)
sequencer
.
ActL1HeadSignal
(
t
)
// Make blocks up till the sequencer drift is about to surpass, but keep the old L1 origin
for
sequencer
.
SyncStatus
()
.
UnsafeL2
.
Time
+
sd
.
RollupCfg
.
BlockTime
<
origin
.
Time
()
+
sd
.
RollupCfg
.
MaxSequencerDrift
{
sequencer
.
ActL2KeepL1Origin
(
t
)
makeL2BlockWithAliceTx
()
require
.
Equal
(
t
,
uint64
(
1
),
sequencer
.
SyncStatus
()
.
UnsafeL2
.
L1Origin
.
Number
,
"expected to keep old L1 origin"
)
}
// We passed the sequencer drift: we can still keep the old origin, but can't include any txs
sequencer
.
ActL2KeepL1Origin
(
t
)
sequencer
.
ActL2StartBlock
(
t
)
require
.
True
(
t
,
engine
.
l2ForceEmpty
,
"engine should not be allowed to include anything after sequencer drift is surpassed"
)
}
op-e2e/actions/l2_verifier.go
View file @
4005ebb1
...
@@ -4,6 +4,9 @@ import (
...
@@ -4,6 +4,9 @@ import (
"errors"
"errors"
"io"
"io"
"github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/eth"
...
@@ -22,9 +25,8 @@ type L2Verifier struct {
...
@@ -22,9 +25,8 @@ type L2Verifier struct {
// L2 rollup
// L2 rollup
derivation
*
derive
.
DerivationPipeline
derivation
*
derive
.
DerivationPipeline
l1Head
eth
.
L1BlockRef
l1
derive
.
L1Fetcher
l1Safe
eth
.
L1BlockRef
l1State
*
driver
.
L1State
l1Finalized
eth
.
L1BlockRef
l2PipelineIdle
bool
l2PipelineIdle
bool
l2Building
bool
l2Building
bool
...
@@ -33,12 +35,15 @@ type L2Verifier struct {
...
@@ -33,12 +35,15 @@ type L2Verifier struct {
}
}
func
NewL2Verifier
(
log
log
.
Logger
,
l1
derive
.
L1Fetcher
,
eng
derive
.
Engine
,
cfg
*
rollup
.
Config
)
*
L2Verifier
{
func
NewL2Verifier
(
log
log
.
Logger
,
l1
derive
.
L1Fetcher
,
eng
derive
.
Engine
,
cfg
*
rollup
.
Config
)
*
L2Verifier
{
pipeline
:=
derive
.
NewDerivationPipeline
(
log
,
cfg
,
l1
,
eng
,
&
testutils
.
TestDerivationMetrics
{})
metrics
:=
&
testutils
.
TestDerivationMetrics
{}
pipeline
:=
derive
.
NewDerivationPipeline
(
log
,
cfg
,
l1
,
eng
,
metrics
)
pipeline
.
Reset
()
pipeline
.
Reset
()
return
&
L2Verifier
{
return
&
L2Verifier
{
log
:
log
,
log
:
log
,
eng
:
eng
,
eng
:
eng
,
derivation
:
pipeline
,
derivation
:
pipeline
,
l1
:
l1
,
l1State
:
driver
.
NewL1State
(
log
,
metrics
),
l2PipelineIdle
:
true
,
l2PipelineIdle
:
true
,
l2Building
:
false
,
l2Building
:
false
,
rollupCfg
:
cfg
,
rollupCfg
:
cfg
,
...
@@ -48,16 +53,32 @@ func NewL2Verifier(log log.Logger, l1 derive.L1Fetcher, eng derive.Engine, cfg *
...
@@ -48,16 +53,32 @@ func NewL2Verifier(log log.Logger, l1 derive.L1Fetcher, eng derive.Engine, cfg *
func
(
s
*
L2Verifier
)
SyncStatus
()
*
eth
.
SyncStatus
{
func
(
s
*
L2Verifier
)
SyncStatus
()
*
eth
.
SyncStatus
{
return
&
eth
.
SyncStatus
{
return
&
eth
.
SyncStatus
{
CurrentL1
:
s
.
derivation
.
Origin
(),
CurrentL1
:
s
.
derivation
.
Origin
(),
HeadL1
:
s
.
l1
Head
,
HeadL1
:
s
.
l1
State
.
L1Head
()
,
SafeL1
:
s
.
l1S
afe
,
SafeL1
:
s
.
l1S
tate
.
L1Safe
()
,
FinalizedL1
:
s
.
l1
Finalized
,
FinalizedL1
:
s
.
l1
State
.
L1Finalized
()
,
UnsafeL2
:
s
.
derivation
.
UnsafeL2Head
(),
UnsafeL2
:
s
.
derivation
.
UnsafeL2Head
(),
SafeL2
:
s
.
derivation
.
SafeL2Head
(),
SafeL2
:
s
.
derivation
.
SafeL2Head
(),
FinalizedL2
:
s
.
derivation
.
Finalized
(),
FinalizedL2
:
s
.
derivation
.
Finalized
(),
}
}
}
}
// TODO: actions to change L1 head/safe/finalized state. Depends on driver refactor work.
func
(
s
*
L2Verifier
)
ActL1HeadSignal
(
t
Testing
)
{
head
,
err
:=
s
.
l1
.
L1BlockRefByLabel
(
t
.
Ctx
(),
eth
.
Unsafe
)
require
.
NoError
(
t
,
err
)
s
.
l1State
.
HandleNewL1HeadBlock
(
head
)
}
func
(
s
*
L2Verifier
)
ActL1SafeSignal
(
t
Testing
)
{
head
,
err
:=
s
.
l1
.
L1BlockRefByLabel
(
t
.
Ctx
(),
eth
.
Safe
)
require
.
NoError
(
t
,
err
)
s
.
l1State
.
HandleNewL1SafeBlock
(
head
)
}
func
(
s
*
L2Verifier
)
ActL1FinalizedSignal
(
t
Testing
)
{
head
,
err
:=
s
.
l1
.
L1BlockRefByLabel
(
t
.
Ctx
(),
eth
.
Finalized
)
require
.
NoError
(
t
,
err
)
s
.
l1State
.
HandleNewL1FinalizedBlock
(
head
)
}
// ActL2PipelineStep runs one iteration of the L2 derivation pipeline
// ActL2PipelineStep runs one iteration of the L2 derivation pipeline
func
(
s
*
L2Verifier
)
ActL2PipelineStep
(
t
Testing
)
{
func
(
s
*
L2Verifier
)
ActL2PipelineStep
(
t
Testing
)
{
...
...
op-node/testutils/metrics.go
View file @
4005ebb1
...
@@ -5,11 +5,18 @@ import "github.com/ethereum-optimism/optimism/op-node/eth"
...
@@ -5,11 +5,18 @@ import "github.com/ethereum-optimism/optimism/op-node/eth"
// TestDerivationMetrics implements the metrics used in the derivation pipeline as no-op operations.
// TestDerivationMetrics implements the metrics used in the derivation pipeline as no-op operations.
// Optionally a test may hook into the metrics
// Optionally a test may hook into the metrics
type
TestDerivationMetrics
struct
{
type
TestDerivationMetrics
struct
{
FnRecordL1ReorgDepth
func
(
d
uint64
)
FnRecordL1Ref
func
(
name
string
,
ref
eth
.
L1BlockRef
)
FnRecordL1Ref
func
(
name
string
,
ref
eth
.
L1BlockRef
)
FnRecordL2Ref
func
(
name
string
,
ref
eth
.
L2BlockRef
)
FnRecordL2Ref
func
(
name
string
,
ref
eth
.
L2BlockRef
)
FnRecordUnsafePayloads
func
(
length
uint64
,
memSize
uint64
,
next
eth
.
BlockID
)
FnRecordUnsafePayloads
func
(
length
uint64
,
memSize
uint64
,
next
eth
.
BlockID
)
}
}
func
(
t
*
TestDerivationMetrics
)
RecordL1ReorgDepth
(
d
uint64
)
{
if
t
.
FnRecordL1ReorgDepth
!=
nil
{
t
.
FnRecordL1ReorgDepth
(
d
)
}
}
func
(
t
*
TestDerivationMetrics
)
RecordL1Ref
(
name
string
,
ref
eth
.
L1BlockRef
)
{
func
(
t
*
TestDerivationMetrics
)
RecordL1Ref
(
name
string
,
ref
eth
.
L1BlockRef
)
{
if
t
.
FnRecordL1Ref
!=
nil
{
if
t
.
FnRecordL1Ref
!=
nil
{
t
.
FnRecordL1Ref
(
name
,
ref
)
t
.
FnRecordL1Ref
(
name
,
ref
)
...
...
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