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
74c63d3c
Unverified
Commit
74c63d3c
authored
Oct 27, 2022
by
mergify[bot]
Committed by
GitHub
Oct 27, 2022
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3793 from ethereum-optimism/jg/frame_validity_reqs
op-node: Add frame validity checks
parents
dc67560f
f0eb5402
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
146 additions
and
25 deletions
+146
-25
channel.go
op-node/rollup/derive/channel.go
+28
-16
channel_test.go
op-node/rollup/derive/channel_test.go
+101
-0
params.go
op-node/rollup/derive/params.go
+8
-0
derivation.md
specs/derivation.md
+9
-9
No files found.
op-node/rollup/derive/channel.go
View file @
74c63d3c
...
@@ -10,9 +10,6 @@ import (
...
@@ -10,9 +10,6 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rlp"
)
)
// TODO: Full state machine for channel
// Open, Closed, Ready (todo - when to construct RLP reader)
// A Channel is a set of batches that are split into at least one, but possibly multiple frames.
// A Channel is a set of batches that are split into at least one, but possibly multiple frames.
// Frames are allowed to be ingested out of order.
// Frames are allowed to be ingested out of order.
// Each frame is ingested one by one. Once a frame with `closed` is added to the channel, the
// Each frame is ingested one by one. Once a frame with `closed` is added to the channel, the
...
@@ -28,17 +25,15 @@ type Channel struct {
...
@@ -28,17 +25,15 @@ type Channel struct {
// true if we have buffered the last frame
// true if we have buffered the last frame
closed
bool
closed
bool
// TODO: implement this check
// highestFrameNumber is the highest frame number yet seen.
// highestFrameNumber is the highest frame number yet seen.
// This must be equal to `endFrameNumber`
highestFrameNumber
uint16
// highestFrameNumber uint16
// endFrameNumber is the frame number of the frame where `isLast` is true
// endFrameNumber is the frame number of the frame where `isLast` is true
// No other frame number must be larger than this.
// No other frame number must be larger than this.
endFrameNumber
uint16
endFrameNumber
uint16
// Store a map of frame number -> frame
data
for constant time ordering
// Store a map of frame number -> frame for constant time ordering
inputs
map
[
uint64
]
[]
byt
e
inputs
map
[
uint64
]
Fram
e
highestL1InclusionBlock
eth
.
L1BlockRef
highestL1InclusionBlock
eth
.
L1BlockRef
}
}
...
@@ -46,7 +41,7 @@ type Channel struct {
...
@@ -46,7 +41,7 @@ type Channel struct {
func
NewChannel
(
id
ChannelID
,
openBlock
eth
.
L1BlockRef
)
*
Channel
{
func
NewChannel
(
id
ChannelID
,
openBlock
eth
.
L1BlockRef
)
*
Channel
{
return
&
Channel
{
return
&
Channel
{
id
:
id
,
id
:
id
,
inputs
:
make
(
map
[
uint64
]
[]
byt
e
),
inputs
:
make
(
map
[
uint64
]
Fram
e
),
openBlock
:
openBlock
,
openBlock
:
openBlock
,
}
}
}
}
...
@@ -58,26 +53,43 @@ func (ch *Channel) AddFrame(frame Frame, l1InclusionBlock eth.L1BlockRef) error
...
@@ -58,26 +53,43 @@ func (ch *Channel) AddFrame(frame Frame, l1InclusionBlock eth.L1BlockRef) error
if
frame
.
ID
!=
ch
.
id
{
if
frame
.
ID
!=
ch
.
id
{
return
fmt
.
Errorf
(
"frame id does not match channel id. Expected %v, got %v"
,
ch
.
id
,
frame
.
ID
)
return
fmt
.
Errorf
(
"frame id does not match channel id. Expected %v, got %v"
,
ch
.
id
,
frame
.
ID
)
}
}
// These checks are specified and cannot be changed without a hard fork.
if
frame
.
IsLast
&&
ch
.
closed
{
if
frame
.
IsLast
&&
ch
.
closed
{
return
fmt
.
Errorf
(
"cannot add ending frame to a closed channel. id %v"
,
ch
.
id
)
return
fmt
.
Errorf
(
"cannot add ending frame to a closed channel. id %v"
,
ch
.
id
)
}
}
if
_
,
ok
:=
ch
.
inputs
[
uint64
(
frame
.
FrameNumber
)];
ok
{
if
_
,
ok
:=
ch
.
inputs
[
uint64
(
frame
.
FrameNumber
)];
ok
{
return
DuplicateErr
return
DuplicateErr
}
}
// TODO: highest seen frame vs endFrameNumber
if
ch
.
closed
&&
frame
.
FrameNumber
>=
ch
.
endFrameNumber
{
return
fmt
.
Errorf
(
"frame number (%d) is greater than or equal to end frame number (%d) of a closed channel"
,
frame
.
FrameNumber
,
ch
.
endFrameNumber
)
}
// Guaranteed to succeed. Now update internal state
// Guaranteed to succeed. Now update internal state
if
frame
.
IsLast
{
if
frame
.
IsLast
{
ch
.
endFrameNumber
=
frame
.
FrameNumber
ch
.
endFrameNumber
=
frame
.
FrameNumber
ch
.
closed
=
true
ch
.
closed
=
true
}
}
// Prune frames with a number higher than the closing frame number when we receive a closing frame
if
frame
.
IsLast
&&
ch
.
endFrameNumber
<
ch
.
highestFrameNumber
{
// Do a linear scan over saved inputs instead of ranging over ID numbers
for
id
,
prunedFrame
:=
range
ch
.
inputs
{
if
id
>=
uint64
(
ch
.
endFrameNumber
)
{
delete
(
ch
.
inputs
,
id
)
}
ch
.
size
-=
frameSize
(
prunedFrame
)
}
ch
.
highestFrameNumber
=
ch
.
endFrameNumber
}
// Update highest seen frame number after pruning
if
frame
.
FrameNumber
>
ch
.
highestFrameNumber
{
ch
.
highestFrameNumber
=
frame
.
FrameNumber
}
if
ch
.
highestL1InclusionBlock
.
Number
<
l1InclusionBlock
.
Number
{
if
ch
.
highestL1InclusionBlock
.
Number
<
l1InclusionBlock
.
Number
{
ch
.
highestL1InclusionBlock
=
l1InclusionBlock
ch
.
highestL1InclusionBlock
=
l1InclusionBlock
}
}
ch
.
inputs
[
uint64
(
frame
.
FrameNumber
)]
=
frame
.
Data
ch
.
inputs
[
uint64
(
frame
.
FrameNumber
)]
=
frame
ch
.
size
+=
uint64
(
len
(
frame
.
Data
))
+
frameOverhead
ch
.
size
+=
frameSize
(
frame
)
// todo use `IsReady` + state to create final output reader
return
nil
return
nil
}
}
...
@@ -121,11 +133,11 @@ func (ch *Channel) IsReady() bool {
...
@@ -121,11 +133,11 @@ func (ch *Channel) IsReady() bool {
func
(
ch
*
Channel
)
Reader
()
io
.
Reader
{
func
(
ch
*
Channel
)
Reader
()
io
.
Reader
{
var
readers
[]
io
.
Reader
var
readers
[]
io
.
Reader
for
i
:=
uint64
(
0
);
i
<=
uint64
(
ch
.
endFrameNumber
);
i
++
{
for
i
:=
uint64
(
0
);
i
<=
uint64
(
ch
.
endFrameNumber
);
i
++
{
data
,
ok
:=
ch
.
inputs
[
i
]
frame
,
ok
:=
ch
.
inputs
[
i
]
if
!
ok
{
if
!
ok
{
panic
(
"dev error in channel.Reader. Must be called after the channel is ready."
)
panic
(
"dev error in channel.Reader. Must be called after the channel is ready."
)
}
}
readers
=
append
(
readers
,
bytes
.
New
Buffer
(
d
ata
))
readers
=
append
(
readers
,
bytes
.
New
Reader
(
frame
.
D
ata
))
}
}
return
io
.
MultiReader
(
readers
...
)
return
io
.
MultiReader
(
readers
...
)
}
}
...
...
op-node/rollup/derive/channel_test.go
0 → 100644
View file @
74c63d3c
package
derive
import
(
"testing"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/stretchr/testify/require"
)
type
frameValidityTC
struct
{
name
string
frames
[]
Frame
shouldErr
[]
bool
sizes
[]
uint64
}
func
(
tc
*
frameValidityTC
)
Run
(
t
*
testing
.
T
)
{
id
:=
[
16
]
byte
{
0xff
}
block
:=
eth
.
L1BlockRef
{}
ch
:=
NewChannel
(
id
,
block
)
if
len
(
tc
.
frames
)
!=
len
(
tc
.
shouldErr
)
||
len
(
tc
.
frames
)
!=
len
(
tc
.
sizes
)
{
t
.
Errorf
(
"lengths should be the same. frames: %d, shouldErr: %d, sizes: %d"
,
len
(
tc
.
frames
),
len
(
tc
.
shouldErr
),
len
(
tc
.
sizes
))
}
for
i
,
frame
:=
range
tc
.
frames
{
err
:=
ch
.
AddFrame
(
frame
,
block
)
if
tc
.
shouldErr
[
i
]
{
require
.
NotNil
(
t
,
err
)
}
else
{
require
.
Nil
(
t
,
err
)
}
require
.
Equal
(
t
,
tc
.
sizes
[
i
],
ch
.
Size
())
}
}
// TestFrameValidity inserts a list of frames into the channel. It checks if an error
// should be returned by `AddFrame` as well as checking the size of the channel.
func
TestFrameValidity
(
t
*
testing
.
T
)
{
id
:=
[
16
]
byte
{
0xff
}
testCases
:=
[]
frameValidityTC
{
{
name
:
"wrong channel"
,
frames
:
[]
Frame
{{
ID
:
[
16
]
byte
{
0xee
}}},
shouldErr
:
[]
bool
{
true
},
sizes
:
[]
uint64
{
0
},
},
{
name
:
"double close"
,
frames
:
[]
Frame
{
{
ID
:
id
,
FrameNumber
:
2
,
IsLast
:
true
,
Data
:
[]
byte
(
"four"
)},
{
ID
:
id
,
FrameNumber
:
1
,
IsLast
:
true
}},
shouldErr
:
[]
bool
{
false
,
true
},
sizes
:
[]
uint64
{
204
,
204
},
},
{
name
:
"duplicate frame"
,
frames
:
[]
Frame
{
{
ID
:
id
,
FrameNumber
:
2
,
Data
:
[]
byte
(
"four"
)},
{
ID
:
id
,
FrameNumber
:
2
,
Data
:
[]
byte
(
"seven__"
)}},
shouldErr
:
[]
bool
{
false
,
true
},
sizes
:
[]
uint64
{
204
,
204
},
},
{
name
:
"duplicate closing frames"
,
frames
:
[]
Frame
{
{
ID
:
id
,
FrameNumber
:
2
,
IsLast
:
true
,
Data
:
[]
byte
(
"four"
)},
{
ID
:
id
,
FrameNumber
:
2
,
IsLast
:
true
,
Data
:
[]
byte
(
"seven__"
)}},
shouldErr
:
[]
bool
{
false
,
true
},
sizes
:
[]
uint64
{
204
,
204
},
},
{
name
:
"frame past closing"
,
frames
:
[]
Frame
{
{
ID
:
id
,
FrameNumber
:
2
,
IsLast
:
true
,
Data
:
[]
byte
(
"four"
)},
{
ID
:
id
,
FrameNumber
:
10
,
Data
:
[]
byte
(
"seven__"
)}},
shouldErr
:
[]
bool
{
false
,
true
},
sizes
:
[]
uint64
{
204
,
204
},
},
{
name
:
"prune after close frame"
,
frames
:
[]
Frame
{
{
ID
:
id
,
FrameNumber
:
10
,
IsLast
:
false
,
Data
:
[]
byte
(
"seven__"
)},
{
ID
:
id
,
FrameNumber
:
2
,
IsLast
:
true
,
Data
:
[]
byte
(
"four"
)}},
shouldErr
:
[]
bool
{
false
,
false
},
sizes
:
[]
uint64
{
207
,
204
},
},
{
name
:
"multiple valid frames"
,
frames
:
[]
Frame
{
{
ID
:
id
,
FrameNumber
:
10
,
Data
:
[]
byte
(
"seven__"
)},
{
ID
:
id
,
FrameNumber
:
2
,
Data
:
[]
byte
(
"four"
)}},
shouldErr
:
[]
bool
{
false
,
false
},
sizes
:
[]
uint64
{
207
,
411
},
},
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
name
,
tc
.
Run
)
}
}
op-node/rollup/derive/params.go
View file @
74c63d3c
...
@@ -8,6 +8,14 @@ import (
...
@@ -8,6 +8,14 @@ import (
// count the tagging info as 200 in terms of buffer size.
// count the tagging info as 200 in terms of buffer size.
const
frameOverhead
=
200
const
frameOverhead
=
200
// frameSize calculates the size of the frame + overhead for
// storing the frame. The sum of the frame size of each frame in
// a channel determines the channel's size. The sum of the channel
// sizes is used for pruning & compared against `MaxChannelBankSize`
func
frameSize
(
frame
Frame
)
uint64
{
return
uint64
(
len
(
frame
.
Data
))
+
frameOverhead
}
const
DerivationVersion0
=
0
const
DerivationVersion0
=
0
// MaxChannelBankSize is the amount of memory space, in number of bytes,
// MaxChannelBankSize is the amount of memory space, in number of bytes,
...
...
specs/derivation.md
View file @
74c63d3c
...
@@ -464,18 +464,18 @@ particular we extract a byte string that corresponds to the concatenation of the
...
@@ -464,18 +464,18 @@ particular we extract a byte string that corresponds to the concatenation of the
transaction]
[
g-batcher-transaction
]
belonging to the block. This byte stream encodes a stream of
[
channel
transaction]
[
g-batcher-transaction
]
belonging to the block. This byte stream encodes a stream of
[
channel
frames]
[
g-channel-frame
]
(
see
the
[
Batch Submission Wire Format
][
wire-format
]
section for more info).
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*
.
These frames are parsed, then grouped per
[
channel
][
g-channel
]
into a structure we call the
*channel bank*
. When
adding frames the the channel, individual frames may be invalid, but the channel does not have a notion of validity
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:
Some frames are ignored:
-
Frames where
`frame.frame_number <= highest_frame_number`
, where
`highest_frame_number`
is the highest frame number
-
Frames with the same frame number as an existing frame in the channel (a duplicate). The first seen frame is used.
that was previously encountered for this channel.
-
Frames that attempt to close an already closed channel. This would be the second frame with
`frame.is_last == 1`
even
-
i.e. in case of duplicate frame, the first frame read from L1 is considered canonical.
if the frame number of the second frame is not the same as the first frame which closed the channel.
-
Frames with a higher number than that of the final frame of the channel (i.e. the first frame marked with
`frame.is_last == 1`
) are ignored.
If a frame with
`is_last == 1`
is added to a channel, all frames with a higher frame number are removed from the
-
These frames could still be written into the channel bank if we haven't seen the final frame yet. But they will
channel.
never be read out from the channel bank.
-
Frames with a channel ID whose timestamp are higher than that of the L1 block on which the frame appears.
Channels are also recorded in FIFO order in a structure called the
*channel queue*
. A channel is added to the channel
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.
queue the first time a frame belonging to the channel is seen. This structure is used in the next stage.
...
...
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