Skip to content

Commit 4cd07f5

Browse files
authored
op-deployer: Add support for dumping calldata (#13680)
* op-deployer: Add support for dumping calldata * Update state.go
1 parent fdb7edd commit 4cd07f5

File tree

13 files changed

+315
-140
lines changed

13 files changed

+315
-140
lines changed

op-deployer/pkg/deployer/apply.go

Lines changed: 90 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ import (
3232
)
3333

3434
type ApplyConfig struct {
35-
L1RPCUrl string
36-
Workdir string
37-
PrivateKey string
38-
Logger log.Logger
35+
L1RPCUrl string
36+
Workdir string
37+
PrivateKey string
38+
DeploymentTarget DeploymentTarget
39+
Logger log.Logger
3940

4041
privateKeyECDSA *ecdsa.PrivateKey
4142
}
@@ -57,16 +58,14 @@ func (a *ApplyConfig) Check() error {
5758
return fmt.Errorf("logger must be specified")
5859
}
5960

60-
return nil
61-
}
62-
63-
func (a *ApplyConfig) CheckLive() error {
64-
if a.privateKeyECDSA == nil {
65-
return fmt.Errorf("private key must be specified")
66-
}
61+
if a.DeploymentTarget == DeploymentTargetLive {
62+
if a.L1RPCUrl == "" {
63+
return fmt.Errorf("l1 RPC URL must be specified for live deployment")
64+
}
6765

68-
if a.L1RPCUrl == "" {
69-
return fmt.Errorf("l1RPCUrl must be specified")
66+
if a.privateKeyECDSA == nil {
67+
return fmt.Errorf("private key must be specified for live deployment")
68+
}
7069
}
7170

7271
return nil
@@ -81,14 +80,19 @@ func ApplyCLI() func(cliCtx *cli.Context) error {
8180
l1RPCUrl := cliCtx.String(L1RPCURLFlagName)
8281
workdir := cliCtx.String(WorkdirFlagName)
8382
privateKey := cliCtx.String(PrivateKeyFlagName)
83+
depTarget, err := NewDeploymentTarget(cliCtx.String(DeploymentTargetFlag.Name))
84+
if err != nil {
85+
return fmt.Errorf("failed to parse deployment target: %w", err)
86+
}
8487

8588
ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context)
8689

8790
return Apply(ctx, ApplyConfig{
88-
L1RPCUrl: l1RPCUrl,
89-
Workdir: workdir,
90-
PrivateKey: privateKey,
91-
Logger: l,
91+
L1RPCUrl: l1RPCUrl,
92+
Workdir: workdir,
93+
PrivateKey: privateKey,
94+
DeploymentTarget: depTarget,
95+
Logger: l,
9296
})
9397
}
9498
}
@@ -110,6 +114,7 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
110114

