Commit 416289e8 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into jg/backoff_when_dial_rpc_proposer_batcher

parents 24126a4a 1e46da0f
package cmd
import (
"fmt"
"os"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/urfave/cli/v2"
)
var (
WitnessInputFlag = &cli.PathFlag{
Name: "input",
Usage: "path of input JSON state.",
TakesFile: true,
Required: true,
}
WitnessOutputFlag = &cli.PathFlag{
Name: "output",
Usage: "path to write binary witness.",
TakesFile: true,
}
)
func Witness(ctx *cli.Context) error {
input := ctx.Path(WitnessInputFlag.Name)
output := ctx.Path(WitnessOutputFlag.Name)
state, err := loadJSON[mipsevm.State](input)
if err != nil {
return fmt.Errorf("invalid input state (%v): %w", input, err)
}
witness := state.EncodeWitness()
h := crypto.Keccak256Hash(witness)
if output != "" {
if err := os.WriteFile(output, witness, 0755); err != nil {
return fmt.Errorf("writing output to %v: %w", output, err)
}
}
fmt.Println(h.Hex())
return nil
}
var WitnessCommand = &cli.Command{
Name: "witness",
Usage: "Convert a Cannon JSON state into a binary witness",
Description: "Convert a Cannon JSON state into a binary witness. The hash of the witness is written to stdout",
Action: Witness,
Flags: []cli.Flag{
WitnessInputFlag,
WitnessOutputFlag,
},
}
......@@ -20,6 +20,7 @@ func main() {
app.Description = "MIPS Fault Proof tool"
app.Commands = []*cli.Command{
cmd.LoadELFCommand,
cmd.WitnessCommand,
cmd.RunCommand,
}
ctx, cancel := context.WithCancel(context.Background())
......
This diff is collapsed.
This diff is collapsed.
......@@ -423,6 +423,19 @@ func checkL2ERC721Bridge(addr common.Address, client *ethclient.Client) error {
if otherBridge == (common.Address{}) {
return errors.New("L2ERC721Bridge.OTHERBRIDGE is zero address")
}
initialized, err := getInitialized("L2ERC721Bridge", addr, client)
if err != nil {
return err
}
log.Info("L2ERC721Bridge", "_initialized", initialized)
initializing, err := getInitializing("L2ERC721Bridge", addr, client)
if err != nil {
return err
}
log.Info("L2ERC721Bridge", "_initializing", initializing)
version, err := contract.Version(&bind.CallOpts{})
if err != nil {
return err
......@@ -614,6 +627,12 @@ func checkL2StandardBridge(addr common.Address, client *ethclient.Client) error
}
log.Info("L2StandardBridge", "_initialized", initialized)
initializing, err := getInitializing("L2StandardBridge", addr, client)
if err != nil {
return err
}
log.Info("L2StandardBridge", "_initializing", initializing)
log.Info("L2StandardBridge version", "version", version)
return nil
}
......@@ -727,6 +746,12 @@ func checkL2CrossDomainMessenger(addr common.Address, client *ethclient.Client)
}
log.Info("L2CrossDomainMessenger", "_initialized", initialized)
initializing, err := getInitializing("L2CrossDomainMessenger", addr, client)
if err != nil {
return err
}
log.Info("L2CrossDomainMessenger", "_initializing", initializing)
log.Info("L2CrossDomainMessenger version", "version", version)
return nil
}
......@@ -823,11 +848,33 @@ func getEIP1967ImplementationAddress(client *ethclient.Client, addr common.Addre
// This is an incrementing number that starts at 1 and increments each time that
// the contract is upgraded.
func getInitialized(name string, addr common.Address, client *ethclient.Client) (*big.Int, error) {
value, err := getStorageValue(name, "_initialized", addr, client)
if err != nil {
return nil, err
}
return new(big.Int).SetBytes(value), nil
}
// getInitializing will get the _initializing value in storage of a contract.
func getInitializing(name string, addr common.Address, client *ethclient.Client) (bool, error) {
value, err := getStorageValue(name, "_initializing", addr, client)
if err != nil {
return false, err
}
if len(value) != 1 {
return false, fmt.Errorf("Unexpected length for _initializing: %d", len(value))
}
return value[0] == 1, nil
}
// getStorageValue will get the value of a named storage slot in a contract. It isn't smart about
// automatically converting from a byte slice to a type, it is the caller's responsibility to do that.
func getStorageValue(name, entryName string, addr common.Address, client *ethclient.Client) ([]byte, error) {
layout, err := bindings.GetStorageLayout(name)
if err != nil {
return nil, err
}
entry, err := layout.GetStorageLayoutEntry("_initialized")
entry, err := layout.GetStorageLayoutEntry(entryName)
if err != nil {
return nil, err
}
......@@ -848,6 +895,5 @@ func getInitialized(name string, addr common.Address, client *ethclient.Client)
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
slice[i], slice[j] = slice[j], slice[i]
}
initialized := new(big.Int).SetBytes(slice[entry.Offset : entry.Offset+typ.NumberOfBytes])
return initialized, nil
return slice[entry.Offset : entry.Offset+typ.NumberOfBytes], nil
}
......@@ -52,7 +52,11 @@ func TestCode(t *testing.T) {
db.SetCode(addr, code)
post := db.GetCode(addr)
require.Equal(t, post, code)
if len(code) == 0 {
require.Nil(t, post)
} else {
require.Equal(t, post, code)
}
size := db.GetCodeSize(addr)
require.Equal(t, size, len(code))
......
......@@ -19,8 +19,23 @@ test:
lint:
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is"
visualize:
./scripts/visualize.sh
alphabet:
./scripts/alphabet/init_game.sh
make alphabet-actors
alphabet-actors:
@./scripts/parallel.sh \
./scripts/alphabet/mallory.sh 'mallory' \
./scripts/alphabet/charlie.sh 'charlie'
.PHONY: \
clean \
op-challenger \
test \
lint
lint \
visualize \
alphabet \
alphabet-actors
......@@ -6,6 +6,7 @@ import (
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
......@@ -15,6 +16,8 @@ import (
var (
l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000"
cannonNetwork = chaincfg.AvailableNetworks()[0]
otherCannonNetwork = chaincfg.AvailableNetworks()[1]
cannonBin = "./bin/cannon"
cannonServer = "./bin/op-program"
cannonPreState = "./pre.json"
......@@ -226,6 +229,67 @@ func TestCannonSnapshotFreq(t *testing.T) {
})
}
func TestRequireEitherCannonNetworkOrRollupAndGenesis(t *testing.T) {
verifyArgsInvalid(
t,
"flag cannon-network or cannon-rollup-config and cannon-l2-genesis is required",
addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-network"))
verifyArgsInvalid(
t,
"flag cannon-network or cannon-rollup-config and cannon-l2-genesis is required",
addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-network", "--cannon-rollup-config=rollup.json"))
verifyArgsInvalid(
t,
"flag cannon-network or cannon-rollup-config and cannon-l2-genesis is required",
addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-network", "--cannon-l2-genesis=gensis.json"))
}
func TestMustNotSpecifyNetworkAndRollup(t *testing.T) {
verifyArgsInvalid(
t,
"flag cannon-network can not be used with cannon-rollup-config and cannon-l2-genesis",
addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-network",
"--cannon-network", cannonNetwork, "--cannon-rollup-config=rollup.json"))
}
func TestCannonNetwork(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-network"))
})
t.Run("NotRequiredWhenRollupAndGenesIsSpecified", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-network",
"--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-network", "--cannon-network", otherCannonNetwork))
require.Equal(t, otherCannonNetwork, cfg.CannonNetwork)
})
}
func TestCannonRollupConfig(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-rollup-config"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json"))
require.Equal(t, "rollup.json", cfg.CannonRollupConfigPath)
})
}
func TestCannonL2Genesis(t *testing.T) {
t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2-genesis"))
})
t.Run("Valid", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeCannon, "--cannon-network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json"))
require.Equal(t, "genesis.json", cfg.CannonL2GenesisPath)
})
}
func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains)
......@@ -273,6 +337,7 @@ func requiredArgs(traceType config.TraceType) map[string]string {
case config.TraceTypeAlphabet:
args["--alphabet"] = alphabetTrace
case config.TraceTypeCannon:
args["--cannon-network"] = cannonNetwork
args["--cannon-bin"] = cannonBin
args["--cannon-server"] = cannonServer
args["--cannon-prestate"] = cannonPreState
......
......@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)
......@@ -18,8 +19,12 @@ var (
ErrMissingAlphabetTrace = errors.New("missing alphabet trace")
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url")
ErrMissingGameAddress = errors.New("missing game address")
ErrMissingPreimageOracleAddress = errors.New("missing pre-image oracle address")
ErrMissingCannonSnapshotFreq = errors.New("missing cannon snapshot freq")
ErrMissingCannonRollupConfig = errors.New("missing cannon network or rollup config path")
ErrMissingCannonL2Genesis = errors.New("missing cannon network or l2 genesis path")
ErrCannonNetworkAndRollupConfig = errors.New("only specify one of network or rollup config path")
ErrCannonNetworkAndL2Genesis = errors.New("only specify one of network or l2 genesis path")
ErrCannonNetworkUnknown = errors.New("unknown cannon network")
)
type TraceType string
......@@ -53,7 +58,7 @@ func ValidTraceType(value TraceType) bool {
return false
}
const DefaultCannonSnapshotFreq = uint(10_000)
const DefaultCannonSnapshotFreq = uint(1_000_000_000)
// Config is a well typed config that is parsed from the CLI params.
// This also contains config options for auxiliary services.
......@@ -73,6 +78,9 @@ type Config struct {
CannonBin string // Path to the cannon executable to run when generating trace data
CannonServer string // Path to the op-program executable that provides the pre-image oracle server
CannonAbsolutePreState string // File to load the absolute pre-state for Cannon traces from
CannonNetwork string
CannonRollupConfigPath string
CannonL2GenesisPath string
CannonDatadir string // Cannon Data Directory
CannonL2 string // L2 RPC Url
CannonSnapshotFreq uint // Frequency of snapshots to create when executing cannon (in VM instructions)
......@@ -119,6 +127,24 @@ func (c Config) Check() error {
if c.CannonServer == "" {
return ErrMissingCannonServer
}
if c.CannonNetwork == "" {
if c.CannonRollupConfigPath == "" {
return ErrMissingCannonRollupConfig
}
if c.CannonL2GenesisPath == "" {
return ErrMissingCannonL2Genesis
}
} else {
if c.CannonRollupConfigPath != "" {
return ErrCannonNetworkAndRollupConfig
}
if c.CannonL2GenesisPath != "" {
return ErrCannonNetworkAndL2Genesis
}
if _, ok := chaincfg.NetworksByName[c.CannonNetwork]; !ok {
return fmt.Errorf("%w: %v", ErrCannonNetworkUnknown, c.CannonNetwork)
}
}
if c.CannonAbsolutePreState == "" {
return ErrMissingCannonAbsolutePreState
}
......
......@@ -14,6 +14,7 @@ var (
validAlphabetTrace = "abcdefgh"
validCannonBin = "./bin/cannon"
validCannonOpProgramBin = "./bin/op-program"
validCannonNetwork = "mainnet"
validCannonAbsolutPreState = "pre.json"
validCannonDatadir = "/tmp/cannon"
validCannonL2 = "http://localhost:9545"
......@@ -32,6 +33,7 @@ func validConfig(traceType TraceType) Config {
cfg.CannonAbsolutePreState = validCannonAbsolutPreState
cfg.CannonDatadir = validCannonDatadir
cfg.CannonL2 = validCannonL2
cfg.CannonNetwork = validCannonNetwork
}
return cfg
}
......@@ -110,3 +112,41 @@ func TestCannonSnapshotFreq(t *testing.T) {
require.ErrorIs(t, cfg.Check(), ErrMissingCannonSnapshotFreq)
})
}
func TestCannonNetworkOrRollupConfigRequired(t *testing.T) {
cfg := validConfig(TraceTypeCannon)
cfg.CannonNetwork = ""
cfg.CannonRollupConfigPath = ""
cfg.CannonL2GenesisPath = "genesis.json"
require.ErrorIs(t, cfg.Check(), ErrMissingCannonRollupConfig)
}
func TestCannonNetworkOrL2GenesisRequired(t *testing.T) {
cfg := validConfig(TraceTypeCannon)
cfg.CannonNetwork = ""
cfg.CannonRollupConfigPath = "foo.json"
cfg.CannonL2GenesisPath = ""
require.ErrorIs(t, cfg.Check(), ErrMissingCannonL2Genesis)
}
func TestMustNotSpecifyNetworkAndRollup(t *testing.T) {
cfg := validConfig(TraceTypeCannon)
cfg.CannonNetwork = validCannonNetwork
cfg.CannonRollupConfigPath = "foo.json"
cfg.CannonL2GenesisPath = ""
require.ErrorIs(t, cfg.Check(), ErrCannonNetworkAndRollupConfig)
}
func TestMustNotSpecifyNetworkAndL2Genesis(t *testing.T) {
cfg := validConfig(TraceTypeCannon)
cfg.CannonNetwork = validCannonNetwork
cfg.CannonRollupConfigPath = ""
cfg.CannonL2GenesisPath = "foo.json"
require.ErrorIs(t, cfg.Check(), ErrCannonNetworkAndL2Genesis)
}
func TestNetworkMustBeValid(t *testing.T) {
cfg := validConfig(TraceTypeCannon)
cfg.CannonNetwork = "unknown"
require.ErrorIs(t, cfg.Check(), ErrCannonNetworkUnknown)
}
package cannon
import (
"encoding/json"
"fmt"
"os"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
func parseState(path string) (*mipsevm.State, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
}
defer file.Close()
var state mipsevm.State
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return nil, fmt.Errorf("invalid mipsevm state (%v): %w", path, err)
}
return &state, nil
}
......@@ -4,11 +4,13 @@ import (
"context"
"errors"
"fmt"
"math"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
......@@ -17,7 +19,8 @@ import (
const (
snapsDir = "snapshots"
preimagesDir = "snapshots"
preimagesDir = "preimages"
finalState = "final.json"
)
var snapshotNameRegexp = regexp.MustCompile(`^[0-9]+\.json$`)
......@@ -32,6 +35,9 @@ type Executor struct {
inputs localGameInputs
cannon string
server string
network string
rollupConfig string
l2Genesis string
absolutePreState string
dataDir string
snapshotFreq uint
......@@ -47,6 +53,9 @@ func NewExecutor(logger log.Logger, cfg *config.Config, inputs localGameInputs)
inputs: inputs,
cannon: cfg.CannonBin,
server: cfg.CannonServer,
network: cfg.CannonNetwork,
rollupConfig: cfg.CannonRollupConfigPath,
l2Genesis: cfg.CannonL2GenesisPath,
absolutePreState: cfg.CannonAbsolutePreState,
dataDir: cfg.CannonDatadir,
snapshotFreq: cfg.CannonSnapshotFreq,
......@@ -63,18 +72,23 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
}
proofDir := filepath.Join(dir, proofsDir)
dataDir := filepath.Join(e.dataDir, preimagesDir)
lastGeneratedState := filepath.Join(dir, finalState)
args := []string{
"run",
"--input", start,
"--output", filepath.Join(dir, "out.json"),
"--output", lastGeneratedState,
"--meta", "",
"--proof-at", "=" + strconv.FormatUint(i, 10),
"--stop-at", "=" + strconv.FormatUint(i+1, 10),
"--proof-fmt", filepath.Join(proofDir, "%d.json"),
"--snapshot-at", "%" + strconv.FormatUint(uint64(e.snapshotFreq), 10),
"--snapshot-fmt", filepath.Join(snapshotDir, "%d.json"),
}
if i < math.MaxUint64 {
args = append(args, "--stop-at", "="+strconv.FormatUint(i+1, 10))
}
args = append(args,
"--",
e.server,
e.server, "--server",
"--l1", e.l1,
"--l2", e.l2,
"--datadir", dataDir,
......@@ -83,6 +97,15 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
"--l2.outputroot", e.inputs.l2OutputRoot.Hex(),
"--l2.claim", e.inputs.l2Claim.Hex(),
"--l2.blocknumber", e.inputs.l2BlockNumber.Text(10),
)
if e.network != "" {
args = append(args, "--network", e.network)
}
if e.rollupConfig != "" {
args = append(args, "--rollup.config", e.rollupConfig)
}
if e.l2Genesis != "" {
args = append(args, "--l2.genesis", e.l2Genesis)
}
if err := os.MkdirAll(snapshotDir, 0755); err != nil {
......@@ -94,7 +117,7 @@ func (e *Executor) GenerateProof(ctx context.Context, dir string, i uint64) erro
if err := os.MkdirAll(proofDir, 0755); err != nil {
return fmt.Errorf("could not create proofs directory %v: %w", proofDir, err)
}
e.logger.Info("Generating trace", "proof", i, "cmd", e.cannon, "args", args)
e.logger.Info("Generating trace", "proof", i, "cmd", e.cannon, "args", strings.Join(args, ", "))
return e.cmdExecutor(ctx, e.logger.New("proof", i), e.cannon, args...)
}
......@@ -102,7 +125,8 @@ func runCmd(ctx context.Context, l log.Logger, binary string, args ...string) er
cmd := exec.CommandContext(ctx, binary, args...)
stdOut := oplog.NewWriter(l, log.LvlInfo)
defer stdOut.Close()
stdErr := oplog.NewWriter(l, log.LvlError)
// Keep stdErr at info level because cannon uses stderr for progress messages
stdErr := oplog.NewWriter(l, log.LvlInfo)
defer stdErr.Close()
cmd.Stdout = stdOut
cmd.Stderr = stdErr
......@@ -123,17 +147,17 @@ func findStartingSnapshot(logger log.Logger, snapDir string, absolutePreState st
bestSnap := uint64(0)
for _, entry := range entries {
if entry.IsDir() {
logger.Warn("Unexpected directory in snapshots dir: %v/%v", snapDir, entry.Name())
logger.Warn("Unexpected directory in snapshots dir", "parent", snapDir, "child", entry.Name())
continue
}
name := entry.Name()
if !snapshotNameRegexp.MatchString(name) {
logger.Warn("Unexpected file in snapshots dir: %v/%v", snapDir, entry.Name())
logger.Warn("Unexpected file in snapshots dir", "parent", snapDir, "child", entry.Name())
continue
}
index, err := strconv.ParseUint(name[0:len(name)-len(".json")], 10, 64)
if err != nil {
logger.Error("Unable to parse trace index of snapshot file: %v/%v", snapDir, entry.Name())
logger.Error("Unable to parse trace index of snapshot file", "parent", snapDir, "child", entry.Name())
continue
}
if index > bestSnap && index < traceIndex {
......
......@@ -3,6 +3,7 @@ package cannon
import (
"context"
"fmt"
"math"
"math/big"
"os"
"path/filepath"
......@@ -35,49 +36,90 @@ func TestGenerateProof(t *testing.T) {
l2Claim: common.Hash{0x44},
l2BlockNumber: big.NewInt(3333),
}
executor := NewExecutor(testlog.Logger(t, log.LvlInfo), &cfg, inputs)
executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) {
return input, nil
}
var binary string
var subcommand string
args := make(map[string]string)
executor.cmdExecutor = func(ctx context.Context, l log.Logger, b string, a ...string) error {
binary = b
subcommand = a[0]
for i := 1; i < len(a); i += 2 {
args[a[i]] = a[i+1]
captureExec := func(t *testing.T, cfg config.Config, proofAt uint64) (string, string, map[string]string) {
executor := NewExecutor(testlog.Logger(t, log.LvlInfo), &cfg, inputs)
executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64) (string, error) {
return input, nil
}
var binary string
var subcommand string
args := make(map[string]string)
executor.cmdExecutor = func(ctx context.Context, l log.Logger, b string, a ...string) error {
binary = b
subcommand = a[0]
for i := 1; i < len(a); {
if a[i] == "--" {
// Skip over the divider between cannon and server program
i += 1
continue
}
args[a[i]] = a[i+1]
i += 2
}
return nil
}
return nil
err := executor.GenerateProof(context.Background(), cfg.CannonDatadir, proofAt)
require.NoError(t, err)
return binary, subcommand, args
}
err := executor.GenerateProof(context.Background(), cfg.CannonDatadir, 150_000_000)
require.NoError(t, err)
require.DirExists(t, filepath.Join(cfg.CannonDatadir, preimagesDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, proofsDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, snapsDir))
require.Equal(t, cfg.CannonBin, binary)
require.Equal(t, "run", subcommand)
require.Equal(t, input, args["--input"])
require.Contains(t, args, "--meta")
require.Equal(t, "", args["--meta"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, "out.json"), args["--output"])
require.Equal(t, "=150000000", args["--proof-at"])
require.Equal(t, "=150000001", args["--stop-at"])
require.Equal(t, "%500", args["--snapshot-at"])
require.Equal(t, cfg.CannonServer, args["--"])
require.Equal(t, cfg.L1EthRpc, args["--l1"])
require.Equal(t, cfg.CannonL2, args["--l2"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, preimagesDir), args["--datadir"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, proofsDir, "%d.json"), args["--proof-fmt"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, snapsDir, "%d.json"), args["--snapshot-fmt"])
// Local game inputs
require.Equal(t, inputs.l1Head.Hex(), args["--l1.head"])
require.Equal(t, inputs.l2Head.Hex(), args["--l2.head"])
require.Equal(t, inputs.l2OutputRoot.Hex(), args["--l2.outputroot"])
require.Equal(t, inputs.l2Claim.Hex(), args["--l2.claim"])
require.Equal(t, "3333", args["--l2.blocknumber"])
t.Run("Network", func(t *testing.T) {
cfg.CannonNetwork = "mainnet"
cfg.CannonRollupConfigPath = ""
cfg.CannonL2GenesisPath = ""
binary, subcommand, args := captureExec(t, cfg, 150_000_000)
require.DirExists(t, filepath.Join(cfg.CannonDatadir, preimagesDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, proofsDir))
require.DirExists(t, filepath.Join(cfg.CannonDatadir, snapsDir))
require.Equal(t, cfg.CannonBin, binary)
require.Equal(t, "run", subcommand)
require.Equal(t, input, args["--input"])
require.Contains(t, args, "--meta")
require.Equal(t, "", args["--meta"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, finalState), args["--output"])
require.Equal(t, "=150000000", args["--proof-at"])
require.Equal(t, "=150000001", args["--stop-at"])
require.Equal(t, "%500", args["--snapshot-at"])
// Slight quirk of how we pair off args
// The server binary winds up as the key and the first arg --server as the value which has no value
// Then everything else pairs off correctly again
require.Equal(t, "--server", args[cfg.CannonServer])
require.Equal(t, cfg.L1EthRpc, args["--l1"])
require.Equal(t, cfg.CannonL2, args["--l2"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, preimagesDir), args["--datadir"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, proofsDir, "%d.json"), args["--proof-fmt"])
require.Equal(t, filepath.Join(cfg.CannonDatadir, snapsDir, "%d.json"), args["--snapshot-fmt"])
require.Equal(t, cfg.CannonNetwork, args["--network"])
require.NotContains(t, args, "--rollup.config")
require.NotContains(t, args, "--l2.genesis")
// Local game inputs
require.Equal(t, inputs.l1Head.Hex(), args["--l1.head"])
require.Equal(t, inputs.l2Head.Hex(), args["--l2.head"])
require.Equal(t, inputs.l2OutputRoot.Hex(), args["--l2.outputroot"])
require.Equal(t, inputs.l2Claim.Hex(), args["--l2.claim"])
require.Equal(t, "3333", args["--l2.blocknumber"])
})
t.Run("RollupAndGenesis", func(t *testing.T) {
cfg.CannonNetwork = ""
cfg.CannonRollupConfigPath = "rollup.json"
cfg.CannonL2GenesisPath = "genesis.json"
_, _, args := captureExec(t, cfg, 150_000_000)
require.NotContains(t, args, "--network")
require.Equal(t, cfg.CannonRollupConfigPath, args["--rollup.config"])
require.Equal(t, cfg.CannonL2GenesisPath, args["--l2.genesis"])
})
t.Run("NoStopAtWhenProofIsMaxUInt", func(t *testing.T) {
cfg.CannonNetwork = "mainnet"
cfg.CannonRollupConfigPath = "rollup.json"
cfg.CannonL2GenesisPath = "genesis.json"
_, _, args := captureExec(t, cfg, math.MaxUint64)
// stop-at would need to be one more than the proof step which would overflow back to 0
// so expect that it will be omitted. We'll ultimately want cannon to execute until the program exits.
require.NotContains(t, args, "--stop-at")
})
}
func TestRunCmdLogsOutput(t *testing.T) {
......
......@@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
......@@ -38,9 +39,14 @@ type ProofGenerator interface {
}
type CannonTraceProvider struct {
logger log.Logger
dir string
prestate string
generator ProofGenerator
// lastStep stores the last step in the actual trace if known. 0 indicates unknown.
// Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace.
lastStep uint64
}
func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller) (*CannonTraceProvider, error) {
......@@ -58,6 +64,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
return nil, fmt.Errorf("fetch local game inputs: %w", err)
}
return &CannonTraceProvider{
logger: logger,
dir: cfg.CannonDatadir,
prestate: cfg.CannonAbsolutePreState,
generator: NewExecutor(logger, cfg, l1Head),
......@@ -65,7 +72,7 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
}
func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) {
proof, err := p.loadProof(ctx, i)
proof, err := p.loadProofData(ctx, i)
if err != nil {
return nil, err
}
......@@ -74,10 +81,14 @@ func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*typ
}
func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) {
proof, err := p.loadProof(ctx, i)
proof, state, err := p.loadProof(ctx, i)
if err != nil {
return common.Hash{}, err
}
if proof == nil && state != nil {
// Use the hash from the final state
return crypto.Keccak256Hash(state.EncodeWitness()), nil
}
value := common.BytesToHash(proof.ClaimValue)
if value == (common.Hash{}) {
......@@ -87,7 +98,7 @@ func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, e
}
func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte, []byte, error) {
proof, err := p.loadProof(ctx, i)
proof, err := p.loadProofData(ctx, i)
if err != nil {
return nil, nil, err
}
......@@ -104,37 +115,77 @@ func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte
func (p *CannonTraceProvider) AbsolutePreState(ctx context.Context) ([]byte, error) {
path := filepath.Join(p.dir, p.prestate)
file, err := os.Open(path)
state, err := parseState(path)
if err != nil {
return []byte{}, fmt.Errorf("cannot open state file (%v): %w", path, err)
return []byte{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
defer file.Close()
var state mipsevm.State
err = json.NewDecoder(file).Decode(&state)
return state.EncodeWitness(), nil
}
// loadProofData loads the proof data for the specified step.
// If the requested index is beyond the end of the actual trace, the proof data from the last step is returned.
// Cannon will be executed a second time if required to generate the full proof data.
func (p *CannonTraceProvider) loadProofData(ctx context.Context, i uint64) (*proofData, error) {
proof, state, err := p.loadProof(ctx, i)
if err != nil {
return []byte{}, fmt.Errorf("invalid mipsevm state (%v): %w", path, err)
return nil, err
} else if proof == nil && state != nil {
p.logger.Info("Re-executing to generate proof for last step", "step", state.Step)
proof, _, err = p.loadProof(ctx, state.Step)
if err != nil {
return nil, err
}
if proof == nil {
return nil, fmt.Errorf("proof at step %v was not generated", i)
}
return proof, nil
}
return state.EncodeWitness(), nil
return proof, nil
}
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, error) {
// loadProof will attempt to load or generate the proof data at the specified index
// If the requested index is beyond the end of the actual trace:
// - When the actual trace length is known, the proof data from the last step is returned with nil state
// - When the actual trace length is not yet know, the state from after the last step is returned with nil proofData
// and the actual trace length is cached for future runs
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, *mipsevm.State, error) {
if p.lastStep != 0 && i > p.lastStep {
// If the requested index is after the last step in the actual trace, use the last step
i = p.lastStep
}
path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json", i))
file, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil {
return nil, fmt.Errorf("generate cannon trace with proof at %v: %w", i, err)
return nil, nil, fmt.Errorf("generate cannon trace with proof at %v: %w", i, err)
}
// Try opening the file again now and it should exist.
file, err = os.Open(path)
if errors.Is(err, os.ErrNotExist) {
// Expected proof wasn't generated, check if we reached the end of execution
state, err := parseState(filepath.Join(p.dir, finalState))
if err != nil {
return nil, nil, fmt.Errorf("cannot read final state: %w", err)
}
if state.Exited && state.Step < i {
p.logger.Warn("Requested proof was after the program exited", "proof", i, "last", state.Step)
// The final instruction has already been applied to this state, so the last step we can execute
// is one before its Step value.
p.lastStep = state.Step - 1
return nil, state, nil
} else {
return nil, nil, fmt.Errorf("expected proof not generated but final state was not exited, requested step %v, final state at step %v", i, state.Step)
}
}
}
if err != nil {
return nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
return nil, nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
}
defer file.Close()
var proof proofData
err = json.NewDecoder(file).Decode(&proof)
if err != nil {
return nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
return nil, nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
}
return &proof, nil
return &proof, nil, nil
}
......@@ -4,12 +4,17 @@ import (
"context"
"embed"
_ "embed"
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
......@@ -19,29 +24,35 @@ var testData embed.FS
func TestGet(t *testing.T) {
dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
provider, generator := setupWithTestData(t, dataDir, prestate)
value, err := provider.Get(context.Background(), 0)
require.NoError(t, err)
require.Equal(t, common.HexToHash("0x45fd9aa59768331c726e719e76aa343e73123af888804604785ae19506e65e87"), value)
require.Empty(t, generator.generated)
})
t.Run("ProofUnavailable", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
_, err := provider.Get(context.Background(), 7)
require.ErrorIs(t, err, os.ErrNotExist)
require.Contains(t, generator.generated, 7, "should have tried to generate the proof")
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
Step: 10,
Exited: true,
}
value, err := provider.Get(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Equal(t, crypto.Keccak256Hash(generator.finalState.EncodeWitness()), value)
})
t.Run("MissingPostHash", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
provider, generator := setupWithTestData(t, dataDir, prestate)
_, err := provider.Get(context.Background(), 1)
require.ErrorContains(t, err, "missing post hash")
require.Empty(t, generator.generated)
})
t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
provider, generator := setupWithTestData(t, dataDir, prestate)
value, err := provider.Get(context.Background(), 2)
require.NoError(t, err)
expected := common.HexToHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
......@@ -53,7 +64,7 @@ func TestGet(t *testing.T) {
func TestGetOracleData(t *testing.T) {
dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
provider, generator := setupWithTestData(t, dataDir, prestate)
oracleData, err := provider.GetOracleData(context.Background(), 420)
require.NoError(t, err)
require.False(t, oracleData.IsLocal)
......@@ -64,15 +75,32 @@ func TestGetOracleData(t *testing.T) {
require.Empty(t, generator.generated)
})
t.Run("ProofUnavailable", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
_, err := provider.GetOracleData(context.Background(), 7)
require.ErrorIs(t, err, os.ErrNotExist)
require.Contains(t, generator.generated, 7, "should have tried to generate the proof")
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
Step: 10,
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa}.Bytes(),
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
oracleData, err := provider.GetOracleData(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Contains(t, generator.generated, 9, "should have regenerated proof from last step")
require.False(t, oracleData.IsLocal)
require.EqualValues(t, generator.proof.OracleKey, oracleData.OracleKey)
require.EqualValues(t, generator.proof.OracleValue, oracleData.OracleData)
})
t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
provider, generator := setupWithTestData(t, dataDir, prestate)
oracleData, err := provider.GetOracleData(context.Background(), 421)
require.NoError(t, err)
require.False(t, oracleData.IsLocal)
......@@ -87,7 +115,7 @@ func TestGetOracleData(t *testing.T) {
func TestGetPreimage(t *testing.T) {
dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, err := provider.GetPreimage(context.Background(), 0)
require.NoError(t, err)
expected := common.Hex2Bytes("b8f068de604c85ea0e2acd437cdb47add074a2d70b81d018390c504b71fe26f400000000000000000000000000000000000000000000000000000000000000000000000000")
......@@ -97,22 +125,38 @@ func TestGetPreimage(t *testing.T) {
require.Empty(t, generator.generated)
})
t.Run("ProofUnavailable", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
_, _, err := provider.GetPreimage(context.Background(), 7)
require.ErrorIs(t, err, os.ErrNotExist)
require.Contains(t, generator.generated, 7, "should have tried to generate the proof")
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
Step: 10,
Exited: true,
}
generator.proof = &proofData{
ClaimValue: common.Hash{0xaa}.Bytes(),
StateData: []byte{0xbb},
ProofData: []byte{0xcc},
OracleKey: common.Hash{0xdd}.Bytes(),
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
preimage, proof, err := provider.GetPreimage(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Contains(t, generator.generated, 9, "should have regenerated proof from last step")
require.EqualValues(t, generator.proof.StateData, preimage)
require.EqualValues(t, generator.proof.ProofData, proof)
})
t.Run("MissingStateData", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
provider, generator := setupWithTestData(t, dataDir, prestate)
_, _, err := provider.GetPreimage(context.Background(), 1)
require.ErrorContains(t, err, "missing state data")
require.Empty(t, generator.generated)
})
t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(dataDir, prestate)
provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, err := provider.GetPreimage(context.Background(), 2)
require.NoError(t, err)
expected := common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")
......@@ -130,21 +174,21 @@ func TestAbsolutePreState(t *testing.T) {
prestate := "state.json"
t.Run("StateUnavailable", func(t *testing.T) {
provider, _ := setupWithTestData("/dir/does/not/exist", prestate)
provider, _ := setupWithTestData(t, "/dir/does/not/exist", prestate)
_, err := provider.AbsolutePreState(context.Background())
require.ErrorIs(t, err, os.ErrNotExist)
})
t.Run("InvalidStateFile", func(t *testing.T) {
setupPreState(t, dataDir, "invalid.json")
provider, _ := setupWithTestData(dataDir, prestate)
provider, _ := setupWithTestData(t, dataDir, prestate)
_, err := provider.AbsolutePreState(context.Background())
require.ErrorContains(t, err, "invalid mipsevm state")
})
t.Run("ExpectedAbsolutePreState", func(t *testing.T) {
setupPreState(t, dataDir, "state.json")
provider, _ := setupWithTestData(dataDir, prestate)
provider, _ := setupWithTestData(t, dataDir, prestate)
preState, err := provider.AbsolutePreState(context.Background())
require.NoError(t, err)
state := mipsevm.State{
......@@ -190,9 +234,10 @@ func setupTestData(t *testing.T) (string, string) {
return dataDir, "state.json"
}
func setupWithTestData(dataDir string, prestate string) (*CannonTraceProvider, *stubGenerator) {
func setupWithTestData(t *testing.T, dataDir string, prestate string) (*CannonTraceProvider, *stubGenerator) {
generator := &stubGenerator{}
return &CannonTraceProvider{
logger: testlog.Logger(t, log.LvlInfo),
dir: dataDir,
generator: generator,
prestate: prestate,
......@@ -200,10 +245,28 @@ func setupWithTestData(dataDir string, prestate string) (*CannonTraceProvider, *
}
type stubGenerator struct {
generated []int // Using int makes assertions easier
generated []int // Using int makes assertions easier
finalState *mipsevm.State
proof *proofData
}
func (e *stubGenerator) GenerateProof(ctx context.Context, dir string, i uint64) error {
e.generated = append(e.generated, int(i))
if e.finalState != nil && e.finalState.Step <= i {
// Requesting a trace index past the end of the trace
data, err := json.Marshal(e.finalState)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(dir, finalState), data, 0644)
}
if e.proof != nil {
proofFile := filepath.Join(dir, proofsDir, fmt.Sprintf("%d.json", i))
data, err := json.Marshal(e.proof)
if err != nil {
return err
}
return os.WriteFile(proofFile, data, 0644)
}
return nil
}
......@@ -5,6 +5,7 @@ import (
"strings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
opservice "github.com/ethereum-optimism/optimism/op-service"
openum "github.com/ethereum-optimism/optimism/op-service/enum"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
......@@ -58,6 +59,21 @@ var (
Usage: "Correct Alphabet Trace (alphabet trace type only)",
EnvVars: prefixEnvVars("ALPHABET"),
}
CannonNetworkFlag = &cli.StringFlag{
Name: "cannon-network",
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s (cannon trace type only)", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVars: prefixEnvVars("CANNON_NETWORK"),
}
CannonRollupConfigFlag = &cli.StringFlag{
Name: "cannon-rollup-config",
Usage: "Rollup chain parameters (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_ROLLUP_CONFIG"),
}
CannonL2GenesisFlag = &cli.StringFlag{
Name: "cannon-l2-genesis",
Usage: "Path to the op-geth genesis file (cannon trace type only)",
EnvVars: prefixEnvVars("CANNON_L2_GENESIS"),
}
CannonBinFlag = &cli.StringFlag{
Name: "cannon-bin",
Usage: "Path to cannon executable to use when generating trace data (cannon trace type only)",
......@@ -103,6 +119,9 @@ var requiredFlags = []cli.Flag{
// optionalFlags is a list of unchecked cli flags
var optionalFlags = []cli.Flag{
AlphabetFlag,
CannonNetworkFlag,
CannonRollupConfigFlag,
CannonL2GenesisFlag,
CannonBinFlag,
CannonServerFlag,
CannonPreStateFlag,
......@@ -130,6 +149,14 @@ func CheckRequired(ctx *cli.Context) error {
gameType := config.TraceType(strings.ToLower(ctx.String(TraceTypeFlag.Name)))
switch gameType {
case config.TraceTypeCannon:
if !ctx.IsSet(CannonNetworkFlag.Name) && !(ctx.IsSet(CannonRollupConfigFlag.Name) && ctx.IsSet(CannonL2GenesisFlag.Name)) {
return fmt.Errorf("flag %v or %v and %v is required",
CannonNetworkFlag.Name, CannonRollupConfigFlag.Name, CannonL2GenesisFlag.Name)
}
if ctx.IsSet(CannonNetworkFlag.Name) && (ctx.IsSet(CannonRollupConfigFlag.Name) || ctx.IsSet(CannonL2GenesisFlag.Name)) {
return fmt.Errorf("flag %v can not be used with %v and %v",
CannonNetworkFlag.Name, CannonRollupConfigFlag.Name, CannonL2GenesisFlag.Name)
}
if !ctx.IsSet(CannonBinFlag.Name) {
return fmt.Errorf("flag %s is required", CannonBinFlag.Name)
}
......@@ -175,6 +202,9 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
TraceType: traceTypeFlag,
GameAddress: dgfAddress,
AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonNetwork: ctx.String(CannonNetworkFlag.Name),
CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name),
CannonL2GenesisPath: ctx.String(CannonL2GenesisFlag.Name),
CannonBin: ctx.String(CannonBinFlag.Name),
CannonServer: ctx.String(CannonServerFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
......
#!/bin/bash
set -euo pipefail
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$DIR"
make
cd ..
make devnet-clean
make devnet-up-deploy
DEVNET_SPONSOR="ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
DISPUTE_GAME_PROXY="0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
CHARLIE_ADDRESS="0xF45B7537828CB2fffBC69996B054c2Aaf36DC778"
CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7"
MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4"
MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
echo "----------------------------------------------------------------"
echo " - Fetching balance of the sponsor"
echo " - Balance: $(cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)"
echo "----------------------------------------------------------------"
echo "Funding Charlie"
cast send $CHARLIE_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR
echo "Funding Mallory"
cast send $MALLORY_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR
# Fault game type = 0
GAME_TYPE=0
# Root claim commits to the entire trace.
# Alphabet game claim construction: keccak256(abi.encode(trace_index, trace[trace_index]))
ROOT_CLAIM=$(cast keccak $(cast abi-encode "f(uint256,uint256)" 15 122))
# Extra data is a dynamic `bytes` type that contains the L2 Block Number of the output proposal that the root claim disagrees with
# Doesn't matter right now since we're not deleting outputs, so just set it to 1
EXTRA_DATA=$(cast to-bytes32 1)
echo "Initializing the game"
cast call --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA
cast send --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA
#!/bin/bash
set -euo pipefail
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$DIR"
SOURCE_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
CHALLENGER_DIR=$(echo ${SOURCE_DIR%/*/*})
DISPUTE_GAME_PROXY="0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
# Check that the fault game address file exists
FAULT_GAME_ADDR_FILE="$CHALLENGER_DIR/.fault-game-address"
if [[ ! -f "$FAULT_GAME_ADDR_FILE" ]]; then
echo "Game not initialized, exiting..."
exit 1
fi
CHARLIE_ADDRESS="0xF45B7537828CB2fffBC69996B054c2Aaf36DC778"
# Charlie's Address: 0xF45B7537828CB2fffBC69996B054c2Aaf36DC778
CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7"
MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4"
MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
FAULT_GAME_ADDRESS=$(cat $FAULT_GAME_ADDR_FILE)
echo "Fault dispute game address: $FAULT_GAME_ADDRESS"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
PREIMAGE_ORACLE_ADDRESS="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"
./bin/op-challenger \
$CHALLENGER_DIR/bin/op-challenger \
--l1-eth-rpc http://localhost:8545 \
--trace-type="alphabet" \
--alphabet "abcdefgh" \
--game-address $FAULT_GAME_ADDRESS \
--preimage-oracle-address $PREIMAGE_ORACLE_ADDRESS \
--private-key $CHARLIE_KEY \
--num-confirmations 1 \
--game-depth 4 \
......
#!/bin/bash
set -euo pipefail
SOURCE_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
CHALLENGER_DIR=$(echo ${SOURCE_DIR%/*/*})
MONOREPO_DIR=$(echo ${SOURCE_DIR%/*/*/*})
cd $CHALLENGER_DIR
make
cd $MONOREPO_DIR
make devnet-clean
make cannon-prestate
make devnet-up
DEVNET_SPONSOR="ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
DISPUTE_GAME_PROXY=$(jq -r .DisputeGameFactoryProxy $MONOREPO_DIR/.devnet/addresses.json)
echo "----------------------------------------------------------------"
echo " Dispute Game Factory at $DISPUTE_GAME_PROXY"
echo "----------------------------------------------------------------"
L2_OUTPUT_ORACLE_PROXY=$(jq -r .L2OutputOracleProxy $MONOREPO_DIR/.devnet/addresses.json)
echo "----------------------------------------------------------------"
echo " L2 Output Oracle Proxy at $L2_OUTPUT_ORACLE_PROXY"
echo "----------------------------------------------------------------"
BLOCK_ORACLE_PROXY=$(jq -r .BlockOracle $MONOREPO_DIR/.devnet/addresses.json)
echo "----------------------------------------------------------------"
echo " Block Oracle Proxy at $BLOCK_ORACLE_PROXY"
echo "----------------------------------------------------------------"
CHARLIE_ADDRESS="0xF45B7537828CB2fffBC69996B054c2Aaf36DC778"
CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7"
MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4"
MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
echo "----------------------------------------------------------------"
echo " - Fetching balance of the sponsor"
echo " - Balance: $(cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)"
echo "----------------------------------------------------------------"
echo "Funding Charlie"
cast send $CHARLIE_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR
echo "Funding Mallory"
cast send $MALLORY_ADDRESS --value 5ether --private-key $DEVNET_SPONSOR
# Loop and wait until there are at least 2 outputs in the l2 output oracle
echo "Waiting until 2 output proposals are in the l2 output oracle..."
echo "NOTE: This may show errors if no output proposals are in the oracle yet."
while [[ $(cast call $L2_OUTPUT_ORACLE_PROXY "latestOutputIndex()" | cast to-dec) -lt 2 ]]
do
echo "[BLOCK: $(cast block-number)] Waiting for output proposals..."
sleep 2
done
# Fetch the latest block number
L2_BLOCK_NUMBER=$(cast call $L2_OUTPUT_ORACLE_PROXY "latestBlockNumber()")
echo "Using the latest L2OO block number: $L2_BLOCK_NUMBER"
# We will use the l2 block number of 1 for the dispute game.
# We need to check that the block oracle contains the corresponding l1 block number.
echo "Checkpointing the block oracle..."
L1_CHECKPOINT=$(cast send --private-key $DEVNET_SPONSOR $BLOCK_ORACLE_PROXY "checkpoint()" --json | jq -r .blockNumber | cast to-dec)
((L1_CHECKPOINT=L1_CHECKPOINT-1))
echo "L1 Checkpoint: $L1_CHECKPOINT"
INDEX=$(cast call $L2_OUTPUT_ORACLE_PROXY "getL2OutputIndexAfter(uint256)" $L2_BLOCK_NUMBER | cast to-dec)
((PRIOR_INDEX=INDEX-1))
echo "Getting the l2 output at index $PRIOR_INDEX"
cast call $L2_OUTPUT_ORACLE_PROXY "getL2Output(uint256)" $PRIOR_INDEX
echo "Getting the l2 output at index $INDEX"
cast call $L2_OUTPUT_ORACLE_PROXY "getL2Output(uint256)" $INDEX
# (Alphabet) Fault game type = 0
GAME_TYPE=0
# Root claim commits to the entire trace.
# Alphabet game claim construction: keccak256(abi.encode(trace_index, trace[trace_index]))
ROOT_CLAIM=$(cast keccak $(cast abi-encode "f(uint256,uint256)" 15 122))
# Fault dispute game extra data is calculated as follows.
# abi.encode(uint256(l2_block_number), uint256(l1 checkpoint))
EXTRA_DATA=$(cast abi-encode "f(uint256,uint256)" $L2_BLOCK_NUMBER $L1_CHECKPOINT)
echo "Initializing the game"
FAULT_GAME_ADDRESS=$(cast call --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA)
echo "Creating game at address $FAULT_GAME_ADDRESS"
cast send --private-key $MALLORY_KEY $DISPUTE_GAME_PROXY "create(uint8,bytes32,bytes)" $GAME_TYPE $ROOT_CLAIM $EXTRA_DATA
FORMATTED_ADDRESS=$(cast parse-bytes32-address $FAULT_GAME_ADDRESS)
echo "Formatted Address: $FORMATTED_ADDRESS"
echo $FORMATTED_ADDRESS > $CHALLENGER_DIR/.fault-game-address
#!/bin/bash
set -euo pipefail
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$DIR"
SOURCE_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
CHALLENGER_DIR=$(echo ${SOURCE_DIR%/*/*})
DISPUTE_GAME_PROXY="0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
# Check that the fault game address file exists
FAULT_GAME_ADDR_FILE="$CHALLENGER_DIR/.fault-game-address"
if [[ ! -f "$FAULT_GAME_ADDR_FILE" ]]; then
echo "Game not initialized, exiting..."
exit 1
fi
CHARLIE_ADDRESS="0xF45B7537828CB2fffBC69996B054c2Aaf36DC778"
CHARLIE_KEY="74feb147d72bfae943e6b4e483410933d9e447d5dc47d52432dcc2c1454dabb7"
MALLORY_ADDRESS="0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4"
# Mallory's Address: 0x4641c704a6c743f73ee1f36C7568Fbf4b80681e4
MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
PREIMAGE_ORACLE_ADDRESS="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"
FAULT_GAME_ADDRESS=$(cat $FAULT_GAME_ADDR_FILE)
echo "Fault dispute game address: $FAULT_GAME_ADDRESS"
./bin/op-challenger \
$CHALLENGER_DIR/bin/op-challenger \
--l1-eth-rpc http://localhost:8545 \
--trace-type="alphabet" \
--alphabet "abcdexyz" \
--game-address $FAULT_GAME_ADDRESS \
--preimage-oracle-address $PREIMAGE_ORACLE_ADDRESS \
--private-key $MALLORY_KEY \
--num-confirmations 1 \
--game-depth 4 \
......
#!/bin/bash
# set -x
trap killgroup SIGINT
killgroup(){
echo killing...
kill 0
}
$1 | sed "s/^/[$2] /" &
$3 | sed "s/^/[$4] /" &
wait
#!/bin/bash
set -euo pipefail
if [ $# -eq 0 ]
then
echo "Missing Fault Dispute Game address argument"
fi
echo ""
echo "Visualize the fault dispute game at https://dispute.clab.by/game?addr=$1"
echo ""
DISPUTE_GAME_PROXY=$(jq .DisputeGameFactoryProxy .devnet/addresses.json)
DISPUTE_GAME_PROXY=$(echo $DISPUTE_GAME_PROXY | tr -d '"')
echo "----------------------------------------------------------------"
echo " Dispute Game Factory at $DISPUTE_GAME_PROXY"
echo "----------------------------------------------------------------"
FAULT_GAME_ADDRESS=$1
DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
DIR=$(echo ${DIR%/*/*})
cd $DIR/packages/contracts-bedrock
forge script scripts/FaultDisputeGameViz.s.sol \
--sig "remote(address)" $FAULT_GAME_ADDRESS \
--fork-url http://localhost:8545
mv dispute_game.svg "$dir"
#!/bin/bash
set -euo pipefail
DISPUTE_GAME_PROXY="0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
dir=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
cd "$dir"
cd ../packages/contracts-bedrock
forge script scripts/FaultDisputeGameViz.s.sol --sig "remote(address)" $FAULT_GAME_ADDRESS --fork-url http://localhost:8545
mv dispute_game.svg "$dir"
......@@ -2,16 +2,21 @@ package disputegame
import (
"context"
"encoding/json"
"os"
"path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/core"
)
type CannonGameHelper struct {
FaultGameHelper
}
func (g *CannonGameHelper) StartChallenger(ctx context.Context, l1Endpoint string, l2Endpoint string, name string, options ...challenger.Option) *challenger.Helper {
func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollup.Config, l2Genesis *core.Genesis, l1Endpoint string, l2Endpoint string, name string, options ...challenger.Option) *challenger.Helper {
opts := []challenger.Option{
func(c *config.Config) {
c.GameAddress = g.addr
......@@ -24,6 +29,18 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, l1Endpoint strin
c.CannonServer = "../op-program/bin/op-program"
c.CannonAbsolutePreState = "../op-program/bin/prestate.json"
c.CannonSnapshotFreq = config.DefaultCannonSnapshotFreq
genesisBytes, err := json.Marshal(l2Genesis)
g.require.NoError(err, "marshall l2 genesis config")
genesisFile := filepath.Join(c.CannonDatadir, "l2-genesis.json")
g.require.NoError(os.WriteFile(genesisFile, genesisBytes, 0644))
c.CannonL2GenesisPath = genesisFile
rollupBytes, err := json.Marshal(rollupCfg)
g.require.NoError(err, "marshall rollup config")
rollupFile := filepath.Join(c.CannonDatadir, "rollup.json")
g.require.NoError(os.WriteFile(rollupFile, rollupBytes, 0644))
c.CannonRollupConfigPath = rollupFile
},
}
opts = append(opts, options...)
......
......@@ -33,7 +33,7 @@ func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
}
func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, time.Minute)
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
err := utils.WaitFor(ctx, time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
......
......@@ -145,7 +145,6 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
}
func TestCannonDisputeGame(t *testing.T) {
t.Skip("CLI-4290: op-challenger doesn't handle trace extension correctly for cannon")
InitParallel(t)
ctx := context.Background()
......@@ -156,7 +155,7 @@ func TestCannonDisputeGame(t *testing.T) {
game := disputeGameFactory.StartCannonGame(ctx, common.Hash{0xaa})
require.NotNil(t, game)
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), sys.NodeEndpoint("sequencer"), "Challenger", func(c *config.Config) {
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("l1"), sys.NodeEndpoint("sequencer"), "Challenger", func(c *config.Config) {
c.AgreeWithProposedOutput = true // Agree with the proposed output, so disagree with the root claim
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
})
......@@ -173,8 +172,10 @@ func TestCannonDisputeGame(t *testing.T) {
func startFaultDisputeSystem(t *testing.T) (*System, *ethclient.Client) {
cfg := DefaultSystemConfig(t)
delete(cfg.Nodes, "verifier")
cfg.DeployConfig.SequencerWindowSize = 4
cfg.DeployConfig.FinalizationPeriodSeconds = 2
cfg.SupportL1TimeTravel = true
cfg.DeployConfig.L2OutputOracleSubmissionInterval = 2
cfg.DeployConfig.L2OutputOracleSubmissionInterval = 1
cfg.NonFinalizedProposals = true // Submit output proposals asap
sys, err := cfg.Start()
require.NoError(t, err, "Error starting up system")
......
......@@ -409,7 +409,7 @@ func TestMixedWithdrawalValidity(t *testing.T) {
l2Verif := sys.Clients["verifier"]
require.NoError(t, err)
systemConfig, err := bindings.NewSystemConfigCaller(cfg.L1Deployments.SystemConfig, l1Client)
systemConfig, err := bindings.NewSystemConfigCaller(cfg.L1Deployments.SystemConfigProxy, l1Client)
require.NoError(t, err)
unsafeBlockSigner, err := systemConfig.UnsafeBlockSigner(nil)
require.NoError(t, err)
......
......@@ -18,21 +18,25 @@ import (
const agreedBlockTrailingDistance = 100
func main() {
if len(os.Args) != 3 {
if len(os.Args) < 3 {
_, _ = fmt.Fprintln(os.Stderr, "Must specify L1 RPC URL and L2 RPC URL as arguments")
os.Exit(2)
}
l1RpcUrl := os.Args[1]
l2RpcUrl := os.Args[2]
l1RpcKind := "alchemy"
if len(os.Args) > 3 {
l1RpcKind = os.Args[3]
}
goerliOutputAddress := common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0")
err := Run(l1RpcUrl, l2RpcUrl, goerliOutputAddress)
err := Run(l1RpcUrl, l1RpcKind, l2RpcUrl, goerliOutputAddress)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed: %v\n", err.Error())
os.Exit(1)
}
}
func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
func Run(l1RpcUrl string, l1RpcKind string, l2RpcUrl string, l2OracleAddr common.Address) error {
ctx := context.Background()
l1RpcClient, err := rpc.Dial(l1RpcUrl)
if err != nil {
......@@ -150,7 +154,7 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
}
fmt.Printf("Configuration: %s\n", args)
fmt.Println("Running in online mode")
err = runFaultProofProgram(ctx, append(args, "--l1", l1RpcUrl, "--l2", l2RpcUrl))
err = runFaultProofProgram(ctx, append(args, "--l1", l1RpcUrl, "--l2", l2RpcUrl, "--l1.rpckind", l1RpcKind))
if err != nil {
return fmt.Errorf("online mode failed: %w", err)
}
......
......@@ -11,10 +11,11 @@ RUN apt-get update && \
chmod +x ./rustup.sh && \
./rustup.sh -y
COPY ./.foundryrc ./.foundryrc
COPY ./.abigenrc ./.abigenrc
# Only diff from upstream docker image is this clone instead
# of COPY. We select a specific commit to use.
COPY ./.foundryrc ./.foundryrc
RUN git clone https://github.com/foundry-rs/foundry.git ./foundry \
&& cd foundry && git checkout $(cat ../.foundryrc)
......@@ -39,7 +40,6 @@ ENV DEBIAN_FRONTEND=noninteractive
COPY --from=foundry-build /opt/foundry/target/release/forge /usr/local/bin/forge
COPY --from=foundry-build /opt/foundry/target/release/cast /usr/local/bin/cast
COPY --from=foundry-build /opt/foundry/target/release/anvil /usr/local/bin/anvil
COPY --from=geth /usr/local/bin/abigen /usr/local/bin/abigen
COPY --from=echidna-test /usr/local/bin/echidna-test /usr/local/bin/echidna-test
RUN apt-get update && \
......@@ -56,6 +56,9 @@ RUN apt-get update && \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.3 && \
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | bash
# Install the specific version of abigen from .abigenrc
RUN go install github.com/ethereum/go-ethereum/cmd/abigen@$(cat .abigenrc)
# We need to isntall yarn because a bespoke dependency installed from github https://codeload.github.com/Saw-mon-and-Natalie/clones-with-immutable-arg needs it
# it is installed from github which means it's postpack script which uses yarn is ran when unpacked into node_modules
RUN echo "downloading pnpm and yarn" && npm i -g pnpm && npm i -g yarn@1 && pnpm --version && yarn --version
......
This diff is collapsed.
......@@ -89,6 +89,7 @@
| batcherHash | bytes32 | 103 | 0 | 32 | src/L1/SystemConfig.sol:SystemConfig |
| gasLimit | uint64 | 104 | 0 | 8 | src/L1/SystemConfig.sol:SystemConfig |
| _resourceConfig | struct ResourceMetering.ResourceConfig | 105 | 0 | 32 | src/L1/SystemConfig.sol:SystemConfig |
| startBlock | uint256 | 106 | 0 | 32 | src/L1/SystemConfig.sol:SystemConfig |
=======================
➡ src/legacy/DeployerWhitelist.sol:DeployerWhitelist
......
......@@ -45,5 +45,6 @@
"l2GenesisRegolithTimeOffset": "0x0",
"faultGameAbsolutePrestate": "0x41c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 4,
"faultGameMaxDuration": 300
"faultGameMaxDuration": 300,
"systemConfigStartBlock": 0
}
# `SystemConfig` Invariants
## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound.
**Test:** [`SystemConfig.t.sol#L44`](../test/invariants/SystemConfig.t.sol#L44)
**Test:** [`SystemConfig.t.sol#L65`](../test/invariants/SystemConfig.t.sol#L65)
......@@ -44,6 +44,6 @@
"solhint": "^3.4.1",
"solhint-plugin-prettier": "^0.0.5",
"ts-node": "^10.9.1",
"typescript": "^4.9.3"
"typescript": "^5.1.6"
}
}
......@@ -385,32 +385,30 @@ contract Deploy is Deployer {
/// @notice Deploy the SystemConfig
function deploySystemConfig() broadcast() public returns (address) {
bytes32 batcherHash = bytes32(uint256(uint160(cfg.batchSenderAddress())));
SystemConfig config = new SystemConfig({
_owner: cfg.finalSystemOwner(),
_overhead: cfg.gasPriceOracleOverhead(),
_scalar: cfg.gasPriceOracleScalar(),
_batcherHash: batcherHash,
_gasLimit: uint64(cfg.l2GenesisBlockGasLimit()),
_unsafeBlockSigner: cfg.p2pSequencerAddress(),
_config: Constants.DEFAULT_RESOURCE_CONFIG()
});
SystemConfig config = new SystemConfig();
require(config.owner() == cfg.finalSystemOwner());
require(config.overhead() == cfg.gasPriceOracleOverhead());
require(config.scalar() == cfg.gasPriceOracleScalar());
require(config.unsafeBlockSigner() == cfg.p2pSequencerAddress());
require(config.batcherHash() == batcherHash);
require(config.owner() == address(0xdEaD));
require(config.overhead() == 0);
require(config.scalar() == 0);
require(config.unsafeBlockSigner() == address(0));
require(config.batcherHash() == bytes32(0));
require(config.gasLimit() == 1);
ResourceMetering.ResourceConfig memory rconfig = Constants.DEFAULT_RESOURCE_CONFIG();
ResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig();
require(resourceConfig.maxResourceLimit == rconfig.maxResourceLimit);
require(resourceConfig.elasticityMultiplier == rconfig.elasticityMultiplier);
require(resourceConfig.baseFeeMaxChangeDenominator == rconfig.baseFeeMaxChangeDenominator);
require(resourceConfig.systemTxMaxGas == rconfig.systemTxMaxGas);
require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee);
require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee);
require(resourceConfig.maxResourceLimit == 1);
require(resourceConfig.elasticityMultiplier == 1);
require(resourceConfig.baseFeeMaxChangeDenominator == 2);
require(resourceConfig.systemTxMaxGas == 0);
require(resourceConfig.minimumBaseFee == 0);
require(resourceConfig.maximumBaseFee == 0);
require(config.l1ERC721Bridge() == address(0));
require(config.l1StandardBridge() == address(0));
require(config.l2OutputOracle() == address(0));
require(config.optimismPortal() == address(0));
require(config.l1CrossDomainMessenger() == address(0));
require(config.optimismMintableERC20Factory() == address(0));
require(config.startBlock() == 0);
save("SystemConfig", address(config));
console.log("SystemConfig deployed at %s", address(config));
......@@ -498,7 +496,17 @@ contract Deploy is Deployer {
batcherHash,
uint64(cfg.l2GenesisBlockGasLimit()),
cfg.p2pSequencerAddress(),
Constants.DEFAULT_RESOURCE_CONFIG()
Constants.DEFAULT_RESOURCE_CONFIG(),
cfg.systemConfigStartBlock(),
cfg.batchInboxAddress(),
SystemConfig.Addresses({
l1CrossDomainMessenger: mustGetAddress("L1CrossDomainMessengerProxy"),
l1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"),
l1StandardBridge: mustGetAddress("L1StandardBridgeProxy"),
l2OutputOracle: mustGetAddress("L2OutputOracleProxy"),
optimismPortal: mustGetAddress("OptimismPortalProxy"),
optimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy")
})
)
)
});
......@@ -522,6 +530,12 @@ contract Deploy is Deployer {
require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee);
require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee);
require(config.l1ERC721Bridge() == mustGetAddress("L1ERC721BridgeProxy"));
require(config.l1StandardBridge() == mustGetAddress("L1StandardBridgeProxy"));
require(config.l2OutputOracle() == mustGetAddress("L2OutputOracleProxy"));
require(config.optimismPortal() == mustGetAddress("OptimismPortalProxy"));
require(config.l1CrossDomainMessenger() == mustGetAddress("L1CrossDomainMessengerProxy"));
require(config.startBlock() == cfg.systemConfigStartBlock());
}
/// @notice Initialize the L1StandardBridge
......
......@@ -47,6 +47,7 @@ contract DeployConfig is Script {
uint256 public faultGameAbsolutePrestate;
uint256 public faultGameMaxDepth;
uint256 public faultGameMaxDuration;
uint256 public systemConfigStartBlock;
constructor(string memory _path) {
console.log("DeployConfig: reading file %s", _path);
......@@ -87,6 +88,7 @@ contract DeployConfig is Script {
gasPriceOracleScalar = stdJson.readUint(_json, "$.gasPriceOracleScalar");
eip1559Denominator = stdJson.readUint(_json, "$.eip1559Denominator");
eip1559Elasticity = stdJson.readUint(_json, "$.eip1559Elasticity");
systemConfigStartBlock = stdJson.readUint(_json, "$.systemConfigStartBlock");
if (block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet) {
faultGameAbsolutePrestate = stdJson.readUint(_json, "$.faultGameAbsolutePrestate");
......
......@@ -4,7 +4,7 @@
"src/L1/L1StandardBridge.sol": "0xa35dc0ab143043063c3bff73c8b065e401c23296a2017258ce8a87f4a4da9416",
"src/L1/L2OutputOracle.sol": "0x8f32ccb4c5cb63a459a0e79ee412177dc03fd279fdaaf1dac69e8c714902e857",
"src/L1/OptimismPortal.sol": "0xeaa47a63e8a3bcfdb7dfd3e6c8608369e34e362d9de82f3acf13cbc27c070bf7",
"src/L1/SystemConfig.sol": "0x8e2b5103d2eb93b74af2e2f96a4505e637cdc3c44d80cf5ec2eca70060e1deff",
"src/L1/SystemConfig.sol": "0x689a3339e9167e62fac105eaae0390b39fc7dfee21b19c5f78636d98ee97148e",
"src/L2/BaseFeeVault.sol": "0xa596e60762f16192cfa86459fcb9f4da9d8f756106eedac643a1ffeafbbfcc5f",
"src/L2/GasPriceOracle.sol": "0xc735a8bf01ad8bca194345748537bfd9924909c0342bc133c4a31e2fb8cb9882",
"src/L2/L1Block.sol": "0x7fbfc8b4da630386636c665570321fdec287b0867dbe0f91c2e7cd5b7697c220",
......
......@@ -202,17 +202,38 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
function setUp() public virtual override {
super.setUp();
ResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG();
systemConfig = new SystemConfig({
_owner: address(1),
_overhead: 0,
_scalar: 10000,
_batcherHash: bytes32(0),
_gasLimit: 30_000_000,
_unsafeBlockSigner: address(0),
_config: config
});
Proxy systemConfigProxy = new Proxy(multisig);
SystemConfig systemConfigImpl = new SystemConfig();
vm.prank(multisig);
systemConfigProxy.upgradeToAndCall(
address(systemConfigImpl),
abi.encodeCall(
SystemConfig.initialize,
(
address(1), //_owner,
0, //_overhead,
10000, //_scalar,
bytes32(0), //_batcherHash,
30_000_000, //_gasLimit,
address(0), //_unsafeBlockSigner,
Constants.DEFAULT_RESOURCE_CONFIG(), //_config,
0, //_startBlock
address(0xff), // _batchInbox
SystemConfig.Addresses({ // _addresses
l1CrossDomainMessenger: address(0),
l1ERC721Bridge: address(0),
l1StandardBridge: address(0),
l2OutputOracle: address(oracle),
optimismPortal: address(op),
optimismMintableERC20Factory: address(0)
})
)
)
);
systemConfig = SystemConfig(address(systemConfigProxy));
opImpl = new OptimismPortal();
......
......@@ -3,24 +3,45 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { SystemConfig } from "../../src/L1/SystemConfig.sol";
import { Proxy } from "../../src/universal/Proxy.sol";
import { ResourceMetering } from "../../src/L1/ResourceMetering.sol";
import { Constants } from "../../src/libraries/Constants.sol";
contract SystemConfig_GasLimitLowerBound_Invariant is Test {
SystemConfig public config;
function setUp() public {
ResourceMetering.ResourceConfig memory cfg = Constants.DEFAULT_RESOURCE_CONFIG();
function setUp() external {
Proxy proxy = new Proxy(msg.sender);
SystemConfig configImpl = new SystemConfig();
config = new SystemConfig({
_owner: address(0xbeef),
_overhead: 2100,
_scalar: 1000000,
_batcherHash: bytes32(hex"abcd"),
_gasLimit: 30_000_000,
_unsafeBlockSigner: address(1),
_config: cfg
});
vm.prank(msg.sender);
proxy.upgradeToAndCall(
address(configImpl),
abi.encodeCall(
configImpl.initialize,
(
address(0xbeef), // owner
2100, // overhead
1000000, // scalar
bytes32(hex"abcd"), // batcher hash
30_000_000, // gas limit
address(1), // unsafe block signer
Constants.DEFAULT_RESOURCE_CONFIG(), // resource config
0, //_startBlock
address(0), // _batchInbox
SystemConfig.Addresses({ // _addrs
l1CrossDomainMessenger: address(0),
l1ERC721Bridge: address(0),
l1StandardBridge: address(0),
l2OutputOracle: address(0),
optimismPortal: address(0),
optimismMintableERC20Factory: address(0)
})
)
)
);
config = SystemConfig(address(proxy));
// Set the target contract to the `config`
targetContract(address(config));
......
......@@ -10,8 +10,8 @@
},
"homepage": "https://optimism.io",
"type": "module",
"main": "dist/constants.js",
"module": "dist/constants.mjs",
"main": "dist/constants.cjs",
"module": "dist/constants.js",
"types": "src/constants.ts",
"exports": {
".": {
......@@ -51,7 +51,7 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react-hooks": "^8.0.1",
"@types/glob": "^8.1.0",
"@vitest/coverage-istanbul": "^0.33.0",
"@vitest/coverage-istanbul": "^0.34.1",
"@wagmi/cli": "^1.3.0",
"@wagmi/core": "^1.3.8",
"abitype": "^0.9.3",
......
......@@ -10,8 +10,16 @@
},
"homepage": "https://optimism.io",
"type": "module",
"main": "dist/estimateFees.js",
"module": "dist/estimateFees.mjs",
"main": "dist/estimateFees.cjs",
"module": "dist/estimateFees.js",
"exports": {
".": {
"import": "./dist/estimateFees.js",
"require": "./dist/estimateFees.cjs",
"default": "./dist/estimateFees.js",
"types": "./src/estimateFees.ts"
}
},
"types": "src/estimateFees.ts",
"files": [
"dist/",
......@@ -29,7 +37,7 @@
"@eth-optimism/contracts-ts": "workspace:^",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react-hooks": "^8.0.1",
"@vitest/coverage-istanbul": "^0.33.0",
"@vitest/coverage-istanbul": "^0.34.1",
"abitype": "^0.9.3",
"isomorphic-fetch": "^3.0.0",
"jest-dom": "link:@types/@testing-library/jest-dom",
......
This diff is collapsed.
......@@ -48,7 +48,7 @@ to each of the different game types. For specification of dispute game types, se
### `GameType.FAULT`
> **Warning**
> **Warning**
> The `FAULT` game type is not yet implemented. In the first iteration of Optimism's decentralization effort,
> challengers will respond to `ATTESTATION` games only.
......@@ -59,7 +59,7 @@ to each of the different game types. For specification of dispute game types, se
**Events and Responses**
- [`L2OutputOracle.OutputProposed`](../packages/contracts-bedrock/contracts/L1/L2OutputOracle.sol#L57-70)
- [`L2OutputOracle.OutputProposed`](../packages/contracts-bedrock/src/L1/L2OutputOracle.sol#L57-70)
The `L2OutputOracle` contract emits this event when a new output is proposed on the data availability
layer. Each time an output is proposed, the Challenger should check to see if the output is equal
the output given by the `optimism_outputAtBlock` endpoint of their `rollup-node`.
......@@ -85,7 +85,7 @@ A full diagram and lifecycle of the Challenger's role in the `ATTESTATION` game
**TODO**
> **Warning**
> **Warning**
> The `VALIDITY` game type is not yet implemented. In the first iteration of Optimism's decentralization effort,
> challengers will respond to `ATTESTATION` games only. A validity proof based dispute game is a possibility,
> but fault proof based dispute games will be the primary focus of the team in the near future.
......
......@@ -306,7 +306,7 @@ The contract has the following solidity interface, and can be interacted with ac
A reference implementation of the L1 Attributes predeploy contract can be found in [L1Block.sol].
[L1Block.sol]: ../packages/contracts-bedrock/contracts/L2/L1Block.sol
[L1Block.sol]: ../packages/contracts-bedrock/src/L2/L1Block.sol
After running `pnpm build` in the `packages/contracts` directory, the bytecode to add to the genesis
file will be located in the `deployedBytecode` field of the build artifacts file at
......
......@@ -70,7 +70,7 @@ or `Bedrock`. Deprecated contracts should not be used.
## LegacyMessagePasser
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/legacy/LegacyMessagePasser.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/legacy/LegacyMessagePasser.sol)
Address: `0x4200000000000000000000000000000000000000`
......@@ -92,7 +92,7 @@ finalized.
## L2ToL1MessagePasser
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L2ToL1MessagePasser.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol)
Address: `0x4200000000000000000000000000000000000016`
......@@ -106,7 +106,7 @@ permissionlessly removed from the L2 supply by calling the `burn()` function.
## DeployerWhitelist
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/legacy/DeployerWhitelist.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/legacy/DeployerWhitelist.sol)
Address: `0x4200000000000000000000000000000000000002`
......@@ -125,7 +125,7 @@ This contract is deprecated and its usage should be avoided.
## LegacyERC20ETH
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/legacy/LegacyERC20ETH.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/legacy/LegacyERC20ETH.sol)
Address: `0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000`
......@@ -141,7 +141,7 @@ This contract is deprecated and its usage should be avoided.
## WETH9
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/vendor/WETH9.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/vendor/WETH9.sol)
Address: `0x4200000000000000000000000000000000000006`
......@@ -151,7 +151,7 @@ deterministic address across Optimism based networks.
## L2CrossDomainMessenger
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L2CrossDomainMessenger.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol)
Address: `0x4200000000000000000000000000000000000007`
......@@ -170,7 +170,7 @@ domain through the remote domain's `relayMessage` function.
## L2StandardBridge
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L2StandardBridge.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2StandardBridge.sol)
Address: `0x4200000000000000000000000000000000000010`
......@@ -197,7 +197,7 @@ withdrawn to L1.
## L1BlockNumber
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/legacy/L1BlockNumber.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/legacy/L1BlockNumber.sol)
Address: `0x4200000000000000000000000000000000000013`
......@@ -210,7 +210,7 @@ L1 on L2.
## GasPriceOracle
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/GasPriceOracle.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/GasPriceOracle.sol)
Address: `0x420000000000000000000000000000000000000F`
......@@ -240,7 +240,7 @@ has been hardcoded to 6.
## L1Block
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L1Block.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L1Block.sol)
Address: `0x4200000000000000000000000000000000000015`
......@@ -251,7 +251,7 @@ maintaining L1 context in L2. This allows for L1 state to be accessed in L2.
## ProxyAdmin
[ProxyAdmin](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/universal/ProxyAdmin.sol)
[ProxyAdmin](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/ProxyAdmin.sol)
Address: `0x4200000000000000000000000000000000000018`
The `ProxyAdmin` is the owner of all of the proxy contracts set at the
......@@ -260,7 +260,7 @@ have the ability to upgrade any of the other predeploy contracts.
## SequencerFeeVault
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/SequencerFeeVault.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SequencerFeeVault.sol)
Address: `0x4200000000000000000000000000000000000011`
......@@ -273,7 +273,7 @@ upgraded by changing its proxy's implementation key.
## OptimismMintableERC20Factory
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/universal/OptimismMintableERC20Factory.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol)
Address: `0x4200000000000000000000000000000000000012`
......@@ -286,7 +286,7 @@ and burn tokens, depending on if the user is depositing from L1 to L2 or withdra
## OptimismMintableERC721Factory
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/universal/OptimismMintableERC721Factory.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC721Factory.sol)
Address: `0x4200000000000000000000000000000000000017`
......@@ -295,7 +295,7 @@ depositing native L1 NFTs into.
## BaseFeeVault
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/BaseFeeVault.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/BaseFeeVault.sol)
Address: `0x4200000000000000000000000000000000000019`
......@@ -306,7 +306,7 @@ L1.
## L1FeeVault
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L1FeeVault.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L1FeeVault.sol)
Address: `0x420000000000000000000000000000000000001a`
......@@ -316,7 +316,7 @@ withdrawn to an immutable address on L1.
## SchemaRegistry
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/EAS/SchemaRegistry.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/EAS/SchemaRegistry.sol)
Address: `0x4200000000000000000000000000000000000020`
......@@ -325,7 +325,7 @@ protocol.
## EAS
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/EAS/EAS.sol)
[Implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/EAS/EAS.sol)
Address: `0x4200000000000000000000000000000000000021`
......
......@@ -214,5 +214,5 @@ whether or not it was 'supposed' to fail, and whether or not it should be 'repla
minimize complexity, we have not provided any replay functionality, this may be implemented in external utility
contracts if desired.
[`WithdrawalTransaction` type]: https://github.com/ethereum-optimism/optimism/blob/6c6d142d7bb95faa11066aab5d8aed7187abfe38/packages/contracts-bedrock/contracts/libraries/Types.sol#L76-L83
[`OutputRootProof` type]: https://github.com/ethereum-optimism/optimism/blob/6c6d142d7bb95faa11066aab5d8aed7187abfe38/packages/contracts-bedrock/contracts/libraries/Types.sol#L33-L38
[`WithdrawalTransaction` type]: https://github.com/ethereum-optimism/optimism/blob/08daf8dbd38c9ffdbd18fc9a211c227606cdb0ad/packages/contracts-bedrock/src/libraries/Types.sol#L62-L69
[`OutputRootProof` type]: https://github.com/ethereum-optimism/optimism/blob/08daf8dbd38c9ffdbd18fc9a211c227606cdb0ad/packages/contracts-bedrock/src/libraries/Types.sol#L25-L30
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