Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(op-deployer): asterisc bootstrap CLI #13113

Merged
merged 6 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions op-deployer/pkg/deployer/bootstrap/asterisc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package bootstrap

import (
"context"
"crypto/ecdsa"
"fmt"
"strings"

"github.com/ethereum-optimism/optimism/op-chain-ops/script"
"github.com/ethereum-optimism/optimism/op-chain-ops/script/forking"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
"github.com/ethereum/go-ethereum/common"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"

"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"
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"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)

type AsteriscConfig struct {
L1RPCUrl string
PrivateKey string
Logger log.Logger
ArtifactsLocator *artifacts.Locator

privateKeyECDSA *ecdsa.PrivateKey

PreimageOracle common.Address
}

func (c *AsteriscConfig) Check() error {
if c.L1RPCUrl == "" {
return fmt.Errorf("l1RPCUrl must be specified")
}

if c.PrivateKey == "" {
return fmt.Errorf("private key must be specified")
}

privECDSA, err := crypto.HexToECDSA(strings.TrimPrefix(c.PrivateKey, "0x"))
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}
c.privateKeyECDSA = privECDSA

if c.Logger == nil {
return fmt.Errorf("logger must be specified")
}

if c.ArtifactsLocator == nil {
return fmt.Errorf("artifacts locator must be specified")
}

if c.PreimageOracle == (common.Address{}) {
return fmt.Errorf("preimage oracle must be specified")
}

return nil
}

func AsteriscCLI(cliCtx *cli.Context) error {
logCfg := oplog.ReadCLIConfig(cliCtx)
l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg)
oplog.SetGlobalLogHandler(l.Handler())

l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName)
privateKey := cliCtx.String(deployer.PrivateKeyFlagName)
artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName)
artifactsLocator := new(artifacts.Locator)
if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil {
return fmt.Errorf("failed to parse artifacts URL: %w", err)
}

preimageOracle := common.HexToAddress(cliCtx.String(PreimageOracleFlagName))

ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context)

return Asterisc(ctx, AsteriscConfig{
L1RPCUrl: l1RPCUrl,
PrivateKey: privateKey,
Logger: l,
ArtifactsLocator: artifactsLocator,
PreimageOracle: preimageOracle,
})
}

func Asterisc(ctx context.Context, cfg AsteriscConfig) error {
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config for Asterisc: %w", err)
}

lgr := cfg.Logger
progressor := func(curr, total int64) {
lgr.Info("artifacts download progress", "current", curr, "total", total)
}

artifactsFS, cleanup, err := artifacts.Download(ctx, cfg.ArtifactsLocator, progressor)
if err != nil {
return fmt.Errorf("failed to download artifacts: %w", err)
}
defer func() {
if err := cleanup(); err != nil {
lgr.Warn("failed to clean up artifacts", "err", err)
}
}()

l1Client, err := ethclient.Dial(cfg.L1RPCUrl)
if err != nil {
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
}

chainID, err := l1Client.ChainID(ctx)
if err != nil {
return fmt.Errorf("failed to get chain ID: %w", err)
}

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

l1RPC, err := rpc.Dial(cfg.L1RPCUrl)
if err != nil {
return fmt.Errorf("failed to connect to L1 RPC: %w", err)
}

l1Host, err := env.DefaultScriptHost(
bcaster,
lgr,
chainDeployer,
artifactsFS,
clabby marked this conversation as resolved.
Show resolved Hide resolved
script.WithForkHook(func(cfg *script.ForkConfig) (forking.ForkSource, error) {
src, err := forking.RPCSourceByNumber(cfg.URLOrAlias, l1RPC, *cfg.BlockNumber)
if err != nil {
return nil, fmt.Errorf("failed to create RPC fork source: %w", err)
}
return forking.Cache(src), nil
}),
)
if err != nil {
return fmt.Errorf("failed to create script host: %w", err)
}

dgo, err := opcm.DeployAsterisc(
l1Host,
opcm.DeployAsteriscInput{
PreimageOracle: cfg.PreimageOracle,
},
)
if err != nil {
return fmt.Errorf("error deploying asterisc VM: %w", err)
}