111115
if err := ApplyPipeline(ctx, ApplyPipelineOpts{
112116
L1RPCUrl: cfg.L1RPCUrl,
117+
DeploymentTarget: cfg.DeploymentTarget,
113118
DeployerPrivateKey: cfg.privateKeyECDSA,
114119
Intent: intent,
115120
State: st,
@@ -129,6 +134,7 @@ type pipelineStage struct {
129134

130135
type ApplyPipelineOpts struct {
131136
L1RPCUrl string
137+
DeploymentTarget DeploymentTarget
132138
DeployerPrivateKey *ecdsa.PrivateKey
133139
Intent *state.Intent
134140
State *state.State
@@ -187,34 +193,11 @@ func ApplyPipeline(
187193
}
188194

189195
var bcaster broadcaster.Broadcaster
196+
var l1RPC *rpc.Client
190197
var l1Client *ethclient.Client
191198
var l1Host *script.Host
192-
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
193-
l1RPC, err := rpc.Dial(opts.L1RPCUrl)
194-
if err != nil {
195-
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
196-
}
197-
198-
l1Client = ethclient.NewClient(l1RPC)
199-
200-
chainID, err := l1Client.ChainID(ctx)
201-
if err != nil {
202-
return fmt.Errorf("failed to get chain ID: %w", err)
203-
}
204-
205-
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(opts.DeployerPrivateKey, chainID))
206-
207-
bcaster, err = broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
208-
Logger: opts.Logger,
209-
ChainID: new(big.Int).SetUint64(intent.L1ChainID),
210-
Client: l1Client,
211-
Signer: signer,
212-
From: deployer,
213-
})
214-
if err != nil {
215-
return fmt.Errorf("failed to create broadcaster: %w", err)
216-
}
217199

200+
initForkHost := func() error {
218201
l1Host, err = env.DefaultScriptHost(
219202
bcaster,
220203
opts.Logger,
@@ -243,7 +226,54 @@ func ApplyPipeline(
243226
); err != nil {
244227
return fmt.Errorf("failed to select fork: %w", err)
245228
}
246-
} else {
229+
230+
return nil
231+
}
232+
233+
switch opts.DeploymentTarget {
234+
case DeploymentTargetLive:
235+
l1RPC, err = rpc.Dial(opts.L1RPCUrl)
236+
if err != nil {
237+
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
238+
}
239+
240+
l1Client = ethclient.NewClient(l1RPC)
241+
242+
chainID, err := l1Client.ChainID(ctx)
243+
if err != nil {
244+
return fmt.Errorf("failed to get chain ID: %w", err)
245+
}
246+
247+
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(opts.DeployerPrivateKey, chainID))
248+
249+
bcaster, err = broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
250+
Logger: opts.Logger,
251+
ChainID: new(big.Int).SetUint64(intent.L1ChainID),
252+
Client: l1Client,
253+
Signer: signer,
254+
From: deployer,
255+
})
256+
if err != nil {
257+
return fmt.Errorf("failed to create broadcaster: %w", err)
258+
}
259+
260+
if err := initForkHost(); err != nil {
261+
return fmt.Errorf("failed to initialize L1 host: %w", err)
262+
}
263+
case DeploymentTargetCalldata, DeploymentTargetNoop:
264+
l1RPC, err = rpc.Dial(opts.L1RPCUrl)
265+
if err != nil {
266+
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
267+
}
268+
269+
l1Client = ethclient.NewClient(l1RPC)
270+
271+
bcaster = new(broadcaster.CalldataBroadcaster)
272+
273+
if err := initForkHost(); err != nil {
274+
return fmt.Errorf("failed to initialize L1 host: %w", err)
275+
}
276+
case DeploymentTargetGenesis:
247277
bcaster = broadcaster.NoopBroadcaster()
248278
l1Host, err = env.DefaultScriptHost(
249279
bcaster,
@@ -254,6 +284,8 @@ func ApplyPipeline(
254284
if err != nil {
255285
return fmt.Errorf("failed to create L1 script host: %w", err)
256286
}
287+
default:
288+
return fmt.Errorf("invalid deployment target: '%s'", opts.DeploymentTarget)
257289
}
258290

259291
pEnv := &pipeline.Env{
@@ -267,11 +299,10 @@ func ApplyPipeline(
267299

268300
pline := []pipelineStage{
269301
{"init", func() error {
270-
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
271-
return pipeline.InitLiveStrategy(ctx, pEnv, intent, st)
272-
} else {
302+
if opts.DeploymentTarget == DeploymentTargetGenesis {
273303
return pipeline.InitGenesisStrategy(pEnv, intent, st)
274304
}
305+
return pipeline.InitLiveStrategy(ctx, pEnv, intent, st)
275306
}},
276307
{"deploy-superchain", func() error {
277308
return pipeline.DeploySuperchain(pEnv, intent, st)
@@ -314,23 +345,22 @@ func ApplyPipeline(
314345
pline = append(pline, pipelineStage{
315346
fmt.Sprintf("set-start-block-%s", chainID.Hex()),
316347
func() error {
317-
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
318-
return pipeline.SetStartBlockLiveStrategy(ctx, pEnv, st, chainID)
319-
} else {
348+
if opts.DeploymentTarget == DeploymentTargetGenesis {
320349
return pipeline.SetStartBlockGenesisStrategy(pEnv, st, chainID)
321350
}
351+
return pipeline.SetStartBlockLiveStrategy(ctx, pEnv, st, chainID)
322352
},
323353
})
324354
}
325355

326-
// Run through the pipeline. The state dump is captured between
327-
// every step.
356+
// Run through the pipeline.
328357
for _, stage := range pline {
329358
if err := stage.apply(); err != nil {
330359
return fmt.Errorf("error in pipeline stage apply: %w", err)
331360
}
332361

333-
if intent.DeploymentStrategy == state.DeploymentStrategyGenesis {
362+
// Some steps use the L1StateDump, so we need to apply it to state after every step.
363+
if opts.DeploymentTarget == DeploymentTargetGenesis {
334364
dump, err := pEnv.L1ScriptHost.StateDump()
335365
if err != nil {
336366
return fmt.Errorf("failed to dump state: %w", err)
@@ -348,6 +378,14 @@ func ApplyPipeline(
348378
}
349379
}
350380

381+
if opts.DeploymentTarget == DeploymentTargetCalldata {
382+
cdCaster := pEnv.Broadcaster.(*broadcaster.CalldataBroadcaster)
383+
st.DeploymentCalldata, err = cdCaster.Dump()
384+
if err != nil {
385+
return fmt.Errorf("failed to dump calldata: %w", err)
386+
}
387+
}
388+
351389
st.AppliedIntent = intent
352390
if err := pEnv.StateWriter.WriteState(st); err != nil {
353391
return fmt.Errorf("failed to write state: %w", err)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package broadcaster
2+
3+
import (
4+
"context"
5+
"sync"
6+
7+
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
8+
"github.com/ethereum-optimism/optimism/op-service/txmgr"
9+
"github.com/ethereum/go-ethereum/common"
10+
"github.com/ethereum/go-ethereum/common/hexutil"
11+
)
12+
13+
const defaultGasLimit = 30_000_000
14+
15+
type CalldataDump struct {
16+
To *common.Address
17+
Data hexutil.Bytes
18+
Value *hexutil.Big
19+
}
20+
21+
type CalldataBroadcaster struct {
22+
txs []txmgr.TxCandidate
23+
mtx sync.Mutex
24+
}
25+
26+
func (d *CalldataBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, error) {
27+
return nil, nil
28+
}
29+
30+
func (d *CalldataBroadcaster) Hook(bcast script.Broadcast) {
31+
candidate := asTxCandidate(bcast, defaultGasLimit)
32+
33+
d.mtx.Lock()
34+
d.txs = append(d.txs, candidate)
35+
d.mtx.Unlock()
36+
}
37+
38+
func (d *CalldataBroadcaster) Dump() ([]CalldataDump, error) {
39+
d.mtx.Lock()
40+
defer d.mtx.Unlock()
41+
42+
var out []CalldataDump
43+
for _, tx := range d.txs {
44+
out = append(out, CalldataDump{
45+
To: tx.To,
46+
Value: (*hexutil.Big)(tx.Value),
47+
Data: tx.TxData,
48+
})
49+
}
50+
d.txs = nil
51+
return out, nil
52+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package broadcaster
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/common/hexutil"
10+
"github.com/holiman/uint256"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestCalldataBroadcaster(t *testing.T) {
15+
bcast := new(CalldataBroadcaster)
16+
17+
bcasts := []script.Broadcast{
18+
{
19+
Type: script.BroadcastCall,
20+
To: common.Address{'1'},
21+
Input: []byte{'D', '1'},
22+
Value: (*hexutil.U256)(new(uint256.Int).SetUint64(123)),
23+
},
24+
{
25+
Type: script.BroadcastCreate,
26+
Input: []byte{'D', '2'},
27+
},
28+
}
29+
for _, b := range bcasts {
30+
bcast.Hook(b)
31+
}
32+
33+
res, err := bcast.Broadcast(context.Background())
34+
require.NoError(t, err)
35+
require.Nil(t, res)
36+
37+
dump, err := bcast.Dump()
38+
require.NoError(t, err)
39+
40+
expValues := make([]CalldataDump, len(bcasts))
41+
for i, b := range bcasts {
42+
var to *common.Address
43+
if b.To != (common.Address{}) {
44+
to = &b.To
45+
}
46+
47+
var value *hexutil.Big
48+
if b.Value != nil {
49+
value = (*hexutil.Big)((*uint256.Int)(b.Value).ToBig())
50+
}
51+
52+
expValues[i] = CalldataDump{
53+
To: to,
54+
Value: value,
55+
Data: b.Input,
56+
}
57+
}
58+
require.EqualValues(t, expValues, dump)
59+
}

op-deployer/pkg/deployer/broadcaster/keyed.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"sync"
88
"time"
99

10+
"github.com/holiman/uint256"
11+
1012
"github.com/ethereum-optimism/optimism/op-service/eth"
1113

1214
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
@@ -18,7 +20,6 @@ import (
1820
"github.com/ethereum/go-ethereum/ethclient"
1921
"github.com/ethereum/go-ethereum/log"
2022
"github.com/hashicorp/go-multierror"
21-
"github.com/holiman/uint256"
2223
)
2324

2425
const (
@@ -184,6 +185,12 @@ func (t *KeyedBroadcaster) broadcast(ctx context.Context, bcast script.Broadcast
184185
ch := make(chan txmgr.SendResponse, 1)
185186

186187
id := bcast.ID()
188+
candidate := asTxCandidate(bcast, blockGasLimit)
189+
t.mgr.SendAsync(ctx, candidate, ch)
190+
return ch, id
191+
}
192+
193+
func asTxCandidate(bcast script.Broadcast, blockGasLimit uint64) txmgr.TxCandidate {
187194
value := ((*uint256.Int)(bcast.Value)).ToBig()
188195
var candidate txmgr.TxCandidate
189196
switch bcast.Type {
@@ -212,10 +219,10 @@ func (t *KeyedBroadcaster) broadcast(ctx context.Context, bcast script.Broadcast
212219
Value: value,
213220
GasLimit: padGasLimit(bcast.Input, bcast.GasUsed, true, blockGasLimit),
214221
}
222+
default:
223+
panic(fmt.Sprintf("unrecognized broadcast type: '%s'", bcast.Type))
215224
}
216-
217-
t.mgr.SendAsync(ctx, candidate, ch)
218-
return ch, id
225+
return candidate
219226
}
220227

221228
// padGasLimit calculates the gas limit for a transaction based on the intrinsic gas and the gas used by

0 commit comments

Comments
 (0)