Commit c6c9c4b5 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

op-deployer: Support L1 alloc deployments (#12517)

* op-deployer: Support L1 alloc deployments

Adds support for deploying to L1 allocs file. When deploying to an allocs file, all contracts necessary for the L2 will be included in the L1's genesis state. This is useful for devnet deployments and test automation.

To use an L1 allocs file, set the field `deploymentStrategy` in the intent to `allocs`. The default is `live`, for live chains.

* make apply command work, fix test

* merge artifacts

* fix merge artifacts

* Update op-deployer/pkg/deployer/state/state.go
Co-authored-by: default avatarBlaine Malone <blainemalone01@gmail.com>

* fix: extra check for singleton contracts in tests.

* Update op-deployer/pkg/deployer/flags.go
Co-authored-by: default avatarBlaine Malone <blainemalone01@gmail.com>

* remove unused param

* fix merge artifact

---------
Co-authored-by: default avatarBlaine Malone <blainemalone01@gmail.com>
parent 1e59d083
bin
.deployer
\ No newline at end of file
......@@ -4,8 +4,13 @@ import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
......@@ -28,23 +33,17 @@ type ApplyConfig struct {
}
func (a *ApplyConfig) Check() error {
if a.L1RPCUrl == "" {
return fmt.Errorf("l1RPCUrl must be specified")
}
if a.Workdir == "" {
return fmt.Errorf("workdir must be specified")
}
if a.PrivateKey == "" {
return fmt.Errorf("private key must be specified")
}
if a.PrivateKey != "" {
privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(a.PrivateKey, "0x"))
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}
a.privateKeyECDSA = privECDSA
}
if a.Logger == nil {
return fmt.Errorf("logger must be specified")
......@@ -53,6 +52,18 @@ func (a *ApplyConfig) Check() error {
return nil
}
func (a *ApplyConfig) CheckLive() error {
if a.privateKeyECDSA == nil {
return fmt.Errorf("private key must be specified")
}
if a.L1RPCUrl == "" {
return fmt.Errorf("l1RPCUrl must be specified")
}
return nil
}
func ApplyCLI() func(cliCtx *cli.Context) error {
return func(cliCtx *cli.Context) error {
logCfg := oplog.ReadCLIConfig(cliCtx)
......@@ -79,57 +90,60 @@ func Apply(ctx context.Context, cfg ApplyConfig) error {
return fmt.Errorf("invalid config for apply: %w", err)
}
l1Client, err := ethclient.Dial(cfg.L1RPCUrl)
intent, err := pipeline.ReadIntent(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
return fmt.Errorf("failed to read intent: %w", err)
}
chainID, err := l1Client.ChainID(ctx)
st, err := pipeline.ReadState(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to get chain ID: %w", err)
return fmt.Errorf("failed to read state: %w", err)
}
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
deployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
var l1Client *ethclient.Client
var deployer common.Address
var bcaster broadcaster.Broadcaster
var startingNonce uint64
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
if err := cfg.CheckLive(); err != nil {
return fmt.Errorf("invalid config for apply: %w", err)
}
intent, err := pipeline.ReadIntent(cfg.Workdir)
l1Client, err = ethclient.Dial(cfg.L1RPCUrl)
if err != nil {
return fmt.Errorf("failed to read intent: %w", err)
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
}
st, err := pipeline.ReadState(cfg.Workdir)
chainID, err := l1Client.ChainID(ctx)
if err != nil {
return fmt.Errorf("failed to read state: %w", err)
return fmt.Errorf("failed to get chain ID: %w", err)
}
env := &pipeline.Env{
Workdir: cfg.Workdir,
L1Client: l1Client,
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
deployer = crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
bcaster, err = broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: cfg.Logger,
ChainID: new(big.Int).SetUint64(intent.L1ChainID),
Client: l1Client,
Signer: signer,
Deployer: deployer,
From: deployer,
})
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
if err := ApplyPipeline(ctx, env, intent, st); err != nil {
return err
startingNonce, err = l1Client.NonceAt(ctx, deployer, nil)
if err != nil {
return fmt.Errorf("failed to get starting nonce: %w", err)
}
} else {
deployer = common.Address{0x01}
bcaster = broadcaster.NoopBroadcaster()
}
return nil
}
type pipelineStage struct {
name string
apply pipeline.Stage
}
func ApplyPipeline(
ctx context.Context,
env *pipeline.Env,
intent *state.Intent,
st *state.State,
) error {
progressor := func(curr, total int64) {
env.Logger.Info("artifacts download progress", "current", curr, "total", total)
cfg.Logger.Info("artifacts download progress", "current", curr, "total", total)
}
l1ArtifactsFS, cleanupL1, err := pipeline.DownloadArtifacts(ctx, intent.L1ContractsLocator, progressor)
......@@ -138,7 +152,7 @@ func ApplyPipeline(
}
defer func() {
if err := cleanupL1(); err != nil {
env.Logger.Warn("failed to clean up L1 artifacts", "err", err)
cfg.Logger.Warn("failed to clean up L1 artifacts", "err", err)
}
}()
......@@ -148,7 +162,7 @@ func ApplyPipeline(
}
defer func() {
if err := cleanupL2(); err != nil {
env.Logger.Warn("failed to clean up L2 artifacts", "err", err)
cfg.Logger.Warn("failed to clean up L2 artifacts", "err", err)
}
}()
......@@ -157,38 +171,114 @@ func ApplyPipeline(
L2: l2ArtifactsFS,
}
l1Host, err := pipeline.DefaultScriptHost(bcaster, cfg.Logger, deployer, bundle.L1, startingNonce)
if err != nil {
return fmt.Errorf("failed to create L1 script host: %w", err)
}
env := &pipeline.Env{
StateWriter: pipeline.WorkdirStateWriter(cfg.Workdir),
L1ScriptHost: l1Host,
L1Client: l1Client,
Logger: cfg.Logger,
Broadcaster: bcaster,
Deployer: deployer,
}
if err := ApplyPipeline(ctx, env, bundle, intent, st); err != nil {
return err
}
return nil
}
type pipelineStage struct {
name string
apply func() error
}
func ApplyPipeline(
ctx context.Context,
env *pipeline.Env,
bundle pipeline.ArtifactsBundle,
intent *state.Intent,
st *state.State,
) error {
pline := []pipelineStage{
{"init", pipeline.Init},
{"deploy-superchain", pipeline.DeploySuperchain},
{"deploy-implementations", pipeline.DeployImplementations},
{"init", func() error {
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
return pipeline.InitLiveStrategy(ctx, env, intent, st)
} else {
return pipeline.InitGenesisStrategy(env, intent, st)
}
}},
{"deploy-superchain", func() error {
return pipeline.DeploySuperchain(env, intent, st)
}},
{"deploy-implementations", func() error {
return pipeline.DeployImplementations(env, intent, st)
}},
}
// Deploy all OP Chains first.
for _, chain := range intent.Chains {
chainID := chain.ID
pline = append(pline, pipelineStage{
fmt.Sprintf("deploy-opchain-%s", chainID.Hex()),
func(ctx context.Context, env *pipeline.Env, bundle pipeline.ArtifactsBundle, intent *state.Intent, st *state.State) error {
return pipeline.DeployOPChain(ctx, env, bundle, intent, st, chainID)
func() error {
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
return pipeline.DeployOPChainLiveStrategy(ctx, env, bundle, intent, st, chainID)
} else {
return pipeline.DeployOPChainGenesisStrategy(env, intent, st, chainID)
}
},
}, pipelineStage{
fmt.Sprintf("generate-l2-genesis-%s", chainID.Hex()),
func(ctx context.Context, env *pipeline.Env, bundle pipeline.ArtifactsBundle, intent *state.Intent, st *state.State) error {
return pipeline.GenerateL2Genesis(ctx, env, bundle, intent, st, chainID)
func() error {
return pipeline.GenerateL2Genesis(env, intent, bundle, st, chainID)
},
})
}
// Set start block after all OP chains have been deployed, since the
// genesis strategy requires all the OP chains to exist in genesis.
for _, chain := range intent.Chains {
chainID := chain.ID
pline = append(pline, pipelineStage{
fmt.Sprintf("set-start-block-%s", chainID.Hex()),
func() error {
if intent.DeploymentStrategy == state.DeploymentStrategyLive {
return pipeline.SetStartBlockLiveStrategy(ctx, env, st, chainID)
} else {
return pipeline.SetStartBlockGenesisStrategy(env, st, chainID)
}
},
})
}
// Run through the pipeline. The state dump is captured between
// every step.
for _, stage := range pline {
if err := stage.apply(ctx, env, bundle, intent, st); err != nil {
if err := stage.apply(); err != nil {
return fmt.Errorf("error in pipeline stage apply: %w", err)
}
if err := pipeline.WriteState(env.Workdir, st); err != nil {
dump, err := env.L1ScriptHost.StateDump()
if err != nil {
return fmt.Errorf("failed to dump state: %w", err)
}
st.L1StateDump = &state.GzipData[foundry.ForgeAllocs]{
Data: dump,
}
if _, err := env.Broadcaster.Broadcast(ctx); err != nil {
return fmt.Errorf("failed to broadcast stage %s: %w", stage.name, err)
}
if err := env.StateWriter.WriteState(st); err != nil {
return fmt.Errorf("failed to write state: %w", err)
}
}
st.AppliedIntent = intent
if err := pipeline.WriteState(env.Workdir, st); err != nil {
if err := env.StateWriter.WriteState(st); err != nil {
return fmt.Errorf("failed to write state: %w", err)
}
......
......@@ -8,11 +8,12 @@ import (
"math/big"
"strings"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/ctxinterrupt"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
......@@ -131,6 +132,33 @@ func OPCM(ctx context.Context, cfg OPCMConfig) error {
signer := opcrypto.SignerFnFromBind(opcrypto.PrivateKeySignerFn(cfg.privateKeyECDSA, chainID))
chainDeployer := crypto.PubkeyToAddress(cfg.privateKeyECDSA.PublicKey)
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: lgr,
ChainID: chainID,
Client: l1Client,
Signer: signer,
From: chainDeployer,
})
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
nonce, err := l1Client.NonceAt(ctx, chainDeployer, nil)
if err != nil {
return fmt.Errorf("failed to get starting nonce: %w", err)
}
host, err := pipeline.DefaultScriptHost(
bcaster,
lgr,
chainDeployer,
artifactsFS,
nonce,
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}
var release string
if cfg.ArtifactsLocator.IsTag() {
release = cfg.ArtifactsLocator.Tag
......@@ -140,18 +168,6 @@ func OPCM(ctx context.Context, cfg OPCMConfig) error {
lgr.Info("deploying OPCM", "release", release)
var dio opcm.DeployImplementationsOutput
err = pipeline.CallScriptBroadcast(
ctx,
pipeline.CallScriptBroadcastOpts{
L1ChainID: chainID,
Logger: lgr,
ArtifactsFS: artifactsFS,
Deployer: chainDeployer,
Signer: signer,
Client: l1Client,
Broadcaster: pipeline.KeyedBroadcaster,
Handler: func(host *script.Host) error {
// We need to etch the Superchain addresses so that they have nonzero code
// and the checks in the OPCM constructor pass.
superchainConfigAddr := common.Address(*superCfg.Config.SuperchainConfigAddr)
......@@ -172,7 +188,7 @@ func OPCM(ctx context.Context, cfg OPCMConfig) error {
return fmt.Errorf("failed to generate CREATE2 salt: %w", err)
}
dio, err = opcm.DeployImplementations(
dio, err := opcm.DeployImplementations(
host,
opcm.DeployImplementationsInput{
Salt: salt,
......@@ -189,14 +205,14 @@ func OPCM(ctx context.Context, cfg OPCMConfig) error {
UseInterop: false,
},
)
return err
},
},
)
if err != nil {
return fmt.Errorf("error deploying implementations: %w", err)
}
if _, err := bcaster.Broadcast(ctx); err != nil {
return fmt.Errorf("failed to broadcast: %w", err)
}
lgr.Info("deployed implementations")
if err := jsonutil.WriteJSON(dio, ioutil.ToStdOut()); err != nil {
......
......@@ -9,7 +9,7 @@ import (
type discardBroadcaster struct {
}
func DiscardBroadcaster() Broadcaster {
func NoopBroadcaster() Broadcaster {
return &discardBroadcaster{}
}
......
......@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math/big"
"sync"
"time"
"github.com/ethereum-optimism/optimism/op-service/eth"
......@@ -29,6 +30,7 @@ type KeyedBroadcaster struct {
mgr txmgr.TxManager
bcasts []script.Broadcast
client *ethclient.Client
mtx sync.Mutex
}
type KeyedBroadcasterOpts struct {
......@@ -88,20 +90,32 @@ func NewKeyedBroadcaster(cfg KeyedBroadcasterOpts) (*KeyedBroadcaster, error) {
}
func (t *KeyedBroadcaster) Hook(bcast script.Broadcast) {
t.mtx.Lock()
t.bcasts = append(t.bcasts, bcast)
t.mtx.Unlock()
}
func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, error) {
results := make([]BroadcastResult, len(t.bcasts))
futures := make([]<-chan txmgr.SendResponse, len(t.bcasts))
ids := make([]common.Hash, len(t.bcasts))
// Empty the internal broadcast buffer as soon as this method is called.
t.mtx.Lock()
bcasts := t.bcasts
t.bcasts = nil
t.mtx.Unlock()
if len(bcasts) == 0 {
return nil, nil
}
results := make([]BroadcastResult, len(bcasts))
futures := make([]<-chan txmgr.SendResponse, len(bcasts))
ids := make([]common.Hash, len(bcasts))
latestBlock, err := t.client.BlockByNumber(ctx, nil)
if err != nil {
return nil, fmt.Errorf("failed to get latest block: %w", err)
}
for i, bcast := range t.bcasts {
for i, bcast := range bcasts {
futures[i], ids[i] = t.broadcast(ctx, bcast, latestBlock.GasLimit())
t.lgr.Info(
"transaction broadcasted",
......@@ -116,7 +130,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er
bcastRes := <-fut
completed++
outRes := BroadcastResult{
Broadcast: t.bcasts[i],
Broadcast: bcasts[i],
}
if bcastRes.Err == nil {
......@@ -131,7 +145,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er
"transaction failed on chain",
"id", ids[i],
"completed", completed,
"total", len(t.bcasts),
"total", len(bcasts),
"hash", outRes.Receipt.TxHash.String(),
"nonce", outRes.Broadcast.Nonce,
)
......@@ -140,7 +154,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er
"transaction confirmed",
"id", ids[i],
"completed", completed,
"total", len(t.bcasts),
"total", len(bcasts),
"hash", outRes.Receipt.TxHash.String(),
"nonce", outRes.Broadcast.Nonce,
"creation", outRes.Receipt.ContractAddress,
......@@ -153,7 +167,7 @@ func (t *KeyedBroadcaster) Broadcast(ctx context.Context) ([]BroadcastResult, er
"transaction failed",
"id", ids[i],
"completed", completed,
"total", len(t.bcasts),
"total", len(bcasts),
"err", bcastRes.Err,
)
}
......
package deployer
import (
"fmt"
"os"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
op_service "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/urfave/cli/v2"
......@@ -16,13 +19,14 @@ const (
WorkdirFlagName = "workdir"
OutdirFlagName = "outdir"
PrivateKeyFlagName = "private-key"
DeploymentStrategyFlagName = "deployment-strategy"
)
var (
L1RPCURLFlag = &cli.StringFlag{
Name: L1RPCURLFlagName,
Usage: "RPC URL for the L1 chain. Can be set to 'genesis' for deployments " +
"that will be deployed at the launch of the L1.",
Usage: "RPC URL for the L1 chain. Must be set for live chains. " +
"Can be blank for chains deploying to local allocs files.",
EnvVars: []string{
"L1_RPC_URL",
},
......@@ -52,6 +56,12 @@ var (
Usage: "Private key of the deployer account.",
EnvVars: PrefixEnvVar("PRIVATE_KEY"),
}
DeploymentStrategyFlag = &cli.StringFlag{
Name: DeploymentStrategyFlagName,
Usage: fmt.Sprintf("Deployment strategy to use. Options: %s, %s", state.DeploymentStrategyLive, state.DeploymentStrategyGenesis),
EnvVars: PrefixEnvVar("DEPLOYMENT_STRATEGY"),
Value: string(state.DeploymentStrategyLive),
}
)
var GlobalFlags = append([]cli.Flag{}, oplog.CLIFlags(EnvVarPrefix)...)
......@@ -60,6 +70,7 @@ var InitFlags = []cli.Flag{
L1ChainIDFlag,
L2ChainIDsFlag,
WorkdirFlag,
DeploymentStrategyFlag,
}
var ApplyFlags = []cli.Flag{
......
......@@ -8,7 +8,7 @@ import (
"strings"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
state2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
op_service "github.com/ethereum-optimism/optimism/op-service"
......@@ -18,12 +18,17 @@ import (
)
type InitConfig struct {
DeploymentStrategy state.DeploymentStrategy
L1ChainID uint64
Outdir string
L2ChainIDs []common.Hash
}
func (c *InitConfig) Check() error {
if err := c.DeploymentStrategy.Check(); err != nil {
return err
}
if c.L1ChainID == 0 {
return fmt.Errorf("l1ChainID must be specified")
}
......@@ -41,21 +46,27 @@ func (c *InitConfig) Check() error {
func InitCLI() func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
deploymentStrategy := ctx.String(DeploymentStrategyFlagName)
l1ChainID := ctx.Uint64(L1ChainIDFlagName)
outdir := ctx.String(OutdirFlagName)
l2ChainIDsRaw := ctx.String(L2ChainIDsFlagName)
if len(l2ChainIDsRaw) == 0 {
return fmt.Errorf("must specify at least one L2 chain ID")
}
l2ChainIDsStr := strings.Split(strings.TrimSpace(l2ChainIDsRaw), ",")
l2ChainIDs := make([]common.Hash, len(l2ChainIDsStr))
for i, idStr := range l2ChainIDsStr {
id, err := op_service.Parse256BitChainID(idStr)
if err != nil {
return fmt.Errorf("invalid chain ID: %w", err)
return fmt.Errorf("invalid L2 chain ID '%s': %w", idStr, err)
}
l2ChainIDs[i] = id
}
err := Init(InitConfig{
DeploymentStrategy: state.DeploymentStrategy(deploymentStrategy),
L1ChainID: l1ChainID,
Outdir: outdir,
L2ChainIDs: l2ChainIDs,
......@@ -74,7 +85,8 @@ func Init(cfg InitConfig) error {
return fmt.Errorf("invalid config for init: %w", err)
}
intent := &state2.Intent{
intent := &state.Intent{
DeploymentStrategy: cfg.DeploymentStrategy,
L1ChainID: cfg.L1ChainID,
FundDevAccounts: true,
L1ContractsLocator: opcm.DefaultL1ContractsLocator,
......@@ -96,7 +108,7 @@ func Init(cfg InitConfig) error {
}
return addr
}
intent.SuperchainRoles = state2.SuperchainRoles{
intent.SuperchainRoles = &state.SuperchainRoles{
ProxyAdminOwner: addrFor(devkeys.L1ProxyAdminOwnerRole.Key(l1ChainIDBig)),
ProtocolVersionsOwner: addrFor(devkeys.SuperchainProtocolVersionsOwner.Key(l1ChainIDBig)),
Guardian: addrFor(devkeys.SuperchainConfigGuardianKey.Key(l1ChainIDBig)),
......@@ -104,14 +116,14 @@ func Init(cfg InitConfig) error {
for _, l2ChainID := range cfg.L2ChainIDs {
l2ChainIDBig := l2ChainID.Big()
intent.Chains = append(intent.Chains, &state2.ChainIntent{
intent.Chains = append(intent.Chains, &state.ChainIntent{
ID: l2ChainID,
BaseFeeVaultRecipient: common.Address{},
L1FeeVaultRecipient: common.Address{},
SequencerFeeVaultRecipient: common.Address{},
Eip1559Denominator: 50,
Eip1559Elasticity: 6,
Roles: state2.ChainRoles{
Roles: state.ChainRoles{
ProxyAdminOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l2ChainIDBig)),
SystemConfigOwner: addrFor(devkeys.SystemConfigOwner.Key(l2ChainIDBig)),
GovernanceTokenOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l2ChainIDBig)),
......@@ -123,7 +135,7 @@ func Init(cfg InitConfig) error {
})
}
st := &state2.State{
st := &state.State{
Version: 1,
}
......
......@@ -4,7 +4,7 @@ import (
"fmt"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/pipeline"
state2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-node/rollup"
......@@ -39,7 +39,7 @@ func GenesisCLI(cliCtx *cli.Context) error {
return nil
}
func GenesisAndRollup(globalState *state2.State, chainID common.Hash) (*core.Genesis, *rollup.Config, error) {
func GenesisAndRollup(globalState *state.State, chainID common.Hash) (*core.Genesis, *rollup.Config, error) {
if globalState.AppliedIntent == nil {
return nil, nil, fmt.Errorf("chain state is not applied - run op-deployer apply")
}
......@@ -54,12 +54,8 @@ func GenesisAndRollup(globalState *state2.State, chainID common.Hash) (*core.Gen
return nil, nil, fmt.Errorf("failed to get chain ID %s: %w", chainID.String(), err)
}
l2Allocs, err := chainState.UnmarshalAllocs()
if err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal genesis: %w", err)
}
config, err := state2.CombineDeployConfig(
l2Allocs := chainState.Allocs.Data
config, err := state.CombineDeployConfig(
globalState.AppliedIntent,
chainIntent,
globalState,
......
......@@ -24,6 +24,8 @@ var ErrUnsupportedArtifactsScheme = errors.New("unsupported artifacts URL scheme
type DownloadProgressor func(current, total int64)
func NoopDownloadProgressor(current, total int64) {}
type CleanupFunc func() error
var noopCleanup = func() error { return nil }
......
......@@ -5,11 +5,13 @@ import (
"fmt"
"path"
state2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
......@@ -17,32 +19,56 @@ import (
)
type Env struct {
Workdir string
StateWriter StateWriter
L1ScriptHost *script.Host
L1Client *ethclient.Client
Signer opcrypto.SignerFn
Broadcaster broadcaster.Broadcaster
Host *script.Host
Deployer common.Address
Logger log.Logger
}
func ReadIntent(workdir string) (*state2.Intent, error) {
type StateWriter interface {
WriteState(st *state.State) error
}
type stateWriterFunc func(st *state.State) error
func (f stateWriterFunc) WriteState(st *state.State) error {
return f(st)
}
func WorkdirStateWriter(workdir string) StateWriter {
return stateWriterFunc(func(st *state.State) error {
return WriteState(workdir, st)
})
}
func NoopStateWriter() StateWriter {
return stateWriterFunc(func(st *state.State) error {
return nil
})
}
func ReadIntent(workdir string) (*state.Intent, error) {
intentPath := path.Join(workdir, "intent.toml")
intent, err := jsonutil.LoadTOML[state2.Intent](intentPath)
intent, err := jsonutil.LoadTOML[state.Intent](intentPath)
if err != nil {
return nil, fmt.Errorf("failed to read intent file: %w", err)
}
return intent, nil
}
func ReadState(workdir string) (*state2.State, error) {
func ReadState(workdir string) (*state.State, error) {
statePath := path.Join(workdir, "state.json")
st, err := jsonutil.LoadJSON[state2.State](statePath)
st, err := jsonutil.LoadJSON[state.State](statePath)
if err != nil {
return nil, fmt.Errorf("failed to read state file: %w", err)
}
return st, nil
}
func WriteState(workdir string, st *state2.State) error {
func WriteState(workdir string, st *state.State) error {
statePath := path.Join(workdir, "state.json")
return st.WriteToFile(statePath)
}
......@@ -52,4 +78,4 @@ type ArtifactsBundle struct {
L2 foundry.StatDirFs
}
type Stage func(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state2.Intent, st *state2.State) error
type Stage func(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State) error
package pipeline
import (
"context"
"fmt"
"math/big"
broadcaster2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
type BroadcasterFactory func(opts CallScriptBroadcastOpts) (broadcaster2.Broadcaster, error)
func KeyedBroadcaster(opts CallScriptBroadcastOpts) (broadcaster2.Broadcaster, error) {
return broadcaster2.NewKeyedBroadcaster(broadcaster2.KeyedBroadcasterOpts{
Logger: opts.Logger,
ChainID: opts.L1ChainID,
Client: opts.Client,
Signer: opts.Signer,
From: opts.Deployer,
})
}
func DiscardBroadcaster(opts CallScriptBroadcastOpts) (broadcaster2.Broadcaster, error) {
return broadcaster2.DiscardBroadcaster(), nil
}
type CallScriptBroadcastOpts struct {
L1ChainID *big.Int
Logger log.Logger
ArtifactsFS foundry.StatDirFs
Deployer common.Address
Signer opcrypto.SignerFn
Client *ethclient.Client
Handler func(host *script.Host) error
Broadcaster BroadcasterFactory
}
func CallScriptBroadcast(
ctx context.Context,
opts CallScriptBroadcastOpts,
) error {
bcaster, err := opts.Broadcaster(opts)
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
func DefaultScriptHost(
bcaster broadcaster.Broadcaster,
lgr log.Logger,
deployer common.Address,
artifacts foundry.StatDirFs,
startingNonce uint64,
) (*script.Host, error) {
scriptCtx := script.DefaultContext
scriptCtx.Sender = opts.Deployer
scriptCtx.Origin = opts.Deployer
artifacts := &foundry.ArtifactsFS{FS: opts.ArtifactsFS}
scriptCtx.Sender = deployer
scriptCtx.Origin = deployer
h := script.NewHost(
opts.Logger,
artifacts,
lgr,
&foundry.ArtifactsFS{FS: artifacts},
nil,
scriptCtx,
script.WithBroadcastHook(bcaster.Hook),
......@@ -66,23 +32,10 @@ func CallScriptBroadcast(
)
if err := h.EnableCheats(); err != nil {
return fmt.Errorf("failed to enable cheats: %w", err)
}
nonce, err := opts.Client.NonceAt(ctx, opts.Deployer, nil)
if err != nil {
return fmt.Errorf("failed to fetch nonce: %w", err)
return nil, fmt.Errorf("failed to enable cheats: %w", err)
}
h.SetNonce(opts.Deployer, nonce)
err = opts.Handler(h)
if err != nil {
return fmt.Errorf("failed to run handler: %w", err)
}
if _, err := bcaster.Broadcast(ctx); err != nil {
return fmt.Errorf("failed to broadcast: %w", err)
}
h.SetNonce(deployer, startingNonce)
return nil
return h, nil
}
package pipeline
import (
"context"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
)
func DeployImplementations(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State) error {
func DeployImplementations(env *Env, intent *state.Intent, st *state.State) error {
lgr := env.Logger.New("stage", "deploy-implementations")
if !shouldDeployImplementations(intent, st) {
......@@ -25,7 +21,7 @@ func DeployImplementations(ctx context.Context, env *Env, bundle ArtifactsBundle
var standardVersionsTOML string
var contractsRelease string
var err error
if intent.L1ContractsLocator.IsTag() {
if intent.L1ContractsLocator.IsTag() && intent.DeploymentStrategy == state.DeploymentStrategyLive {
standardVersionsTOML, err = opcm.StandardL1VersionsDataFor(intent.L1ChainID)
if err != nil {
return fmt.Errorf("error getting standard versions TOML: %w", err)
......@@ -35,23 +31,10 @@ func DeployImplementations(ctx context.Context, env *Env, bundle ArtifactsBundle
contractsRelease = "dev"
}
var dump *foundry.ForgeAllocs
var dio opcm.DeployImplementationsOutput
err = CallScriptBroadcast(
ctx,
CallScriptBroadcastOpts{
L1ChainID: big.NewInt(int64(intent.L1ChainID)),
Logger: lgr,
ArtifactsFS: bundle.L1,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: KeyedBroadcaster,
Handler: func(host *script.Host) error {
host.ImportState(st.SuperchainDeployment.StateDump)
env.L1ScriptHost.ImportState(st.L1StateDump.Data)
dio, err = opcm.DeployImplementations(
host,
dio, err := opcm.DeployImplementations(
env.L1ScriptHost,
opcm.DeployImplementationsInput{
Salt: st.Create2Salt,
WithdrawalDelaySeconds: big.NewInt(604800),
......@@ -71,17 +54,6 @@ func DeployImplementations(ctx context.Context, env *Env, bundle ArtifactsBundle
if err != nil {
return fmt.Errorf("error deploying implementations: %w", err)
}
dump, err = host.StateDump()
if err != nil {
return fmt.Errorf("error dumping state: %w", err)
}
return nil
},
},
)
if err != nil {
return fmt.Errorf("error deploying implementations: %w", err)
}
st.ImplementationsDeployment = &state.ImplementationsDeployment{
OpcmProxyAddress: dio.OpcmProxy,
......@@ -95,7 +67,6 @@ func DeployImplementations(ctx context.Context, env *Env, bundle ArtifactsBundle
L1StandardBridgeImplAddress: dio.L1StandardBridgeImpl,
OptimismMintableERC20FactoryImplAddress: dio.OptimismMintableERC20FactoryImpl,
DisputeGameFactoryImplAddress: dio.DisputeGameFactoryImpl,
StateDump: dump,
}
return nil
......
......@@ -6,7 +6,7 @@ import (
"fmt"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
state2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
......@@ -17,20 +17,12 @@ func IsSupportedStateVersion(version int) bool {
return version == 1
}
func Init(ctx context.Context, env *Env, _ ArtifactsBundle, intent *state2.Intent, st *state2.State) error {
lgr := env.Logger.New("stage", "init")
func InitLiveStrategy(ctx context.Context, env *Env, intent *state.Intent, st *state.State) error {
lgr := env.Logger.New("stage", "init", "strategy", "live")
lgr.Info("initializing pipeline")
// Ensure the state version is supported.
if !IsSupportedStateVersion(st.Version) {
return fmt.Errorf("unsupported state version: %d", st.Version)
}
if st.Create2Salt == (common.Hash{}) {
_, err := rand.Read(st.Create2Salt[:])
if err != nil {
return fmt.Errorf("failed to generate CREATE2 salt: %w", err)
}
if err := initCommonChecks(st); err != nil {
return err
}
if intent.L1ContractsLocator.IsTag() {
......@@ -46,7 +38,7 @@ func Init(ctx context.Context, env *Env, _ ArtifactsBundle, intent *state2.Inten
// Have to do this weird pointer thing below because the Superchain Registry defines its
// own Address type.
st.SuperchainDeployment = &state2.SuperchainDeployment{
st.SuperchainDeployment = &state.SuperchainDeployment{
ProxyAdminAddress: proxyAdmin,
ProtocolVersionsProxyAddress: common.Address(*superCfg.Config.ProtocolVersionsAddr),
SuperchainConfigProxyAddress: common.Address(*superCfg.Config.SuperchainConfigAddr),
......@@ -56,7 +48,7 @@ func Init(ctx context.Context, env *Env, _ ArtifactsBundle, intent *state2.Inten
if err != nil {
return fmt.Errorf("error getting OPCM proxy address: %w", err)
}
st.ImplementationsDeployment = &state2.ImplementationsDeployment{
st.ImplementationsDeployment = &state.ImplementationsDeployment{
OpcmProxyAddress: opcmProxy,
}
}
......@@ -99,6 +91,38 @@ func Init(ctx context.Context, env *Env, _ ArtifactsBundle, intent *state2.Inten
return nil
}
func initCommonChecks(st *state.State) error {
// Ensure the state version is supported.
if !IsSupportedStateVersion(st.Version) {
return fmt.Errorf("unsupported state version: %d", st.Version)
}
if st.Create2Salt == (common.Hash{}) {
_, err := rand.Read(st.Create2Salt[:])
if err != nil {
return fmt.Errorf("failed to generate CREATE2 salt: %w", err)
}
}
return nil
}
func InitGenesisStrategy(env *Env, intent *state.Intent, st *state.State) error {
lgr := env.Logger.New("stage", "init", "strategy", "genesis")
lgr.Info("initializing pipeline")
if err := initCommonChecks(st); err != nil {
return err
}
if intent.SuperchainRoles == nil {
return fmt.Errorf("superchain roles must be set for genesis strategy")
}
// Mostly a stub for now.
return nil
}
func immutableErr(field string, was, is any) error {
return fmt.Errorf("%s is immutable: was %v, is %v", field, was, is)
}
package pipeline
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
state2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum/go-ethereum/common"
)
func GenerateL2Genesis(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state2.Intent, st *state2.State, chainID common.Hash) error {
func GenerateL2Genesis(env *Env, intent *state.Intent, bundle ArtifactsBundle, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "generate-l2-genesis")
lgr.Info("generating L2 genesis", "id", chainID.Hex())
thisIntent, err := intent.Chain(chainID)
if err != nil {
return fmt.Errorf("failed to get chain intent: %w", err)
......@@ -31,64 +24,54 @@ func GenerateL2Genesis(ctx context.Context, env *Env, bundle ArtifactsBundle, in
return fmt.Errorf("failed to get chain state: %w", err)
}
initCfg, err := state2.CombineDeployConfig(intent, thisIntent, st, thisChainState)
if !shouldGenerateL2Genesis(thisChainState) {
lgr.Info("L2 genesis generation not needed")
return nil
}
lgr.Info("generating L2 genesis", "id", chainID.Hex())
initCfg, err := state.CombineDeployConfig(intent, thisIntent, st, thisChainState)
if err != nil {
return fmt.Errorf("failed to combine L2 init config: %w", err)
}
var dump *foundry.ForgeAllocs
err = CallScriptBroadcast(
ctx,
CallScriptBroadcastOpts{
L1ChainID: big.NewInt(int64(intent.L1ChainID)),
Logger: lgr,
ArtifactsFS: bundle.L2,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: DiscardBroadcaster,
Handler: func(host *script.Host) error {
err := opcm.L2Genesis(host, &opcm.L2GenesisInput{
host, err := DefaultScriptHost(
broadcaster.NoopBroadcaster(),
env.Logger,
env.Deployer,
bundle.L2,
0,
)
if err != nil {
return fmt.Errorf("failed to create L2 script host: %w", err)
}
if err := opcm.L2Genesis(host, &opcm.L2GenesisInput{
L1Deployments: opcm.L1Deployments{
L1CrossDomainMessengerProxy: thisChainState.L1CrossDomainMessengerProxyAddress,
L1StandardBridgeProxy: thisChainState.L1StandardBridgeProxyAddress,
L1ERC721BridgeProxy: thisChainState.L1ERC721BridgeProxyAddress,
},
L2Config: initCfg.L2InitializationConfig,
})
if err != nil {
}); err != nil {
return fmt.Errorf("failed to call L2Genesis script: %w", err)
}
host.Wipe(env.Deployer)
dump, err = host.StateDump()
dump, err := host.StateDump()
if err != nil {
return fmt.Errorf("failed to dump state: %w", err)
}
return nil
},
},
)
if err != nil {
return fmt.Errorf("failed to call L2Genesis script: %w", err)
thisChainState.Allocs = &state.GzipData[foundry.ForgeAllocs]{
Data: dump,
}
var buf bytes.Buffer
gw := gzip.NewWriter(&buf)
if err := json.NewEncoder(gw).Encode(dump); err != nil {
return fmt.Errorf("failed to encode state dump: %w", err)
}
if err := gw.Close(); err != nil {
return fmt.Errorf("failed to close gzip writer: %w", err)
}
thisChainState.Allocs = buf.Bytes()
startHeader, err := env.L1Client.HeaderByNumber(ctx, nil)
if err != nil {
return fmt.Errorf("failed to get start block: %w", err)
}
thisChainState.StartBlock = startHeader
return nil
}
func shouldGenerateL2Genesis(thisChainState *state.ChainState) bool {
return thisChainState.Allocs == nil
}
......@@ -5,71 +5,35 @@ import (
"encoding/hex"
"errors"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
state2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func DeployOPChain(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state2.Intent, st *state2.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "deploy-opchain")
func DeployOPChainLiveStrategy(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state.Intent, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "deploy-opchain", "strategy", "live")
if !shouldDeployOPChain(st, chainID) {
lgr.Info("opchain deployment not needed")
return nil
}
lgr.Info("deploying OP chain", "id", chainID.Hex())
thisIntent, err := intent.Chain(chainID)
if err != nil {
return fmt.Errorf("failed to get chain intent: %w", err)
}
opcmProxyAddress := st.ImplementationsDeployment.OpcmProxyAddress
input := opcm.DeployOPChainInput{
OpChainProxyAdminOwner: thisIntent.Roles.ProxyAdminOwner,
SystemConfigOwner: thisIntent.Roles.SystemConfigOwner,
Batcher: thisIntent.Roles.Batcher,
UnsafeBlockSigner: thisIntent.Roles.UnsafeBlockSigner,
Proposer: thisIntent.Roles.Proposer,
Challenger: thisIntent.Roles.Challenger,
BasefeeScalar: 1368,
BlobBaseFeeScalar: 801949,
L2ChainId: chainID.Big(),
OpcmProxy: opcmProxyAddress,
SaltMixer: st.Create2Salt.String(), // passing through salt generated at state initialization
GasLimit: 60_000_000,
DisputeGameType: 1, // PERMISSIONED_CANNON Game Type
DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"),
DisputeMaxGameDepth: 73,
DisputeSplitDepth: 30,
DisputeClockExtension: 10800, // 3 hours (input in seconds)
DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds)
}
input := makeDCI(thisIntent, chainID, st)
var dco opcm.DeployOPChainOutput
lgr.Info("deploying using existing OPCM", "address", opcmProxyAddress.Hex())
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: lgr,
ChainID: big.NewInt(int64(intent.L1ChainID)),
Client: env.L1Client,
Signer: env.Signer,
From: env.Deployer,
})
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
lgr.Info("deploying OP chain using existing OPCM", "id", chainID.Hex(), "opcmAddress", st.ImplementationsDeployment.OpcmProxyAddress.Hex())
dco, err = opcm.DeployOPChainRaw(
ctx,
env.L1Client,
bcaster,
env.Broadcaster,
env.Deployer,
bundle.L1,
input,
......@@ -78,25 +42,8 @@ func DeployOPChain(ctx context.Context, env *Env, bundle ArtifactsBundle, intent
return fmt.Errorf("error deploying OP chain: %w", err)
}
st.Chains = append(st.Chains, &state2.ChainState{
ID: chainID,
ProxyAdminAddress: dco.OpChainProxyAdmin,
AddressManagerAddress: dco.AddressManager,
L1ERC721BridgeProxyAddress: dco.L1ERC721BridgeProxy,
SystemConfigProxyAddress: dco.SystemConfigProxy,
OptimismMintableERC20FactoryProxyAddress: dco.OptimismMintableERC20FactoryProxy,
L1StandardBridgeProxyAddress: dco.L1StandardBridgeProxy,
L1CrossDomainMessengerProxyAddress: dco.L1CrossDomainMessengerProxy,
OptimismPortalProxyAddress: dco.OptimismPortalProxy,
DisputeGameFactoryProxyAddress: dco.DisputeGameFactoryProxy,
AnchorStateRegistryProxyAddress: dco.AnchorStateRegistryProxy,
AnchorStateRegistryImplAddress: dco.AnchorStateRegistryImpl,
FaultDisputeGameAddress: dco.FaultDisputeGame,
PermissionedDisputeGameAddress: dco.PermissionedDisputeGame,
DelayedWETHPermissionedGameProxyAddress: dco.DelayedWETHPermissionedGameProxy,
DelayedWETHPermissionlessGameProxyAddress: dco.DelayedWETHPermissionlessGameProxy,
})
st.Chains = append(st.Chains, makeChainState(chainID, dco))
opcmProxyAddress := st.ImplementationsDeployment.OpcmProxyAddress
err = conditionallySetImplementationAddresses(ctx, env.L1Client, intent, st, dco, opcmProxyAddress)
if err != nil {
return fmt.Errorf("failed to set implementation addresses: %w", err)
......@@ -107,7 +54,7 @@ func DeployOPChain(ctx context.Context, env *Env, bundle ArtifactsBundle, intent
// Only try to set the implementation addresses if we reused existing implementations from a release tag.
// The reason why these addresses could be empty is because only DeployOPChain.s.sol is invoked as part of the pipeline.
func conditionallySetImplementationAddresses(ctx context.Context, client *ethclient.Client, intent *state2.Intent, st *state.State, dco opcm.DeployOPChainOutput, opcmProxyAddress common.Address) error {
func conditionallySetImplementationAddresses(ctx context.Context, client *ethclient.Client, intent *state.Intent, st *state.State, dco opcm.DeployOPChainOutput, opcmProxyAddress common.Address) error {
if !intent.L1ContractsLocator.IsTag() {
return nil
}
......@@ -166,8 +113,6 @@ func conditionallySetImplementationAddresses(ctx context.Context, client *ethcli
return fmt.Errorf("failed to set implementation addresses: %w", lastTaskErr)
}
fmt.Printf("st.ImplementationsDeployment: %+v\n", st.ImplementationsDeployment)
return nil
}
......@@ -194,6 +139,82 @@ func setPreimageOracleAddress(ctx context.Context, client *ethclient.Client, err
errCh <- err
}
func DeployOPChainGenesisStrategy(env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "deploy-opchain", "strategy", "genesis")
if !shouldDeployOPChain(st, chainID) {
lgr.Info("opchain deployment not needed")
return nil
}
thisIntent, err := intent.Chain(chainID)
if err != nil {
return fmt.Errorf("failed to get chain intent: %w", err)
}
input := makeDCI(thisIntent, chainID, st)
env.L1ScriptHost.ImportState(st.L1StateDump.Data)
var dco opcm.DeployOPChainOutput
lgr.Info("deploying OP chain using local allocs", "id", chainID.Hex())
dco, err = opcm.DeployOPChain(
env.L1ScriptHost,
input,
)
if err != nil {
return fmt.Errorf("error deploying OP chain: %w", err)
}
st.Chains = append(st.Chains, makeChainState(chainID, dco))
return nil
}
func makeDCI(thisIntent *state.ChainIntent, chainID common.Hash, st *state.State) opcm.DeployOPChainInput {
return opcm.DeployOPChainInput{
OpChainProxyAdminOwner: thisIntent.Roles.ProxyAdminOwner,
SystemConfigOwner: thisIntent.Roles.SystemConfigOwner,
Batcher: thisIntent.Roles.Batcher,
UnsafeBlockSigner: thisIntent.Roles.UnsafeBlockSigner,
Proposer: thisIntent.Roles.Proposer,
Challenger: thisIntent.Roles.Challenger,
BasefeeScalar: 1368,
BlobBaseFeeScalar: 801949,
L2ChainId: chainID.Big(),
OpcmProxy: st.ImplementationsDeployment.OpcmProxyAddress,
SaltMixer: st.Create2Salt.String(), // passing through salt generated at state initialization
GasLimit: 60_000_000,
DisputeGameType: 1, // PERMISSIONED_CANNON Game Type
DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"),
DisputeMaxGameDepth: 73,
DisputeSplitDepth: 30,
DisputeClockExtension: 10800, // 3 hours (input in seconds)
DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds)
}
}
func makeChainState(chainID common.Hash, dco opcm.DeployOPChainOutput) *state.ChainState {
return &state.ChainState{
ID: chainID,
ProxyAdminAddress: dco.OpChainProxyAdmin,
AddressManagerAddress: dco.AddressManager,
L1ERC721BridgeProxyAddress: dco.L1ERC721BridgeProxy,
SystemConfigProxyAddress: dco.SystemConfigProxy,
OptimismMintableERC20FactoryProxyAddress: dco.OptimismMintableERC20FactoryProxy,
L1StandardBridgeProxyAddress: dco.L1StandardBridgeProxy,
L1CrossDomainMessengerProxyAddress: dco.L1CrossDomainMessengerProxy,
OptimismPortalProxyAddress: dco.OptimismPortalProxy,
DisputeGameFactoryProxyAddress: dco.DisputeGameFactoryProxy,
AnchorStateRegistryProxyAddress: dco.AnchorStateRegistryProxy,
AnchorStateRegistryImplAddress: dco.AnchorStateRegistryImpl,
FaultDisputeGameAddress: dco.FaultDisputeGame,
PermissionedDisputeGameAddress: dco.PermissionedDisputeGame,
DelayedWETHPermissionedGameProxyAddress: dco.DelayedWETHPermissionedGameProxy,
DelayedWETHPermissionlessGameProxyAddress: dco.DelayedWETHPermissionlessGameProxy,
}
}
func setRDPImplementationAddress(ctx context.Context, client *ethclient.Client, errCh chan error, addressManager common.Address, implAddress *common.Address, getNameArg string) {
if *implAddress != (common.Address{}) {
errCh <- nil
......
package pipeline
import (
"context"
"fmt"
"time"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func SetStartBlockLiveStrategy(ctx context.Context, env *Env, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "set-start-block", "strategy", "live")
lgr.Info("setting start block", "id", chainID.Hex())
thisChainState, err := st.Chain(chainID)
if err != nil {
return fmt.Errorf("failed to get chain state: %w", err)
}
startHeader, err := env.L1Client.HeaderByNumber(ctx, nil)
if err != nil {
return fmt.Errorf("failed to get start block: %w", err)
}
thisChainState.StartBlock = startHeader
return nil
}
func SetStartBlockGenesisStrategy(env *Env, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "set-start-block", "strategy", "genesis")
lgr.Info("setting start block", "id", chainID.Hex())
thisChainState, err := st.Chain(chainID)
if err != nil {
return fmt.Errorf("failed to get chain state: %w", err)
}
deployConfig := &genesis.DeployConfig{
DevL1DeployConfig: genesis.DevL1DeployConfig{
L1BlockTime: 12,
L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()),
},
L2InitializationConfig: genesis.L2InitializationConfig{
L2CoreDeployConfig: genesis.L2CoreDeployConfig{
L1ChainID: 900,
},
DevDeployConfig: genesis.DevDeployConfig{
FundDevAccounts: true,
},
},
}
devGenesis, err := genesis.BuildL1DeveloperGenesis(deployConfig, st.L1StateDump.Data, &genesis.L1Deployments{})
if err != nil {
return fmt.Errorf("failed to build L1 developer genesis: %w", err)
}
thisChainState.StartBlock = devGenesis.ToBlock().Header()
return nil
}
package pipeline
import (
"context"
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm"
state2 "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state"
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-node/rollup"
)
func DeploySuperchain(ctx context.Context, env *Env, bundle ArtifactsBundle, intent *state2.Intent, st *state2.State) error {
func DeploySuperchain(env *Env, intent *state.Intent, st *state.State) error {
lgr := env.Logger.New("stage", "deploy-superchain")
if !shouldDeploySuperchain(intent, st) {
......@@ -24,22 +19,8 @@ func DeploySuperchain(ctx context.Context, env *Env, bundle ArtifactsBundle, int
lgr.Info("deploying superchain")
var dump *foundry.ForgeAllocs
var dso opcm.DeploySuperchainOutput
var err error
err = CallScriptBroadcast(
ctx,
CallScriptBroadcastOpts{
L1ChainID: big.NewInt(int64(intent.L1ChainID)),
Logger: lgr,
ArtifactsFS: bundle.L1,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: KeyedBroadcaster,
Handler: func(host *script.Host) error {
dso, err = opcm.DeploySuperchain(
host,
dso, err := opcm.DeploySuperchain(
env.L1ScriptHost,
opcm.DeploySuperchainInput{
SuperchainProxyAdminOwner: intent.SuperchainRoles.ProxyAdminOwner,
ProtocolVersionsOwner: intent.SuperchainRoles.ProtocolVersionsOwner,
......@@ -52,30 +33,18 @@ func DeploySuperchain(ctx context.Context, env *Env, bundle ArtifactsBundle, int
if err != nil {
return fmt.Errorf("failed to deploy superchain: %w", err)
}
dump, err = host.StateDump()
if err != nil {
return fmt.Errorf("error dumping state: %w", err)
}
return nil
},
},
)
if err != nil {
return fmt.Errorf("error deploying superchain: %w", err)
}
st.SuperchainDeployment = &state2.SuperchainDeployment{
st.SuperchainDeployment = &state.SuperchainDeployment{
ProxyAdminAddress: dso.SuperchainProxyAdmin,
SuperchainConfigProxyAddress: dso.SuperchainConfigProxy,
SuperchainConfigImplAddress: dso.SuperchainConfigImpl,
ProtocolVersionsProxyAddress: dso.ProtocolVersionsProxy,
ProtocolVersionsImplAddress: dso.ProtocolVersionsImpl,
StateDump: dump,
}
return nil
}
func shouldDeploySuperchain(intent *state2.Intent, st *state2.State) bool {
func shouldDeploySuperchain(intent *state.Intent, st *state.State) bool {
return st.SuperchainDeployment == nil
}
package state
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
)
type GzipData[T any] struct {
Data *T
}
func (g *GzipData[T]) MarshalJSON() ([]byte, error) {
jsonData, err := json.Marshal(g.Data)
if err != nil {
return nil, fmt.Errorf("failed to encode json: %w", err)
}
var buf bytes.Buffer
gw := gzip.NewWriter(&buf)
if _, err := gw.Write(jsonData); err != nil {
return nil, fmt.Errorf("failed to write gzip data: %w", err)
}
if err := gw.Close(); err != nil {
return nil, fmt.Errorf("failed to close gzip writer: %w", err)
}
return json.Marshal(Base64Bytes(buf.Bytes()))
}
func (g *GzipData[T]) UnmarshalJSON(b []byte) error {
var b64B Base64Bytes
if err := json.Unmarshal(b, &b64B); err != nil {
return fmt.Errorf("failed to decode gzip data: %w", err)
}
gr, err := gzip.NewReader(bytes.NewReader(b64B))
if err != nil {
return fmt.Errorf("failed to create gzip reader: %w", err)
}
defer gr.Close()
var data T
if err := json.NewDecoder(gr).Decode(&data); err != nil {
return fmt.Errorf("failed to decode gzip data: %w", err)
}
g.Data = &data
return nil
}
package state
import (
"encoding/json"
"fmt"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)
func TestGzipData_Marshaling(t *testing.T) {
type ts struct {
Field *GzipData[foundry.ForgeAllocs]
}
tests := []struct {
name string
in ts
out string
}{
{
name: "empty",
in: ts{},
out: "null",
},
{
name: "contains some data",
in: ts{
Field: &GzipData[foundry.ForgeAllocs]{
Data: &foundry.ForgeAllocs{
Accounts: map[common.Address]types.Account{
common.HexToAddress("0x1"): {
Balance: big.NewInt(1),
},
},
},
},
},
out: `"H4sIAAAAAAAA/6pWMqgwIA4YKllVKyUl5iTmJacqWSkZVBgq1dYCAgAA//9hulF0QAAAAA=="`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := json.Marshal(tt.in)
require.NoError(t, err)
require.Equal(t, fmt.Sprintf(`{"Field":%s}`, tt.out), string(data))
var unmarshalled ts
err = json.Unmarshal(data, &unmarshalled)
require.NoError(t, err)
require.EqualValues(t, tt.in, unmarshalled)
})
}
}
......@@ -11,12 +11,30 @@ import (
"github.com/ethereum/go-ethereum/common"
)
type DeploymentStrategy string
const (
DeploymentStrategyLive DeploymentStrategy = "live"
DeploymentStrategyGenesis DeploymentStrategy = "genesis"
)
func (d DeploymentStrategy) Check() error {
switch d {
case DeploymentStrategyLive, DeploymentStrategyGenesis:
return nil
default:
return fmt.Errorf("deployment strategy must be 'live' or 'genesis'")
}
}
var emptyAddress common.Address
type Intent struct {
DeploymentStrategy DeploymentStrategy `json:"deploymentStrategy" toml:"deploymentStrategy"`
L1ChainID uint64 `json:"l1ChainID" toml:"l1ChainID"`
SuperchainRoles SuperchainRoles `json:"superchainRoles" toml:"-"`
SuperchainRoles *SuperchainRoles `json:"superchainRoles" toml:"superchainRoles,omitempty"`
FundDevAccounts bool `json:"fundDevAccounts" toml:"fundDevAccounts"`
......@@ -34,6 +52,10 @@ func (c *Intent) L1ChainIDBig() *big.Int {
}
func (c *Intent) Check() error {
if c.DeploymentStrategy != DeploymentStrategyLive && c.DeploymentStrategy != DeploymentStrategyGenesis {
return fmt.Errorf("deploymentStrategy must be 'live' or 'local'")
}
if c.L1ChainID == 0 {
return fmt.Errorf("l1ChainID must be set")
}
......
package state
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/core/types"
......@@ -40,6 +37,9 @@ type State struct {
// Chains contains data about L2 chain deployments.
Chains []*ChainState `json:"opChainDeployments"`
// L1StateDump contains the complete L1 state dump of the deployment.
L1StateDump *GzipData[foundry.ForgeAllocs] `json:"l1StateDump"`
}
func (s *State) WriteToFile(path string) error {
......@@ -61,7 +61,6 @@ type SuperchainDeployment struct {
SuperchainConfigImplAddress common.Address `json:"superchainConfigImplAddress"`
ProtocolVersionsProxyAddress common.Address `json:"protocolVersionsProxyAddress"`
ProtocolVersionsImplAddress common.Address `json:"protocolVersionsImplAddress"`
StateDump *foundry.ForgeAllocs `json:"-"`
}
type ImplementationsDeployment struct {
......@@ -76,7 +75,6 @@ type ImplementationsDeployment struct {
L1StandardBridgeImplAddress common.Address `json:"l1StandardBridgeImplAddress"`
OptimismMintableERC20FactoryImplAddress common.Address `json:"optimismMintableERC20FactoryImplAddress"`
DisputeGameFactoryImplAddress common.Address `json:"disputeGameFactoryImplAddress"`
StateDump *foundry.ForgeAllocs `json:"-"`
}
type ChainState struct {
......@@ -98,22 +96,7 @@ type ChainState struct {
DelayedWETHPermissionedGameProxyAddress common.Address `json:"delayedWETHPermissionedGameProxyAddress"`
DelayedWETHPermissionlessGameProxyAddress common.Address `json:"delayedWETHPermissionlessGameProxyAddress"`
Allocs Base64Bytes `json:"allocs"`
Allocs *GzipData[foundry.ForgeAllocs] `json:"allocs"`
StartBlock *types.Header `json:"startBlock"`
}
func (c *ChainState) UnmarshalAllocs() (*foundry.ForgeAllocs, error) {
gr, err := gzip.NewReader(bytes.NewReader(c.Allocs))
if err != nil {
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
}
defer gr.Close()
var allocs foundry.ForgeAllocs
if err := json.NewDecoder(gr).Decode(&allocs); err != nil {
return nil, fmt.Errorf("failed to decode allocs: %w", err)
}
return &allocs, nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment