Skip to content

Commit

Permalink
Add L2 genesis generation support to op-deployer
Browse files Browse the repository at this point in the history
Adds support for generating L2 genesis files to `op-deployer. The L2 initialization config is generated by merging in overrides as specified in the intent into a default config with sane values. The outputted genesis file is stored in the stage as a GZIP-compressed, base64-encoded string.
  • Loading branch information
mslipper committed Sep 17, 2024
1 parent 994f634 commit cca29ce
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 21 deletions.
5 changes: 5 additions & 0 deletions op-chain-ops/deployer/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ func ApplyPipeline(
func(ctx context.Context, env *pipeline.Env, intent *state.Intent, st *state.State) error {
return pipeline.DeployOPChain(ctx, env, intent, st, chain.ID)
},
}, pipelineStage{
fmt.Sprintf("generate-l2-genesis-%s", chain.ID.Hex()),
func(ctx context.Context, env *pipeline.Env, intent *state.Intent, st *state.State) error {
return pipeline.GenerateL2Genesis(ctx, env, intent, st, chain.ID)
},
})
}

Expand Down
6 changes: 5 additions & 1 deletion op-chain-ops/deployer/integration_test/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func TestEndToEndApply(t *testing.T) {
UseFaultProofs: true,
FundDevAccounts: true,
ContractArtifactsURL: (*state.ArtifactsURL)(artifactsURL),
Chains: []state.ChainIntent{
Chains: []*state.ChainIntent{
{
ID: id.Bytes32(),
Roles: state.ChainRoles{
Expand Down Expand Up @@ -196,5 +196,9 @@ func TestEndToEndApply(t *testing.T) {
require.NotEmpty(t, code, "contracts %s at %s for chain %s has no code", addr.name, addr.addr, chainState.ID)
})
}

t.Run("l2 genesis", func(t *testing.T) {
require.Greater(t, len(chainState.Genesis), 0)
})
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package deployers
package opsm

import (
"fmt"
Expand Down
25 changes: 18 additions & 7 deletions op-chain-ops/deployer/pipeline/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ import (
"github.com/ethereum/go-ethereum/log"
)

type BroadcasterFactory func(opts CallScriptBroadcastOpts) (broadcaster.Broadcaster, error)

func KeyedBroadcaster(opts CallScriptBroadcastOpts) (broadcaster.Broadcaster, error) {
return broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: opts.Logger,
ChainID: opts.L1ChainID,
Client: opts.Client,
Signer: opts.Signer,
From: opts.Deployer,
})
}

func DiscardBroadcaster(opts CallScriptBroadcastOpts) (broadcaster.Broadcaster, error) {
return broadcaster.DiscardBroadcaster(), nil
}

type CallScriptBroadcastOpts struct {
L1ChainID *big.Int
Logger log.Logger
Expand All @@ -22,19 +38,14 @@ type CallScriptBroadcastOpts struct {
Signer opcrypto.SignerFn
Client *ethclient.Client
Handler func(host *script.Host) error
Broadcaster BroadcasterFactory
}

func CallScriptBroadcast(
ctx context.Context,
opts CallScriptBroadcastOpts,
) error {
bcaster, err := broadcaster.NewKeyedBroadcaster(broadcaster.KeyedBroadcasterOpts{
Logger: opts.Logger,
ChainID: opts.L1ChainID,
Client: opts.Client,
Signer: opts.Signer,
From: opts.Deployer,
})
bcaster, err := opts.Broadcaster(opts)
if err != nil {
return fmt.Errorf("failed to create broadcaster: %w", err)
}
Expand Down
1 change: 1 addition & 0 deletions op-chain-ops/deployer/pipeline/implementations.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func DeployImplementations(ctx context.Context, env *Env, intent *state.Intent,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: KeyedBroadcaster,
Handler: func(host *script.Host) error {
host.SetEnvVar("IMPL_SALT", st.Create2Salt.Hex()[2:])
host.ImportState(st.SuperchainDeployment.StateDump)
Expand Down
10 changes: 10 additions & 0 deletions op-chain-ops/deployer/pipeline/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"crypto/rand"
"fmt"

"github.com/ethereum-optimism/optimism/op-chain-ops/script"

"github.com/ethereum/go-ethereum/common"

"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state"
Expand Down Expand Up @@ -63,6 +65,14 @@ func Init(ctx context.Context, env *Env, intent *state.Intent, st *state.State)
return fmt.Errorf("L1 chain ID mismatch: got %d, expected %d", l1ChainID, intent.L1ChainID)
}

deployerCode, err := env.L1Client.CodeAt(ctx, script.DeterministicDeployerAddress, nil)
if err != nil {
return fmt.Errorf("failed to get deployer code: %w", err)
}
if len(deployerCode) == 0 {
return fmt.Errorf("deterministic deployer is not deployed on this chain - please deploy it first")
}

// TODO: validate individual L2s

return nil
Expand Down
102 changes: 102 additions & 0 deletions op-chain-ops/deployer/pipeline/l2genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package pipeline

import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"math/big"
"os"

"github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opsm"
"github.com/ethereum-optimism/optimism/op-chain-ops/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, intent *state.Intent, st *state.State, chainID common.Hash) error {
lgr := env.Logger.New("stage", "generate-l2-genesis")

lgr.Info("generating L2 genesis", "id", chainID.Hex())

var artifactsFS foundry.StatDirFs
var err error
if intent.ContractArtifactsURL.Scheme == "file" {
fs := os.DirFS(intent.ContractArtifactsURL.Path)
artifactsFS = fs.(foundry.StatDirFs)
} else {
return fmt.Errorf("only file:// artifacts URLs are supported")
}

thisIntent, err := intent.Chain(chainID)
if err != nil {
return fmt.Errorf("failed to get chain intent: %w", err)
}

thisChainState, err := st.Chain(chainID)
if err != nil {
return fmt.Errorf("failed to get chain state: %w", err)
}

initCfg, err := state.CombineL2InitConfig(intent, thisIntent)
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: artifactsFS,
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: DiscardBroadcaster,
Handler: func(host *script.Host) error {
err := opsm.L2Genesis(host, &opsm.L2GenesisInput{
L1Deployments: opsm.L1Deployments{
L1CrossDomainMessengerProxy: thisChainState.L1CrossDomainMessengerProxyAddress,
L1StandardBridgeProxy: thisChainState.L1StandardBridgeProxyAddress,
L1ERC721BridgeProxy: thisChainState.L1ERC721BridgeProxyAddress,
},
L2Config: initCfg,
})
if err != nil {
return fmt.Errorf("failed to call L2Genesis script: %w", err)
}

host.Wipe(env.Deployer)

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)
}

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.Genesis = buf.Bytes()

if err := env.WriteState(st); err != nil {
return fmt.Errorf("failed to write state: %w", err)
}

return nil
}
3 changes: 2 additions & 1 deletion op-chain-ops/deployer/pipeline/opchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func DeployOPChain(ctx context.Context, env *Env, intent *state.Intent, st *stat
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: KeyedBroadcaster,
Handler: func(host *script.Host) error {
host.ImportState(st.ImplementationsDeployment.StateDump)
dco, err = opsm.DeployOPChain(
Expand All @@ -72,7 +73,7 @@ func DeployOPChain(ctx context.Context, env *Env, intent *state.Intent, st *stat
return fmt.Errorf("error deploying OP chain: %w", err)
}

st.Chains = append(st.Chains, state.ChainState{
st.Chains = append(st.Chains, &state.ChainState{
ID: chainID,

ProxyAdminAddress: dco.OpChainProxyAdmin,
Expand Down
1 change: 1 addition & 0 deletions op-chain-ops/deployer/pipeline/superchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func DeploySuperchain(ctx context.Context, env *Env, intent *state.Intent, st *s
Deployer: env.Deployer,
Signer: env.Signer,
Client: env.L1Client,
Broadcaster: KeyedBroadcaster,
Handler: func(host *script.Host) error {
dso, err = opsm.DeploySuperchain(
host,
Expand Down
30 changes: 30 additions & 0 deletions op-chain-ops/deployer/state/base64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package state

import (
"encoding/base64"
"encoding/json"
)

type Base64Bytes []byte

func (b Base64Bytes) MarshalJSON() ([]byte, error) {
if len(b) == 0 {
return []byte(`null`), nil
}

encoded := base64.StdEncoding.EncodeToString(b)
return []byte(`"` + encoded + `"`), nil
}

func (b *Base64Bytes) UnmarshalJSON(data []byte) error {
var dataStr string
if err := json.Unmarshal(data, &dataStr); err != nil {
return err
}
decoded, err := base64.StdEncoding.DecodeString(dataStr)
if err != nil {
return err
}
*b = decoded
return nil
}
38 changes: 38 additions & 0 deletions op-chain-ops/deployer/state/base64_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package state

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestBase64BytesMarshaling(t *testing.T) {
tests := []struct {
name string
in Base64Bytes
out string
}{
{
name: "empty",
in: Base64Bytes{},
out: "null",
},
{
name: "non-empty",
in: Base64Bytes{0x01, 0x02, 0x03},
out: `"AQID"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := tt.in.MarshalJSON()
require.NoError(t, err)
require.Equal(t, tt.out, string(data))

