Skip to content

Commit f5a3456

Browse files
protolambdaRoberto Bayardo
authored andcommitted
op-node: EIP-4844 support
Original rebased prototype by proto, plus changes from Roberto: - encapsulate data <-> blob conversion code, add unit tests - update 4844 code to be compatible with latest beacon node api - remove stray file, include one more invalid blob test - appropriate fee bumping & blob fee estimation for blob transactions - misc other improvements
1 parent d9e4363 commit f5a3456

File tree

34 files changed

+1169
-281
lines changed

34 files changed

+1169
-281
lines changed

op-batcher/batcher/driver.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ func (l *BatchSubmitter) publishTxToL1(ctx context.Context, queue *txmgr.Queue[t
315315
// send all available transactions
316316
l1tip, err := l.l1Tip(ctx)
317317
if err != nil {
318-
l.Log.Error("Failed to query L1 tip", "error", err)
318+
l.Log.Error("Failed to query L1 tip", "err", err)
319319
return err
320320
}
321321
l.recordL1Tip(l1tip)
@@ -334,22 +334,37 @@ func (l *BatchSubmitter) publishTxToL1(ctx context.Context, queue *txmgr.Queue[t
334334
return nil
335335
}
336336

337-
// sendTransaction creates & submits a transaction to the batch inbox address with the given `data`.
337+
// sendTransaction creates & submits a transaction to the batch inbox address with the given `txData`.
338338
// It currently uses the underlying `txmgr` to handle transaction sending & price management.
339339
// This is a blocking method. It should not be called concurrently.
340340
func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txData], receiptsCh chan txmgr.TxReceipt[txData]) {
341341
// Do the gas estimation offline. A value of 0 will cause the [txmgr] to estimate the gas limit.
342342
data := txdata.Bytes()
343+
344+
var blobs []*eth.Blob
345+
if l.RollupConfig.BlobsEnabledL1Timestamp != nil && *l.RollupConfig.BlobsEnabledL1Timestamp <= uint64(time.Now().Unix()) {
346+
var b eth.Blob
347+
if err := b.FromData(data); err != nil {
348+
l.Log.Error("data could not be converted to blob", "err", err)
349+
return
350+
}
351+
blobs = append(blobs, &b)
352+
353+
// no calldata
354+
data = []byte{}
355+
}
356+
343357
intrinsicGas, err := core.IntrinsicGas(data, nil, false, true, true, false)
344358
if err != nil {
345-
l.Log.Error("Failed to calculate intrinsic gas", "error", err)
359+
l.Log.Error("Failed to calculate intrinsic gas", "err", err)
346360
return
347361
}
348362

349363
candidate := txmgr.TxCandidate{
350364
To: &l.RollupConfig.BatchInboxAddress,
351365
TxData: data,
352366
GasLimit: intrinsicGas,
367+
Blobs: blobs,
353368
}
354369
queue.Send(txdata, candidate, receiptsCh)
355370
}

op-chain-ops/genesis/config.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ type DeployConfig struct {
230230

231231
// When Cancun activates. Relative to L1 genesis.
232232
L1CancunTimeOffset *uint64 `json:"l1CancunTimeOffset,omitempty"`
233+
234+
// When 4844 blob-tx functionality for rollup DA actives. Relative to L2 genesis.
235+
L2BlobsUpgradeTimeOffset *uint64 `json:"l2BlobsUpgradeTimeOffset,omitempty"`
233236
}
234237

235238
// Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
@@ -522,6 +525,17 @@ func (d *DeployConfig) InteropTime(genesisTime uint64) *uint64 {
522525
return &v
523526
}
524527

528+
func (d *DeployConfig) BlobsUpgradeTime(genesisTime uint64) *uint64 {
529+
if d.L2BlobsUpgradeTimeOffset == nil {
530+
return nil
531+
}
532+
v := uint64(0)
533+
if offset := *d.L2BlobsUpgradeTimeOffset; offset > 0 {
534+
v = genesisTime + uint64(offset)
535+
}
536+
return &v
537+
}
538+
525539
// RollupConfig converts a DeployConfig to a rollup.Config
526540
func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHash common.Hash, l2GenesisBlockNumber uint64) (*rollup.Config, error) {
527541
if d.OptimismPortalProxy == (common.Address{}) {
@@ -564,6 +578,8 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas
564578
EclipseTime: d.EclipseTime(l1StartBlock.Time()),
565579
FjordTime: d.FjordTime(l1StartBlock.Time()),
566580
InteropTime: d.InteropTime(l1StartBlock.Time()),
581+
// 4844 blobs usage activation for rollup DA
582+
BlobsEnabledL1Timestamp: d.BlobsUpgradeTime(l1StartBlock.Time()),
567583
}, nil
568584
}
569585

