Skip to content

Commit

Permalink
feat(op-deployer): asterisc bootstrap CLI (#13113)
Browse files Browse the repository at this point in the history
* feat(op-deployer): `asterisc` bootstrap CLI

* rename file

* flags

* mslipper review

* golint

---------

Co-authored-by: Matthew Slipper <me@matthewslipper.com>
  • Loading branch information
clabby and mslipper authored Nov 27, 2024
1 parent cf13a17 commit af169db
Show file tree
Hide file tree
Showing 6 changed files with 397 additions and 2 deletions.
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,
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 {
return true
}

type DeployAsteriscOutput struct {
AsteriscSingleton common.Address
}

func (output *DeployAsteriscOutput) CheckOutput(input common.Address) error {
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

0 comments on commit af169db

Please sign in to comment.