var b Base64Bytes
err = b.UnmarshalJSON(data)
require.NoError(t, err)
require.Equal(t, tt.in, b)
})
}
}
17 changes: 10 additions & 7 deletions op-chain-ops/deployer/state/intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ type Intent struct {

ContractArtifactsURL *ArtifactsURL `json:"contractArtifactsURL" toml:"contractArtifactsURL"`

Chains []ChainIntent `json:"chains" toml:"chains"`
Chains []*ChainIntent `json:"chains" toml:"chains"`

GlobalInitOverrides map[string]any `json:"globalInitOverrides" toml:"globalInitOverrides"`
}

func (c Intent) L1ChainIDBig() *big.Int {
func (c *Intent) L1ChainIDBig() *big.Int {
return big.NewInt(int64(c.L1ChainID))
}
func (c Intent) Check() error {

func (c *Intent) Check() error {
if c.L1ChainID == 0 {
return fmt.Errorf("l1ChainID must be set")
}
Expand Down Expand Up @@ -62,17 +65,17 @@ func (c Intent) Check() error {
return nil
}

func (c Intent) Chain(id common.Hash) (ChainIntent, error) {
func (c *Intent) Chain(id common.Hash) (*ChainIntent, error) {
for i := range c.Chains {
if c.Chains[i].ID == id {
return c.Chains[i], nil
}
}

return ChainIntent{}, fmt.Errorf("chain %d not found", id)
return nil, fmt.Errorf("chain %d not found", id)
}

func (c Intent) WriteToFile(path string) error {
func (c *Intent) WriteToFile(path string) error {
return jsonutil.WriteTOML(c, ioutil.ToAtomicFile(path, 0o755))
}

Expand All @@ -89,7 +92,7 @@ type ChainIntent struct {

Roles ChainRoles `json:"roles" toml:"roles"`

Overrides map[string]any `json:"overrides" toml:"overrides"`
InitOverrides map[string]any `json:"initOverrides" toml:"initOverrides"`
}

type ChainRoles struct {
Expand Down
Loading

0 comments on commit cca29ce

Please sign in to comment.