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
9a198e90
Unverified
Commit
9a198e90
authored
Jan 10, 2023
by
protolambda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
op-node,specs: fix framequeue reset, fix channel-bank and frame-queue specs
parent
f03df0a5
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
79 additions
and
68 deletions
+79
-68
channel_bank.go
op-node/rollup/derive/channel_bank.go
+3
-3
frame_queue.go
op-node/rollup/derive/frame_queue.go
+3
-2
pipeline.go
op-node/rollup/derive/pipeline.go
+1
-1
derivation.md
specs/derivation.md
+72
-62
No files found.
op-node/rollup/derive/channel_bank.go
View file @
9a198e90
...
@@ -77,7 +77,7 @@ func (cb *ChannelBank) prune() {
...
@@ -77,7 +77,7 @@ func (cb *ChannelBank) prune() {
// Read() should be called repeatedly first, until everything has been read, before adding new data.
// Read() should be called repeatedly first, until everything has been read, before adding new data.
func
(
cb
*
ChannelBank
)
IngestFrame
(
f
Frame
)
{
func
(
cb
*
ChannelBank
)
IngestFrame
(
f
Frame
)
{
origin
:=
cb
.
Origin
()
origin
:=
cb
.
Origin
()
log
:=
log
.
New
(
"origin"
,
origin
,
"channel"
,
f
.
ID
,
"length"
,
len
(
f
.
Data
),
"frame_number"
,
f
.
FrameNumber
)
log
:=
log
.
New
(
"origin"
,
origin
,
"channel"
,
f
.
ID
,
"length"
,
len
(
f
.
Data
),
"frame_number"
,
f
.
FrameNumber
,
"is_last"
,
f
.
IsLast
)
log
.
Debug
(
"channel bank got new data"
)
log
.
Debug
(
"channel bank got new data"
)
currentCh
,
ok
:=
cb
.
channels
[
f
.
ID
]
currentCh
,
ok
:=
cb
.
channels
[
f
.
ID
]
...
@@ -117,7 +117,7 @@ func (cb *ChannelBank) Read() (data []byte, err error) {
...
@@ -117,7 +117,7 @@ func (cb *ChannelBank) Read() (data []byte, err error) {
cb
.
log
.
Debug
(
"channel timed out"
,
"channel"
,
first
,
"frames"
,
len
(
ch
.
inputs
))
cb
.
log
.
Debug
(
"channel timed out"
,
"channel"
,
first
,
"frames"
,
len
(
ch
.
inputs
))
delete
(
cb
.
channels
,
first
)
delete
(
cb
.
channels
,
first
)
cb
.
channelQueue
=
cb
.
channelQueue
[
1
:
]
cb
.
channelQueue
=
cb
.
channelQueue
[
1
:
]
return
nil
,
io
.
EOF
return
nil
,
nil
// multiple different channels may all be timed out
}
}
if
!
ch
.
IsReady
()
{
if
!
ch
.
IsReady
()
{
return
nil
,
io
.
EOF
return
nil
,
io
.
EOF
...
@@ -126,7 +126,7 @@ func (cb *ChannelBank) Read() (data []byte, err error) {
...
@@ -126,7 +126,7 @@ func (cb *ChannelBank) Read() (data []byte, err error) {
delete
(
cb
.
channels
,
first
)
delete
(
cb
.
channels
,
first
)
cb
.
channelQueue
=
cb
.
channelQueue
[
1
:
]
cb
.
channelQueue
=
cb
.
channelQueue
[
1
:
]
r
:=
ch
.
Reader
()
r
:=
ch
.
Reader
()
// Sup
r
ress error here. io.ReadAll does return nil instead of io.EOF though.
// Sup
p
ress error here. io.ReadAll does return nil instead of io.EOF though.
data
,
_
=
io
.
ReadAll
(
r
)
data
,
_
=
io
.
ReadAll
(
r
)
return
data
,
nil
return
data
,
nil
}
}
...
...
op-node/rollup/derive/frame_queue.go
View file @
9a198e90
...
@@ -4,8 +4,9 @@ import (
...
@@ -4,8 +4,9 @@ import (
"context"
"context"
"io"
"io"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth"
)
)
var
_
NextFrameProvider
=
&
FrameQueue
{}
var
_
NextFrameProvider
=
&
FrameQueue
{}
...
@@ -55,7 +56,7 @@ func (fq *FrameQueue) NextFrame(ctx context.Context) (Frame, error) {
...
@@ -55,7 +56,7 @@ func (fq *FrameQueue) NextFrame(ctx context.Context) (Frame, error) {
return
ret
,
nil
return
ret
,
nil
}
}
func
(
fq
*
FrameQueue
)
Reset
(
ctx
context
.
Context
,
base
eth
.
L1BlockRef
)
error
{
func
(
fq
*
FrameQueue
)
Reset
(
_
context
.
Context
,
_
eth
.
L1BlockRef
,
_
eth
.
SystemConfig
)
error
{
fq
.
frames
=
fq
.
frames
[
:
0
]
fq
.
frames
=
fq
.
frames
[
:
0
]
return
io
.
EOF
return
io
.
EOF
}
}
op-node/rollup/derive/pipeline.go
View file @
9a198e90
...
@@ -82,7 +82,7 @@ func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetch
...
@@ -82,7 +82,7 @@ func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetch
// Reset from engine queue then up from L1 Traversal. The stages do not talk to each other during
// Reset from engine queue then up from L1 Traversal. The stages do not talk to each other during
// the reset, but after the engine queue, this is the order in which the stages could talk to each other.
// the reset, but after the engine queue, this is the order in which the stages could talk to each other.
// Note: The engine queue stage is the only reset that can fail.
// Note: The engine queue stage is the only reset that can fail.
stages
:=
[]
ResetableStage
{
eng
,
l1Traversal
,
l1Src
,
bank
,
chInReader
,
batchQueue
,
attributesQueue
}
stages
:=
[]
ResetableStage
{
eng
,
l1Traversal
,
l1Src
,
frameQueue
,
bank
,
chInReader
,
batchQueue
,
attributesQueue
}
return
&
DerivationPipeline
{
return
&
DerivationPipeline
{
log
:
log
,
log
:
log
,
...
...
specs/derivation.md
View file @
9a198e90
...
@@ -63,7 +63,12 @@
...
@@ -63,7 +63,12 @@
-
[
L2 Chain Derivation Pipeline
](
#l2-chain-derivation-pipeline
)
-
[
L2 Chain Derivation Pipeline
](
#l2-chain-derivation-pipeline
)
-
[
L1 Traversal
](
#l1-traversal
)
-
[
L1 Traversal
](
#l1-traversal
)
-
[
L1 Retrieval
](
#l1-retrieval
)
-
[
L1 Retrieval
](
#l1-retrieval
)
-
[
Frame Queue
](
#frame-queue
)
-
[
Channel Bank
](
#channel-bank
)
-
[
Channel Bank
](
#channel-bank
)
-
[
Pruning
](
#pruning
)
-
[
Timeouts
](
#timeouts
)
-
[
Reading
](
#reading
)
-
[
Loading frames
](
#loading-frames
)
-
[
Batch Decoding
](
#batch-decoding
)
-
[
Batch Decoding
](
#batch-decoding
)
-
[
Batch Buffering
](
#batch-buffering
)
-
[
Batch Buffering
](
#batch-buffering
)
-
[
Payload Attributes Derivation
](
#payload-attributes-derivation
)
-
[
Payload Attributes Derivation
](
#payload-attributes-derivation
)
...
@@ -466,84 +471,89 @@ updated, such that the batch-sender authentication is always accurate to the exa
...
@@ -466,84 +471,89 @@ updated, such that the batch-sender authentication is always accurate to the exa
### L1 Retrieval
### L1 Retrieval
In the
*L1 Retrieval*
stage, we read the block we get from the outer stage (L1 traversal), and extract data for it. In
In the
*L1 Retrieval*
stage, we read the block we get from the outer stage (L1 traversal), and extract data from it.
particular we extract a byte string that corresponds to the concatenation of the data in all the
[
batcher
By default, the rollup operates on calldata retrieved from
[
batcher transactions
][
g-batcher-transaction
]
in the block,
transaction]
[
g-batcher-transaction
]
belonging to the block. This byte stream encodes a stream of
[
channel
for each transaction:
frames]
[
g-channel-frame
]
(
see
the
[
Batch Submission Wire Format
][
wire-format
]
section for more info).
These frames are parsed, then grouped per
[
channel
][
g-channel
]
into a structure we call the
*channel bank*
. When
-
The receiver must be the configured batcher inbox address.
adding frames the the channel, individual frames may be invalid, but the channel does not have a notion of validity
-
The sender must match the batcher address loaded from the system config matching the L1 block of the data.
until the channel timeout is up. This enables adding the option to do a partial read from the channel in the future.
Some frames are ignored:
Each data-transaction is versioned and contains a series of
[
channel frames
][
g-channel-frame
]
to be read by the
Frame Queue, see
[
Batch Submission Wire Format
][
wire-format
]
.
-
Frames with the same frame number as an existing frame in the channel (a duplicate). The first seen frame is used.
### Frame Queue
-
Frames that attempt to close an already closed channel. This would be the second frame with
`frame.is_last == 1`
even
if the frame number of the second frame is not the same as the first frame which closed the channel.
If a frame with
`is_last == 1`
is added to a channel, all frames with a higher frame number are removed from the
The Frame Queue buffers one data-transaction at a time,
channel.
decoded into
[
channel frames
][
g-channel-frame
]
, to be consumed by the next stage.
See
[
Batcher transaction format
](
#batcher-transaction-format
)
and
[
Frame format
](
#frame-format
)
specifications.
Channels are also recorded in FIFO order in a structure called the
*channel queue*
. A channel is added to the channel
queue the first time a frame belonging to the channel is seen. This structure is used in the next stage.
### Channel Bank
### Channel Bank
The
*Channel Bank*
stage is responsible for managing buffering from the channel bank that was written to by the L1
The
*Channel Bank*
stage is responsible for managing buffering from the channel bank that was written to by the L1
retrieval stage. A step in the channel bank stage tries to read data from channels that are "ready".
retrieval stage. A step in the channel bank stage tries to read data from channels that are "ready".
In principle, we should be able to read any channel that has any number of sequential frames at the "front" of the
Channels are currently fully buffered until read or dropped,
channel (i.e. right after any frames that have been read from the bank already) and decompress batches from them. (Note
streaming channels may be supported in a future version of the ChannelBank.
that if we did this, we'd need to keep partially decompressed batches around.)
To bound resource usage, the Channel Bank prunes based on channel size, and times out old channels.
However, our current implementation doesn't support streaming decompression, so currently we have to wait until either:
Channels are recorded in FIFO order in a structure called the
*channel queue*
. A channel is added to the channel
-
We have received all frames in the channel: i.e. we received the last frame in the channel (
`is_last == 1`
) and every
queue the first time a frame belonging to the channel is seen.
frame with a lower number.
-
The channel has timed out (in which we case we read all contiguous sequential frames from the start of the channel).
#### Pruning
-
A channel is considered to be
*timed out*
if
`currentL1Block.number > channeld_id.starting_l1_number + CHANNEL_TIMEOUT`
.
After successfully inserting a new frame, the ChannelBank is pruned:
-
where
`currentL1Block`
is the L1 block maintained by this stage, which is the most recent L1 block whose frames
channels are dropped in FIFO order, until
`total_size <= MAX_CHANNEL_BANK_SIZE`
, where:
have been added to the channel bank.
-
The channel is pruned out of the channel bank (see below), in which case it isn't passed to the further stages.
-
`total_size`
is the sum of the sizes of each channel, which is the sum of all buffered frame data of the channel,
with an additional frame-overhead of
`200`
bytes per frame.
> **TODO** specify CHANNEL_TIMEOUT (currently 120s on Goerli testnet)
-
`MAX_CHANNEL_BANK_SIZE`
is a protocol constant of 100,000,000 bytes.
As currently implemented, each step in this stage performs the following actions:
#### Timeouts
-
Try to prune the channel bank.
The L1 origin that the channel was opened in is tracked with the channel as
`channel.open_l1_block`
,
-
This occurs if the size of the channel bank exceeds
`MAX_CHANNEL_BANK_SIZE`
(currently set to 100,000,000 bytes).
and determines the maximum span of L1 blocks that the channel data is retained for, before being pruned.
-
The size of channel bank is the sum of the sizes (in btes) of all the frames contained within it.
-
In this case, channels are dropped from the front of the
*channel queue*
(see previous stage), and the frames
A channel is timed out if:
`current_l1_block.number > channel.open_l1_block.number + CHANNEL_TIMEOUT`
, where:
belonging from these channels are dropped from the channel bank.
-
As many channels are dropped as is necessary so that the channel bank size falls back below
-
`current_l1_block`
is the L1 origin that the stage is currently traversing.
`MAX_CHANNEL_BANK_SIZE`
.
-
`CHANNEL_TIMEOUT`
is a rollup-configurable, expressed in number of L1 blocks.
-
Take the first channel and the
*channel queue*
, determine if it is ready, and process it if so.
-
A channel is ready if all its frames have been received or it timed out (see list above for details).
New frames for timed-out channels are dropped instead of buffered.
-
If the channel is ready, determine its
*contiguous frame sequence*
, which is a contiguous sequence of frames,
starting from the first frame in the channel.
#### Reading
-
For a full channel, those are all the frames.
-
For a timed channel, those are all the frames until the first missing frame. Frames after the first missing
The channel-bank can only output data from the first opened channel.
frame are discarded.
-
Concatenate the data of the
*contiguous frame sequence*
(in sequential order) and push it to the next stage.
Upon reading, first all timed-out channels are dropped.
The ordering of these actions is very important to be consistent across nodes & pipeline resets. The rollup node
After pruning timed-out channels, the first remaining channel, if any, is read if it is ready:
must attempt to do the following in order to maintain a consistent channel bank even in the presence of pruning.
-
The channel must be closed
1.
Attempt to read as many channels as possible from the channel bank.
-
The channel must have a contiguous sequence of frames until the closing frame
2.
Load in a single frame
3.
Check if channel bank needs to be pruned & do so if needed.
If no channel is ready, the next frame is read and ingested into the channel bank.
4.
Go to step 1 once the channel bank is under it's size limit.
#### Loading frames
> **TODO** Instead of waiting on the first seen channel (which might not contain the oldest batches, meaning buffering
> further down the pipeline), we could process any channel in the queue that is ready. We could do this by checking for
When a channel ID referenced by a frame is not already present in the Channel Bank,
> channel readiness upon writing into the bank, and moving ready channel to the front of the queue.
a new channel is opened, tagged with the current L1 block, and appended to the channel-queue.
Frame insertion conditions:
-
New frames matching existing timed-out channels are dropped.
-
Duplicate frames (by frame number) are dropped.
-
Duplicate closes (new frame
`is_last == 1`
, but the channel has already seen a closing frame) are dropped.
If a frame is closing (
`is_last == 1`
) any existing higher-numbered frames are removed from the channel.
### Batch Decoding
### Batch Decoding
In the
*Batch Decoding*
stage, we decompress the channel we received in the last stage, then parse
In the
*Batch Decoding*
stage, we decompress the channel we received in the last stage, then parse
[
batches
][
g-sequencer-batch
]
from the decompressed byte stream.
[
batches
][
g-sequencer-batch
]
from the decompressed byte stream.
See
[
Batch Format
][
batch-format
]
for decompression and decoding specification.
### Batch Buffering
### Batch Buffering
[
batch-buffering
]:
#batch-buffering
[
batch-buffering
]:
#batch-buffering
...
...
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