op-e2e/actions/l2_engine.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ package actions
33
import (
44
"errors"
55

6-
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
7-
"github.com/ethereum-optimism/optimism/op-program/client/l2/engineapi"
86
"github.com/stretchr/testify/require"
97

108
"github.com/ethereum/go-ethereum/common"
119
"github.com/ethereum/go-ethereum/core"
10+
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
1211
"github.com/ethereum/go-ethereum/core/types"
1312
geth "github.com/ethereum/go-ethereum/eth"
1413
"github.com/ethereum/go-ethereum/eth/ethconfig"
@@ -20,9 +19,11 @@ import (
2019
"github.com/ethereum/go-ethereum/node"
2120
"github.com/ethereum/go-ethereum/rpc"
2221

22+
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
2323
"github.com/ethereum-optimism/optimism/op-node/rollup"
24+
"github.com/ethereum-optimism/optimism/op-program/client/l2/engineapi"
2425
"github.com/ethereum-optimism/optimism/op-service/client"
25-
"github.com/ethereum-optimism/optimism/op-service/eth"
26+
opeth "github.com/ethereum-optimism/optimism/op-service/eth"
2627
"github.com/ethereum-optimism/optimism/op-service/sources"
2728
"github.com/ethereum-optimism/optimism/op-service/testutils"
2829
)
@@ -48,7 +49,7 @@ type L2Engine struct {
4849

4950
type EngineOption func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error
5051

51-
func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, rollupGenesisL1 eth.BlockID, jwtPath string, options ...EngineOption) *L2Engine {
52+
func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, rollupGenesisL1 opeth.BlockID, jwtPath string, options ...EngineOption) *L2Engine {
5253
n, ethBackend, apiBackend := newBackend(t, genesis, jwtPath, options)
5354
engineApi := engineapi.NewL2EngineAPI(log, apiBackend)
5455
chain := ethBackend.BlockChain()
@@ -59,7 +60,7 @@ func NewL2Engine(t Testing, log log.Logger, genesis *core.Genesis, rollupGenesis
5960
eth: ethBackend,
6061
rollupGenesis: &rollup.Genesis{
6162
L1: rollupGenesisL1,
62-
L2: eth.BlockID{Hash: genesisBlock.Hash(), Number: genesisBlock.NumberU64()},
63+
L2: opeth.BlockID{Hash: genesisBlock.Hash(), Number: genesisBlock.NumberU64()},
6364
L2Time: genesis.Timestamp,
6465
},
6566
l2Chain: chain,
@@ -84,6 +85,11 @@ func newBackend(t e2eutils.TestingBase, genesis *core.Genesis, jwtPath string, o
8485
ethCfg := &ethconfig.Config{
8586
NetworkId: genesis.Config.ChainID.Uint64(),
8687
Genesis: genesis,
88+
BlobPool: blobpool.Config{
89+
Datadir: t.TempDir(),
90+
Datacap: blobpool.DefaultConfig.Datacap,
91+
PriceBump: blobpool.DefaultConfig.PriceBump,
92+
},
8793
}
8894
nodeCfg := &node.Config{
8995
Name: "l2-geth",

op-e2e/actions/l2_verifier.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ type L2API interface {
5959

6060
func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, eng L2API, cfg *rollup.Config, syncCfg *sync.Config) *L2Verifier {
6161
metrics := &testutils.TestDerivationMetrics{}
62-
pipeline := derive.NewDerivationPipeline(log, cfg, l1, eng, metrics, syncCfg)
62+
// TODO blob testing
63+
pipeline := derive.NewDerivationPipeline(log, cfg, l1, nil, eng, metrics, syncCfg)
6364
pipeline.Reset()
6465

6566
rollupNode := &L2Verifier{

op-e2e/e2eutils/geth/geth.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func defaultNodeConfig(name string, jwtPath string) *node.Config {
8383
type GethOption func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error
8484

8585
// InitL2 inits a L2 geth node.
86-
func InitL2(name string, l2ChainID *big.Int, genesis *core.Genesis, jwtPath string, opts ...GethOption) (*node.Node, *eth.Ethereum, error) {
86+
func InitL2(name string, l2ChainID *big.Int, genesis *core.Genesis, jwtPath string, blobPoolDir string, opts ...GethOption) (*node.Node, *eth.Ethereum, error) {
8787
ethConfig := &ethconfig.Config{
8888
NetworkId: l2ChainID.Uint64(),
8989
Genesis: genesis,
@@ -96,6 +96,11 @@ func InitL2(name string, l2ChainID *big.Int, genesis *core.Genesis, jwtPath stri
9696
Recommit: 0,
9797
NewPayloadTimeout: 0,
9898
},
99+
BlobPool: blobpool.Config{
100+
Datadir: blobPoolDir,
101+
Datacap: blobpool.DefaultConfig.Datacap,
102+
PriceBump: blobpool.DefaultConfig.PriceBump,
103+
},
99104
}
100105
nodeConfig := defaultNodeConfig(fmt.Sprintf("l2-geth-%v", name), jwtPath)
101106
return createGethNode(true, nodeConfig, ethConfig, opts...)

op-e2e/eip4844_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package op_e2e
2+
3+
import (
4+
"context"
5+
"math/big"
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/ethereum-optimism/optimism/op-service/client"
12+
"github.com/ethereum-optimism/optimism/op-service/sources"
13+
"github.com/ethereum-optimism/optimism/op-service/testlog"
14+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
15+
"github.com/ethereum/go-ethereum/common"
16+
"github.com/ethereum/go-ethereum/log"
17+
"github.com/ethereum/go-ethereum/rpc"
18+
)
19+
20+
// TestSystem4844E2E runs the SystemE2E test with 4844 enabled on L1,
21+
// and active on the rollup in the op-batcher and verifier.
22+
func TestSystem4844E2E(t *testing.T) {
23+
InitParallel(t)
24+
25+
cfg := DefaultSystemConfig(t)
26+
genesisActivation := uint64(0)
27+
cfg.DeployConfig.L1CancunTimeOffset = &genesisActivation
28+
cfg.DeployConfig.L2BlobsUpgradeTimeOffset = &genesisActivation
29+
30+
sys, err := cfg.Start(t)
31+
require.Nil(t, err, "Error starting up system")
32+
defer sys.Close()
33+
34+
log := testlog.Logger(t, log.LvlInfo)
35+
log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time)
36+
37+
l1Client := sys.Clients["l1"]
38+
l2Seq := sys.Clients["sequencer"]
39+
l2Verif := sys.Clients["verifier"]
40+
41+
// Transactor Account
42+
ethPrivKey := cfg.Secrets.Alice
43+
44+
// Send Transaction & wait for success
45+
fromAddr := cfg.Secrets.Addresses().Alice
46+
log.Info("alice", "addr", fromAddr)
47+
48+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
49+
defer cancel()
50+
startBalance, err := l2Verif.BalanceAt(ctx, fromAddr, nil)
51+
require.Nil(t, err)
52+
53+
// Send deposit transaction
54+
opts, err := bind.NewKeyedTransactorWithChainID(ethPrivKey, cfg.L1ChainIDBig())
55+
require.Nil(t, err)
56+
mintAmount := big.NewInt(1_000_000_000_000)
57+
opts.Value = mintAmount
58+
SendDepositTx(t, cfg, l1Client, l2Verif, opts, func(l2Opts *DepositTxOpts) {})
59+
60+
// Confirm balance
61+
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
62+
defer cancel()
63+
endBalance, err := l2Verif.BalanceAt(ctx, fromAddr, nil)
64+
require.Nil(t, err)
65+
66+
diff := new(big.Int)
67+
diff = diff.Sub(endBalance, startBalance)
68+
require.Equal(t, mintAmount, diff, "Did not get expected balance change")
69+
70+
// Submit TX to L2 sequencer node
71+
receipt := SendL2Tx(t, cfg, l2Seq, ethPrivKey, func(opts *TxOpts) {
72+
opts.Value = big.NewInt(1_000_000_000)
73+
opts.Nonce = 1 // Already have deposit
74+
opts.ToAddr = &common.Address{0xff, 0xff}
75+
opts.VerifyOnClients(l2Verif)
76+
})
77+
78+
// Verify blocks match after batch submission on verifiers and sequencers
79+
verifBlock, err := l2Verif.BlockByNumber(context.Background(), receipt.BlockNumber)
80+
require.Nil(t, err)
81+
seqBlock, err := l2Seq.BlockByNumber(context.Background(), receipt.BlockNumber)
82+
require.Nil(t, err)
83+
require.Equal(t, verifBlock.NumberU64(), seqBlock.NumberU64(), "Verifier and sequencer blocks not the same after including a batch tx")
84+
require.Equal(t, verifBlock.ParentHash(), seqBlock.ParentHash(), "Verifier and sequencer blocks parent hashes not the same after including a batch tx")
85+
require.Equal(t, verifBlock.Hash(), seqBlock.Hash(), "Verifier and sequencer blocks not the same after including a batch tx")
86+
87+
rollupRPCClient, err := rpc.DialContext(context.Background(), sys.RollupNodes["sequencer"].HTTPEndpoint())
88+
require.Nil(t, err)
89+
rollupClient := sources.NewRollupClient(client.NewBaseRPCClient(rollupRPCClient))
90+
// basic check that sync status works
91+
seqStatus, err := rollupClient.SyncStatus(context.Background())
92+
require.Nil(t, err)
93+
require.LessOrEqual(t, seqBlock.NumberU64(), seqStatus.UnsafeL2.Number)
94+
// basic check that version endpoint works
95+
seqVersion, err := rollupClient.Version(context.Background())
96+
require.Nil(t, err)
97+
require.NotEqual(t, "", seqVersion)
98+
99+
// quick check that the batch submitter works
100+
for i := 0; i < 10; i++ {
101+
// wait for chain to be marked as "safe" (i.e. confirm batch-submission works)
102+
stat, err := rollupClient.SyncStatus(context.Background())
103+
require.NoError(t, err)
104+
if stat.SafeL2.Number > 0 {
105+
return
106+
}
107+
time.Sleep(2 * time.Second)
108+
}
109+
t.Fatal("expected L2 to be batch-submitted and labeled as safe")
110+
}

op-e2e/op_geth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e
7676

7777
var node EthInstance
7878
if cfg.ExternalL2Shim == "" {
79-
gethNode, _, err := geth.InitL2("l2", big.NewInt(int64(cfg.DeployConfig.L2ChainID)), l2Genesis, cfg.JWTFilePath)
79+
gethNode, _, err := geth.InitL2("l2", big.NewInt(int64(cfg.DeployConfig.L2ChainID)), l2Genesis, cfg.JWTFilePath, t.TempDir())
8080
require.Nil(t, err)
8181
require.Nil(t, gethNode.Start())
8282
node = gethNode

op-e2e/setup.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
494494
FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
495495
InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
496496
ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy,
497+
// 4844
498+
BlobsEnabledL1Timestamp: cfg.DeployConfig.BlobsUpgradeTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
497499
}
498500
}
499501
defaultConfig := makeRollupConfig()
@@ -509,8 +511,8 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
509511
_ = bcn.Close()
510512
})
511513
require.NoError(t, bcn.Start("127.0.0.1:0"))
512-
beaconApiAddr := bcn.BeaconAddr()
513-
require.NotEmpty(t, beaconApiAddr, "beacon API listener must be up")
514+
sys.L1BeaconAPIAddr = bcn.BeaconAddr()
515+
require.NotEmpty(t, sys.L1BeaconAPIAddr, "beacon API listener must be up")
514516

515517
// Initialize nodes
516518
l1Node, l1Backend, err := geth.InitL1(cfg.DeployConfig.L1ChainID, cfg.DeployConfig.L1BlockTime, l1Genesis, c,
@@ -529,8 +531,9 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
529531

530532
for name := range cfg.Nodes {
531533
var ethClient EthInstance
534+
blobPoolPath := path.Join(cfg.BlobsPath, name)
532535
if cfg.ExternalL2Shim == "" {
533-
node, backend, err := geth.InitL2(name, big.NewInt(int64(cfg.DeployConfig.L2ChainID)), l2Genesis, cfg.JWTFilePath, cfg.GethOptions[name]...)
536+
node, backend, err := geth.InitL2(name, big.NewInt(int64(cfg.DeployConfig.L2ChainID)), l2Genesis, cfg.JWTFilePath, blobPoolPath, cfg.GethOptions[name]...)
534537
if err != nil {
535538
return nil, err
536539
}
@@ -548,10 +551,11 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
548551
t.Skip("External L2 nodes do not support configuration through GethOptions")
549552
}
550553
ethClient = (&ExternalRunner{
551-
Name: name,
552-
BinPath: cfg.ExternalL2Shim,
553-
Genesis: l2Genesis,
554-
JWTPath: cfg.JWTFilePath,
554+
Name: name,
555+
BinPath: cfg.ExternalL2Shim,
556+
Genesis: l2Genesis,
557+
JWTPath: cfg.JWTFilePath,
558+
BlobPoolPath: blobPoolPath,
555559
}).Run(t)
556560
}
557561
sys.EthInstances[name] = ethClient
@@ -562,7 +566,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
562566
// of only websockets (which are required for external eth client tests).
563567
for name, rollupCfg := range cfg.Nodes {
564568
configureL1(rollupCfg, sys.EthInstances["l1"])
565-
configureL2(rollupCfg, sys.EthInstances[name], cfg.JWTSecret)
569+
configureL2(rollupCfg, sys.EthInstances[name], cfg.JWTSecret, sys.L1BeaconAPIAddr)
566570

567571
rollupCfg.L2Sync = &rollupNode.PreparedL2SyncEndpoint{
568572
Client: nil,
@@ -870,7 +874,7 @@ type WSOrHTTPEndpoint interface {
870874
HTTPAuthEndpoint() string
871875
}
872876

873-
func configureL2(rollupNodeCfg *rollupNode.Config, l2Node WSOrHTTPEndpoint, jwtSecret [32]byte) {
877+
func configureL2(rollupNodeCfg *rollupNode.Config, l2Node WSOrHTTPEndpoint, jwtSecret [32]byte, l1BeaconAPIAddr string) {
874878
l2EndpointConfig := l2Node.WSAuthEndpoint()
875879
if UseHTTP() {
876880
l2EndpointConfig = l2Node.HTTPAuthEndpoint()
@@ -880,6 +884,7 @@ func configureL2(rollupNodeCfg *rollupNode.Config, l2Node WSOrHTTPEndpoint, jwtS
880884
L2EngineAddr: l2EndpointConfig,
881885
L2EngineJWTSecret: jwtSecret,
882886
}
887+
rollupNodeCfg.Beacon = &rollupNode.L1BeaconEndpointConfig{BeaconAddr: l1BeaconAPIAddr}
883888
}
884889

885890
func (cfg SystemConfig) L1ChainIDBig() *big.Int {

op-e2e/system_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,11 +780,11 @@ func TestSystemP2PAltSync(t *testing.T) {
780780
},
781781
}
782782
configureL1(syncNodeCfg, sys.EthInstances["l1"])
783-
syncerL2Engine, _, err := geth.InitL2("syncer", big.NewInt(int64(cfg.DeployConfig.L2ChainID)), sys.L2GenesisCfg, cfg.JWTFilePath)
783+
syncerL2Engine, _, err := geth.InitL2("syncer", big.NewInt(int64(cfg.DeployConfig.L2ChainID)), sys.L2GenesisCfg, cfg.JWTFilePath, t.TempDir())
784784
require.NoError(t, err)
785785
require.NoError(t, syncerL2Engine.Start())
786786

787-
configureL2(syncNodeCfg, syncerL2Engine, cfg.JWTSecret)
787+
configureL2(syncNodeCfg, syncerL2Engine, cfg.JWTSecret, sys.L1BeaconAPIAddr)
788788

789789
syncerNode, err := rollupNode.New(context.Background(), syncNodeCfg, cfg.Loggers["syncer"], snapLog, "", metrics.NewMetrics(""))
790790
require.NoError(t, err)

0 commit comments

Comments
 (0)