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
665f5269
Commit
665f5269
authored
Mar 14, 2023
by
Joshua Gutow
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
batch_decoder: Force close transactions
parent
ca0ce854
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
188 additions
and
11 deletions
+188
-11
README.md
op-node/cmd/batch_decoder/README.md
+10
-1
main.go
op-node/cmd/batch_decoder/main.go
+41
-0
reassemble.go
op-node/cmd/batch_decoder/reassemble/reassemble.go
+15
-10
channel_out.go
op-node/rollup/derive/channel_out.go
+55
-0
channel_out_test.go
op-node/rollup/derive/channel_out_test.go
+67
-0
No files found.
op-node/cmd/batch_decoder/README.md
View file @
665f5269
...
@@ -26,6 +26,16 @@ the transaction hash.
...
@@ -26,6 +26,16 @@ the transaction hash.
into channels. It then stores the channels with metadata on disk where the file name is the Channel ID.
into channels. It then stores the channels with metadata on disk where the file name is the Channel ID.
### Force Close
`batch_decoder force-close`
will create a transaction data that can be sent from the batcher address to
the batch inbox address which will force close the given channels. This will allow future channels to
be read without waiting for the channel timeout. It uses uses the results from
`batch_decoder fetch`
to
create the close transaction because the transaction it creates for a specific channel requires information
about if the channel has been closed or not. If it has been closed already but is missing specific frames
those frames need to be generated differently than simply closing the channel.
## JQ Cheat Sheet
## JQ Cheat Sheet
`jq`
is a really useful utility for manipulating JSON files.
`jq`
is a really useful utility for manipulating JSON files.
...
@@ -48,7 +58,6 @@ jq "select(.is_ready == false)|[.id, .frames[0].inclusion_block, .frames[0].tran
...
@@ -48,7 +58,6 @@ jq "select(.is_ready == false)|[.id, .frames[0].inclusion_block, .frames[0].tran
## Roadmap
## Roadmap
-
Parallel transaction fetching (CLI-3563)
-
Parallel transaction fetching (CLI-3563)
-
Create force-close channel tx data from channel ID (CLI-3564)
-
Pull the batches out of channels & store that information inside the ChannelWithMetadata (CLI-3565)
-
Pull the batches out of channels & store that information inside the ChannelWithMetadata (CLI-3565)
-
Transaction Bytes used
-
Transaction Bytes used
-
Total uncompressed (different from tx bytes) + compressed bytes
-
Total uncompressed (different from tx bytes) + compressed bytes
...
...
op-node/cmd/batch_decoder/main.go
View file @
665f5269
...
@@ -9,6 +9,7 @@ import (
...
@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/fetch"
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/fetch"
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/reassemble"
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/reassemble"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli"
"github.com/urfave/cli"
...
@@ -113,6 +114,46 @@ func main() {
...
@@ -113,6 +114,46 @@ func main() {
return
nil
return
nil
},
},
},
},
{
Name
:
"force-close"
,
Usage
:
"Create the tx data which will force close a channel"
,
Flags
:
[]
cli
.
Flag
{
cli
.
StringFlag
{
Name
:
"id"
,
Required
:
true
,
Usage
:
"ID of the channel to close"
,
},
cli
.
StringFlag
{
Name
:
"inbox"
,
Value
:
"0x0000000000000000000000000000000000000000"
,
Usage
:
"(Optional) Batch Inbox Address"
,
},
cli
.
StringFlag
{
Name
:
"in"
,
Value
:
"/tmp/batch_decoder/transactions_cache"
,
Usage
:
"Cache directory for the found transactions"
,
},
},
Action
:
func
(
cliCtx
*
cli
.
Context
)
error
{
var
id
derive
.
ChannelID
if
err
:=
(
&
id
)
.
UnmarshalText
([]
byte
(
cliCtx
.
String
(
"id"
)));
err
!=
nil
{
log
.
Fatal
(
err
)
}
frames
:=
reassemble
.
LoadFrames
(
cliCtx
.
String
(
"in"
),
common
.
HexToAddress
(
cliCtx
.
String
(
"inbox"
)))
var
filteredFrames
[]
derive
.
Frame
for
_
,
frame
:=
range
frames
{
if
frame
.
Frame
.
ID
==
id
{
filteredFrames
=
append
(
filteredFrames
,
frame
.
Frame
)
}
}
data
,
err
:=
derive
.
ForceCloseTxData
(
filteredFrames
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
fmt
.
Printf
(
"%x
\n
"
,
data
)
return
nil
},
},
}
}
if
err
:=
app
.
Run
(
os
.
Args
);
err
!=
nil
{
if
err
:=
app
.
Run
(
os
.
Args
);
err
!=
nil
{
...
...
op-node/cmd/batch_decoder/reassemble/reassemble.go
View file @
665f5269
...
@@ -38,14 +38,8 @@ type Config struct {
...
@@ -38,14 +38,8 @@ type Config struct {
OutDirectory
string
OutDirectory
string
}
}
// Channels loads all transactions from the given input directory that are submitted to the
func
LoadFrames
(
directory
string
,
inbox
common
.
Address
)
[]
FrameWithMetadata
{
// specified batch inbox and then re-assembles all channels & writes the re-assembled channels
txns
:=
loadTransactions
(
directory
,
inbox
)
// to the out directory.
func
Channels
(
config
Config
)
{
if
err
:=
os
.
MkdirAll
(
config
.
OutDirectory
,
0750
);
err
!=
nil
{
log
.
Fatal
(
err
)
}
txns
:=
loadTransactions
(
config
.
InDirectory
,
config
.
BatchInbox
)
// Sort first by block number then by transaction index inside the block number range.
// Sort first by block number then by transaction index inside the block number range.
// This is to match the order they are processed in derivation.
// This is to match the order they are processed in derivation.
sort
.
Slice
(
txns
,
func
(
i
,
j
int
)
bool
{
sort
.
Slice
(
txns
,
func
(
i
,
j
int
)
bool
{
...
@@ -56,7 +50,17 @@ func Channels(config Config) {
...
@@ -56,7 +50,17 @@ func Channels(config Config) {
}
}
})
})
frames
:=
transactionsToFrames
(
txns
)
return
transactionsToFrames
(
txns
)
}
// Channels loads all transactions from the given input directory that are submitted to the
// specified batch inbox and then re-assembles all channels & writes the re-assembled channels
// to the out directory.
func
Channels
(
config
Config
)
{
if
err
:=
os
.
MkdirAll
(
config
.
OutDirectory
,
0750
);
err
!=
nil
{
log
.
Fatal
(
err
)
}
frames
:=
LoadFrames
(
config
.
InDirectory
,
config
.
BatchInbox
)
framesByChannel
:=
make
(
map
[
derive
.
ChannelID
][]
FrameWithMetadata
)
framesByChannel
:=
make
(
map
[
derive
.
ChannelID
][]
FrameWithMetadata
)
for
_
,
frame
:=
range
frames
{
for
_
,
frame
:=
range
frames
{
framesByChannel
[
frame
.
Frame
.
ID
]
=
append
(
framesByChannel
[
frame
.
Frame
.
ID
],
frame
)
framesByChannel
[
frame
.
Frame
.
ID
]
=
append
(
framesByChannel
[
frame
.
Frame
.
ID
],
frame
)
...
@@ -143,6 +147,7 @@ func transactionsToFrames(txns []fetch.TransactionWithMetadata) []FrameWithMetad
...
@@ -143,6 +147,7 @@ func transactionsToFrames(txns []fetch.TransactionWithMetadata) []FrameWithMetad
return
out
return
out
}
}
// if inbox is the zero address, it will load all frames
func
loadTransactions
(
dir
string
,
inbox
common
.
Address
)
[]
fetch
.
TransactionWithMetadata
{
func
loadTransactions
(
dir
string
,
inbox
common
.
Address
)
[]
fetch
.
TransactionWithMetadata
{
files
,
err
:=
os
.
ReadDir
(
dir
)
files
,
err
:=
os
.
ReadDir
(
dir
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -152,7 +157,7 @@ func loadTransactions(dir string, inbox common.Address) []fetch.TransactionWithM
...
@@ -152,7 +157,7 @@ func loadTransactions(dir string, inbox common.Address) []fetch.TransactionWithM
for
_
,
file
:=
range
files
{
for
_
,
file
:=
range
files
{
f
:=
path
.
Join
(
dir
,
file
.
Name
())
f
:=
path
.
Join
(
dir
,
file
.
Name
())
txm
:=
loadTransactionsFile
(
f
)
txm
:=
loadTransactionsFile
(
f
)
if
txm
.
InboxAddr
==
inbox
&&
txm
.
ValidSender
{
if
(
inbox
==
common
.
Address
{}
||
txm
.
InboxAddr
==
inbox
)
&&
txm
.
ValidSender
{
out
=
append
(
out
,
txm
)
out
=
append
(
out
,
txm
)
}
}
}
}
...
...
op-node/rollup/derive/channel_out.go
View file @
665f5269
...
@@ -213,3 +213,58 @@ func BlockToBatch(block *types.Block) (*BatchData, error) {
...
@@ -213,3 +213,58 @@ func BlockToBatch(block *types.Block) (*BatchData, error) {
},
},
},
nil
},
nil
}
}
// ForceCloseTxData generates the transaction data for a transaction which will force close
// a channel. It should be given every frame of that channel which has been submitted on
// chain. The frames should be given in order that they appear on L1.
func
ForceCloseTxData
(
frames
[]
Frame
)
([]
byte
,
error
)
{
if
len
(
frames
)
==
0
{
return
nil
,
errors
.
New
(
"must provide at least one frame"
)
}
frameNumbers
:=
make
(
map
[
uint16
]
struct
{})
id
:=
frames
[
0
]
.
ID
closeNumber
:=
uint16
(
0
)
closed
:=
false
for
i
,
frame
:=
range
frames
{
if
!
closed
&&
frame
.
IsLast
{
closeNumber
=
frame
.
FrameNumber
}
closed
=
closed
||
frame
.
IsLast
frameNumbers
[
frame
.
FrameNumber
]
=
struct
{}{}
if
frame
.
ID
!=
id
{
return
nil
,
fmt
.
Errorf
(
"invalid ID in list: first ID: %v, %vth ID: %v"
,
id
,
i
,
frame
.
ID
)
}
}
var
out
bytes
.
Buffer
out
.
WriteByte
(
DerivationVersion0
)
if
!
closed
{
f
:=
Frame
{
ID
:
id
,
FrameNumber
:
0
,
Data
:
nil
,
IsLast
:
true
,
}
if
err
:=
f
.
MarshalBinary
(
&
out
);
err
!=
nil
{
return
nil
,
err
}
}
else
{
for
i
:=
uint16
(
0
);
i
<=
closeNumber
;
i
++
{
if
_
,
ok
:=
frameNumbers
[
i
];
ok
{
continue
}
f
:=
Frame
{
ID
:
id
,
FrameNumber
:
i
,
Data
:
nil
,
IsLast
:
false
,
}
if
err
:=
f
.
MarshalBinary
(
&
out
);
err
!=
nil
{
return
nil
,
err
}
}
}
return
out
.
Bytes
(),
nil
}
op-node/rollup/derive/channel_out_test.go
View file @
665f5269
...
@@ -5,6 +5,7 @@ import (
...
@@ -5,6 +5,7 @@ import (
"math/big"
"math/big"
"testing"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
...
@@ -49,3 +50,69 @@ func TestRLPByteLimit(t *testing.T) {
...
@@ -49,3 +50,69 @@ func TestRLPByteLimit(t *testing.T) {
require
.
Equal
(
t
,
err
,
rlp
.
ErrValueTooLarge
)
require
.
Equal
(
t
,
err
,
rlp
.
ErrValueTooLarge
)
require
.
Equal
(
t
,
out2
,
""
)
require
.
Equal
(
t
,
out2
,
""
)
}
}
func
TestForceCloseTxData
(
t
*
testing
.
T
)
{
id
:=
[
16
]
byte
{
0xde
,
0xad
,
0xbe
,
0xef
,
0xde
,
0xad
,
0xbe
,
0xef
,
0xde
,
0xad
,
0xbe
,
0xef
,
0xde
,
0xad
,
0xbe
,
0xef
}
tests
:=
[]
struct
{
frames
[]
Frame
errors
bool
output
string
}{
{
frames
:
[]
Frame
{},
errors
:
true
,
output
:
""
,
},
{
frames
:
[]
Frame
{
Frame
{
FrameNumber
:
0
,
IsLast
:
false
},
Frame
{
ID
:
id
,
FrameNumber
:
1
,
IsLast
:
true
}},
errors
:
true
,
output
:
""
,
},
{
frames
:
[]
Frame
{
Frame
{
ID
:
id
,
FrameNumber
:
0
,
IsLast
:
false
}},
errors
:
false
,
output
:
"00deadbeefdeadbeefdeadbeefdeadbeef00000000000001"
,
},
{
frames
:
[]
Frame
{
Frame
{
ID
:
id
,
FrameNumber
:
0
,
IsLast
:
true
}},
errors
:
false
,
output
:
"00"
,
},
{
frames
:
[]
Frame
{
Frame
{
ID
:
id
,
FrameNumber
:
1
,
IsLast
:
false
}},
errors
:
false
,
output
:
"00deadbeefdeadbeefdeadbeefdeadbeef00000000000001"
,
},
{
frames
:
[]
Frame
{
Frame
{
ID
:
id
,
FrameNumber
:
1
,
IsLast
:
true
}},
errors
:
false
,
output
:
"00deadbeefdeadbeefdeadbeefdeadbeef00000000000000"
,
},
{
frames
:
[]
Frame
{
Frame
{
ID
:
id
,
FrameNumber
:
2
,
IsLast
:
true
}},
errors
:
false
,
output
:
"00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00010000000000"
,
},
{
frames
:
[]
Frame
{
Frame
{
ID
:
id
,
FrameNumber
:
1
,
IsLast
:
false
},
Frame
{
ID
:
id
,
FrameNumber
:
3
,
IsLast
:
true
}},
errors
:
false
,
output
:
"00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00020000000000"
,
},
{
frames
:
[]
Frame
{
Frame
{
ID
:
id
,
FrameNumber
:
1
,
IsLast
:
false
},
Frame
{
ID
:
id
,
FrameNumber
:
3
,
IsLast
:
true
},
Frame
{
ID
:
id
,
FrameNumber
:
5
,
IsLast
:
true
}},
errors
:
false
,
output
:
"00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00020000000000"
,
},
}
for
i
,
test
:=
range
tests
{
out
,
err
:=
ForceCloseTxData
(
test
.
frames
)
if
test
.
errors
{
require
.
NotNil
(
t
,
err
,
"Should error on tc %v"
,
i
)
require
.
Nil
(
t
,
out
,
"Should return no value in tc %v"
,
i
)
}
else
{
require
.
NoError
(
t
,
err
,
"Should not error on tc %v"
,
i
)
require
.
Equal
(
t
,
common
.
FromHex
(
test
.
output
),
out
,
"Should match output tc %v"
,
i
)
}
}
}
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