if _, err := bcaster.Broadcast(ctx); err != nil {
return fmt.Errorf("failed to broadcast: %w", err)
}

lgr.Info("deployed asterisc VM")

if err := jsonutil.WriteJSON(dgo, ioutil.ToStdOut()); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}
return nil
}
13 changes: 11 additions & 2 deletions op-deployer/pkg/deployer/bootstrap/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,17 @@ var DisputeGameFlags = []cli.Flag{
ChallengerFlag,
}

var MIPSFlags = []cli.Flag{
var BaseFPVMFlags = []cli.Flag{
deployer.L1RPCURLFlag,
deployer.PrivateKeyFlag,
ArtifactsLocatorFlag,
PreimageOracleFlag,
MIPSVersionFlag,
}

var MIPSFlags = append(BaseFPVMFlags, MIPSVersionFlag)

var AsteriscFlags = BaseFPVMFlags

var Commands = []*cli.Command{
{
Name: "opcm",
Expand All @@ -227,4 +230,10 @@ var Commands = []*cli.Command{
Flags: cliapp.ProtectFlags(MIPSFlags),
Action: MIPSCLI,
},
{
Name: "asterisc",
Usage: "Bootstrap an instance of Asterisc.",
Flags: cliapp.ProtectFlags(AsteriscFlags),
Action: AsteriscCLI,
},
}
64 changes: 64 additions & 0 deletions op-deployer/pkg/deployer/opcm/asterisc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package opcm

import (
"fmt"

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

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

type DeployAsteriscInput struct {
PreimageOracle common.Address
}

func (input *DeployAsteriscInput) InputSet() bool {
clabby marked this conversation as resolved.
Show resolved Hide resolved
return true
}

type DeployAsteriscOutput struct {
AsteriscSingleton common.Address
}

func (output *DeployAsteriscOutput) CheckOutput(input common.Address) error {
clabby marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

type DeployAsteriscScript struct {
Run func(input, output common.Address) error
}

func DeployAsterisc(
host *script.Host,
input DeployAsteriscInput,
) (DeployAsteriscOutput, error) {
var output DeployAsteriscOutput
inputAddr := host.NewScriptAddress()
outputAddr := host.NewScriptAddress()

cleanupInput, err := script.WithPrecompileAtAddress[*DeployAsteriscInput](host, inputAddr, &input)
if err != nil {
return output, fmt.Errorf("failed to insert DeployAsteriscInput precompile: %w", err)
}
defer cleanupInput()

cleanupOutput, err := script.WithPrecompileAtAddress[*DeployAsteriscOutput](host, outputAddr, &output,
script.WithFieldSetter[*DeployAsteriscOutput])
if err != nil {
return output, fmt.Errorf("failed to insert DeployAsteriscOutput precompile: %w", err)
}
defer cleanupOutput()

implContract := "DeployAsterisc"
deployScript, cleanupDeploy, err := script.WithScript[DeployAsteriscScript](host, "DeployAsterisc.s.sol", implContract)
if err != nil {
return output, fmt.Errorf("failed to load %s script: %w", implContract, err)
}
defer cleanupDeploy()

if err := deployScript.Run(inputAddr, outputAddr); err != nil {
return output, fmt.Errorf("failed to run %s script: %w", implContract, err)
}

return output, nil
}
34 changes: 34 additions & 0 deletions op-deployer/pkg/deployer/opcm/asterisc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package opcm

import (
"testing"

"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/env"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)

func TestDeployAsterisc(t *testing.T) {
_, artifacts := testutil.LocalArtifacts(t)

host, err := env.DefaultScriptHost(
broadcaster.NoopBroadcaster(),
testlog.Logger(t, log.LevelInfo),
common.Address{'D'},
artifacts,
)
require.NoError(t, err)

input := DeployAsteriscInput{
PreimageOracle: common.Address{0xab},
}

output, err := DeployAsterisc(host, input)
require.NoError(t, err)

require.NotEmpty(t, output.AsteriscSingleton)
}
Loading