From 45af6ff5dc184fc9390c0c747d0fe5dcbcb6d410 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Fri, 20 Sep 2024 22:25:22 -0600 Subject: [PATCH] Add builds for op-deployer, bugfixes, artifacts downloads (#12033) * Add builds for op-deployer, bugfixes, artifacts downloads Adds Docker builds for op-deployer, makes some bugfixes, and adds support for downloading remote artifacts. * Apply code scanning fix for arbitrary file access during archive extraction ("zip slip") Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix compile error * lint * fix test * Update from code review, add docker build * fix versioning * remove errant dispatch * update target --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .circleci/config.yml | 4 + docker-bake.hcl | 16 ++ op-chain-ops/Makefile | 25 +++- op-chain-ops/cmd/op-deployer/main.go | 12 ++ op-chain-ops/deployer/apply.go | 35 +++-- op-chain-ops/deployer/flags.go | 15 +- op-chain-ops/deployer/init.go | 81 ++++++---- op-chain-ops/deployer/inspect/flags.go | 40 +---- op-chain-ops/deployer/inspect/genesis.go | 89 +++++++---- op-chain-ops/deployer/inspect/rollup.go | 58 +------- .../deployer/integration_test/apply_test.go | 2 +- op-chain-ops/deployer/pipeline/downloader.go | 138 ++++++++++++++++++ .../deployer/pipeline/downloader_test.go | 42 ++++++ op-chain-ops/deployer/pipeline/env.go | 4 +- .../deployer/pipeline/implementations.go | 14 +- op-chain-ops/deployer/pipeline/init.go | 6 +- op-chain-ops/deployer/pipeline/l2genesis.go | 14 +- op-chain-ops/deployer/pipeline/opchain.go | 12 +- op-chain-ops/deployer/pipeline/superchain.go | 13 +- .../pipeline/testdata/artifacts.tar.gz | Bin 0 -> 12116 bytes op-chain-ops/deployer/state/intent.go | 4 - op-chain-ops/deployer/state/state.go | 8 +- op-chain-ops/deployer/version/version.go | 6 + op-e2e/e2eutils/challenger/helper.go | 4 +- op-e2e/e2eutils/secrets.go | 13 -- op-e2e/e2eutils/setuputils/utils.go | 5 +- op-service/crypto/secrets.go | 19 +++ op-service/util.go | 31 ++++ op-service/util_test.go | 56 +++++++ ops/docker/deployment-utils/Dockerfile | 35 +++++ ops/docker/deployment-utils/README.md | 16 ++ ops/docker/op-stack-go/Dockerfile | 9 ++ 32 files changed, 588 insertions(+), 238 deletions(-) create mode 100644 op-chain-ops/deployer/pipeline/downloader.go create mode 100644 op-chain-ops/deployer/pipeline/downloader_test.go create mode 100644 op-chain-ops/deployer/pipeline/testdata/artifacts.tar.gz create mode 100644 op-chain-ops/deployer/version/version.go create mode 100644 op-service/crypto/secrets.go create mode 100644 ops/docker/deployment-utils/Dockerfile create mode 100644 ops/docker/deployment-utils/README.md diff --git a/.circleci/config.yml b/.circleci/config.yml index 43c36cb981245..f4975442dd878 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1683,6 +1683,7 @@ workflows: - op-conductor - da-server - op-supervisor + - op-deployer - cannon-prestate: requires: - go-mod-download @@ -1742,6 +1743,7 @@ workflows: - da-server - op-ufm - op-supervisor + - op-deployer name: <>-docker-release docker_tags: <> platforms: "linux/amd64,linux/arm64" @@ -1770,6 +1772,7 @@ workflows: - da-server - op-ufm - op-supervisor + - op-deployer name: <>-cross-platform requires: - op-node-docker-release @@ -1781,6 +1784,7 @@ workflows: - da-server-docker-release - op-ufm-docker-release - op-supervisor-docker-release + - op-deployer-docker-release # Standard (xlarge) AMD-only docker images go here - docker-build: matrix: diff --git a/docker-bake.hcl b/docker-bake.hcl index b587e5bde6ea5..788b9a651745f 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -69,6 +69,9 @@ variable "OP_CONDUCTOR_VERSION" { default = "${GIT_VERSION}" } +variable "OP_DEPLOYER_VERSION" { + default = "${GIT_VERSION}" +} target "op-node" { dockerfile = "ops/docker/op-stack-go/Dockerfile" @@ -236,3 +239,16 @@ target "contracts-bedrock" { platforms = ["linux/amd64"] tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/contracts-bedrock:${tag}"] } + +target "op-deployer" { + dockerfile = "ops/docker/op-stack-go/Dockerfile" + context = "." + args = { + GIT_COMMIT = "${GIT_COMMIT}" + GIT_DATE = "${GIT_DATE}" + OP_DEPLOYER_VERSION = "${OP_DEPLOYER_VERSION}" + } + target = "op-deployer-target" + platforms = split(",", PLATFORMS) + tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/op-deployer:${tag}"] +} diff --git a/op-chain-ops/Makefile b/op-chain-ops/Makefile index a18f769615999..630167f7b60ef 100644 --- a/op-chain-ops/Makefile +++ b/op-chain-ops/Makefile @@ -1,3 +1,26 @@ +GITCOMMIT ?= $(shell git rev-parse HEAD) +GITDATE ?= $(shell git show -s --format='%ct') + +# Find the github tag that points to this commit. If none are found, set the version string to "untagged" +# Prioritizes release tag, if one exists, over tags suffixed with "-rc" +VERSION ?= $(shell tags=$$(git tag --points-at $(GITCOMMIT) | grep '^op-deployer/' | sed 's/op-deployer\///' | sort -V); \ + preferred_tag=$$(echo "$$tags" | grep -v -- '-rc' | tail -n 1); \ + if [ -z "$$preferred_tag" ]; then \ + if [ -z "$$tags" ]; then \ + echo "untagged"; \ + else \ + echo "$$tags" | tail -n 1; \ + fi \ + else \ + echo $$preferred_tag; \ + fi) + +LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) +LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) +LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Version=$(VERSION) +LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Meta=$(VERSION_META) +LDFLAGS := -ldflags "$(LDFLAGSSTRING)" + # Use the old Apple linker to workaround broken xcode - https://github.com/golang/go/issues/65169 ifeq ($(shell uname),Darwin) FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic @@ -13,7 +36,7 @@ test: go test ./... op-deployer: - go build -o ./bin/op-deployer ./cmd/op-deployer/main.go + GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=0 go build -v $(LDFLAGS) -o ./bin/op-deployer ./cmd/op-deployer/main.go fuzz: go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzEncodeDecodeWithdrawal ./crossdomain diff --git a/op-chain-ops/cmd/op-deployer/main.go b/op-chain-ops/cmd/op-deployer/main.go index cb5dae586acfa..023d8adca39d1 100644 --- a/op-chain-ops/cmd/op-deployer/main.go +++ b/op-chain-ops/cmd/op-deployer/main.go @@ -4,6 +4,9 @@ import ( "fmt" "os" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version" + opservice "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/inspect" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer" @@ -11,8 +14,17 @@ import ( "github.com/urfave/cli/v2" ) +var ( + GitCommit = "" + GitDate = "" +) + +// VersionWithMeta holds the textual version string including the metadata. +var VersionWithMeta = opservice.FormatVersion(version.Version, GitCommit, GitDate, version.Meta) + func main() { app := cli.NewApp() + app.Version = VersionWithMeta app.Name = "op-deployer" app.Usage = "Tool to configure and deploy OP Chains." app.Flags = cliapp.ProtectFlags(deployer.GlobalFlags) diff --git a/op-chain-ops/deployer/apply.go b/op-chain-ops/deployer/apply.go index 27de5af4b2ff2..ca34d4266df79 100644 --- a/op-chain-ops/deployer/apply.go +++ b/op-chain-ops/deployer/apply.go @@ -6,6 +6,8 @@ import ( "fmt" "strings" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" @@ -128,7 +130,7 @@ func Apply(ctx context.Context, cfg ApplyConfig) error { type pipelineStage struct { name string - stage pipeline.Stage + apply pipeline.Stage } func ApplyPipeline( @@ -137,6 +139,20 @@ func ApplyPipeline( intent *state.Intent, st *state.State, ) error { + progressor := func(curr, total int64) { + env.Logger.Info("artifacts download progress", "current", curr, "total", total) + } + + artifactsFS, cleanup, err := pipeline.DownloadArtifacts(ctx, intent.ContractArtifactsURL, progressor) + if err != nil { + return fmt.Errorf("failed to download artifacts: %w", err) + } + defer func() { + if err := cleanup(); err != nil { + env.Logger.Warn("failed to clean up artifacts", "err", err) + } + }() + pline := []pipelineStage{ {"init", pipeline.Init}, {"deploy-superchain", pipeline.DeploySuperchain}, @@ -144,22 +160,23 @@ func ApplyPipeline( } for _, chain := range intent.Chains { + chainID := chain.ID pline = append(pline, pipelineStage{ - fmt.Sprintf("deploy-opchain-%s", chain.ID.Hex()), - func(ctx context.Context, env *pipeline.Env, intent *state.Intent, st *state.State) error { - return pipeline.DeployOPChain(ctx, env, intent, st, chain.ID) + fmt.Sprintf("deploy-opchain-%s", chainID.Hex()), + func(ctx context.Context, env *pipeline.Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { + return pipeline.DeployOPChain(ctx, env, artifactsFS, intent, st, chainID) }, }, 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) + fmt.Sprintf("generate-l2-genesis-%s", chainID.Hex()), + func(ctx context.Context, env *pipeline.Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { + return pipeline.GenerateL2Genesis(ctx, env, artifactsFS, intent, st, chainID) }, }) } for _, stage := range pline { - if err := stage.stage(ctx, env, intent, st); err != nil { - return fmt.Errorf("error in pipeline stage: %w", err) + if err := stage.apply(ctx, env, artifactsFS, intent, st); err != nil { + return fmt.Errorf("error in pipeline stage apply: %w", err) } } diff --git a/op-chain-ops/deployer/flags.go b/op-chain-ops/deployer/flags.go index d2bfd0eff5b40..e0ab864bdada2 100644 --- a/op-chain-ops/deployer/flags.go +++ b/op-chain-ops/deployer/flags.go @@ -12,9 +12,9 @@ const ( EnvVarPrefix = "DEPLOYER" L1RPCURLFlagName = "l1-rpc-url" L1ChainIDFlagName = "l1-chain-id" + L2ChainIDsFlagName = "l2-chain-ids" WorkdirFlagName = "workdir" OutdirFlagName = "outdir" - DevFlagName = "dev" PrivateKeyFlagName = "private-key" ) @@ -33,6 +33,11 @@ var ( EnvVars: prefixEnvVar("L1_CHAIN_ID"), Value: 900, } + L2ChainIDsFlag = &cli.StringFlag{ + Name: L2ChainIDsFlagName, + Usage: "Comma-separated list of L2 chain IDs to deploy.", + EnvVars: prefixEnvVar("L2_CHAIN_IDS"), + } WorkdirFlag = &cli.StringFlag{ Name: WorkdirFlagName, Usage: "Directory storing intent and stage. Defaults to the current directory.", @@ -42,12 +47,6 @@ var ( OutdirFlagName, }, } - DevFlag = &cli.BoolFlag{ - Name: DevFlagName, - Usage: "Use development mode. This will use the development mnemonic to own the chain" + - " and fund dev accounts.", - EnvVars: prefixEnvVar("DEV"), - } PrivateKeyFlag = &cli.StringFlag{ Name: PrivateKeyFlagName, @@ -60,8 +59,8 @@ var GlobalFlags = append([]cli.Flag{}, oplog.CLIFlags(EnvVarPrefix)...) var InitFlags = []cli.Flag{ L1ChainIDFlag, + L2ChainIDsFlag, WorkdirFlag, - DevFlag, } var ApplyFlags = []cli.Flag{ diff --git a/op-chain-ops/deployer/init.go b/op-chain-ops/deployer/init.go index f3f181c2186fe..0cc288b40ffba 100644 --- a/op-chain-ops/deployer/init.go +++ b/op-chain-ops/deployer/init.go @@ -3,6 +3,9 @@ package deployer import ( "fmt" "path" + "strings" + + op_service "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" @@ -10,12 +13,10 @@ import ( "github.com/urfave/cli/v2" ) -const devMnemonic = "test test test test test test test test test test test junk" - type InitConfig struct { - L1ChainID uint64 - Outdir string - Dev bool + L1ChainID uint64 + Outdir string + L2ChainIDs []common.Hash } func (c *InitConfig) Check() error { @@ -27,6 +28,10 @@ func (c *InitConfig) Check() error { return fmt.Errorf("outdir must be specified") } + if len(c.L2ChainIDs) == 0 { + return fmt.Errorf("must specify at least one L2 chain ID") + } + return nil } @@ -34,12 +39,22 @@ func InitCLI() func(ctx *cli.Context) error { return func(ctx *cli.Context) error { l1ChainID := ctx.Uint64(L1ChainIDFlagName) outdir := ctx.String(OutdirFlagName) - dev := ctx.Bool(DevFlagName) + + l2ChainIDsRaw := ctx.String(L2ChainIDsFlagName) + l2ChainIDsStr := strings.Split(l2ChainIDsRaw, ",") + l2ChainIDs := make([]common.Hash, 0, len(l2ChainIDsStr)) + for _, idStr := range l2ChainIDsStr { + id, err := op_service.Parse256BitChainID(idStr) + if err != nil { + return fmt.Errorf("invalid chain ID: %w", err) + } + l2ChainIDs = append(l2ChainIDs, id) + } return Init(InitConfig{ - L1ChainID: l1ChainID, - Outdir: outdir, - Dev: dev, + L1ChainID: l1ChainID, + Outdir: outdir, + L2ChainIDs: l2ChainIDs, }) } } @@ -52,30 +67,44 @@ func Init(cfg InitConfig) error { intent := &state.Intent{ L1ChainID: cfg.L1ChainID, UseFaultProofs: true, - FundDevAccounts: cfg.Dev, + FundDevAccounts: true, } l1ChainIDBig := intent.L1ChainIDBig() - if cfg.Dev { - dk, err := devkeys.NewMnemonicDevKeys(devMnemonic) + dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + if err != nil { + return fmt.Errorf("failed to create dev keys: %w", err) + } + + addrFor := func(key devkeys.Key) common.Address { + // The error below should never happen, so panic if it does. + addr, err := dk.Address(key) if err != nil { - return fmt.Errorf("failed to create dev keys: %w", err) + panic(err) } + return addr + } + intent.SuperchainRoles = state.SuperchainRoles{ + ProxyAdminOwner: addrFor(devkeys.L1ProxyAdminOwnerRole.Key(l1ChainIDBig)), + ProtocolVersionsOwner: addrFor(devkeys.SuperchainProtocolVersionsOwner.Key(l1ChainIDBig)), + Guardian: addrFor(devkeys.SuperchainConfigGuardianKey.Key(l1ChainIDBig)), + } - addrFor := func(key devkeys.Key) common.Address { - // The error below should never happen, so panic if it does. - addr, err := dk.Address(key) - if err != nil { - panic(err) - } - return addr - } - intent.SuperchainRoles = state.SuperchainRoles{ - ProxyAdminOwner: addrFor(devkeys.L1ProxyAdminOwnerRole.Key(l1ChainIDBig)), - ProtocolVersionsOwner: addrFor(devkeys.SuperchainDeployerKey.Key(l1ChainIDBig)), - Guardian: addrFor(devkeys.SuperchainConfigGuardianKey.Key(l1ChainIDBig)), - } + for _, l2ChainID := range cfg.L2ChainIDs { + l2ChainIDBig := l2ChainID.Big() + intent.Chains = append(intent.Chains, &state.ChainIntent{ + ID: l2ChainID, + Roles: state.ChainRoles{ + ProxyAdminOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l2ChainIDBig)), + SystemConfigOwner: addrFor(devkeys.SystemConfigOwner.Key(l2ChainIDBig)), + GovernanceTokenOwner: addrFor(devkeys.L2ProxyAdminOwnerRole.Key(l2ChainIDBig)), + UnsafeBlockSigner: addrFor(devkeys.SequencerP2PRole.Key(l2ChainIDBig)), + Batcher: addrFor(devkeys.BatcherRole.Key(l2ChainIDBig)), + Proposer: addrFor(devkeys.ProposerRole.Key(l2ChainIDBig)), + Challenger: addrFor(devkeys.ChallengerRole.Key(l2ChainIDBig)), + }, + }) } st := &state.State{ diff --git a/op-chain-ops/deployer/inspect/flags.go b/op-chain-ops/deployer/inspect/flags.go index ad3ea679b8249..601e28ba85a5e 100644 --- a/op-chain-ops/deployer/inspect/flags.go +++ b/op-chain-ops/deployer/inspect/flags.go @@ -3,9 +3,9 @@ package inspect import ( "fmt" + op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" "github.com/ethereum/go-ethereum/common" "github.com/urfave/cli/v2" ) @@ -70,7 +70,7 @@ func readConfig(cliCtx *cli.Context) (cliConfig, error) { return cfg, fmt.Errorf("chain-id argument is required") } - chainID, err := chainIDStrToHash(chainIDStr) + chainID, err := op_service.Parse256BitChainID(chainIDStr) if err != nil { return cfg, fmt.Errorf("failed to parse chain ID: %w", err) } @@ -81,37 +81,3 @@ func readConfig(cliCtx *cli.Context) (cliConfig, error) { ChainID: chainID, }, nil } - -type inspectState struct { - GlobalState *state.State - ChainIntent *state.ChainIntent - ChainState *state.ChainState -} - -func bootstrapState(cfg cliConfig) (*inspectState, error) { - env := &pipeline.Env{Workdir: cfg.Workdir} - globalState, err := env.ReadState() - if err != nil { - return nil, fmt.Errorf("failed to read intent: %w", err) - } - - if globalState.AppliedIntent == nil { - return nil, fmt.Errorf("chain state is not applied - run op-deployer apply") - } - - chainIntent, err := globalState.AppliedIntent.Chain(cfg.ChainID) - if err != nil { - return nil, fmt.Errorf("failed to get applied chain intent: %w", err) - } - - chainState, err := globalState.Chain(cfg.ChainID) - if err != nil { - return nil, fmt.Errorf("failed to get chain ID %s: %w", cfg.ChainID.String(), err) - } - - return &inspectState{ - GlobalState: globalState, - ChainIntent: chainIntent, - ChainState: chainState, - }, nil -} diff --git a/op-chain-ops/deployer/inspect/genesis.go b/op-chain-ops/deployer/inspect/genesis.go index 7e2f13b6d6729..4c2fbad010929 100644 --- a/op-chain-ops/deployer/inspect/genesis.go +++ b/op-chain-ops/deployer/inspect/genesis.go @@ -2,9 +2,12 @@ package inspect import ( "fmt" - "math/big" - "strconv" - "strings" + + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil" @@ -18,40 +21,72 @@ func GenesisCLI(cliCtx *cli.Context) error { return err } - st, err := bootstrapState(cfg) + env := &pipeline.Env{Workdir: cfg.Workdir} + globalState, err := env.ReadState() if err != nil { - return err + return fmt.Errorf("failed to read intent: %w", err) } - genesis, err := st.ChainState.UnmarshalGenesis() + l2Genesis, _, err := GenesisAndRollup(globalState, cfg.ChainID) if err != nil { - return fmt.Errorf("failed to unmarshal genesis: %w", err) + return fmt.Errorf("failed to generate genesis block: %w", err) } - if err := jsonutil.WriteJSON(genesis, ioutil.ToStdOutOrFileOrNoop(cfg.Outfile, 0o666)); err != nil { + if err := jsonutil.WriteJSON(l2Genesis, ioutil.ToStdOutOrFileOrNoop(cfg.Outfile, 0o666)); err != nil { return fmt.Errorf("failed to write genesis: %w", err) } return nil } -func chainIDStrToHash(in string) (common.Hash, error) { - var chainIDBig *big.Int - if strings.HasPrefix(in, "0x") { - in = strings.TrimPrefix(in, "0x") - var ok bool - chainIDBig, ok = new(big.Int).SetString(in, 16) - if !ok { - return common.Hash{}, fmt.Errorf("failed to parse chain ID %s", in) - } - } else { - inUint, err := strconv.ParseUint(in, 10, 64) - if err != nil { - return common.Hash{}, fmt.Errorf("failed to parse chain ID %s: %w", in, err) - } - - chainIDBig = new(big.Int).SetUint64(inUint) - } - - return common.BigToHash(chainIDBig), nil +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") + } + + chainIntent, err := globalState.AppliedIntent.Chain(chainID) + if err != nil { + return nil, nil, fmt.Errorf("failed to get applied chain intent: %w", err) + } + + chainState, err := globalState.Chain(chainID) + if err != nil { + 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 := state.CombineDeployConfig( + globalState.AppliedIntent, + chainIntent, + globalState, + chainState, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to combine L2 init config: %w", err) + } + + l2GenesisBuilt, err := genesis.BuildL2Genesis(&config, l2Allocs, chainState.StartBlock) + if err != nil { + return nil, nil, fmt.Errorf("failed to build L2 genesis: %w", err) + } + l2GenesisBlock := l2GenesisBuilt.ToBlock() + + rollupConfig, err := config.RollupConfig( + chainState.StartBlock, + l2GenesisBlock.Hash(), + l2GenesisBlock.Number().Uint64(), + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to build rollup config: %w", err) + } + + if err := rollupConfig.Check(); err != nil { + return nil, nil, fmt.Errorf("generated rollup config does not pass validation: %w", err) + } + + return l2GenesisBuilt, rollupConfig, nil } diff --git a/op-chain-ops/deployer/inspect/rollup.go b/op-chain-ops/deployer/inspect/rollup.go index 8d9d39e1c49c0..60cbf4f5c46c5 100644 --- a/op-chain-ops/deployer/inspect/rollup.go +++ b/op-chain-ops/deployer/inspect/rollup.go @@ -4,12 +4,8 @@ import ( "fmt" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/pipeline" - "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" - "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" - "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/ioutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil" - "github.com/ethereum/go-ethereum/common" "github.com/urfave/cli/v2" ) @@ -25,7 +21,7 @@ func RollupCLI(cliCtx *cli.Context) error { return fmt.Errorf("failed to read intent: %w", err) } - rollupConfig, err := Rollup(globalState, cfg.ChainID) + _, rollupConfig, err := GenesisAndRollup(globalState, cfg.ChainID) if err != nil { return fmt.Errorf("failed to generate rollup config: %w", err) } @@ -36,55 +32,3 @@ func RollupCLI(cliCtx *cli.Context) error { return nil } - -func Rollup(globalState *state.State, chainID common.Hash) (*rollup.Config, error) { - if globalState.AppliedIntent == nil { - return nil, fmt.Errorf("chain state is not applied - run op-deployer apply") - } - - chainIntent, err := globalState.AppliedIntent.Chain(chainID) - if err != nil { - return nil, fmt.Errorf("failed to get applied chain intent: %w", err) - } - - chainState, err := globalState.Chain(chainID) - if err != nil { - return nil, fmt.Errorf("failed to get chain ID %s: %w", chainID.String(), err) - } - - l2Allocs, err := chainState.UnmarshalGenesis() - if err != nil { - return nil, fmt.Errorf("failed to unmarshal genesis: %w", err) - } - - config, err := state.CombineDeployConfig( - globalState.AppliedIntent, - chainIntent, - globalState, - chainState, - ) - if err != nil { - return nil, fmt.Errorf("failed to combine L2 init config: %w", err) - } - - l2GenesisBuilt, err := genesis.BuildL2Genesis(&config, l2Allocs, chainState.StartBlock) - if err != nil { - return nil, fmt.Errorf("failed to build L2 genesis: %w", err) - } - l2GenesisBlock := l2GenesisBuilt.ToBlock() - - rollupConfig, err := config.RollupConfig( - chainState.StartBlock, - l2GenesisBlock.Hash(), - l2GenesisBlock.Number().Uint64(), - ) - if err != nil { - return nil, fmt.Errorf("failed to build rollup config: %w", err) - } - - if err := rollupConfig.Check(); err != nil { - return nil, fmt.Errorf("generated rollup config does not pass validation: %w", err) - } - - return rollupConfig, nil -} diff --git a/op-chain-ops/deployer/integration_test/apply_test.go b/op-chain-ops/deployer/integration_test/apply_test.go index 624367ad36770..6d673ed037919 100644 --- a/op-chain-ops/deployer/integration_test/apply_test.go +++ b/op-chain-ops/deployer/integration_test/apply_test.go @@ -197,7 +197,7 @@ func TestEndToEndApply(t *testing.T) { } t.Run("l2 genesis", func(t *testing.T) { - require.Greater(t, len(chainState.Genesis), 0) + require.Greater(t, len(chainState.Allocs), 0) }) } } diff --git a/op-chain-ops/deployer/pipeline/downloader.go b/op-chain-ops/deployer/pipeline/downloader.go new file mode 100644 index 0000000000000..8932792f822ff --- /dev/null +++ b/op-chain-ops/deployer/pipeline/downloader.go @@ -0,0 +1,138 @@ +package pipeline + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "strings" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" +) + +var ErrUnsupportedArtifactsScheme = errors.New("unsupported artifacts URL scheme") + +type DownloadProgressor func(current, total int64) + +type CleanupFunc func() error + +var noopCleanup = func() error { return nil } + +func DownloadArtifacts(ctx context.Context, artifactsURL *state.ArtifactsURL, progress DownloadProgressor) (foundry.StatDirFs, CleanupFunc, error) { + switch artifactsURL.Scheme { + case "http", "https": + req, err := http.NewRequestWithContext(ctx, http.MethodGet, (*url.URL)(artifactsURL).String(), nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("failed to download artifacts: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, nil, fmt.Errorf("failed to download artifacts: invalid status code %s", resp.Status) + } + + tmpDir, err := os.MkdirTemp("", "op-deployer-artifacts-*") + if err != nil { + return nil, nil, fmt.Errorf("failed to create temp dir: %w", err) + } + + pr := &progressReader{ + r: resp.Body, + progress: progress, + total: resp.ContentLength, + } + + gr, err := gzip.NewReader(pr) + if err != nil { + return nil, nil, fmt.Errorf("failed to create gzip reader: %w", err) + } + defer gr.Close() + + tr := tar.NewReader(gr) + if err := untar(tmpDir, tr); err != nil { + return nil, nil, fmt.Errorf("failed to untar: %w", err) + } + + fs := os.DirFS(path.Join(tmpDir, "forge-artifacts")) + cleanup := func() error { + return os.RemoveAll(tmpDir) + } + return fs.(foundry.StatDirFs), cleanup, nil + case "file": + fs := os.DirFS(artifactsURL.Path) + return fs.(foundry.StatDirFs), noopCleanup, nil + default: + return nil, nil, ErrUnsupportedArtifactsScheme + } +} + +type progressReader struct { + r io.Reader + progress DownloadProgressor + curr int64 + total int64 + lastPrint time.Time +} + +func (pr *progressReader) Read(p []byte) (int, error) { + + n, err := pr.r.Read(p) + pr.curr += int64(n) + if pr.progress != nil && time.Since(pr.lastPrint) > 1*time.Second { + pr.progress(pr.curr, pr.total) + pr.lastPrint = time.Now() + } + return n, err +} + +func untar(dir string, tr *tar.Reader) error { + for { + hdr, err := tr.Next() + if err == io.EOF { + return nil + } + if err != nil { + return fmt.Errorf("failed to read tar header: %w", err) + } + + cleanedName := path.Clean(hdr.Name) + if strings.Contains(cleanedName, "..") { + return fmt.Errorf("invalid file path: %s", hdr.Name) + } + dst := path.Join(dir, cleanedName) + if hdr.FileInfo().IsDir() { + if err := os.MkdirAll(dst, 0o755); err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + continue + } + + f, err := os.Create(dst) + buf := bufio.NewWriter(f) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + if _, err := io.Copy(buf, tr); err != nil { + _ = f.Close() + return fmt.Errorf("failed to write file: %w", err) + } + if err := buf.Flush(); err != nil { + return fmt.Errorf("failed to flush buffer: %w", err) + } + _ = f.Close() + } +} diff --git a/op-chain-ops/deployer/pipeline/downloader_test.go b/op-chain-ops/deployer/pipeline/downloader_test.go new file mode 100644 index 0000000000000..277409461a41e --- /dev/null +++ b/op-chain-ops/deployer/pipeline/downloader_test.go @@ -0,0 +1,42 @@ +package pipeline + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" + "github.com/stretchr/testify/require" +) + +func TestDownloadArtifacts(t *testing.T) { + f, err := os.OpenFile("testdata/artifacts.tar.gz", os.O_RDONLY, 0o644) + require.NoError(t, err) + defer f.Close() + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := io.Copy(w, f) + require.NoError(t, err) + })) + defer ts.Close() + + ctx := context.Background() + artifactsURL, err := url.Parse(ts.URL) + require.NoError(t, err) + + fs, cleanup, err := DownloadArtifacts(ctx, (*state.ArtifactsURL)(artifactsURL), nil) + require.NoError(t, err) + require.NotNil(t, fs) + defer func() { + require.NoError(t, cleanup()) + }() + + info, err := fs.Stat("WETH98.sol/WETH98.json") + require.NoError(t, err) + require.Greater(t, info.Size(), int64(0)) +} diff --git a/op-chain-ops/deployer/pipeline/env.go b/op-chain-ops/deployer/pipeline/env.go index 9ee28e3843f6c..d0778122d27a1 100644 --- a/op-chain-ops/deployer/pipeline/env.go +++ b/op-chain-ops/deployer/pipeline/env.go @@ -5,6 +5,8 @@ import ( "fmt" "path" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-service/jsonutil" @@ -44,4 +46,4 @@ func (e *Env) WriteState(st *state.State) error { return st.WriteToFile(statePath) } -type Stage func(ctx context.Context, env *Env, intent *state.Intent, state2 *state.State) error +type Stage func(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, state2 *state.State) error diff --git a/op-chain-ops/deployer/pipeline/implementations.go b/op-chain-ops/deployer/pipeline/implementations.go index f7e2acd1130cb..f9e125e4150bc 100644 --- a/op-chain-ops/deployer/pipeline/implementations.go +++ b/op-chain-ops/deployer/pipeline/implementations.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/big" - "os" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opsm" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" @@ -12,7 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-chain-ops/script" ) -func DeployImplementations(ctx context.Context, env *Env, intent *state.Intent, st *state.State) error { +func DeployImplementations(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { lgr := env.Logger.New("stage", "deploy-implementations") if !shouldDeployImplementations(intent, st) { @@ -22,17 +21,9 @@ func DeployImplementations(ctx context.Context, env *Env, intent *state.Intent, lgr.Info("deploying implementations") - 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") - } - var dump *foundry.ForgeAllocs var dio opsm.DeployImplementationsOutput + var err error err = CallScriptBroadcast( ctx, CallScriptBroadcastOpts{ @@ -49,6 +40,7 @@ func DeployImplementations(ctx context.Context, env *Env, intent *state.Intent, dio, err = opsm.DeployImplementations( host, opsm.DeployImplementationsInput{ + Salt: st.Create2Salt, WithdrawalDelaySeconds: big.NewInt(604800), MinProposalSizeBytes: big.NewInt(126000), ChallengePeriodSeconds: big.NewInt(86400), diff --git a/op-chain-ops/deployer/pipeline/init.go b/op-chain-ops/deployer/pipeline/init.go index 26fbb7c2667c1..094e103aa940c 100644 --- a/op-chain-ops/deployer/pipeline/init.go +++ b/op-chain-ops/deployer/pipeline/init.go @@ -5,6 +5,8 @@ import ( "crypto/rand" "fmt" + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" "github.com/ethereum/go-ethereum/common" @@ -16,7 +18,7 @@ func IsSupportedStateVersion(version int) bool { return version == 1 } -func Init(ctx context.Context, env *Env, intent *state.Intent, st *state.State) error { +func Init(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { lgr := env.Logger.New("stage", "init") lgr.Info("initializing pipeline") @@ -73,7 +75,7 @@ func Init(ctx context.Context, env *Env, intent *state.Intent, st *state.State) return fmt.Errorf("deterministic deployer is not deployed on this chain - please deploy it first") } - // TODO: validate individual L2s + // TODO: validate individual return nil } diff --git a/op-chain-ops/deployer/pipeline/l2genesis.go b/op-chain-ops/deployer/pipeline/l2genesis.go index 95613fada9127..f74c6e8336209 100644 --- a/op-chain-ops/deployer/pipeline/l2genesis.go +++ b/op-chain-ops/deployer/pipeline/l2genesis.go @@ -7,7 +7,6 @@ import ( "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" @@ -16,20 +15,11 @@ import ( "github.com/ethereum/go-ethereum/common" ) -func GenerateL2Genesis(ctx context.Context, env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error { +func GenerateL2Genesis(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, 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) @@ -92,7 +82,7 @@ func GenerateL2Genesis(ctx context.Context, env *Env, intent *state.Intent, st * if err := gw.Close(); err != nil { return fmt.Errorf("failed to close gzip writer: %w", err) } - thisChainState.Genesis = buf.Bytes() + thisChainState.Allocs = buf.Bytes() startHeader, err := env.L1Client.HeaderByNumber(ctx, nil) if err != nil { return fmt.Errorf("failed to get start block: %w", err) diff --git a/op-chain-ops/deployer/pipeline/opchain.go b/op-chain-ops/deployer/pipeline/opchain.go index c6d64255c6507..90d03b0281429 100644 --- a/op-chain-ops/deployer/pipeline/opchain.go +++ b/op-chain-ops/deployer/pipeline/opchain.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/big" - "os" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/opsm" "github.com/ethereum-optimism/optimism/op-chain-ops/deployer/state" @@ -13,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) -func DeployOPChain(ctx context.Context, env *Env, intent *state.Intent, st *state.State, chainID common.Hash) error { +func DeployOPChain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State, chainID common.Hash) error { lgr := env.Logger.New("stage", "deploy-opchain") if !shouldDeployOPChain(intent, st, chainID) { @@ -23,15 +22,6 @@ func DeployOPChain(ctx context.Context, env *Env, intent *state.Intent, st *stat lgr.Info("deploying OP chain", "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) diff --git a/op-chain-ops/deployer/pipeline/superchain.go b/op-chain-ops/deployer/pipeline/superchain.go index a3c1dc2827a37..21aeda0e23dc6 100644 --- a/op-chain-ops/deployer/pipeline/superchain.go +++ b/op-chain-ops/deployer/pipeline/superchain.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/big" - "os" "github.com/ethereum-optimism/optimism/op-chain-ops/script" @@ -14,7 +13,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" ) -func DeploySuperchain(ctx context.Context, env *Env, intent *state.Intent, st *state.State) error { +func DeploySuperchain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs, intent *state.Intent, st *state.State) error { lgr := env.Logger.New("stage", "deploy-superchain") if !shouldDeploySuperchain(intent, st) { @@ -24,17 +23,9 @@ func DeploySuperchain(ctx context.Context, env *Env, intent *state.Intent, st *s lgr.Info("deploying superchain") - 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") - } - var dump *foundry.ForgeAllocs var dso opsm.DeploySuperchainOutput + var err error err = CallScriptBroadcast( ctx, CallScriptBroadcastOpts{ diff --git a/op-chain-ops/deployer/pipeline/testdata/artifacts.tar.gz b/op-chain-ops/deployer/pipeline/testdata/artifacts.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..58f034254f46aa79a18fcfaa10429b447e08c78d GIT binary patch literal 12116 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�V#Myc*C*u#uwP|27&cxe^TNt0-uyqS4i-PB zJ=cQMaXPG)Y*y_#cPi|5zFlPQI@;IE$#0gc?K~+SOy-+OG;w#HPFA^nJh{Djvqsx@ zlCRd2m#f7*g`u4U&-AmOo>>)mvAFwYSJPe0ml@2#pR@Ff#y~s9vSU)Iyd~Kh!XcCe zZ`N587~^)6rC+btE2})?SOB+W%MCX7pWlD`lsyZIA%^-NfARQv{5*ahKaZdDeEvTk KJ+klsegOcQ)RBn* literal 0 HcmV?d00001 diff --git a/op-chain-ops/deployer/state/intent.go b/op-chain-ops/deployer/state/intent.go index 5b91dbbf7c139..c737dab37dd0c 100644 --- a/op-chain-ops/deployer/state/intent.go +++ b/op-chain-ops/deployer/state/intent.go @@ -58,10 +58,6 @@ func (c *Intent) Check() error { return fmt.Errorf("contractArtifactsURL must be set") } - if c.ContractArtifactsURL.Scheme != "file" { - return fmt.Errorf("contractArtifactsURL must be a file URL") - } - return nil } diff --git a/op-chain-ops/deployer/state/state.go b/op-chain-ops/deployer/state/state.go index 6fdc4f7dbae60..098fa7a731d74 100644 --- a/op-chain-ops/deployer/state/state.go +++ b/op-chain-ops/deployer/state/state.go @@ -98,13 +98,13 @@ type ChainState struct { DelayedWETHPermissionedGameProxyAddress common.Address `json:"delayedWETHPermissionedGameProxyAddress"` DelayedWETHPermissionlessGameProxyAddress common.Address `json:"delayedWETHPermissionlessGameProxyAddress"` - Genesis Base64Bytes `json:"genesis"` + Allocs Base64Bytes `json:"allocs"` StartBlock *types.Header `json:"startBlock"` } -func (c *ChainState) UnmarshalGenesis() (*foundry.ForgeAllocs, error) { - gr, err := gzip.NewReader(bytes.NewReader(c.Genesis)) +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) } @@ -112,7 +112,7 @@ func (c *ChainState) UnmarshalGenesis() (*foundry.ForgeAllocs, error) { var allocs foundry.ForgeAllocs if err := json.NewDecoder(gr).Decode(&allocs); err != nil { - return nil, fmt.Errorf("failed to decode genesis: %w", err) + return nil, fmt.Errorf("failed to decode allocs: %w", err) } return &allocs, nil diff --git a/op-chain-ops/deployer/version/version.go b/op-chain-ops/deployer/version/version.go new file mode 100644 index 0000000000000..2456f656d45c1 --- /dev/null +++ b/op-chain-ops/deployer/version/version.go @@ -0,0 +1,6 @@ +package version + +var ( + Version = "v0.0.0" + Meta = "dev" +) diff --git a/op-e2e/e2eutils/challenger/helper.go b/op-e2e/e2eutils/challenger/helper.go index 3b037ef71c6df..87a51d96a5f0f 100644 --- a/op-e2e/e2eutils/challenger/helper.go +++ b/op-e2e/e2eutils/challenger/helper.go @@ -11,6 +11,8 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/require" @@ -72,7 +74,7 @@ func WithGameAddress(addr common.Address) Option { func WithPrivKey(key *ecdsa.PrivateKey) Option { return func(c *config.Config) { - c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(key) + c.TxMgrConfig.PrivateKey = crypto.EncodePrivKeyToString(key) } } diff --git a/op-e2e/e2eutils/secrets.go b/op-e2e/e2eutils/secrets.go index 7c934cc65f5a5..cd4c91e1e09e5 100644 --- a/op-e2e/e2eutils/secrets.go +++ b/op-e2e/e2eutils/secrets.go @@ -8,7 +8,6 @@ import ( hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) @@ -134,18 +133,6 @@ type Secrets struct { Wallet *hdwallet.Wallet } -// EncodePrivKey encodes the given private key in 32 bytes -func EncodePrivKey(priv *ecdsa.PrivateKey) hexutil.Bytes { - privkey := make([]byte, 32) - blob := priv.D.Bytes() - copy(privkey[32-len(blob):], blob) - return privkey -} - -func EncodePrivKeyToString(priv *ecdsa.PrivateKey) string { - return hexutil.Encode(EncodePrivKey(priv)) -} - // Addresses computes the ethereum address of each account, // which can then be kept around for fast precomputed address access. func (s *Secrets) Addresses() *Addresses { diff --git a/op-e2e/e2eutils/setuputils/utils.go b/op-e2e/e2eutils/setuputils/utils.go index 9c82f3a5602d3..12f6bca83f424 100644 --- a/op-e2e/e2eutils/setuputils/utils.go +++ b/op-e2e/e2eutils/setuputils/utils.go @@ -4,15 +4,16 @@ import ( "crypto/ecdsa" "time" + "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-service/endpoint" "github.com/ethereum-optimism/optimism/op-service/txmgr" ) func hexPriv(in *ecdsa.PrivateKey) string { - b := e2eutils.EncodePrivKey(in) + b := crypto.EncodePrivKey(in) return hexutil.Encode(b) } diff --git a/op-service/crypto/secrets.go b/op-service/crypto/secrets.go new file mode 100644 index 0000000000000..410de63cb3ca8 --- /dev/null +++ b/op-service/crypto/secrets.go @@ -0,0 +1,19 @@ +package crypto + +import ( + "crypto/ecdsa" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// EncodePrivKey encodes the given private key in 32 bytes +func EncodePrivKey(priv *ecdsa.PrivateKey) hexutil.Bytes { + privkey := make([]byte, 32) + blob := priv.D.Bytes() + copy(privkey[32-len(blob):], blob) + return privkey +} + +func EncodePrivKeyToString(priv *ecdsa.PrivateKey) string { + return hexutil.Encode(EncodePrivKey(priv)) +} diff --git a/op-service/util.go b/op-service/util.go index 26960a911c6be..5c42d3de5ddeb 100644 --- a/op-service/util.go +++ b/op-service/util.go @@ -4,9 +4,11 @@ import ( "context" "errors" "fmt" + "math/big" "os" "path/filepath" "reflect" + "strconv" "strings" "time" @@ -132,3 +134,32 @@ func FindMonorepoRoot(startDir string) (string, error) { } return "", errors.New("monorepo root not found") } + +// Parse256BitChainID parses a 256-bit chain ID from a string. Chain IDs +// can be defined as either an integer or a hex string. If the string +// starts with "0x", it is treated as a hex string, otherwise it is +// treated as an integer string. +func Parse256BitChainID(in string) (common.Hash, error) { + var chainIDBig *big.Int + if strings.HasPrefix(in, "0x") { + in = strings.TrimPrefix(in, "0x") + var ok bool + chainIDBig, ok = new(big.Int).SetString(in, 16) + if !ok { + return common.Hash{}, fmt.Errorf("failed to parse chain ID %s", in) + } + } else { + inUint, err := strconv.ParseUint(in, 10, 64) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to parse chain ID %s: %w", in, err) + } + + chainIDBig = new(big.Int).SetUint64(inUint) + } + + if chainIDBig.BitLen() > 256 { + return common.Hash{}, fmt.Errorf("chain ID %s is too large", in) + } + + return common.BigToHash(chainIDBig), nil +} diff --git a/op-service/util_test.go b/op-service/util_test.go index 12d28f3b34144..940bb7001fc88 100644 --- a/op-service/util_test.go +++ b/op-service/util_test.go @@ -3,6 +3,8 @@ package op_service import ( "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" ) @@ -30,3 +32,57 @@ func TestValidateEnvVars(t *testing.T) { invalids := validateEnvVars("OP_BATCHER", provided, defined) require.ElementsMatch(t, invalids, []string{"OP_BATCHER_FAKE=false"}) } + +func TestParse256BitChainID(t *testing.T) { + tests := []struct { + name string + input string + expected common.Hash + err bool + }{ + { + name: "valid int", + input: "12345", + expected: common.Hash{30: 0x30, 31: 0x39}, + err: false, + }, + { + name: "invalid hash", + input: common.Hash{0x00: 0xff}.String(), + expected: common.Hash{0x00: 0xff}, + err: false, + }, + { + name: "hash overflow", + input: "0xff0000000000000000000000000000000000000000000000000000000000000000", + err: true, + }, + { + name: "number overflow", + // (2^256 - 1) + 1 + input: "115792089237316195423570985008687907853269984665640564039457584007913129639936", + err: true, + }, + { + name: "invalid hex", + input: "0xnope", + err: true, + }, + { + name: "invalid number", + input: "nope", + err: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := Parse256BitChainID(tt.input) + if tt.err { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, res) + } + }) + } +} diff --git a/ops/docker/deployment-utils/Dockerfile b/ops/docker/deployment-utils/Dockerfile new file mode 100644 index 0000000000000..c0f82e5a3405d --- /dev/null +++ b/ops/docker/deployment-utils/Dockerfile @@ -0,0 +1,35 @@ +FROM golang:1.23.1-bookworm AS go-base + +RUN go install github.com/tomwright/dasel/v2/cmd/dasel@master + +FROM debian:12.7-slim AS base + +SHELL ["/bin/bash", "-c"] + +ENV PATH=/root/.cargo/bin:/root/.foundry/bin:$PATH +ENV DEBIAN_FRONTEND=noninteractive +ENV SHELL=/bin/bash + +RUN apt-get update && apt-get install -y curl git jq build-essential + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \ + chmod +x ./rustup.sh && \ + sh rustup.sh -y + +RUN source $HOME/.profile && rustup update nightly +RUN curl -L https://foundry.paradigm.xyz | bash +RUN foundryup + +FROM debian:12.7-slim + +ENV PATH=/root/.cargo/bin:/root/.foundry/bin:$PATH +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y bash curl jq + +SHELL ["/bin/bash", "-c"] + +COPY --from=base /root/.foundry/bin/forge /usr/local/bin/forge +COPY --from=base /root/.foundry/bin/cast /usr/local/bin/cast +COPY --from=base /root/.foundry/bin/anvil /usr/local/bin/anvil +COPY --from=go-base /go/bin/dasel /usr/local/bin/dasel \ No newline at end of file diff --git a/ops/docker/deployment-utils/README.md b/ops/docker/deployment-utils/README.md new file mode 100644 index 0000000000000..450af3423b11d --- /dev/null +++ b/ops/docker/deployment-utils/README.md @@ -0,0 +1,16 @@ +# deployment-utils + +This image provides a minimal set of Foundry and Bash tools for use with builder images like Kurtosis. It contains the +following packages: + +- The Foundry suite (`forge`, `cast`, `anvil`) +- [`Dasel`](https://github.com/TomWright/dasel), for TOML/YAML manipulation. +- `jq` for JSON manipulation. +- `curl`, for when you need to cURLs. +- A default `bash` shell. + +## Image Size + +According to `dive`, this image is 255MB in size including the base Debian image. Most of the additional size comes from +the tools themselves. I'd like to keep it this way. This image should not contain toolchains, libraries, etc. - it is +designed to run prebuilt software and manipulate configuration files. Use the CI builder for everything else. \ No newline at end of file diff --git a/ops/docker/op-stack-go/Dockerfile b/ops/docker/op-stack-go/Dockerfile index 18163a86c3ccb..a395968fd07a9 100644 --- a/ops/docker/op-stack-go/Dockerfile +++ b/ops/docker/op-stack-go/Dockerfile @@ -101,6 +101,11 @@ ARG OP_SUPERVISOR_VERSION=v0.0.0 RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd op-supervisor && make op-supervisor \ GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_SUPERVISOR_VERSION" +FROM --platform=$BUILDPLATFORM builder AS op-deployer-builder +ARG OP_NODE_VERSION=v0.0.0 +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd op-chain-ops && make op-deployer \ + GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_DEPLOYER_VERSION" + FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS cannon-target COPY --from=cannon-builder /app/cannon/bin/cannon /usr/local/bin/ CMD ["cannon"] @@ -150,3 +155,7 @@ CMD ["da-server"] FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS op-supervisor-target COPY --from=op-supervisor-builder /app/op-supervisor/bin/op-supervisor /usr/local/bin/ CMD ["op-supervisor"] + +FROM --platform=$TARGETPLATFORM $TARGET_BASE_IMAGE AS op-deployer-target +COPY --from=op-deployer-builder /app/op-chain-ops/bin/op-deployer /usr/local/bin/ +CMD ["op-deployer"] \ No newline at end of file