Commit 1a391b16 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into remove-auth-check

parents a0e8417b 44491387
FROM golang:1.18.0-alpine3.15 as builder FROM golang:1.19.9-alpine3.16 as builder
COPY ./endpoint-monitor /app
WORKDIR /app
RUN apk --no-cache add make jq bash git alpine-sdk RUN apk --no-cache add make jq bash git alpine-sdk
COPY ./endpoint-monitor /app/endpoint-monitor
COPY ./op-service /app/op-service
COPY ./op-node /app/op-node
COPY ./go.mod /app/go.mod
COPY ./go.sum /app/go.sum
COPY ./.git /app/.git
WORKDIR /app/endpoint-monitor
RUN go mod download
RUN make build RUN make build
FROM alpine:3.15 FROM alpine:3.16
RUN apk --no-cache add ca-certificates RUN apk --no-cache add ca-certificates
RUN addgroup -S app && adduser -S app -G app RUN addgroup -S app && adduser -S app -G app
USER app USER app
WORKDIR /app WORKDIR /app
COPY --from=builder /app/bin/endpoint-monitor /app COPY --from=builder /app/endpoint-monitor/bin/endpoint-monitor /app
ENTRYPOINT ["/app/endpoint-monitor"] ENTRYPOINT ["/app/endpoint-monitor"]
...@@ -4,7 +4,7 @@ The endpoint-monitor runs websocket checks on edge-proxyd endpoints and downstre ...@@ -4,7 +4,7 @@ The endpoint-monitor runs websocket checks on edge-proxyd endpoints and downstre
## Setup ## Setup
Install go1.18 Install go1.19
```bash ```bash
make build make build
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
endpointMonitor "github.com/ethereum-optimism/optimism/endpoint-monitor" endpointMonitor "github.com/ethereum-optimism/optimism/endpoint-monitor"
) )
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
type ProviderConfig struct { type ProviderConfig struct {
...@@ -24,24 +24,27 @@ const ( ...@@ -24,24 +24,27 @@ const (
) )
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
prefixEnvVars := func(name string) []string {
return opservice.PrefixEnvVar(envPrefix, name)
}
flags := []cli.Flag{ flags := []cli.Flag{
cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: ProvidersFlagName, Name: ProvidersFlagName,
Usage: "List of providers", Usage: "List of providers",
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envPrefix, "PROVIDERS"), EnvVars: prefixEnvVars("PROVIDERS"),
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: CheckIntervalFlagName, Name: CheckIntervalFlagName,
Usage: "Check interval duration", Usage: "Check interval duration",
Value: 5 * time.Minute, Value: 5 * time.Minute,
EnvVar: opservice.PrefixEnvVar(envPrefix, "CHECK_INTERVAL"), EnvVars: prefixEnvVars("CHECK_INTERVAL"),
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: CheckDurationFlagName, Name: CheckDurationFlagName,
Usage: "Check duration", Usage: "Check duration",
Value: 4 * time.Minute, Value: 4 * time.Minute,
EnvVar: opservice.PrefixEnvVar(envPrefix, "CHECK_DURATION"), EnvVars: prefixEnvVars("CHECK_DURATION"),
}, },
} }
flags = append(flags, opmetrics.CLIFlags(envPrefix)...) flags = append(flags, opmetrics.CLIFlags(envPrefix)...)
...@@ -73,9 +76,9 @@ func (c Config) Check() error { ...@@ -73,9 +76,9 @@ func (c Config) Check() error {
func NewConfig(ctx *cli.Context) Config { func NewConfig(ctx *cli.Context) Config {
return Config{ return Config{
Providers: ctx.GlobalStringSlice(ProvidersFlagName), Providers: ctx.StringSlice(ProvidersFlagName),
CheckInterval: ctx.GlobalDuration(CheckIntervalFlagName), CheckInterval: ctx.Duration(CheckIntervalFlagName),
CheckDuration: ctx.GlobalDuration(CheckDurationFlagName), CheckDuration: ctx.Duration(CheckDurationFlagName),
LogConfig: oplog.ReadCLIConfig(ctx), LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx), MetricsConfig: opmetrics.ReadCLIConfig(ctx),
} }
......
...@@ -11,10 +11,10 @@ import ( ...@@ -11,10 +11,10 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/l2geth/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum-optimism/optimism/l2geth/ethclient" "github.com/ethereum/go-ethereum/ethclient"
) )
var ( var (
......
module github.com/ethereum-optimism/optimism/endpoint-monitor
go 1.18
require (
github.com/ethereum-optimism/optimism/l2geth v0.0.0-20220923210602-7121648c1f26
github.com/ethereum-optimism/optimism/op-service v0.8.8
github.com/ethereum/go-ethereum v1.10.26
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.13.0
github.com/urfave/cli v1.22.9
)
replace github.com/ethereum-optimism/optimism/l2geth v0.0.0-20220923210602-7121648c1f26 => github.com/ethereum-optimism/optimism-legacy/l2geth v0.0.0-20220923210602-7121648c1f26
require (
github.com/VictoriaMetrics/fastcache v1.9.0 // indirect
github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.22.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/elastic/gosigar v0.12.0 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rs/cors v1.8.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/sys v0.0.0-20220701225701-179beb0bd1a1 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
)
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -26,11 +26,11 @@ require ( ...@@ -26,11 +26,11 @@ require (
github.com/multiformats/go-multiaddr v0.8.0 github.com/multiformats/go-multiaddr v0.8.0
github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.7.0 github.com/pkg/profile v1.7.0
github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_golang v1.14.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.9 github.com/urfave/cli/v2 v2.25.7
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
golang.org/x/crypto v0.6.0 golang.org/x/crypto v0.6.0
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
golang.org/x/sync v0.1.0 golang.org/x/sync v0.1.0
...@@ -134,7 +134,6 @@ require ( ...@@ -134,7 +134,6 @@ require (
github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/common v0.39.0 // indirect
......
...@@ -10,7 +10,7 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy ...@@ -10,7 +10,7 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
...@@ -681,10 +681,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT ...@@ -681,10 +681,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q=
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
_ "net/http/pprof" _ "net/http/pprof"
gethrpc "github.com/ethereum/go-ethereum/rpc" gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/metrics"
......
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/compressor"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
...@@ -118,17 +118,17 @@ func (c CLIConfig) Check() error { ...@@ -118,17 +118,17 @@ func (c CLIConfig) Check() error {
func NewConfig(ctx *cli.Context) CLIConfig { func NewConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
/* Required Flags */ /* Required Flags */
L1EthRpc: ctx.GlobalString(flags.L1EthRpcFlag.Name), L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name),
L2EthRpc: ctx.GlobalString(flags.L2EthRpcFlag.Name), L2EthRpc: ctx.String(flags.L2EthRpcFlag.Name),
RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name), RollupRpc: ctx.String(flags.RollupRpcFlag.Name),
SubSafetyMargin: ctx.GlobalUint64(flags.SubSafetyMarginFlag.Name), SubSafetyMargin: ctx.Uint64(flags.SubSafetyMarginFlag.Name),
PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name), PollInterval: ctx.Duration(flags.PollIntervalFlag.Name),
/* Optional Flags */ /* Optional Flags */
MaxPendingTransactions: ctx.GlobalUint64(flags.MaxPendingTransactionsFlag.Name), MaxPendingTransactions: ctx.Uint64(flags.MaxPendingTransactionsFlag.Name),
MaxChannelDuration: ctx.GlobalUint64(flags.MaxChannelDurationFlag.Name), MaxChannelDuration: ctx.Uint64(flags.MaxChannelDurationFlag.Name),
MaxL1TxSize: ctx.GlobalUint64(flags.MaxL1TxSizeBytesFlag.Name), MaxL1TxSize: ctx.Uint64(flags.MaxL1TxSizeBytesFlag.Name),
Stopped: ctx.GlobalBool(flags.StoppedFlag.Name), Stopped: ctx.Bool(flags.StoppedFlag.Name),
TxMgrConfig: txmgr.ReadCLIConfig(ctx), TxMgrConfig: txmgr.ReadCLIConfig(ctx),
RPCConfig: rpc.ReadCLIConfig(ctx), RPCConfig: rpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx), LogConfig: oplog.ReadCLIConfig(ctx),
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var Subcommands = cli.Commands{ var Subcommands = cli.Commands{
...@@ -16,7 +16,7 @@ var Subcommands = cli.Commands{ ...@@ -16,7 +16,7 @@ var Subcommands = cli.Commands{
Name: "metrics", Name: "metrics",
Usage: "Dumps a list of supported metrics to stdout", Usage: "Dumps a list of supported metrics to stdout",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "format", Name: "format",
Value: "markdown", Value: "markdown",
Usage: "Output format (json|markdown)", Usage: "Output format (json|markdown)",
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/batcher" "github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/ethereum-optimism/optimism/op-batcher/cmd/doc" "github.com/ethereum-optimism/optimism/op-batcher/cmd/doc"
...@@ -29,7 +29,7 @@ func main() { ...@@ -29,7 +29,7 @@ func main() {
app.Usage = "Batch Submitter Service" app.Usage = "Batch Submitter Service"
app.Description = "Service for generating and submitting L2 tx batches to L1" app.Description = "Service for generating and submitting L2 tx batches to L1"
app.Action = curryMain(Version) app.Action = curryMain(Version)
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "doc", Name: "doc",
Subcommands: doc.Subcommands, Subcommands: doc.Subcommands,
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"strings" "strings"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const ( const (
...@@ -16,28 +16,28 @@ const ( ...@@ -16,28 +16,28 @@ const (
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
cli.Uint64Flag{ &cli.Uint64Flag{
Name: TargetL1TxSizeBytesFlagName, Name: TargetL1TxSizeBytesFlagName,
Usage: "The target size of a batch tx submitted to L1.", Usage: "The target size of a batch tx submitted to L1.",
Value: 100_000, Value: 100_000,
EnvVar: opservice.PrefixEnvVar(envPrefix, "TARGET_L1_TX_SIZE_BYTES"), EnvVars: opservice.PrefixEnvVar(envPrefix, "TARGET_L1_TX_SIZE_BYTES"),
}, },
cli.IntFlag{ &cli.IntFlag{
Name: TargetNumFramesFlagName, Name: TargetNumFramesFlagName,
Usage: "The target number of frames to create per channel", Usage: "The target number of frames to create per channel",
Value: 1, Value: 1,
EnvVar: opservice.PrefixEnvVar(envPrefix, "TARGET_NUM_FRAMES"), EnvVars: opservice.PrefixEnvVar(envPrefix, "TARGET_NUM_FRAMES"),
}, },
cli.Float64Flag{ &cli.Float64Flag{
Name: ApproxComprRatioFlagName, Name: ApproxComprRatioFlagName,
Usage: "The approximate compression ratio (<= 1.0)", Usage: "The approximate compression ratio (<= 1.0)",
Value: 0.4, Value: 0.4,
EnvVar: opservice.PrefixEnvVar(envPrefix, "APPROX_COMPR_RATIO"), EnvVars: opservice.PrefixEnvVar(envPrefix, "APPROX_COMPR_RATIO"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: KindFlagName, Name: KindFlagName,
Usage: "The type of compressor. Valid options: " + strings.Join(KindKeys, ", "), Usage: "The type of compressor. Valid options: " + strings.Join(KindKeys, ", "),
EnvVar: opservice.PrefixEnvVar(envPrefix, "COMPRESSOR"), EnvVars: opservice.PrefixEnvVar(envPrefix, "COMPRESSOR"),
Value: RatioKind, Value: RatioKind,
}, },
} }
...@@ -70,9 +70,9 @@ func (c *CLIConfig) Config() Config { ...@@ -70,9 +70,9 @@ func (c *CLIConfig) Config() Config {
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
Kind: ctx.GlobalString(KindFlagName), Kind: ctx.String(KindFlagName),
TargetL1TxSizeBytes: ctx.GlobalUint64(TargetL1TxSizeBytesFlagName), TargetL1TxSizeBytes: ctx.Uint64(TargetL1TxSizeBytesFlagName),
TargetNumFrames: ctx.GlobalInt(TargetNumFramesFlagName), TargetNumFrames: ctx.Int(TargetNumFramesFlagName),
ApproxComprRatio: ctx.GlobalFloat64(ApproxComprRatioFlagName), ApproxComprRatio: ctx.Float64(ApproxComprRatioFlagName),
} }
} }
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/compressor"
"github.com/ethereum-optimism/optimism/op-batcher/rpc" "github.com/ethereum-optimism/optimism/op-batcher/rpc"
...@@ -18,60 +18,64 @@ import ( ...@@ -18,60 +18,64 @@ import (
const EnvVarPrefix = "OP_BATCHER" const EnvVarPrefix = "OP_BATCHER"
func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(EnvVarPrefix, name)
}
var ( var (
// Required flags // Required flags
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = &cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1", Usage: "HTTP provider URL for L1",
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L1_ETH_RPC"), EnvVars: prefixEnvVars("L1_ETH_RPC"),
} }
L2EthRpcFlag = cli.StringFlag{ L2EthRpcFlag = &cli.StringFlag{
Name: "l2-eth-rpc", Name: "l2-eth-rpc",
Usage: "HTTP provider URL for L2 execution engine", Usage: "HTTP provider URL for L2 execution engine",
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L2_ETH_RPC"), EnvVars: prefixEnvVars("L2_ETH_RPC"),
} }
RollupRpcFlag = cli.StringFlag{ RollupRpcFlag = &cli.StringFlag{
Name: "rollup-rpc", Name: "rollup-rpc",
Usage: "HTTP provider URL for Rollup node", Usage: "HTTP provider URL for Rollup node",
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_RPC"), EnvVars: prefixEnvVars("ROLLUP_RPC"),
} }
// Optional flags // Optional flags
SubSafetyMarginFlag = cli.Uint64Flag{ SubSafetyMarginFlag = &cli.Uint64Flag{
Name: "sub-safety-margin", Name: "sub-safety-margin",
Usage: "The batcher tx submission safety margin (in #L1-blocks) to subtract " + Usage: "The batcher tx submission safety margin (in #L1-blocks) to subtract " +
"from a channel's timeout and sequencing window, to guarantee safe inclusion " + "from a channel's timeout and sequencing window, to guarantee safe inclusion " +
"of a channel on L1.", "of a channel on L1.",
Value: 10, Value: 10,
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "SUB_SAFETY_MARGIN"), EnvVars: prefixEnvVars("SUB_SAFETY_MARGIN"),
} }
PollIntervalFlag = cli.DurationFlag{ PollIntervalFlag = &cli.DurationFlag{
Name: "poll-interval", Name: "poll-interval",
Usage: "How frequently to poll L2 for new blocks", Usage: "How frequently to poll L2 for new blocks",
Value: 6 * time.Second, Value: 6 * time.Second,
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "POLL_INTERVAL"), EnvVars: prefixEnvVars("POLL_INTERVAL"),
} }
MaxPendingTransactionsFlag = cli.Uint64Flag{ MaxPendingTransactionsFlag = &cli.Uint64Flag{
Name: "max-pending-tx", Name: "max-pending-tx",
Usage: "The maximum number of pending transactions. 0 for no limit.", Usage: "The maximum number of pending transactions. 0 for no limit.",
Value: 1, Value: 1,
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_PENDING_TX"), EnvVars: prefixEnvVars("MAX_PENDING_TX"),
} }
MaxChannelDurationFlag = cli.Uint64Flag{ MaxChannelDurationFlag = &cli.Uint64Flag{
Name: "max-channel-duration", Name: "max-channel-duration",
Usage: "The maximum duration of L1-blocks to keep a channel open. 0 to disable.", Usage: "The maximum duration of L1-blocks to keep a channel open. 0 to disable.",
Value: 0, Value: 0,
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_CHANNEL_DURATION"), EnvVars: prefixEnvVars("MAX_CHANNEL_DURATION"),
} }
MaxL1TxSizeBytesFlag = cli.Uint64Flag{ MaxL1TxSizeBytesFlag = &cli.Uint64Flag{
Name: "max-l1-tx-size-bytes", Name: "max-l1-tx-size-bytes",
Usage: "The maximum size of a batch tx submitted to L1.", Usage: "The maximum size of a batch tx submitted to L1.",
Value: 120_000, Value: 120_000,
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_L1_TX_SIZE_BYTES"), EnvVars: prefixEnvVars("MAX_L1_TX_SIZE_BYTES"),
} }
StoppedFlag = cli.BoolFlag{ StoppedFlag = &cli.BoolFlag{
Name: "stopped", Name: "stopped",
Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC", Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC",
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "STOPPED"), EnvVars: prefixEnvVars("STOPPED"),
} }
// Legacy Flags // Legacy Flags
SequencerHDPathFlag = txmgr.SequencerHDPathFlag SequencerHDPathFlag = txmgr.SequencerHDPathFlag
...@@ -110,8 +114,8 @@ var Flags []cli.Flag ...@@ -110,8 +114,8 @@ var Flags []cli.Flag
func CheckRequired(ctx *cli.Context) error { func CheckRequired(ctx *cli.Context) error {
for _, f := range requiredFlags { for _, f := range requiredFlags {
if !ctx.GlobalIsSet(f.GetName()) { if !ctx.IsSet(f.Names()[0]) {
return fmt.Errorf("flag %s is required", f.GetName()) return fmt.Errorf("flag %s is required", f.Names()[0])
} }
} }
return nil return nil
......
package rpc package rpc
import ( import (
"github.com/urfave/cli" "github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
...@@ -13,10 +13,10 @@ const ( ...@@ -13,10 +13,10 @@ const (
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: EnableAdminFlagName, Name: EnableAdminFlagName,
Usage: "Enable the admin API (experimental)", Usage: "Enable the admin API (experimental)",
EnvVar: opservice.PrefixEnvVar(envPrefix, "RPC_ENABLE_ADMIN"), EnvVars: opservice.PrefixEnvVar(envPrefix, "RPC_ENABLE_ADMIN"),
}, },
} }
} }
...@@ -29,6 +29,6 @@ type CLIConfig struct { ...@@ -29,6 +29,6 @@ type CLIConfig struct {
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
CLIConfig: oprpc.ReadCLIConfig(ctx), CLIConfig: oprpc.ReadCLIConfig(ctx),
EnableAdmin: ctx.GlobalBool(EnableAdminFlagName), EnableAdmin: ctx.Bool(EnableAdminFlagName),
} }
} }
...@@ -16,7 +16,7 @@ import ( ...@@ -16,7 +16,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
type gossipNoop struct{} type gossipNoop struct{}
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bootnode/bootnode" "github.com/ethereum-optimism/optimism/op-bootnode/bootnode"
"github.com/ethereum-optimism/optimism/op-bootnode/flags" "github.com/ethereum-optimism/optimism/op-bootnode/flags"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func main() { func main() {
......
...@@ -8,21 +8,25 @@ import ( ...@@ -8,21 +8,25 @@ import (
"github.com/ethereum-optimism/optimism/op-node/flags" "github.com/ethereum-optimism/optimism/op-node/flags"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const envVarPrefix = "OP_BOOTNODE" const envVarPrefix = "OP_BOOTNODE"
func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(envVarPrefix, name)
}
var ( var (
RollupConfig = cli.StringFlag{ RollupConfig = &cli.StringFlag{
Name: flags.RollupConfig.Name, Name: flags.RollupConfig.Name,
Usage: "Rollup chain parameters", Usage: "Rollup chain parameters",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_CONFIG"), EnvVars: prefixEnvVars("ROLLUP_CONFIG"),
} }
Network = cli.StringFlag{ Network = &cli.StringFlag{
Name: flags.Network.Name, Name: flags.Network.Name,
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")), Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "NETWORK"), EnvVars: prefixEnvVars("NETWORK"),
} }
) )
......
...@@ -244,6 +244,11 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -244,6 +244,11 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
return nil, err return nil, err
} }
if config.FundDevAccounts {
FundDevAccounts(stateDB)
SetPrecompileBalances(stateDB)
}
for _, dep := range deployments { for _, dep := range deployments {
st, err := stateDB.StorageTrie(dep.Address) st, err := stateDB.StorageTrie(dep.Address)
if err != nil { if err != nil {
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"os" "os"
log "github.com/ethereum/go-ethereum/log" log "github.com/ethereum/go-ethereum/log"
cli "github.com/urfave/cli" cli "github.com/urfave/cli/v2"
watch "github.com/ethereum-optimism/optimism/op-challenger/cmd/watch" watch "github.com/ethereum-optimism/optimism/op-challenger/cmd/watch"
config "github.com/ethereum-optimism/optimism/op-challenger/config" config "github.com/ethereum-optimism/optimism/op-challenger/config"
...@@ -70,7 +70,7 @@ func run(args []string, action ConfigAction) error { ...@@ -70,7 +70,7 @@ func run(args []string, action ConfigAction) error {
} }
return action(logger, VersionWithMeta, cfg) return action(logger, VersionWithMeta, cfg)
} }
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "watch", Name: "watch",
Subcommands: watch.Subcommands, Subcommands: watch.Subcommands,
......
package watch package watch
import ( import (
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
) )
......
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
flags "github.com/ethereum-optimism/optimism/op-challenger/flags" flags "github.com/ethereum-optimism/optimism/op-challenger/flags"
...@@ -141,19 +141,19 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -141,19 +141,19 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
if err := flags.CheckRequired(ctx); err != nil { if err := flags.CheckRequired(ctx); err != nil {
return nil, err return nil, err
} }
l1EthRpc := ctx.GlobalString(flags.L1EthRpcFlag.Name) l1EthRpc := ctx.String(flags.L1EthRpcFlag.Name)
if l1EthRpc == "" { if l1EthRpc == "" {
return nil, ErrMissingL1EthRPC return nil, ErrMissingL1EthRPC
} }
rollupRpc := ctx.GlobalString(flags.RollupRpcFlag.Name) rollupRpc := ctx.String(flags.RollupRpcFlag.Name)
if rollupRpc == "" { if rollupRpc == "" {
return nil, ErrMissingRollupRpc return nil, ErrMissingRollupRpc
} }
l2ooAddress, err := opservice.ParseAddress(ctx.GlobalString(flags.L2OOAddressFlag.Name)) l2ooAddress, err := opservice.ParseAddress(ctx.String(flags.L2OOAddressFlag.Name))
if err != nil { if err != nil {
return nil, ErrMissingL2OOAddress return nil, ErrMissingL2OOAddress
} }
dgfAddress, err := opservice.ParseAddress(ctx.GlobalString(flags.DGFAddressFlag.Name)) dgfAddress, err := opservice.ParseAddress(ctx.String(flags.DGFAddressFlag.Name))
if err != nil { if err != nil {
return nil, ErrMissingDGFAddress return nil, ErrMissingDGFAddress
} }
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
log "github.com/ethereum/go-ethereum/log" log "github.com/ethereum/go-ethereum/log"
cli "github.com/urfave/cli" cli "github.com/urfave/cli/v2"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
) )
......
...@@ -18,10 +18,10 @@ func main() { ...@@ -18,10 +18,10 @@ func main() {
p.Attack() p.Attack()
p.Print(3) p.Print(3)
// Trace Position is 0000 Trace Depth is: 0 Trace Index is: 8 // GIN: 1 Trace Position is 0 Trace Depth is: 0 Trace Index is: 8
// Trace Position is 0000 Trace Depth is: 1 Trace Index is: 4 // GIN: 10 Trace Position is 0 Trace Depth is: 1 Trace Index is: 4
// Trace Position is 0010 Trace Depth is: 2 Trace Index is: 6 // GIN: 110 Trace Position is 10 Trace Depth is: 2 Trace Index is: 6
// Trace Position is 0100 Trace Depth is: 3 Trace Index is: 5 // GIN: 1100 Trace Position is 100 Trace Depth is: 3 Trace Index is: 5
// Example 2 // Example 2
// abcdefgh // abcdefgh
......
...@@ -3,36 +3,42 @@ package fault ...@@ -3,36 +3,42 @@ package fault
import "fmt" import "fmt"
// Position is a golang wrapper around the dispute game Position type. // Position is a golang wrapper around the dispute game Position type.
// Depth refers to how many bisection steps have occurred.
// IndexAtDepth refers to the path that the bisection has taken
// where 1 = goes right & 0 = goes left.
type Position struct { type Position struct {
Depth int depth int
IndexAtDepth int indexAtDepth int
} }
// TraceIndex calculates the what the index of the claim value func NewPosition(depth, indexAtDepth int) Position {
// would be inside the trace. return Position{depth, indexAtDepth}
}
func NewPositionFromGIndex(x uint64) Position {
depth := MSBIndex(x)
indexAtDepth := ^(1 << depth) & x
return NewPosition(depth, int(indexAtDepth))
}
func (p *Position) Depth() int {
return p.depth
}
func (p *Position) IndexAtDepth() int {
return p.indexAtDepth
}
// TraceIndex calculates the what the index of the claim value would be inside the trace.
// It is equivalent to going right until the final depth has been reached.
func (p *Position) TraceIndex(maxDepth int) int { func (p *Position) TraceIndex(maxDepth int) int {
lo := 0 // When we go right, we do a shift left and set the bottom bit to be 1.
hi := 1 << maxDepth // To do this in a single step, do all the shifts at once & or in all 1s for the bottom bits.
mid := hi rd := maxDepth - p.depth
path := p.IndexAtDepth return p.indexAtDepth<<rd | ((1 << rd) - 1)
for i := p.Depth - 1; i >= 0; i-- {
mid = (lo + hi) / 2
mask := 1 << i
if path&mask == mask {
lo = mid
} else {
hi = mid
}
}
return mid
} }
// move goes to the left or right child.
func (p *Position) move(right bool) { func (p *Position) move(right bool) {
p.Depth++ p.depth++
p.IndexAtDepth = (p.IndexAtDepth << 1) | boolToInt(right) p.indexAtDepth = (p.indexAtDepth << 1) | boolToInt(right)
} }
func boolToInt(b bool) int { func boolToInt(b bool) int {
...@@ -43,15 +49,18 @@ func boolToInt(b bool) int { ...@@ -43,15 +49,18 @@ func boolToInt(b bool) int {
} }
} }
// parent moves up to the parent.
func (p *Position) parent() { func (p *Position) parent() {
p.Depth-- p.depth--
p.IndexAtDepth = p.IndexAtDepth >> 1 p.indexAtDepth = p.indexAtDepth >> 1
} }
// Attack moves this position to a position to the left which disagrees with this position.
func (p *Position) Attack() { func (p *Position) Attack() {
p.move(false) p.move(false)
} }
// Defend moves this position to the right which agrees with this position. Note:
func (p *Position) Defend() { func (p *Position) Defend() {
p.parent() p.parent()
p.move(true) p.move(true)
...@@ -59,5 +68,21 @@ func (p *Position) Defend() { ...@@ -59,5 +68,21 @@ func (p *Position) Defend() {
} }
func (p *Position) Print(maxDepth int) { func (p *Position) Print(maxDepth int) {
fmt.Printf("Trace Position is %04b\tTrace Depth is: %d\tTrace Index is: %d\n", p.IndexAtDepth, p.Depth, p.TraceIndex(maxDepth)) fmt.Printf("GIN: %4b\tTrace Position is %4b\tTrace Depth is: %d\tTrace Index is: %d\n", p.ToGIndex(), p.indexAtDepth, p.depth, p.TraceIndex(maxDepth))
}
func (p *Position) ToGIndex() uint64 {
return uint64(1<<p.depth | p.indexAtDepth)
}
// MSBIndex returns the index of the most significant bit
func MSBIndex(x uint64) int {
if x == 0 {
return 0
}
out := 0
for ; x != 0; out++ {
x = x >> 1
}
return out - 1
} }
package fault package fault
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMSBIndex(t *testing.T) {
tests := []struct {
input uint64
expected int
}{
{0, 0},
{1, 0},
{2, 1},
{4, 2},
{8, 3},
{16, 4},
{255, 7},
{1024, 10},
{18446744073709551615, 63},
}
for _, test := range tests {
result := MSBIndex(test.input)
if result != test.expected {
t.Errorf("MSBIndex(%d) expected %d, but got %d", test.input, test.expected, result)
}
}
}
type testNodeInfo struct {
GIndex uint64
Depth int
IndexAtDepth int
TraceIndex int
}
var treeNodesMaxDepth4 = []testNodeInfo{
{GIndex: 1, Depth: 0, IndexAtDepth: 0, TraceIndex: 15},
{GIndex: 2, Depth: 1, IndexAtDepth: 0, TraceIndex: 7},
{GIndex: 3, Depth: 1, IndexAtDepth: 1, TraceIndex: 15},
{GIndex: 4, Depth: 2, IndexAtDepth: 0, TraceIndex: 3},
{GIndex: 5, Depth: 2, IndexAtDepth: 1, TraceIndex: 7},
{GIndex: 6, Depth: 2, IndexAtDepth: 2, TraceIndex: 11},
{GIndex: 7, Depth: 2, IndexAtDepth: 3, TraceIndex: 15},
{GIndex: 8, Depth: 3, IndexAtDepth: 0, TraceIndex: 1},
{GIndex: 9, Depth: 3, IndexAtDepth: 1, TraceIndex: 3},
{GIndex: 10, Depth: 3, IndexAtDepth: 2, TraceIndex: 5},
{GIndex: 11, Depth: 3, IndexAtDepth: 3, TraceIndex: 7},
{GIndex: 12, Depth: 3, IndexAtDepth: 4, TraceIndex: 9},
{GIndex: 13, Depth: 3, IndexAtDepth: 5, TraceIndex: 11},
{GIndex: 14, Depth: 3, IndexAtDepth: 6, TraceIndex: 13},
{GIndex: 15, Depth: 3, IndexAtDepth: 7, TraceIndex: 15},
{GIndex: 16, Depth: 4, IndexAtDepth: 0, TraceIndex: 0},
{GIndex: 17, Depth: 4, IndexAtDepth: 1, TraceIndex: 1},
{GIndex: 18, Depth: 4, IndexAtDepth: 2, TraceIndex: 2},
{GIndex: 19, Depth: 4, IndexAtDepth: 3, TraceIndex: 3},
{GIndex: 20, Depth: 4, IndexAtDepth: 4, TraceIndex: 4},
{GIndex: 21, Depth: 4, IndexAtDepth: 5, TraceIndex: 5},
{GIndex: 22, Depth: 4, IndexAtDepth: 6, TraceIndex: 6},
{GIndex: 23, Depth: 4, IndexAtDepth: 7, TraceIndex: 7},
{GIndex: 24, Depth: 4, IndexAtDepth: 8, TraceIndex: 8},
{GIndex: 25, Depth: 4, IndexAtDepth: 9, TraceIndex: 9},
{GIndex: 26, Depth: 4, IndexAtDepth: 10, TraceIndex: 10},
{GIndex: 27, Depth: 4, IndexAtDepth: 11, TraceIndex: 11},
{GIndex: 28, Depth: 4, IndexAtDepth: 12, TraceIndex: 12},
{GIndex: 29, Depth: 4, IndexAtDepth: 13, TraceIndex: 13},
{GIndex: 30, Depth: 4, IndexAtDepth: 14, TraceIndex: 14},
{GIndex: 31, Depth: 4, IndexAtDepth: 15, TraceIndex: 15},
}
// TestGINConversions does To & From the generalized index on the treeNodesMaxDepth4 data
func TestGINConversions(t *testing.T) {
for _, test := range treeNodesMaxDepth4 {
from := NewPositionFromGIndex(test.GIndex)
pos := NewPosition(test.Depth, test.IndexAtDepth)
require.Equal(t, pos, from)
to := pos.ToGIndex()
require.Equal(t, test.GIndex, to)
}
}
// TestTraceIndex creates the position & then tests the trace index function on the treeNodesMaxDepth4 data
func TestTraceIndex(t *testing.T) {
for _, test := range treeNodesMaxDepth4 {
pos := NewPosition(test.Depth, test.IndexAtDepth)
result := pos.TraceIndex(4)
require.Equal(t, test.TraceIndex, result)
}
}
...@@ -3,7 +3,7 @@ package flags ...@@ -3,7 +3,7 @@ package flags
import ( import (
"fmt" "fmt"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
...@@ -15,27 +15,31 @@ import ( ...@@ -15,27 +15,31 @@ import (
const envVarPrefix = "OP_CHALLENGER" const envVarPrefix = "OP_CHALLENGER"
func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(envVarPrefix, name)
}
var ( var (
// Required Flags // Required Flags
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = &cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1.", Usage: "HTTP provider URL for L1.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L1_ETH_RPC"), EnvVars: prefixEnvVars("L1_ETH_RPC"),
} }
RollupRpcFlag = cli.StringFlag{ RollupRpcFlag = &cli.StringFlag{
Name: "rollup-rpc", Name: "rollup-rpc",
Usage: "HTTP provider URL for the rollup node.", Usage: "HTTP provider URL for the rollup node.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"), EnvVars: prefixEnvVars("ROLLUP_RPC"),
} }
L2OOAddressFlag = cli.StringFlag{ L2OOAddressFlag = &cli.StringFlag{
Name: "l2oo-address", Name: "l2oo-address",
Usage: "Address of the L2OutputOracle contract.", Usage: "Address of the L2OutputOracle contract.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L2OO_ADDRESS"), EnvVars: prefixEnvVars("L2OO_ADDRESS"),
} }
DGFAddressFlag = cli.StringFlag{ DGFAddressFlag = &cli.StringFlag{
Name: "dgf-address", Name: "dgf-address",
Usage: "Address of the DisputeGameFactory contract.", Usage: "Address of the DisputeGameFactory contract.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "DGF_ADDRESS"), EnvVars: prefixEnvVars("DGF_ADDRESS"),
} }
) )
...@@ -65,8 +69,8 @@ var Flags []cli.Flag ...@@ -65,8 +69,8 @@ var Flags []cli.Flag
func CheckRequired(ctx *cli.Context) error { func CheckRequired(ctx *cli.Context) error {
for _, f := range requiredFlags { for _, f := range requiredFlags {
if !ctx.GlobalIsSet(f.GetName()) { if !ctx.IsSet(f.Names()[0]) {
return fmt.Errorf("flag %s is required", f.GetName()) return fmt.Errorf("flag %s is required", f.Names()[0])
} }
} }
return nil return nil
......
...@@ -5,14 +5,14 @@ import ( ...@@ -5,14 +5,14 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags. // TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags.
func TestUniqueFlags(t *testing.T) { func TestUniqueFlags(t *testing.T) {
seenCLI := make(map[string]struct{}) seenCLI := make(map[string]struct{})
for _, flag := range Flags { for _, flag := range Flags {
name := flag.GetName() name := flag.Names()[0]
if _, ok := seenCLI[name]; ok { if _, ok := seenCLI[name]; ok {
t.Errorf("duplicate flag %s", name) t.Errorf("duplicate flag %s", name)
continue continue
...@@ -38,22 +38,22 @@ func TestCorrectEnvVarPrefix(t *testing.T) { ...@@ -38,22 +38,22 @@ func TestCorrectEnvVarPrefix(t *testing.T) {
for _, flag := range Flags { for _, flag := range Flags {
envVar := envVarForFlag(flag) envVar := envVarForFlag(flag)
if envVar == "" { if envVar == "" {
t.Errorf("Failed to find EnvVar for flag %v", flag.GetName()) t.Errorf("Failed to find EnvVar for flag %v", flag.Names()[0])
} }
if !strings.HasPrefix(envVar, "OP_CHALLENGER_") { if !strings.HasPrefix(envVar, "OP_CHALLENGER_") {
t.Errorf("Flag %v env var (%v) does not start with OP_CHALLENGER_", flag.GetName(), envVar) t.Errorf("Flag %v env var (%v) does not start with OP_CHALLENGER_", flag.Names()[0], envVar)
} }
if strings.Contains(envVar, "__") { if strings.Contains(envVar, "__") {
t.Errorf("Flag %v env var (%v) has duplicate underscores", flag.GetName(), envVar) t.Errorf("Flag %v env var (%v) has duplicate underscores", flag.Names()[0], envVar)
} }
} }
} }
func envVarForFlag(flag cli.Flag) string { func envVarForFlag(flag cli.Flag) string {
values := reflect.ValueOf(flag) values := reflect.ValueOf(flag)
envVarValue := values.FieldByName("EnvVar") envVarValue := values.Elem().FieldByName("EnvVars")
if envVarValue == (reflect.Value{}) { if envVarValue == (reflect.Value{}) || envVarValue.Len() == 0 {
return "" return ""
} }
return envVarValue.String() return envVarValue.Index(0).String()
} }
FROM golang:1.18.0-alpine3.15 as builder FROM golang:1.19.9-alpine3.16 as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash
# build op-heartbeat with local monorepo go modules # build op-heartbeat with local monorepo go modules
COPY ./op-heartbeat /app/op-heartbeat COPY ./op-heartbeat /app/op-heartbeat
COPY ./op-node /app/op-node
COPY ./op-service /app/op-service
COPY ./go.mod /app/go.mod
COPY ./go.sum /app/go.sum
COPY ./.git /app/.git COPY ./.git /app/.git
COPY ./op-heartbeat/go.mod /app/go.mod
COPY ./op-heartbeat/go.sum /app/go.sum
WORKDIR /app/op-heartbeat WORKDIR /app/op-heartbeat
RUN make op-heartbeat RUN make op-heartbeat
FROM alpine:3.15 FROM alpine:3.16
COPY --from=builder /app/op-heartbeat/bin/op-heartbeat /usr/local/bin COPY --from=builder /app/op-heartbeat/bin/op-heartbeat /usr/local/bin
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-heartbeat/flags" "github.com/ethereum-optimism/optimism/op-heartbeat/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
......
...@@ -7,7 +7,7 @@ import ( ...@@ -7,7 +7,7 @@ import (
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
type Config struct { type Config struct {
...@@ -42,8 +42,8 @@ func (c Config) Check() error { ...@@ -42,8 +42,8 @@ func (c Config) Check() error {
func NewConfig(ctx *cli.Context) Config { func NewConfig(ctx *cli.Context) Config {
return Config{ return Config{
HTTPAddr: ctx.GlobalString(flags.HTTPAddrFlag.Name), HTTPAddr: ctx.String(flags.HTTPAddrFlag.Name),
HTTPPort: ctx.GlobalInt(flags.HTTPPortFlag.Name), HTTPPort: ctx.Int(flags.HTTPPortFlag.Name),
Log: oplog.ReadCLIConfig(ctx), Log: oplog.ReadCLIConfig(ctx),
Metrics: opmetrics.ReadCLIConfig(ctx), Metrics: opmetrics.ReadCLIConfig(ctx),
Pprof: oppprof.ReadCLIConfig(ctx), Pprof: oppprof.ReadCLIConfig(ctx),
......
...@@ -4,28 +4,32 @@ import ( ...@@ -4,28 +4,32 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const envPrefix = "OP_HEARTBEAT" const envPrefix = "OP_HEARTBEAT"
func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(envPrefix, name)
}
const ( const (
HTTPAddrFlagName = "http.addr" HTTPAddrFlagName = "http.addr"
HTTPPortFlagName = "http.port" HTTPPortFlagName = "http.port"
) )
var ( var (
HTTPAddrFlag = cli.StringFlag{ HTTPAddrFlag = &cli.StringFlag{
Name: HTTPAddrFlagName, Name: HTTPAddrFlagName,
Usage: "Address the server should listen on", Usage: "Address the server should listen on",
Value: "0.0.0.0", Value: "0.0.0.0",
EnvVar: opservice.PrefixEnvVar(envPrefix, "HTTP_ADDR"), EnvVars: prefixEnvVars("HTTP_ADDR"),
} }
HTTPPortFlag = cli.IntFlag{ HTTPPortFlag = &cli.IntFlag{
Name: HTTPPortFlagName, Name: HTTPPortFlagName,
Usage: "Port the server should listen on", Usage: "Port the server should listen on",
Value: 8080, Value: 8080,
EnvVar: opservice.PrefixEnvVar(envPrefix, "HTTP_PORT"), EnvVars: prefixEnvVars("HTTP_PORT"),
} }
) )
......
module github.com/ethereum-optimism/optimism/op-heartbeat
go 1.18
require (
github.com/ethereum-optimism/optimism/op-node v0.10.13
github.com/ethereum-optimism/optimism/op-service v0.10.13
github.com/ethereum/go-ethereum v1.10.26
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/prometheus/client_golang v1.14.0
github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.10
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/btcsuite/btcd v0.23.3 h1:4KH/JKy9WiCd+iUS9Mu0Zp7Dnj17TGdKrg9xc/FGj24=
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/optimism/op-node v0.10.13 h1:a49FPr/gROrtDUyrkZGkWF3mzq6SnTFQkJrGOduYSfY=
github.com/ethereum-optimism/optimism/op-node v0.10.13/go.mod h1:PcoBNd7eLGN6qRx7pxhTyUxRtfCh5Y/Ll8VipivXHDw=
github.com/ethereum-optimism/optimism/op-service v0.10.13 h1:4wv2LgbVD0aLVNtY0H/49Dvoof2KRT6w40OGeb+9pic=
github.com/ethereum-optimism/optimism/op-service v0.10.13/go.mod h1:X+qyGWCSVnf4DU+mzbpKFchrMfrpdscjL/zxiaMZ0rM=
github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s=
github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A=
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
...@@ -14,7 +14,7 @@ import ( ...@@ -14,7 +14,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
......
...@@ -12,48 +12,48 @@ import ( ...@@ -12,48 +12,48 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
app.Name = "batch-decoder" app.Name = "batch-decoder"
app.Usage = "Optimism Batch Decoding Utility" app.Usage = "Optimism Batch Decoding Utility"
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "fetch", Name: "fetch",
Usage: "Fetches batches in the specified range", Usage: "Fetches batches in the specified range",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.IntFlag{ &cli.IntFlag{
Name: "start", Name: "start",
Required: true, Required: true,
Usage: "First block (inclusive) to fetch", Usage: "First block (inclusive) to fetch",
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "end", Name: "end",
Required: true, Required: true,
Usage: "Last block (exclusive) to fetch", Usage: "Last block (exclusive) to fetch",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "inbox", Name: "inbox",
Required: true, Required: true,
Usage: "Batch Inbox Address", Usage: "Batch Inbox Address",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "sender", Name: "sender",
Required: true, Required: true,
Usage: "Batch Sender Address", Usage: "Batch Sender Address",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "out", Name: "out",
Value: "/tmp/batch_decoder/transactions_cache", Value: "/tmp/batch_decoder/transactions_cache",
Usage: "Cache directory for the found transactions", Usage: "Cache directory for the found transactions",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "l1", Name: "l1",
Required: true, Required: true,
Usage: "L1 RPC URL", Usage: "L1 RPC URL",
EnvVar: "L1_RPC", EnvVars: []string{"L1_RPC"},
}, },
}, },
Action: func(cliCtx *cli.Context) error { Action: func(cliCtx *cli.Context) error {
...@@ -88,17 +88,17 @@ func main() { ...@@ -88,17 +88,17 @@ func main() {
Name: "reassemble", Name: "reassemble",
Usage: "Reassembles channels from fetched batches", Usage: "Reassembles channels from fetched batches",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "inbox", Name: "inbox",
Value: "0xff00000000000000000000000000000000000420", Value: "0xff00000000000000000000000000000000000420",
Usage: "Batch Inbox Address", Usage: "Batch Inbox Address",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "in", Name: "in",
Value: "/tmp/batch_decoder/transactions_cache", Value: "/tmp/batch_decoder/transactions_cache",
Usage: "Cache directory for the found transactions", Usage: "Cache directory for the found transactions",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "out", Name: "out",
Value: "/tmp/batch_decoder/channel_cache", Value: "/tmp/batch_decoder/channel_cache",
Usage: "Cache directory for the found channels", Usage: "Cache directory for the found channels",
...@@ -118,17 +118,17 @@ func main() { ...@@ -118,17 +118,17 @@ func main() {
Name: "force-close", Name: "force-close",
Usage: "Create the tx data which will force close a channel", Usage: "Create the tx data which will force close a channel",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "id", Name: "id",
Required: true, Required: true,
Usage: "ID of the channel to close", Usage: "ID of the channel to close",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "inbox", Name: "inbox",
Value: "0x0000000000000000000000000000000000000000", Value: "0x0000000000000000000000000000000000000000",
Usage: "(Optional) Batch Inbox Address", Usage: "(Optional) Batch Inbox Address",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "in", Name: "in",
Value: "/tmp/batch_decoder/transactions_cache", Value: "/tmp/batch_decoder/transactions_cache",
Usage: "Cache directory for the found transactions", Usage: "Cache directory for the found transactions",
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var Subcommands = cli.Commands{ var Subcommands = cli.Commands{
...@@ -16,7 +16,7 @@ var Subcommands = cli.Commands{ ...@@ -16,7 +16,7 @@ var Subcommands = cli.Commands{
Name: "metrics", Name: "metrics",
Usage: "Dumps a list of supported metrics to stdout", Usage: "Dumps a list of supported metrics to stdout",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "format", Name: "format",
Value: "markdown", Value: "markdown",
Usage: "Output format (json|markdown)", Usage: "Output format (json|markdown)",
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
...@@ -22,19 +22,19 @@ var Subcommands = cli.Commands{ ...@@ -22,19 +22,19 @@ var Subcommands = cli.Commands{
Name: "devnet", Name: "devnet",
Usage: "Initialize new L1 and L2 genesis files and rollup config suitable for a local devnet", Usage: "Initialize new L1 and L2 genesis files and rollup config suitable for a local devnet",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "deploy-config", Name: "deploy-config",
Usage: "Path to hardhat deploy config file", Usage: "Path to hardhat deploy config file",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "outfile.l1", Name: "outfile.l1",
Usage: "Path to L1 genesis output file", Usage: "Path to L1 genesis output file",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "outfile.l2", Name: "outfile.l2",
Usage: "Path to L2 genesis output file", Usage: "Path to L2 genesis output file",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "outfile.rollup", Name: "outfile.rollup",
Usage: "Path to rollup output file", Usage: "Path to rollup output file",
}, },
...@@ -85,23 +85,23 @@ var Subcommands = cli.Commands{ ...@@ -85,23 +85,23 @@ var Subcommands = cli.Commands{
Name: "l2", Name: "l2",
Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network", Usage: "Generates an L2 genesis file and rollup config suitable for a deployed network",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "l1-rpc", Name: "l1-rpc",
Usage: "L1 RPC URL", Usage: "L1 RPC URL",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "deploy-config", Name: "deploy-config",
Usage: "Path to hardhat deploy config file", Usage: "Path to hardhat deploy config file",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "deployment-dir", Name: "deployment-dir",
Usage: "Path to deployment directory", Usage: "Path to deployment directory",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "outfile.l2", Name: "outfile.l2",
Usage: "Path to L2 genesis output file", Usage: "Path to L2 genesis output file",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "outfile.rollup", Name: "outfile.rollup",
Usage: "Path to rollup output file", Usage: "Path to rollup output file",
}, },
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/cmd/doc" "github.com/ethereum-optimism/optimism/op-node/cmd/doc"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -59,7 +59,7 @@ func main() { ...@@ -59,7 +59,7 @@ func main() {
app.Usage = "Optimism Rollup Node" app.Usage = "Optimism Rollup Node"
app.Description = "The Optimism Rollup Node derives L2 block inputs from L1 data and drives an external L2 Execution Engine to build a L2 chain." app.Description = "The Optimism Rollup Node derives L2 block inputs from L1 data and drives an external L2 Execution Engine to build a L2 chain."
app.Action = RollupNodeMain app.Action = RollupNodeMain
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "p2p", Name: "p2p",
Subcommands: p2p.Subcommands, Subcommands: p2p.Subcommands,
......
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
"github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func Priv2PeerID(r io.Reader) (string, error) { func Priv2PeerID(r io.Reader) (string, error) {
......
...@@ -10,202 +10,202 @@ import ( ...@@ -10,202 +10,202 @@ import (
openum "github.com/ethereum-optimism/optimism/op-service/enum" openum "github.com/ethereum-optimism/optimism/op-service/enum"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// Flags // Flags
const EnvVarPrefix = "OP_NODE" const EnvVarPrefix = "OP_NODE"
func prefixEnvVar(name string) string { func prefixEnvVars(name string) []string {
return EnvVarPrefix + "_" + name return []string{EnvVarPrefix + "_" + name}
} }
var ( var (
/* Required Flags */ /* Required Flags */
L1NodeAddr = cli.StringFlag{ L1NodeAddr = &cli.StringFlag{
Name: "l1", Name: "l1",
Usage: "Address of L1 User JSON-RPC endpoint to use (eth namespace required)", Usage: "Address of L1 User JSON-RPC endpoint to use (eth namespace required)",
Value: "http://127.0.0.1:8545", Value: "http://127.0.0.1:8545",
EnvVar: prefixEnvVar("L1_ETH_RPC"), EnvVars: prefixEnvVars("L1_ETH_RPC"),
} }
L2EngineAddr = cli.StringFlag{ L2EngineAddr = &cli.StringFlag{
Name: "l2", Name: "l2",
Usage: "Address of L2 Engine JSON-RPC endpoints to use (engine and eth namespace required)", Usage: "Address of L2 Engine JSON-RPC endpoints to use (engine and eth namespace required)",
EnvVar: prefixEnvVar("L2_ENGINE_RPC"), EnvVars: prefixEnvVars("L2_ENGINE_RPC"),
} }
RollupConfig = cli.StringFlag{ RollupConfig = &cli.StringFlag{
Name: "rollup.config", Name: "rollup.config",
Usage: "Rollup chain parameters", Usage: "Rollup chain parameters",
EnvVar: prefixEnvVar("ROLLUP_CONFIG"), EnvVars: prefixEnvVars("ROLLUP_CONFIG"),
} }
Network = cli.StringFlag{ Network = &cli.StringFlag{
Name: "network", Name: "network",
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")), Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVar: prefixEnvVar("NETWORK"), EnvVars: prefixEnvVars("NETWORK"),
} }
RPCListenAddr = cli.StringFlag{ RPCListenAddr = &cli.StringFlag{
Name: "rpc.addr", Name: "rpc.addr",
Usage: "RPC listening address", Usage: "RPC listening address",
EnvVar: prefixEnvVar("RPC_ADDR"), EnvVars: prefixEnvVars("RPC_ADDR"),
} }
RPCListenPort = cli.IntFlag{ RPCListenPort = &cli.IntFlag{
Name: "rpc.port", Name: "rpc.port",
Usage: "RPC listening port", Usage: "RPC listening port",
EnvVar: prefixEnvVar("RPC_PORT"), EnvVars: prefixEnvVars("RPC_PORT"),
} }
RPCEnableAdmin = cli.BoolFlag{ RPCEnableAdmin = &cli.BoolFlag{
Name: "rpc.enable-admin", Name: "rpc.enable-admin",
Usage: "Enable the admin API (experimental)", Usage: "Enable the admin API (experimental)",
EnvVar: prefixEnvVar("RPC_ENABLE_ADMIN"), EnvVars: prefixEnvVars("RPC_ENABLE_ADMIN"),
} }
/* Optional Flags */ /* Optional Flags */
L1TrustRPC = cli.BoolFlag{ L1TrustRPC = &cli.BoolFlag{
Name: "l1.trustrpc", Name: "l1.trustrpc",
Usage: "Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data", Usage: "Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data",
EnvVar: prefixEnvVar("L1_TRUST_RPC"), EnvVars: prefixEnvVars("L1_TRUST_RPC"),
} }
L1RPCProviderKind = cli.GenericFlag{ L1RPCProviderKind = &cli.GenericFlag{
Name: "l1.rpckind", Name: "l1.rpckind",
Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " + Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " +
openum.EnumString(sources.RPCProviderKinds), openum.EnumString(sources.RPCProviderKinds),
EnvVar: prefixEnvVar("L1_RPC_KIND"), EnvVars: prefixEnvVars("L1_RPC_KIND"),
Value: func() *sources.RPCProviderKind { Value: func() *sources.RPCProviderKind {
out := sources.RPCKindBasic out := sources.RPCKindBasic
return &out return &out
}(), }(),
} }
L1RPCRateLimit = cli.Float64Flag{ L1RPCRateLimit = &cli.Float64Flag{
Name: "l1.rpc-rate-limit", Name: "l1.rpc-rate-limit",
Usage: "Optional self-imposed global rate-limit on L1 RPC requests, specified in requests / second. Disabled if set to 0.", Usage: "Optional self-imposed global rate-limit on L1 RPC requests, specified in requests / second. Disabled if set to 0.",
EnvVar: prefixEnvVar("L1_RPC_RATE_LIMIT"), EnvVars: prefixEnvVars("L1_RPC_RATE_LIMIT"),
Value: 0, Value: 0,
} }
L1RPCMaxBatchSize = cli.IntFlag{ L1RPCMaxBatchSize = &cli.IntFlag{
Name: "l1.rpc-max-batch-size", Name: "l1.rpc-max-batch-size",
Usage: "Maximum number of RPC requests to bundle, e.g. during L1 blocks receipt fetching. The L1 RPC rate limit counts this as N items, but allows it to burst at once.", Usage: "Maximum number of RPC requests to bundle, e.g. during L1 blocks receipt fetching. The L1 RPC rate limit counts this as N items, but allows it to burst at once.",
EnvVar: prefixEnvVar("L1_RPC_MAX_BATCH_SIZE"), EnvVars: prefixEnvVars("L1_RPC_MAX_BATCH_SIZE"),
Value: 20, Value: 20,
} }
L1HTTPPollInterval = cli.DurationFlag{ L1HTTPPollInterval = &cli.DurationFlag{
Name: "l1.http-poll-interval", Name: "l1.http-poll-interval",
Usage: "Polling interval for latest-block subscription when using an HTTP RPC provider. Ignored for other types of RPC endpoints.", Usage: "Polling interval for latest-block subscription when using an HTTP RPC provider. Ignored for other types of RPC endpoints.",
EnvVar: prefixEnvVar("L1_HTTP_POLL_INTERVAL"), EnvVars: prefixEnvVars("L1_HTTP_POLL_INTERVAL"),
Value: time.Second * 12, Value: time.Second * 12,
} }
L2EngineJWTSecret = cli.StringFlag{ L2EngineJWTSecret = &cli.StringFlag{
Name: "l2.jwt-secret", Name: "l2.jwt-secret",
Usage: "Path to JWT secret key. Keys are 32 bytes, hex encoded in a file. A new key will be generated if left empty.", Usage: "Path to JWT secret key. Keys are 32 bytes, hex encoded in a file. A new key will be generated if left empty.",
EnvVar: prefixEnvVar("L2_ENGINE_AUTH"), EnvVars: prefixEnvVars("L2_ENGINE_AUTH"),
Required: false, Required: false,
Value: "", Value: "",
Destination: new(string), Destination: new(string),
} }
VerifierL1Confs = cli.Uint64Flag{ VerifierL1Confs = &cli.Uint64Flag{
Name: "verifier.l1-confs", Name: "verifier.l1-confs",
Usage: "Number of L1 blocks to keep distance from the L1 head before deriving L2 data from. Reorgs are supported, but may be slow to perform.", Usage: "Number of L1 blocks to keep distance from the L1 head before deriving L2 data from. Reorgs are supported, but may be slow to perform.",
EnvVar: prefixEnvVar("VERIFIER_L1_CONFS"), EnvVars: prefixEnvVars("VERIFIER_L1_CONFS"),
Required: false, Required: false,
Value: 0, Value: 0,
} }
SequencerEnabledFlag = cli.BoolFlag{ SequencerEnabledFlag = &cli.BoolFlag{
Name: "sequencer.enabled", Name: "sequencer.enabled",
Usage: "Enable sequencing of new L2 blocks. A separate batch submitter has to be deployed to publish the data for verifiers.", Usage: "Enable sequencing of new L2 blocks. A separate batch submitter has to be deployed to publish the data for verifiers.",
EnvVar: prefixEnvVar("SEQUENCER_ENABLED"), EnvVars: prefixEnvVars("SEQUENCER_ENABLED"),
} }
SequencerStoppedFlag = cli.BoolFlag{ SequencerStoppedFlag = &cli.BoolFlag{
Name: "sequencer.stopped", Name: "sequencer.stopped",
Usage: "Initialize the sequencer in a stopped state. The sequencer can be started using the admin_startSequencer RPC", Usage: "Initialize the sequencer in a stopped state. The sequencer can be started using the admin_startSequencer RPC",
EnvVar: prefixEnvVar("SEQUENCER_STOPPED"), EnvVars: prefixEnvVars("SEQUENCER_STOPPED"),
} }
SequencerMaxSafeLagFlag = cli.Uint64Flag{ SequencerMaxSafeLagFlag = &cli.Uint64Flag{
Name: "sequencer.max-safe-lag", Name: "sequencer.max-safe-lag",
Usage: "Maximum number of L2 blocks for restricting the distance between L2 safe and unsafe. Disabled if 0.", Usage: "Maximum number of L2 blocks for restricting the distance between L2 safe and unsafe. Disabled if 0.",
EnvVar: prefixEnvVar("SEQUENCER_MAX_SAFE_LAG"), EnvVars: prefixEnvVars("SEQUENCER_MAX_SAFE_LAG"),
Required: false, Required: false,
Value: 0, Value: 0,
} }
SequencerL1Confs = cli.Uint64Flag{ SequencerL1Confs = &cli.Uint64Flag{
Name: "sequencer.l1-confs", Name: "sequencer.l1-confs",
Usage: "Number of L1 blocks to keep distance from the L1 head as a sequencer for picking an L1 origin.", Usage: "Number of L1 blocks to keep distance from the L1 head as a sequencer for picking an L1 origin.",
EnvVar: prefixEnvVar("SEQUENCER_L1_CONFS"), EnvVars: prefixEnvVars("SEQUENCER_L1_CONFS"),
Required: false, Required: false,
Value: 4, Value: 4,
} }
L1EpochPollIntervalFlag = cli.DurationFlag{ L1EpochPollIntervalFlag = &cli.DurationFlag{
Name: "l1.epoch-poll-interval", Name: "l1.epoch-poll-interval",
Usage: "Poll interval for retrieving new L1 epoch updates such as safe and finalized block changes. Disabled if 0 or negative.", Usage: "Poll interval for retrieving new L1 epoch updates such as safe and finalized block changes. Disabled if 0 or negative.",
EnvVar: prefixEnvVar("L1_EPOCH_POLL_INTERVAL"), EnvVars: prefixEnvVars("L1_EPOCH_POLL_INTERVAL"),
Required: false, Required: false,
Value: time.Second * 12 * 32, Value: time.Second * 12 * 32,
} }
MetricsEnabledFlag = cli.BoolFlag{ MetricsEnabledFlag = &cli.BoolFlag{
Name: "metrics.enabled", Name: "metrics.enabled",
Usage: "Enable the metrics server", Usage: "Enable the metrics server",
EnvVar: prefixEnvVar("METRICS_ENABLED"), EnvVars: prefixEnvVars("METRICS_ENABLED"),
} }
MetricsAddrFlag = cli.StringFlag{ MetricsAddrFlag = &cli.StringFlag{
Name: "metrics.addr", Name: "metrics.addr",
Usage: "Metrics listening address", Usage: "Metrics listening address",
Value: "0.0.0.0", Value: "0.0.0.0",
EnvVar: prefixEnvVar("METRICS_ADDR"), EnvVars: prefixEnvVars("METRICS_ADDR"),
} }
MetricsPortFlag = cli.IntFlag{ MetricsPortFlag = &cli.IntFlag{
Name: "metrics.port", Name: "metrics.port",
Usage: "Metrics listening port", Usage: "Metrics listening port",
Value: 7300, Value: 7300,
EnvVar: prefixEnvVar("METRICS_PORT"), EnvVars: prefixEnvVars("METRICS_PORT"),
} }
PprofEnabledFlag = cli.BoolFlag{ PprofEnabledFlag = &cli.BoolFlag{
Name: "pprof.enabled", Name: "pprof.enabled",
Usage: "Enable the pprof server", Usage: "Enable the pprof server",
EnvVar: prefixEnvVar("PPROF_ENABLED"), EnvVars: prefixEnvVars("PPROF_ENABLED"),
} }
PprofAddrFlag = cli.StringFlag{ PprofAddrFlag = &cli.StringFlag{
Name: "pprof.addr", Name: "pprof.addr",
Usage: "pprof listening address", Usage: "pprof listening address",
Value: "0.0.0.0", Value: "0.0.0.0",
EnvVar: prefixEnvVar("PPROF_ADDR"), EnvVars: prefixEnvVars("PPROF_ADDR"),
} }
PprofPortFlag = cli.IntFlag{ PprofPortFlag = &cli.IntFlag{
Name: "pprof.port", Name: "pprof.port",
Usage: "pprof listening port", Usage: "pprof listening port",
Value: 6060, Value: 6060,
EnvVar: prefixEnvVar("PPROF_PORT"), EnvVars: prefixEnvVars("PPROF_PORT"),
} }
SnapshotLog = cli.StringFlag{ SnapshotLog = &cli.StringFlag{
Name: "snapshotlog.file", Name: "snapshotlog.file",
Usage: "Path to the snapshot log file", Usage: "Path to the snapshot log file",
EnvVar: prefixEnvVar("SNAPSHOT_LOG"), EnvVars: prefixEnvVars("SNAPSHOT_LOG"),
} }
HeartbeatEnabledFlag = cli.BoolFlag{ HeartbeatEnabledFlag = &cli.BoolFlag{
Name: "heartbeat.enabled", Name: "heartbeat.enabled",
Usage: "Enables or disables heartbeating", Usage: "Enables or disables heartbeating",
EnvVar: prefixEnvVar("HEARTBEAT_ENABLED"), EnvVars: prefixEnvVars("HEARTBEAT_ENABLED"),
} }
HeartbeatMonikerFlag = cli.StringFlag{ HeartbeatMonikerFlag = &cli.StringFlag{
Name: "heartbeat.moniker", Name: "heartbeat.moniker",
Usage: "Sets a moniker for this node", Usage: "Sets a moniker for this node",
EnvVar: prefixEnvVar("HEARTBEAT_MONIKER"), EnvVars: prefixEnvVars("HEARTBEAT_MONIKER"),
} }
HeartbeatURLFlag = cli.StringFlag{ HeartbeatURLFlag = &cli.StringFlag{
Name: "heartbeat.url", Name: "heartbeat.url",
Usage: "Sets the URL to heartbeat to", Usage: "Sets the URL to heartbeat to",
EnvVar: prefixEnvVar("HEARTBEAT_URL"), EnvVars: prefixEnvVars("HEARTBEAT_URL"),
Value: "https://heartbeat.optimism.io", Value: "https://heartbeat.optimism.io",
} }
BackupL2UnsafeSyncRPC = cli.StringFlag{ BackupL2UnsafeSyncRPC = &cli.StringFlag{
Name: "l2.backup-unsafe-sync-rpc", Name: "l2.backup-unsafe-sync-rpc",
Usage: "Set the backup L2 unsafe sync RPC endpoint.", Usage: "Set the backup L2 unsafe sync RPC endpoint.",
EnvVar: prefixEnvVar("L2_BACKUP_UNSAFE_SYNC_RPC"), EnvVars: prefixEnvVars("L2_BACKUP_UNSAFE_SYNC_RPC"),
Required: false, Required: false,
} }
BackupL2UnsafeSyncRPCTrustRPC = cli.StringFlag{ BackupL2UnsafeSyncRPCTrustRPC = &cli.StringFlag{
Name: "l2.backup-unsafe-sync-rpc.trustrpc", Name: "l2.backup-unsafe-sync-rpc.trustrpc",
Usage: "Like l1.trustrpc, configure if response data from the RPC needs to be verified, e.g. blockhash computation." + Usage: "Like l1.trustrpc, configure if response data from the RPC needs to be verified, e.g. blockhash computation." +
"This does not include checks if the blockhash is part of the canonical chain.", "This does not include checks if the blockhash is part of the canonical chain.",
EnvVar: prefixEnvVar("L2_BACKUP_UNSAFE_SYNC_RPC_TRUST_RPC"), EnvVars: prefixEnvVars("L2_BACKUP_UNSAFE_SYNC_RPC_TRUST_RPC"),
Required: false, Required: false,
} }
) )
...@@ -258,8 +258,8 @@ func init() { ...@@ -258,8 +258,8 @@ func init() {
func CheckRequired(ctx *cli.Context) error { func CheckRequired(ctx *cli.Context) error {
for _, f := range requiredFlags { for _, f := range requiredFlags {
if !ctx.GlobalIsSet(f.GetName()) { if !ctx.IsSet(f.Names()[0]) {
return fmt.Errorf("flag %s is required", f.GetName()) return fmt.Errorf("flag %s is required", f.Names()[0])
} }
} }
return nil return nil
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// TestOptionalFlagsDontSetRequired asserts that all flags deemed optional set // TestOptionalFlagsDontSetRequired asserts that all flags deemed optional set
...@@ -21,7 +21,7 @@ func TestOptionalFlagsDontSetRequired(t *testing.T) { ...@@ -21,7 +21,7 @@ func TestOptionalFlagsDontSetRequired(t *testing.T) {
func TestUniqueFlags(t *testing.T) { func TestUniqueFlags(t *testing.T) {
seenCLI := make(map[string]struct{}) seenCLI := make(map[string]struct{})
for _, flag := range Flags { for _, flag := range Flags {
name := flag.GetName() name := flag.Names()[0]
if _, ok := seenCLI[name]; ok { if _, ok := seenCLI[name]; ok {
t.Errorf("duplicate flag %s", name) t.Errorf("duplicate flag %s", name)
continue continue
......
...@@ -4,41 +4,41 @@ import ( ...@@ -4,41 +4,41 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/p2p"
) )
func p2pEnv(v string) string { func p2pEnv(v string) []string {
return prefixEnvVar("P2P_" + v) return prefixEnvVars("P2P_" + v)
} }
var ( var (
DisableP2P = cli.BoolFlag{ DisableP2P = &cli.BoolFlag{
Name: "p2p.disable", Name: "p2p.disable",
Usage: "Completely disable the P2P stack", Usage: "Completely disable the P2P stack",
Required: false, Required: false,
EnvVar: p2pEnv("DISABLE"), EnvVars: p2pEnv("DISABLE"),
} }
NoDiscovery = cli.BoolFlag{ NoDiscovery = &cli.BoolFlag{
Name: "p2p.no-discovery", Name: "p2p.no-discovery",
Usage: "Disable Discv5 (node discovery)", Usage: "Disable Discv5 (node discovery)",
Required: false, Required: false,
EnvVar: p2pEnv("NO_DISCOVERY"), EnvVars: p2pEnv("NO_DISCOVERY"),
} }
Scoring = cli.StringFlag{ Scoring = &cli.StringFlag{
Name: "p2p.scoring", Name: "p2p.scoring",
Usage: "Sets the peer scoring strategy for the P2P stack. Can be one of: none or light.", Usage: "Sets the peer scoring strategy for the P2P stack. Can be one of: none or light.",
Required: false, Required: false,
EnvVar: p2pEnv("PEER_SCORING"), EnvVars: p2pEnv("PEER_SCORING"),
} }
PeerScoring = cli.StringFlag{ PeerScoring = &cli.StringFlag{
Name: "p2p.scoring.peers", Name: "p2p.scoring.peers",
Usage: fmt.Sprintf("Deprecated: Use %v instead", Scoring.Name), Usage: fmt.Sprintf("Deprecated: Use %v instead", Scoring.Name),
Required: false, Required: false,
Hidden: true, Hidden: true,
} }
PeerScoreBands = cli.StringFlag{ PeerScoreBands = &cli.StringFlag{
Name: "p2p.score.bands", Name: "p2p.score.bands",
Usage: "Deprecated. This option is ignored and is only present for backwards compatibility.", Usage: "Deprecated. This option is ignored and is only present for backwards compatibility.",
Required: false, Required: false,
...@@ -47,185 +47,185 @@ var ( ...@@ -47,185 +47,185 @@ var (
} }
// Banning Flag - whether or not we want to act on the scoring // Banning Flag - whether or not we want to act on the scoring
Banning = cli.BoolFlag{ Banning = &cli.BoolFlag{
Name: "p2p.ban.peers", Name: "p2p.ban.peers",
Usage: "Enables peer banning. This should ONLY be enabled once certain peer scoring is working correctly.", Usage: "Enables peer banning. This should ONLY be enabled once certain peer scoring is working correctly.",
Required: false, Required: false,
EnvVar: p2pEnv("PEER_BANNING"), EnvVars: p2pEnv("PEER_BANNING"),
} }
BanningThreshold = cli.Float64Flag{ BanningThreshold = &cli.Float64Flag{
Name: "p2p.ban.threshold", Name: "p2p.ban.threshold",
Usage: "The minimum score below which peers are disconnected and banned.", Usage: "The minimum score below which peers are disconnected and banned.",
Required: false, Required: false,
Value: -100, Value: -100,
EnvVar: p2pEnv("PEER_BANNING_THRESHOLD"), EnvVars: p2pEnv("PEER_BANNING_THRESHOLD"),
} }
BanningDuration = cli.DurationFlag{ BanningDuration = &cli.DurationFlag{
Name: "p2p.ban.duration", Name: "p2p.ban.duration",
Usage: "The duration that peers are banned for.", Usage: "The duration that peers are banned for.",
Required: false, Required: false,
Value: 1 * time.Hour, Value: 1 * time.Hour,
EnvVar: p2pEnv("PEER_BANNING_DURATION"), EnvVars: p2pEnv("PEER_BANNING_DURATION"),
} }
TopicScoring = cli.StringFlag{ TopicScoring = &cli.StringFlag{
Name: "p2p.scoring.topics", Name: "p2p.scoring.topics",
Usage: fmt.Sprintf("Deprecated: Use %v instead", Scoring.Name), Usage: fmt.Sprintf("Deprecated: Use %v instead", Scoring.Name),
Required: false, Required: false,
Hidden: true, Hidden: true,
} }
P2PPrivPath = cli.StringFlag{ P2PPrivPath = &cli.StringFlag{
Name: "p2p.priv.path", Name: "p2p.priv.path",
Usage: "Read the hex-encoded 32-byte private key for the peer ID from this txt file. Created if not already exists." + Usage: "Read the hex-encoded 32-byte private key for the peer ID from this txt file. Created if not already exists." +
"Important to persist to keep the same network identity after restarting, maintaining the previous advertised identity.", "Important to persist to keep the same network identity after restarting, maintaining the previous advertised identity.",
Required: false, Required: false,
Value: "opnode_p2p_priv.txt", Value: "opnode_p2p_priv.txt",
EnvVar: p2pEnv("PRIV_PATH"), EnvVars: p2pEnv("PRIV_PATH"),
TakesFile: true, TakesFile: true,
} }
P2PPrivRaw = cli.StringFlag{ P2PPrivRaw = &cli.StringFlag{
// sometimes it may be ok to not persist the peer priv key as file, and instead pass it directly. // sometimes it may be ok to not persist the peer priv key as file, and instead pass it directly.
Name: "p2p.priv.raw", Name: "p2p.priv.raw",
Usage: "The hex-encoded 32-byte private key for the peer ID", Usage: "The hex-encoded 32-byte private key for the peer ID",
Required: false, Required: false,
Hidden: true, Hidden: true,
Value: "", Value: "",
EnvVar: p2pEnv("PRIV_RAW"), EnvVars: p2pEnv("PRIV_RAW"),
} }
ListenIP = cli.StringFlag{ ListenIP = &cli.StringFlag{
Name: "p2p.listen.ip", Name: "p2p.listen.ip",
Usage: "IP to bind LibP2P and Discv5 to", Usage: "IP to bind LibP2P and Discv5 to",
Required: false, Required: false,
Value: "0.0.0.0", Value: "0.0.0.0",
EnvVar: p2pEnv("LISTEN_IP"), EnvVars: p2pEnv("LISTEN_IP"),
} }
ListenTCPPort = cli.UintFlag{ ListenTCPPort = &cli.UintFlag{
Name: "p2p.listen.tcp", Name: "p2p.listen.tcp",
Usage: "TCP port to bind LibP2P to. Any available system port if set to 0.", Usage: "TCP port to bind LibP2P to. Any available system port if set to 0.",
Required: false, Required: false,
Value: 9222, Value: 9222,
EnvVar: p2pEnv("LISTEN_TCP_PORT"), EnvVars: p2pEnv("LISTEN_TCP_PORT"),
} }
ListenUDPPort = cli.UintFlag{ ListenUDPPort = &cli.UintFlag{
Name: "p2p.listen.udp", Name: "p2p.listen.udp",
Usage: "UDP port to bind Discv5 to. Same as TCP port if left 0.", Usage: "UDP port to bind Discv5 to. Same as TCP port if left 0.",
Required: false, Required: false,
Value: 0, // can simply match the TCP libp2p port Value: 0, // can simply match the TCP libp2p port
EnvVar: p2pEnv("LISTEN_UDP_PORT"), EnvVars: p2pEnv("LISTEN_UDP_PORT"),
} }
AdvertiseIP = cli.StringFlag{ AdvertiseIP = &cli.StringFlag{
Name: "p2p.advertise.ip", Name: "p2p.advertise.ip",
Usage: "The IP address to advertise in Discv5, put into the ENR of the node. This may also be a hostname / domain name to resolve to an IP.", Usage: "The IP address to advertise in Discv5, put into the ENR of the node. This may also be a hostname / domain name to resolve to an IP.",
Required: false, Required: false,
// Ignored by default, nodes can discover their own external IP in the happy case, // Ignored by default, nodes can discover their own external IP in the happy case,
// by communicating with bootnodes. Fixed IP is recommended for faster bootstrap though. // by communicating with bootnodes. Fixed IP is recommended for faster bootstrap though.
Value: "", Value: "",
EnvVar: p2pEnv("ADVERTISE_IP"), EnvVars: p2pEnv("ADVERTISE_IP"),
} }
AdvertiseTCPPort = cli.UintFlag{ AdvertiseTCPPort = &cli.UintFlag{
Name: "p2p.advertise.tcp", Name: "p2p.advertise.tcp",
Usage: "The TCP port to advertise in Discv5, put into the ENR of the node. Set to p2p.listen.tcp value if 0.", Usage: "The TCP port to advertise in Discv5, put into the ENR of the node. Set to p2p.listen.tcp value if 0.",
Required: false, Required: false,
Value: 0, Value: 0,
EnvVar: p2pEnv("ADVERTISE_TCP"), EnvVars: p2pEnv("ADVERTISE_TCP"),
} }
AdvertiseUDPPort = cli.UintFlag{ AdvertiseUDPPort = &cli.UintFlag{
Name: "p2p.advertise.udp", Name: "p2p.advertise.udp",
Usage: "The UDP port to advertise in Discv5 as fallback if not determined by Discv5, put into the ENR of the node. Set to p2p.listen.udp value if 0.", Usage: "The UDP port to advertise in Discv5 as fallback if not determined by Discv5, put into the ENR of the node. Set to p2p.listen.udp value if 0.",
Required: false, Required: false,
Value: 0, Value: 0,
EnvVar: p2pEnv("ADVERTISE_UDP"), EnvVars: p2pEnv("ADVERTISE_UDP"),
} }
Bootnodes = cli.StringFlag{ Bootnodes = &cli.StringFlag{
Name: "p2p.bootnodes", Name: "p2p.bootnodes",
Usage: "Comma-separated base64-format ENR list. Bootnodes to start discovering other node records from.", Usage: "Comma-separated base64-format ENR list. Bootnodes to start discovering other node records from.",
Required: false, Required: false,
Value: "", Value: "",
EnvVar: p2pEnv("BOOTNODES"), EnvVars: p2pEnv("BOOTNODES"),
} }
StaticPeers = cli.StringFlag{ StaticPeers = &cli.StringFlag{
Name: "p2p.static", Name: "p2p.static",
Usage: "Comma-separated multiaddr-format peer list. Static connections to make and maintain, these peers will be regarded as trusted.", Usage: "Comma-separated multiaddr-format peer list. Static connections to make and maintain, these peers will be regarded as trusted.",
Required: false, Required: false,
Value: "", Value: "",
EnvVar: p2pEnv("STATIC"), EnvVars: p2pEnv("STATIC"),
} }
HostMux = cli.StringFlag{ HostMux = &cli.StringFlag{
Name: "p2p.mux", Name: "p2p.mux",
Usage: "Comma-separated list of multiplexing protocols in order of preference. At least 1 required. Options: 'yamux','mplex'.", Usage: "Comma-separated list of multiplexing protocols in order of preference. At least 1 required. Options: 'yamux','mplex'.",
Hidden: true, Hidden: true,
Required: false, Required: false,
Value: "yamux,mplex", Value: "yamux,mplex",
EnvVar: p2pEnv("MUX"), EnvVars: p2pEnv("MUX"),
} }
HostSecurity = cli.StringFlag{ HostSecurity = &cli.StringFlag{
Name: "p2p.security", Name: "p2p.security",
Usage: "Comma-separated list of transport security protocols in order of preference. At least 1 required. Options: 'noise','tls'. Set to 'none' to disable.", Usage: "Comma-separated list of transport security protocols in order of preference. At least 1 required. Options: 'noise','tls'. Set to 'none' to disable.",
Hidden: true, Hidden: true,
Required: false, Required: false,
Value: "noise", Value: "noise",
EnvVar: p2pEnv("SECURITY"), EnvVars: p2pEnv("SECURITY"),
} }
PeersLo = cli.UintFlag{ PeersLo = &cli.UintFlag{
Name: "p2p.peers.lo", Name: "p2p.peers.lo",
Usage: "Low-tide peer count. The node actively searches for new peer connections if below this amount.", Usage: "Low-tide peer count. The node actively searches for new peer connections if below this amount.",
Required: false, Required: false,
Value: 20, Value: 20,
EnvVar: p2pEnv("PEERS_LO"), EnvVars: p2pEnv("PEERS_LO"),
} }
PeersHi = cli.UintFlag{ PeersHi = &cli.UintFlag{
Name: "p2p.peers.hi", Name: "p2p.peers.hi",
Usage: "High-tide peer count. The node starts pruning peer connections slowly after reaching this number.", Usage: "High-tide peer count. The node starts pruning peer connections slowly after reaching this number.",
Required: false, Required: false,
Value: 30, Value: 30,
EnvVar: p2pEnv("PEERS_HI"), EnvVars: p2pEnv("PEERS_HI"),
} }
PeersGrace = cli.DurationFlag{ PeersGrace = &cli.DurationFlag{
Name: "p2p.peers.grace", Name: "p2p.peers.grace",
Usage: "Grace period to keep a newly connected peer around, if it is not misbehaving.", Usage: "Grace period to keep a newly connected peer around, if it is not misbehaving.",
Required: false, Required: false,
Value: 30 * time.Second, Value: 30 * time.Second,
EnvVar: p2pEnv("PEERS_GRACE"), EnvVars: p2pEnv("PEERS_GRACE"),
} }
NAT = cli.BoolFlag{ NAT = &cli.BoolFlag{
Name: "p2p.nat", Name: "p2p.nat",
Usage: "Enable NAT traversal with PMP/UPNP devices to learn external IP.", Usage: "Enable NAT traversal with PMP/UPNP devices to learn external IP.",
Required: false, Required: false,
EnvVar: p2pEnv("NAT"), EnvVars: p2pEnv("NAT"),
} }
UserAgent = cli.StringFlag{ UserAgent = &cli.StringFlag{
Name: "p2p.useragent", Name: "p2p.useragent",
Usage: "User-agent string to share via LibP2P identify. If empty it defaults to 'optimism'.", Usage: "User-agent string to share via LibP2P identify. If empty it defaults to 'optimism'.",
Hidden: true, Hidden: true,
Required: false, Required: false,
Value: "optimism", Value: "optimism",
EnvVar: p2pEnv("AGENT"), EnvVars: p2pEnv("AGENT"),
} }
TimeoutNegotiation = cli.DurationFlag{ TimeoutNegotiation = &cli.DurationFlag{
Name: "p2p.timeout.negotiation", Name: "p2p.timeout.negotiation",
Usage: "Negotiation timeout, time for new peer connections to share their their supported p2p protocols", Usage: "Negotiation timeout, time for new peer connections to share their their supported p2p protocols",
Hidden: true, Hidden: true,
Required: false, Required: false,
Value: 10 * time.Second, Value: 10 * time.Second,
EnvVar: p2pEnv("TIMEOUT_NEGOTIATION"), EnvVars: p2pEnv("TIMEOUT_NEGOTIATION"),
} }
TimeoutAccept = cli.DurationFlag{ TimeoutAccept = &cli.DurationFlag{
Name: "p2p.timeout.accept", Name: "p2p.timeout.accept",
Usage: "Accept timeout, time for connection to be accepted.", Usage: "Accept timeout, time for connection to be accepted.",
Hidden: true, Hidden: true,
Required: false, Required: false,
Value: 10 * time.Second, Value: 10 * time.Second,
EnvVar: p2pEnv("TIMEOUT_ACCEPT"), EnvVars: p2pEnv("TIMEOUT_ACCEPT"),
} }
TimeoutDial = cli.DurationFlag{ TimeoutDial = &cli.DurationFlag{
Name: "p2p.timeout.dial", Name: "p2p.timeout.dial",
Usage: "Dial timeout for outgoing connection requests", Usage: "Dial timeout for outgoing connection requests",
Hidden: true, Hidden: true,
Required: false, Required: false,
Value: 10 * time.Second, Value: 10 * time.Second,
EnvVar: p2pEnv("TIMEOUT_DIAL"), EnvVars: p2pEnv("TIMEOUT_DIAL"),
} }
PeerstorePath = cli.StringFlag{ PeerstorePath = &cli.StringFlag{
Name: "p2p.peerstore.path", Name: "p2p.peerstore.path",
Usage: "Peerstore database location. Persisted peerstores help recover peers after restarts. " + Usage: "Peerstore database location. Persisted peerstores help recover peers after restarts. " +
"Set to 'memory' to never persist the peerstore. Peerstore records will be pruned / expire as necessary. " + "Set to 'memory' to never persist the peerstore. Peerstore records will be pruned / expire as necessary. " +
...@@ -233,67 +233,67 @@ var ( ...@@ -233,67 +233,67 @@ var (
Required: false, Required: false,
TakesFile: true, TakesFile: true,
Value: "opnode_peerstore_db", Value: "opnode_peerstore_db",
EnvVar: p2pEnv("PEERSTORE_PATH"), EnvVars: p2pEnv("PEERSTORE_PATH"),
} }
DiscoveryPath = cli.StringFlag{ DiscoveryPath = &cli.StringFlag{
Name: "p2p.discovery.path", Name: "p2p.discovery.path",
Usage: "Discovered ENRs are persisted in a database to recover from a restart without having to bootstrap the discovery process again. Set to 'memory' to never persist the peerstore.", Usage: "Discovered ENRs are persisted in a database to recover from a restart without having to bootstrap the discovery process again. Set to 'memory' to never persist the peerstore.",
Required: false, Required: false,
TakesFile: true, TakesFile: true,
Value: "opnode_discovery_db", Value: "opnode_discovery_db",
EnvVar: p2pEnv("DISCOVERY_PATH"), EnvVars: p2pEnv("DISCOVERY_PATH"),
} }
SequencerP2PKeyFlag = cli.StringFlag{ SequencerP2PKeyFlag = &cli.StringFlag{
Name: "p2p.sequencer.key", Name: "p2p.sequencer.key",
Usage: "Hex-encoded private key for signing off on p2p application messages as sequencer.", Usage: "Hex-encoded private key for signing off on p2p application messages as sequencer.",
Required: false, Required: false,
Value: "", Value: "",
EnvVar: p2pEnv("SEQUENCER_KEY"), EnvVars: p2pEnv("SEQUENCER_KEY"),
} }
GossipMeshDFlag = cli.UintFlag{ GossipMeshDFlag = &cli.UintFlag{
Name: "p2p.gossip.mesh.d", Name: "p2p.gossip.mesh.d",
Usage: "Configure GossipSub topic stable mesh target count, a.k.a. desired outbound degree, number of peers to gossip to", Usage: "Configure GossipSub topic stable mesh target count, a.k.a. desired outbound degree, number of peers to gossip to",
Required: false, Required: false,
Hidden: true, Hidden: true,
Value: p2p.DefaultMeshD, Value: p2p.DefaultMeshD,
EnvVar: p2pEnv("GOSSIP_MESH_D"), EnvVars: p2pEnv("GOSSIP_MESH_D"),
} }
GossipMeshDloFlag = cli.UintFlag{ GossipMeshDloFlag = &cli.UintFlag{
Name: "p2p.gossip.mesh.lo", Name: "p2p.gossip.mesh.lo",
Usage: "Configure GossipSub topic stable mesh low watermark, a.k.a. lower bound of outbound degree", Usage: "Configure GossipSub topic stable mesh low watermark, a.k.a. lower bound of outbound degree",
Required: false, Required: false,
Hidden: true, Hidden: true,
Value: p2p.DefaultMeshDlo, Value: p2p.DefaultMeshDlo,
EnvVar: p2pEnv("GOSSIP_MESH_DLO"), EnvVars: p2pEnv("GOSSIP_MESH_DLO"),
} }
GossipMeshDhiFlag = cli.UintFlag{ GossipMeshDhiFlag = &cli.UintFlag{
Name: "p2p.gossip.mesh.dhi", Name: "p2p.gossip.mesh.dhi",
Usage: "Configure GossipSub topic stable mesh high watermark, a.k.a. upper bound of outbound degree, additional peers will not receive gossip", Usage: "Configure GossipSub topic stable mesh high watermark, a.k.a. upper bound of outbound degree, additional peers will not receive gossip",
Required: false, Required: false,
Hidden: true, Hidden: true,
Value: p2p.DefaultMeshDhi, Value: p2p.DefaultMeshDhi,
EnvVar: p2pEnv("GOSSIP_MESH_DHI"), EnvVars: p2pEnv("GOSSIP_MESH_DHI"),
} }
GossipMeshDlazyFlag = cli.UintFlag{ GossipMeshDlazyFlag = &cli.UintFlag{
Name: "p2p.gossip.mesh.dlazy", Name: "p2p.gossip.mesh.dlazy",
Usage: "Configure GossipSub gossip target, a.k.a. target degree for gossip only (not messaging like p2p.gossip.mesh.d, just announcements of IHAVE", Usage: "Configure GossipSub gossip target, a.k.a. target degree for gossip only (not messaging like p2p.gossip.mesh.d, just announcements of IHAVE",
Required: false, Required: false,
Hidden: true, Hidden: true,
Value: p2p.DefaultMeshDlazy, Value: p2p.DefaultMeshDlazy,
EnvVar: p2pEnv("GOSSIP_MESH_DLAZY"), EnvVars: p2pEnv("GOSSIP_MESH_DLAZY"),
} }
GossipFloodPublishFlag = cli.BoolFlag{ GossipFloodPublishFlag = &cli.BoolFlag{
Name: "p2p.gossip.mesh.floodpublish", Name: "p2p.gossip.mesh.floodpublish",
Usage: "Configure GossipSub to publish messages to all known peers on the topic, outside of the mesh, also see Dlazy as less aggressive alternative.", Usage: "Configure GossipSub to publish messages to all known peers on the topic, outside of the mesh, also see Dlazy as less aggressive alternative.",
Required: false, Required: false,
Hidden: true, Hidden: true,
EnvVar: p2pEnv("GOSSIP_FLOOD_PUBLISH"), EnvVars: p2pEnv("GOSSIP_FLOOD_PUBLISH"),
} }
SyncReqRespFlag = cli.BoolFlag{ SyncReqRespFlag = &cli.BoolFlag{
Name: "p2p.sync.req-resp", Name: "p2p.sync.req-resp",
Usage: "Enables experimental P2P req-resp alternative sync method, on both server and client side.", Usage: "Enables experimental P2P req-resp alternative sync method, on both server and client side.",
Required: false, Required: false,
EnvVar: p2pEnv("SYNC_REQ_RESP"), EnvVars: p2pEnv("SYNC_REQ_RESP"),
} }
) )
......
package p2p
import (
"time"
"github.com/ethereum-optimism/optimism/op-node/rollup"
)
type ApplicationScoreParams struct {
ValidResponseCap float64
ValidResponseWeight float64
ValidResponseDecay float64
ErrorResponseCap float64
ErrorResponseWeight float64
ErrorResponseDecay float64
RejectedPayloadCap float64
RejectedPayloadWeight float64
RejectedPayloadDecay float64
DecayToZero float64
DecayInterval time.Duration
}
func LightApplicationScoreParams(cfg *rollup.Config) ApplicationScoreParams {
slot := time.Duration(cfg.BlockTime) * time.Second
if slot == 0 {
slot = 2 * time.Second
}
// We initialize an "epoch" as 6 blocks suggesting 6 blocks,
// each taking ~ 2 seconds, is 12 seconds
epoch := 6 * slot
tenEpochs := 10 * epoch
return ApplicationScoreParams{
// Max positive score from valid responses: 5
ValidResponseCap: 10,
ValidResponseWeight: 0.5,
ValidResponseDecay: ScoreDecay(tenEpochs, slot),
// Takes 20 error responses to reach the default ban threshold of -100
// But at most we track 10. These errors include not supporting p2p sync
// so we don't (yet) want to ban a peer based on this measure alone.
ErrorResponseCap: 10,
ErrorResponseWeight: -5,
ErrorResponseDecay: ScoreDecay(tenEpochs, slot),
// Takes 5 rejected payloads to reach the default ban threshold of -100
RejectedPayloadCap: 20,
RejectedPayloadWeight: -20,
RejectedPayloadDecay: ScoreDecay(tenEpochs, slot),
DecayToZero: DecayToZero,
DecayInterval: slot,
}
}
package p2p
import (
"context"
"sync"
"github.com/ethereum-optimism/optimism/op-node/p2p/store"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/log"
"github.com/libp2p/go-libp2p/core/peer"
)
type ScoreBook interface {
GetPeerScores(id peer.ID) (store.PeerScores, error)
SetScore(id peer.ID, diff store.ScoreDiff) (store.PeerScores, error)
}
type ApplicationScorer interface {
ApplicationScore(id peer.ID) float64
onValidResponse(id peer.ID)
onResponseError(id peer.ID)
onRejectedPayload(id peer.ID)
start()
stop()
}
type peerApplicationScorer struct {
ctx context.Context
cancelFunc context.CancelFunc
log log.Logger
clock clock.Clock
params *ApplicationScoreParams
scorebook ScoreBook
connectedPeers func() []peer.ID
done sync.WaitGroup
}
var _ ApplicationScorer = (*peerApplicationScorer)(nil)
func newPeerApplicationScorer(ctx context.Context, logger log.Logger, clock clock.Clock, params *ApplicationScoreParams, scorebook ScoreBook, connectedPeers func() []peer.ID) *peerApplicationScorer {
ctx, cancelFunc := context.WithCancel(ctx)
return &peerApplicationScorer{
ctx: ctx,
cancelFunc: cancelFunc,
log: logger,
clock: clock,
params: params,
scorebook: scorebook,
connectedPeers: connectedPeers,
}
}
func (s *peerApplicationScorer) ApplicationScore(id peer.ID) float64 {
scores, err := s.scorebook.GetPeerScores(id)
if err != nil {
s.log.Error("Failed to load peer scores", "peer", id, "err", err)
return 0
}
score := scores.ReqResp.ValidResponses * s.params.ValidResponseWeight
score += scores.ReqResp.ErrorResponses * s.params.ErrorResponseWeight
score += scores.ReqResp.RejectedPayloads * s.params.RejectedPayloadWeight
return score
}
func (s *peerApplicationScorer) onValidResponse(id peer.ID) {
_, err := s.scorebook.SetScore(id, store.IncrementValidResponses{Cap: s.params.ValidResponseCap})
if err != nil {
s.log.Error("Unable to update peer score", "peer", id, "err", err)
return
}
}
func (s *peerApplicationScorer) onResponseError(id peer.ID) {
_, err := s.scorebook.SetScore(id, store.IncrementErrorResponses{Cap: s.params.ErrorResponseCap})
if err != nil {
s.log.Error("Unable to update peer score", "peer", id, "err", err)
return
}
}
func (s *peerApplicationScorer) onRejectedPayload(id peer.ID) {
_, err := s.scorebook.SetScore(id, store.IncrementRejectedPayloads{Cap: s.params.RejectedPayloadCap})
if err != nil {
s.log.Error("Unable to update peer score", "peer", id, "err", err)
return
}
}
func (s *peerApplicationScorer) decayScores(id peer.ID) {
_, err := s.scorebook.SetScore(id, &store.DecayApplicationScores{
ValidResponseDecay: s.params.ValidResponseDecay,
ErrorResponseDecay: s.params.ErrorResponseDecay,
RejectedPayloadDecay: s.params.RejectedPayloadDecay,
DecayToZero: s.params.DecayToZero,
})
if err != nil {
s.log.Error("Unable to decay peer score", "peer", id, "err", err)
return
}
}
func (s *peerApplicationScorer) decayConnectedPeerScores() {
for _, id := range s.connectedPeers() {
s.decayScores(id)
}
}
func (s *peerApplicationScorer) start() {
s.done.Add(1)
go func() {
defer s.done.Done()
ticker := s.clock.NewTicker(s.params.DecayInterval)
for {
select {
case <-s.ctx.Done():
return
case <-ticker.Ch():
s.decayConnectedPeerScores()
}
}
}()
}
func (s *peerApplicationScorer) stop() {
s.cancelFunc()
s.done.Wait()
}
type NoopApplicationScorer struct{}
func (n *NoopApplicationScorer) ApplicationScore(_ peer.ID) float64 {
return 0
}
func (n *NoopApplicationScorer) onValidResponse(_ peer.ID) {
}
func (n *NoopApplicationScorer) onResponseError(_ peer.ID) {
}
func (n *NoopApplicationScorer) onRejectedPayload(_ peer.ID) {
}
func (n *NoopApplicationScorer) start() {
}
func (n *NoopApplicationScorer) stop() {
}
var _ ApplicationScorer = (*NoopApplicationScorer)(nil)
package p2p
import (
"context"
"errors"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-node/p2p/store"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum/go-ethereum/log"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/require"
)
type stubScoreBookUpdate struct {
id peer.ID
diff store.ScoreDiff
}
type stubScoreBook struct {
err error
scores map[peer.ID]store.PeerScores
updates chan stubScoreBookUpdate
}
func (s *stubScoreBook) GetPeerScores(id peer.ID) (store.PeerScores, error) {
if s.err != nil {
return store.PeerScores{}, s.err
}
scores, ok := s.scores[id]
if !ok {
return store.PeerScores{}, nil
}
return scores, nil
}
func (s *stubScoreBook) SetScore(id peer.ID, diff store.ScoreDiff) (store.PeerScores, error) {
s.updates <- stubScoreBookUpdate{id, diff}
return s.GetPeerScores(id)
}
type appScoreTestData struct {
ctx context.Context
logger log.Logger
clock *clock.DeterministicClock
peers []peer.ID
scorebook *stubScoreBook
}
func (a *appScoreTestData) WaitForNextScoreBookUpdate(t *testing.T) stubScoreBookUpdate {
ctx, cancelFunc := context.WithTimeout(a.ctx, 30*time.Second)
defer cancelFunc()
select {
case update := <-a.scorebook.updates:
return update
case <-ctx.Done():
t.Fatal("Did not receive expected scorebook update")
return stubScoreBookUpdate{}
}
}
func setupPeerApplicationScorerTest(t *testing.T, params *ApplicationScoreParams) (*appScoreTestData, *peerApplicationScorer) {
data := &appScoreTestData{
ctx: context.Background(),
logger: testlog.Logger(t, log.LvlInfo),
clock: clock.NewDeterministicClock(time.UnixMilli(1000)),
peers: []peer.ID{},
scorebook: &stubScoreBook{
scores: make(map[peer.ID]store.PeerScores),
updates: make(chan stubScoreBookUpdate, 10),
},
}
appScorer := newPeerApplicationScorer(data.ctx, data.logger, data.clock, params, data.scorebook, func() []peer.ID {
return data.peers
})
return data, appScorer
}
func TestIncrementValidResponses(t *testing.T) {
data, appScorer := setupPeerApplicationScorerTest(t, &ApplicationScoreParams{
ValidResponseCap: 10,
})
appScorer.onValidResponse("aaa")
require.Len(t, data.scorebook.updates, 1)
update := <-data.scorebook.updates
require.Equal(t, stubScoreBookUpdate{peer.ID("aaa"), store.IncrementValidResponses{Cap: 10}}, update)
}
func TestIncrementErrorResponses(t *testing.T) {
data, appScorer := setupPeerApplicationScorerTest(t, &ApplicationScoreParams{
ErrorResponseCap: 10,
})
appScorer.onResponseError("aaa")
require.Len(t, data.scorebook.updates, 1)
update := <-data.scorebook.updates
require.Equal(t, stubScoreBookUpdate{peer.ID("aaa"), store.IncrementErrorResponses{Cap: 10}}, update)
}
func TestIncrementRejectedPayloads(t *testing.T) {
data, appScorer := setupPeerApplicationScorerTest(t, &ApplicationScoreParams{
RejectedPayloadCap: 10,
})
appScorer.onRejectedPayload("aaa")
require.Len(t, data.scorebook.updates, 1)
update := <-data.scorebook.updates
require.Equal(t, stubScoreBookUpdate{peer.ID("aaa"), store.IncrementRejectedPayloads{Cap: 10}}, update)
}
func TestApplicationScore(t *testing.T) {
data, appScorer := setupPeerApplicationScorerTest(t, &ApplicationScoreParams{
ValidResponseWeight: 0.8,
ErrorResponseWeight: 0.6,
RejectedPayloadWeight: 0.4,
})
peerScore := store.PeerScores{
ReqResp: store.ReqRespScores{
ValidResponses: 1,
ErrorResponses: 2,
RejectedPayloads: 3,
},
}
data.scorebook.scores["aaa"] = peerScore
score := appScorer.ApplicationScore("aaa")
require.Equal(t, 1*0.8+2*0.6+3*0.4, score)
}
func TestApplicationScoreZeroWhenScoreDoesNotLoad(t *testing.T) {
data, appScorer := setupPeerApplicationScorerTest(t, &ApplicationScoreParams{})
data.scorebook.err = errors.New("boom")
score := appScorer.ApplicationScore("aaa")
require.Zero(t, score)
}
func TestDecayScoresAfterDecayInterval(t *testing.T) {
params := &ApplicationScoreParams{
ValidResponseDecay: 0.8,
ErrorResponseDecay: 0.7,
RejectedPayloadDecay: 0.3,
DecayToZero: 0.1,
DecayInterval: 90 * time.Second,
}
data, appScorer := setupPeerApplicationScorerTest(t, params)
data.peers = []peer.ID{"aaa", "bbb"}
expectedDecay := &store.DecayApplicationScores{
ValidResponseDecay: 0.8,
ErrorResponseDecay: 0.7,
RejectedPayloadDecay: 0.3,
DecayToZero: 0.1,
}
appScorer.start()
defer appScorer.stop()
data.clock.WaitForNewPendingTaskWithTimeout(30 * time.Second)
data.clock.AdvanceTime(params.DecayInterval)
require.Equal(t, stubScoreBookUpdate{id: "aaa", diff: expectedDecay}, data.WaitForNextScoreBookUpdate(t))
require.Equal(t, stubScoreBookUpdate{id: "bbb", diff: expectedDecay}, data.WaitForNextScoreBookUpdate(t))
}
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/flags" "github.com/ethereum-optimism/optimism/op-node/flags"
"github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/p2p"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
) )
...@@ -28,7 +28,7 @@ import ( ...@@ -28,7 +28,7 @@ import (
func NewConfig(ctx *cli.Context, rollupCfg *rollup.Config) (*p2p.Config, error) { func NewConfig(ctx *cli.Context, rollupCfg *rollup.Config) (*p2p.Config, error) {
conf := &p2p.Config{} conf := &p2p.Config{}
if ctx.GlobalBool(flags.DisableP2P.Name) { if ctx.Bool(flags.DisableP2P.Name) {
conf.DisableP2P = true conf.DisableP2P = true
return conf, nil return conf, nil
} }
...@@ -63,7 +63,7 @@ func NewConfig(ctx *cli.Context, rollupCfg *rollup.Config) (*p2p.Config, error) ...@@ -63,7 +63,7 @@ func NewConfig(ctx *cli.Context, rollupCfg *rollup.Config) (*p2p.Config, error)
return nil, fmt.Errorf("failed to load banning option: %w", err) return nil, fmt.Errorf("failed to load banning option: %w", err)
} }
conf.EnableReqRespSync = ctx.GlobalBool(flags.SyncReqRespFlag.Name) conf.EnableReqRespSync = ctx.Bool(flags.SyncReqRespFlag.Name)
return conf, nil return conf, nil
} }
...@@ -83,13 +83,13 @@ func validatePort(p uint) (uint16, error) { ...@@ -83,13 +83,13 @@ func validatePort(p uint) (uint16, error) {
// loadScoringParams loads the peer scoring options from the CLI context. // loadScoringParams loads the peer scoring options from the CLI context.
func loadScoringParams(conf *p2p.Config, ctx *cli.Context, rollupCfg *rollup.Config) error { func loadScoringParams(conf *p2p.Config, ctx *cli.Context, rollupCfg *rollup.Config) error {
scoringLevel := ctx.GlobalString(flags.Scoring.Name) scoringLevel := ctx.String(flags.Scoring.Name)
// Check old names for backwards compatibility // Check old names for backwards compatibility
if scoringLevel == "" { if scoringLevel == "" {
scoringLevel = ctx.GlobalString(flags.PeerScoring.Name) scoringLevel = ctx.String(flags.PeerScoring.Name)
} }
if scoringLevel == "" { if scoringLevel == "" {
scoringLevel = ctx.GlobalString(flags.TopicScoring.Name) scoringLevel = ctx.String(flags.TopicScoring.Name)
} }
if scoringLevel != "" { if scoringLevel != "" {
params, err := p2p.GetScoringParams(scoringLevel, rollupCfg) params, err := p2p.GetScoringParams(scoringLevel, rollupCfg)
...@@ -104,14 +104,14 @@ func loadScoringParams(conf *p2p.Config, ctx *cli.Context, rollupCfg *rollup.Con ...@@ -104,14 +104,14 @@ func loadScoringParams(conf *p2p.Config, ctx *cli.Context, rollupCfg *rollup.Con
// loadBanningOptions loads whether or not to ban peers from the CLI context. // loadBanningOptions loads whether or not to ban peers from the CLI context.
func loadBanningOptions(conf *p2p.Config, ctx *cli.Context) error { func loadBanningOptions(conf *p2p.Config, ctx *cli.Context) error {
conf.BanningEnabled = ctx.GlobalBool(flags.Banning.Name) conf.BanningEnabled = ctx.Bool(flags.Banning.Name)
conf.BanningThreshold = ctx.GlobalFloat64(flags.BanningThreshold.Name) conf.BanningThreshold = ctx.Float64(flags.BanningThreshold.Name)
conf.BanningDuration = ctx.GlobalDuration(flags.BanningDuration.Name) conf.BanningDuration = ctx.Duration(flags.BanningDuration.Name)
return nil return nil
} }
func loadListenOpts(conf *p2p.Config, ctx *cli.Context) error { func loadListenOpts(conf *p2p.Config, ctx *cli.Context) error {
listenIP := ctx.GlobalString(flags.ListenIP.Name) listenIP := ctx.String(flags.ListenIP.Name)
if listenIP != "" { // optional if listenIP != "" { // optional
conf.ListenIP = net.ParseIP(listenIP) conf.ListenIP = net.ParseIP(listenIP)
if conf.ListenIP == nil { if conf.ListenIP == nil {
...@@ -119,11 +119,11 @@ func loadListenOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -119,11 +119,11 @@ func loadListenOpts(conf *p2p.Config, ctx *cli.Context) error {
} }
} }
var err error var err error
conf.ListenTCPPort, err = validatePort(ctx.GlobalUint(flags.ListenTCPPort.Name)) conf.ListenTCPPort, err = validatePort(ctx.Uint(flags.ListenTCPPort.Name))
if err != nil { if err != nil {
return fmt.Errorf("bad listen TCP port: %w", err) return fmt.Errorf("bad listen TCP port: %w", err)
} }
conf.ListenUDPPort, err = validatePort(ctx.GlobalUint(flags.ListenUDPPort.Name)) conf.ListenUDPPort, err = validatePort(ctx.Uint(flags.ListenUDPPort.Name))
if err != nil { if err != nil {
return fmt.Errorf("bad listen UDP port: %w", err) return fmt.Errorf("bad listen UDP port: %w", err)
} }
...@@ -131,20 +131,20 @@ func loadListenOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -131,20 +131,20 @@ func loadListenOpts(conf *p2p.Config, ctx *cli.Context) error {
} }
func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error { func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error {
if ctx.GlobalBool(flags.NoDiscovery.Name) { if ctx.Bool(flags.NoDiscovery.Name) {
conf.NoDiscovery = true conf.NoDiscovery = true
} }
var err error var err error
conf.AdvertiseTCPPort, err = validatePort(ctx.GlobalUint(flags.AdvertiseTCPPort.Name)) conf.AdvertiseTCPPort, err = validatePort(ctx.Uint(flags.AdvertiseTCPPort.Name))
if err != nil { if err != nil {
return fmt.Errorf("bad advertised TCP port: %w", err) return fmt.Errorf("bad advertised TCP port: %w", err)
} }
conf.AdvertiseUDPPort, err = validatePort(ctx.GlobalUint(flags.AdvertiseUDPPort.Name)) conf.AdvertiseUDPPort, err = validatePort(ctx.Uint(flags.AdvertiseUDPPort.Name))
if err != nil { if err != nil {
return fmt.Errorf("bad advertised UDP port: %w", err) return fmt.Errorf("bad advertised UDP port: %w", err)
} }
adIP := ctx.GlobalString(flags.AdvertiseIP.Name) adIP := ctx.String(flags.AdvertiseIP.Name)
if adIP != "" { // optional if adIP != "" { // optional
ips, err := net.LookupIP(adIP) ips, err := net.LookupIP(adIP)
if err != nil { if err != nil {
...@@ -162,7 +162,7 @@ func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -162,7 +162,7 @@ func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error {
} }
} }
dbPath := ctx.GlobalString(flags.DiscoveryPath.Name) dbPath := ctx.String(flags.DiscoveryPath.Name)
if dbPath == "" { if dbPath == "" {
dbPath = "opnode_discovery_db" dbPath = "opnode_discovery_db"
} }
...@@ -175,7 +175,7 @@ func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -175,7 +175,7 @@ func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error {
} }
bootnodes := make([]*enode.Node, 0) bootnodes := make([]*enode.Node, 0)
records := strings.Split(ctx.GlobalString(flags.Bootnodes.Name), ",") records := strings.Split(ctx.String(flags.Bootnodes.Name), ",")
for i, recordB64 := range records { for i, recordB64 := range records {
recordB64 = strings.TrimSpace(recordB64) recordB64 = strings.TrimSpace(recordB64)
if recordB64 == "" { // ignore empty records if recordB64 == "" { // ignore empty records
...@@ -197,7 +197,7 @@ func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -197,7 +197,7 @@ func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error {
} }
func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error { func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error {
addrs := strings.Split(ctx.GlobalString(flags.StaticPeers.Name), ",") addrs := strings.Split(ctx.String(flags.StaticPeers.Name), ",")
for i, addr := range addrs { for i, addr := range addrs {
addr = strings.TrimSpace(addr) addr = strings.TrimSpace(addr)
if addr == "" { if addr == "" {
...@@ -210,7 +210,7 @@ func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -210,7 +210,7 @@ func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error {
conf.StaticPeers = append(conf.StaticPeers, a) conf.StaticPeers = append(conf.StaticPeers, a)
} }
for _, v := range strings.Split(ctx.GlobalString(flags.HostMux.Name), ",") { for _, v := range strings.Split(ctx.String(flags.HostMux.Name), ",") {
v = strings.ToLower(strings.TrimSpace(v)) v = strings.ToLower(strings.TrimSpace(v))
switch v { switch v {
case "yamux": case "yamux":
...@@ -222,7 +222,7 @@ func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -222,7 +222,7 @@ func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error {
} }
} }
secArr := strings.Split(ctx.GlobalString(flags.HostSecurity.Name), ",") secArr := strings.Split(ctx.String(flags.HostSecurity.Name), ",")
for _, v := range secArr { for _, v := range secArr {
v = strings.ToLower(strings.TrimSpace(v)) v = strings.ToLower(strings.TrimSpace(v))
switch v { switch v {
...@@ -240,16 +240,16 @@ func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -240,16 +240,16 @@ func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error {
} }
} }
conf.PeersLo = ctx.GlobalUint(flags.PeersLo.Name) conf.PeersLo = ctx.Uint(flags.PeersLo.Name)
conf.PeersHi = ctx.GlobalUint(flags.PeersHi.Name) conf.PeersHi = ctx.Uint(flags.PeersHi.Name)
conf.PeersGrace = ctx.GlobalDuration(flags.PeersGrace.Name) conf.PeersGrace = ctx.Duration(flags.PeersGrace.Name)
conf.NAT = ctx.GlobalBool(flags.NAT.Name) conf.NAT = ctx.Bool(flags.NAT.Name)
conf.UserAgent = ctx.GlobalString(flags.UserAgent.Name) conf.UserAgent = ctx.String(flags.UserAgent.Name)
conf.TimeoutNegotiation = ctx.GlobalDuration(flags.TimeoutNegotiation.Name) conf.TimeoutNegotiation = ctx.Duration(flags.TimeoutNegotiation.Name)
conf.TimeoutAccept = ctx.GlobalDuration(flags.TimeoutAccept.Name) conf.TimeoutAccept = ctx.Duration(flags.TimeoutAccept.Name)
conf.TimeoutDial = ctx.GlobalDuration(flags.TimeoutDial.Name) conf.TimeoutDial = ctx.Duration(flags.TimeoutDial.Name)
peerstorePath := ctx.GlobalString(flags.PeerstorePath.Name) peerstorePath := ctx.String(flags.PeerstorePath.Name)
if peerstorePath == "" { if peerstorePath == "" {
return errors.New("peerstore path must be specified, use 'memory' to explicitly not persist peer records") return errors.New("peerstore path must be specified, use 'memory' to explicitly not persist peer records")
} }
...@@ -270,11 +270,11 @@ func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error { ...@@ -270,11 +270,11 @@ func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error {
} }
func loadNetworkPrivKey(ctx *cli.Context) (*crypto.Secp256k1PrivateKey, error) { func loadNetworkPrivKey(ctx *cli.Context) (*crypto.Secp256k1PrivateKey, error) {
raw := ctx.GlobalString(flags.P2PPrivRaw.Name) raw := ctx.String(flags.P2PPrivRaw.Name)
if raw != "" { if raw != "" {
return parsePriv(raw) return parsePriv(raw)
} }
keyPath := ctx.GlobalString(flags.P2PPrivPath.Name) keyPath := ctx.String(flags.P2PPrivPath.Name)
if keyPath == "" { if keyPath == "" {
return nil, errors.New("no p2p private key path specified, cannot auto-generate key without path") return nil, errors.New("no p2p private key path specified, cannot auto-generate key without path")
} }
...@@ -324,10 +324,10 @@ func parsePriv(data string) (*crypto.Secp256k1PrivateKey, error) { ...@@ -324,10 +324,10 @@ func parsePriv(data string) (*crypto.Secp256k1PrivateKey, error) {
} }
func loadGossipOptions(conf *p2p.Config, ctx *cli.Context) error { func loadGossipOptions(conf *p2p.Config, ctx *cli.Context) error {
conf.MeshD = ctx.GlobalInt(flags.GossipMeshDFlag.Name) conf.MeshD = ctx.Int(flags.GossipMeshDFlag.Name)
conf.MeshDLo = ctx.GlobalInt(flags.GossipMeshDloFlag.Name) conf.MeshDLo = ctx.Int(flags.GossipMeshDloFlag.Name)
conf.MeshDHi = ctx.GlobalInt(flags.GossipMeshDhiFlag.Name) conf.MeshDHi = ctx.Int(flags.GossipMeshDhiFlag.Name)
conf.MeshDLazy = ctx.GlobalInt(flags.GossipMeshDlazyFlag.Name) conf.MeshDLazy = ctx.Int(flags.GossipMeshDlazyFlag.Name)
conf.FloodPublish = ctx.GlobalBool(flags.GossipFloodPublishFlag.Name) conf.FloodPublish = ctx.Bool(flags.GossipFloodPublishFlag.Name)
return nil return nil
} }
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/flags" "github.com/ethereum-optimism/optimism/op-node/flags"
"github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/p2p"
...@@ -15,7 +15,7 @@ import ( ...@@ -15,7 +15,7 @@ import (
// LoadSignerSetup loads a configuration for a Signer to be set up later // LoadSignerSetup loads a configuration for a Signer to be set up later
func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) { func LoadSignerSetup(ctx *cli.Context) (p2p.SignerSetup, error) {
key := ctx.GlobalString(flags.SequencerP2PKeyFlag.Name) key := ctx.String(flags.SequencerP2PKeyFlag.Name)
if key != "" { if key != "" {
// Mnemonics are bad because they leak *all* keys when they leak. // Mnemonics are bad because they leak *all* keys when they leak.
// Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions). // Unencrypted keys from file are bad because they are easy to leak (and we are not checking file permissions).
......
...@@ -54,6 +54,7 @@ type SetupP2P interface { ...@@ -54,6 +54,7 @@ type SetupP2P interface {
// ScoringParams defines the various types of peer scoring parameters. // ScoringParams defines the various types of peer scoring parameters.
type ScoringParams struct { type ScoringParams struct {
PeerScoring pubsub.PeerScoreParams PeerScoring pubsub.PeerScoreParams
ApplicationScoring ApplicationScoreParams
} }
// Config sets up a p2p host and discv5 service from configuration. // Config sets up a p2p host and discv5 service from configuration.
...@@ -137,11 +138,11 @@ func (conf *Config) Disabled() bool { ...@@ -137,11 +138,11 @@ func (conf *Config) Disabled() bool {
return conf.DisableP2P return conf.DisableP2P
} }
func (conf *Config) PeerScoringParams() *pubsub.PeerScoreParams { func (conf *Config) PeerScoringParams() *ScoringParams {
if conf.ScoringParams == nil { if conf.ScoringParams == nil {
return nil return nil
} }
return &conf.ScoringParams.PeerScoring return conf.ScoringParams
} }
func (conf *Config) BanPeers() bool { func (conf *Config) BanPeers() bool {
......
...@@ -51,7 +51,7 @@ var MessageDomainInvalidSnappy = [4]byte{0, 0, 0, 0} ...@@ -51,7 +51,7 @@ var MessageDomainInvalidSnappy = [4]byte{0, 0, 0, 0}
var MessageDomainValidSnappy = [4]byte{1, 0, 0, 0} var MessageDomainValidSnappy = [4]byte{1, 0, 0, 0}
type GossipSetupConfigurables interface { type GossipSetupConfigurables interface {
PeerScoringParams() *pubsub.PeerScoreParams PeerScoringParams() *ScoringParams
// ConfigureGossip creates configuration options to apply to the GossipSub setup // ConfigureGossip creates configuration options to apply to the GossipSub setup
ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option
} }
......
...@@ -149,7 +149,7 @@ func (conf *Config) Host(log log.Logger, reporter metrics.Reporter, metrics Host ...@@ -149,7 +149,7 @@ func (conf *Config) Host(log log.Logger, reporter metrics.Reporter, metrics Host
var scoreRetention time.Duration var scoreRetention time.Duration
if peerScoreParams != nil { if peerScoreParams != nil {
// Use the same retention period as gossip will if available // Use the same retention period as gossip will if available
scoreRetention = peerScoreParams.RetainScore scoreRetention = peerScoreParams.PeerScoring.RetainScore
} else { } else {
// Disable score GC if peer scoring is disabled // Disable score GC if peer scoring is disabled
scoreRetention = 0 scoreRetention = 0
......
...@@ -37,6 +37,7 @@ type NodeP2P struct { ...@@ -37,6 +37,7 @@ type NodeP2P struct {
connMgr connmgr.ConnManager // p2p conn manager, to keep a reliable number of peers, may be nil even with p2p enabled connMgr connmgr.ConnManager // p2p conn manager, to keep a reliable number of peers, may be nil even with p2p enabled
peerMonitor *monitor.PeerMonitor // peer monitor to disconnect bad peers, may be nil even with p2p enabled peerMonitor *monitor.PeerMonitor // peer monitor to disconnect bad peers, may be nil even with p2p enabled
store store.ExtendedPeerstore // peerstore of host, with extra bindings for scoring and banning store store.ExtendedPeerstore // peerstore of host, with extra bindings for scoring and banning
appScorer ApplicationScorer
log log.Logger log log.Logger
// the below components are all optional, and may be nil. They require the host to not be nil. // the below components are all optional, and may be nil. They require the host to not be nil.
dv5Local *enode.LocalNode // p2p discovery identity dv5Local *enode.LocalNode // p2p discovery identity
...@@ -89,9 +90,21 @@ func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, l ...@@ -89,9 +90,21 @@ func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, l
n.gater = extra.ConnectionGater() n.gater = extra.ConnectionGater()
n.connMgr = extra.ConnectionManager() n.connMgr = extra.ConnectionManager()
} }
eps, ok := n.host.Peerstore().(store.ExtendedPeerstore)
if !ok {
return fmt.Errorf("cannot init without extended peerstore: %w", err)
}
n.store = eps
scoreParams := setup.PeerScoringParams()
if scoreParams != nil {
n.appScorer = newPeerApplicationScorer(resourcesCtx, log, clock.SystemClock, &scoreParams.ApplicationScoring, eps, n.host.Network().Peers)
} else {
n.appScorer = &NoopApplicationScorer{}
}
// Activate the P2P req-resp sync if enabled by feature-flag. // Activate the P2P req-resp sync if enabled by feature-flag.
if setup.ReqRespSyncEnabled() { if setup.ReqRespSyncEnabled() {
n.syncCl = NewSyncClient(log, rollupCfg, n.host.NewStream, gossipIn.OnUnsafeL2Payload, metrics) n.syncCl = NewSyncClient(log, rollupCfg, n.host.NewStream, gossipIn.OnUnsafeL2Payload, metrics, n.appScorer)
n.host.Network().Notify(&network.NotifyBundle{ n.host.Network().Notify(&network.NotifyBundle{
ConnectedF: func(nw network.Network, conn network.Conn) { ConnectedF: func(nw network.Network, conn network.Conn) {
n.syncCl.AddPeer(conn.RemotePeer()) n.syncCl.AddPeer(conn.RemotePeer())
...@@ -112,12 +125,7 @@ func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, l ...@@ -112,12 +125,7 @@ func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, l
n.host.SetStreamHandler(PayloadByNumberProtocolID(rollupCfg.L2ChainID), payloadByNumber) n.host.SetStreamHandler(PayloadByNumberProtocolID(rollupCfg.L2ChainID), payloadByNumber)
} }
} }
eps, ok := n.host.Peerstore().(store.ExtendedPeerstore) n.scorer = NewScorer(rollupCfg, eps, metrics, n.appScorer, log)
if !ok {
return fmt.Errorf("cannot init without extended peerstore: %w", err)
}
n.store = eps
n.scorer = NewScorer(rollupCfg, eps, metrics, log)
// notify of any new connections/streams/etc. // notify of any new connections/streams/etc.
n.host.Network().Notify(NewNetworkNotifier(log, metrics)) n.host.Network().Notify(NewNetworkNotifier(log, metrics))
// note: the IDDelta functionality was removed from libP2P, and no longer needs to be explicitly disabled. // note: the IDDelta functionality was removed from libP2P, and no longer needs to be explicitly disabled.
...@@ -150,6 +158,7 @@ func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, l ...@@ -150,6 +158,7 @@ func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, l
n.peerMonitor = monitor.NewPeerMonitor(resourcesCtx, log, clock.SystemClock, n, setup.BanThreshold(), setup.BanDuration()) n.peerMonitor = monitor.NewPeerMonitor(resourcesCtx, log, clock.SystemClock, n, setup.BanThreshold(), setup.BanDuration())
n.peerMonitor.Start() n.peerMonitor.Start()
} }
n.appScorer.start()
} }
return nil return nil
} }
...@@ -258,6 +267,9 @@ func (n *NodeP2P) Close() error { ...@@ -258,6 +267,9 @@ func (n *NodeP2P) Close() error {
} }
} }
} }
if n.appScorer != nil {
n.appScorer.stop()
}
return result.ErrorOrNil() return result.ErrorOrNil()
} }
......
...@@ -32,7 +32,7 @@ func ScoreDecay(duration time.Duration, slot time.Duration) float64 { ...@@ -32,7 +32,7 @@ func ScoreDecay(duration time.Duration, slot time.Duration) float64 {
// See [PeerScoreParams] for detailed documentation. // See [PeerScoreParams] for detailed documentation.
// //
// [PeerScoreParams]: https://pkg.go.dev/github.com/libp2p/go-libp2p-pubsub@v0.8.1#PeerScoreParams // [PeerScoreParams]: https://pkg.go.dev/github.com/libp2p/go-libp2p-pubsub@v0.8.1#PeerScoreParams
var LightPeerScoreParams = func(cfg *rollup.Config) pubsub.PeerScoreParams { func LightPeerScoreParams(cfg *rollup.Config) pubsub.PeerScoreParams {
slot := time.Duration(cfg.BlockTime) * time.Second slot := time.Duration(cfg.BlockTime) * time.Second
if slot == 0 { if slot == 0 {
slot = 2 * time.Second slot = 2 * time.Second
...@@ -92,6 +92,7 @@ func GetScoringParams(name string, cfg *rollup.Config) (*ScoringParams, error) { ...@@ -92,6 +92,7 @@ func GetScoringParams(name string, cfg *rollup.Config) (*ScoringParams, error) {
case "light": case "light":
return &ScoringParams{ return &ScoringParams{
PeerScoring: LightPeerScoreParams(cfg), PeerScoring: LightPeerScoreParams(cfg),
ApplicationScoring: LightApplicationScoreParams(cfg),
}, nil }, nil
case "none": case "none":
return nil, nil return nil, nil
......
...@@ -81,6 +81,19 @@ func (testSuite *PeerParamsTestSuite) TestGetPeerScoreParams_Light() { ...@@ -81,6 +81,19 @@ func (testSuite *PeerParamsTestSuite) TestGetPeerScoreParams_Light() {
testSuite.Equal(peerParams.DecayInterval, slot) testSuite.Equal(peerParams.DecayInterval, slot)
testSuite.Equal(peerParams.DecayToZero, DecayToZero) testSuite.Equal(peerParams.DecayToZero, DecayToZero)
testSuite.Equal(peerParams.RetainScore, oneHundredEpochs) testSuite.Equal(peerParams.RetainScore, oneHundredEpochs)
appParams := scoringParams.ApplicationScoring
testSuite.Positive(appParams.ValidResponseCap)
testSuite.Positive(appParams.ValidResponseWeight)
testSuite.Positive(appParams.ValidResponseDecay)
testSuite.Positive(appParams.ErrorResponseCap)
testSuite.Negative(appParams.ErrorResponseWeight)
testSuite.Positive(appParams.ErrorResponseDecay)
testSuite.Positive(appParams.RejectedPayloadCap)
testSuite.Negative(appParams.RejectedPayloadWeight)
testSuite.Positive(appParams.RejectedPayloadDecay)
testSuite.Equal(DecayToZero, appParams.DecayToZero)
testSuite.Equal(slot, appParams.DecayInterval)
} }
// TestParamsZeroBlockTime validates peer score params use default slot for 0 block time. // TestParamsZeroBlockTime validates peer score params use default slot for 0 block time.
...@@ -91,4 +104,5 @@ func (testSuite *PeerParamsTestSuite) TestParamsZeroBlockTime() { ...@@ -91,4 +104,5 @@ func (testSuite *PeerParamsTestSuite) TestParamsZeroBlockTime() {
params, err := GetScoringParams("light", &cfg) params, err := GetScoringParams("light", &cfg)
testSuite.NoError(err) testSuite.NoError(err)
testSuite.Equal(params.PeerScoring.DecayInterval, slot) testSuite.Equal(params.PeerScoring.DecayInterval, slot)
testSuite.Equal(params.ApplicationScoring.DecayInterval, slot)
} }
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
type scorer struct { type scorer struct {
peerStore Peerstore peerStore Peerstore
metricer ScoreMetrics metricer ScoreMetrics
appScorer ApplicationScorer
log log.Logger log log.Logger
cfg *rollup.Config cfg *rollup.Config
} }
...@@ -36,6 +37,7 @@ type Peerstore interface { ...@@ -36,6 +37,7 @@ type Peerstore interface {
// Scorer is a peer scorer that scores peers based on application-specific metrics. // Scorer is a peer scorer that scores peers based on application-specific metrics.
type Scorer interface { type Scorer interface {
SnapshotHook() pubsub.ExtendedPeerScoreInspectFn SnapshotHook() pubsub.ExtendedPeerScoreInspectFn
ApplicationScore(peer.ID) float64
} }
//go:generate mockery --name ScoreMetrics --output mocks/ //go:generate mockery --name ScoreMetrics --output mocks/
...@@ -44,10 +46,11 @@ type ScoreMetrics interface { ...@@ -44,10 +46,11 @@ type ScoreMetrics interface {
} }
// NewScorer returns a new peer scorer. // NewScorer returns a new peer scorer.
func NewScorer(cfg *rollup.Config, peerStore Peerstore, metricer ScoreMetrics, log log.Logger) Scorer { func NewScorer(cfg *rollup.Config, peerStore Peerstore, metricer ScoreMetrics, appScorer ApplicationScorer, log log.Logger) Scorer {
return &scorer{ return &scorer{
peerStore: peerStore, peerStore: peerStore,
metricer: metricer, metricer: metricer,
appScorer: appScorer,
log: log, log: log,
cfg: cfg, cfg: cfg,
} }
...@@ -84,3 +87,7 @@ func (s *scorer) SnapshotHook() pubsub.ExtendedPeerScoreInspectFn { ...@@ -84,3 +87,7 @@ func (s *scorer) SnapshotHook() pubsub.ExtendedPeerScoreInspectFn {
s.metricer.SetPeerScores(allScores) s.metricer.SetPeerScores(allScores)
} }
} }
func (s *scorer) ApplicationScore(id peer.ID) float64 {
return s.appScorer.ApplicationScore(id)
}
...@@ -44,6 +44,7 @@ func (testSuite *PeerScorerTestSuite) TestScorer_SnapshotHook() { ...@@ -44,6 +44,7 @@ func (testSuite *PeerScorerTestSuite) TestScorer_SnapshotHook() {
&rollup.Config{L2ChainID: big.NewInt(123)}, &rollup.Config{L2ChainID: big.NewInt(123)},
testSuite.mockStore, testSuite.mockStore,
testSuite.mockMetricer, testSuite.mockMetricer,
&p2p.NoopApplicationScorer{},
testSuite.logger, testSuite.logger,
) )
inspectFn := scorer.SnapshotHook() inspectFn := scorer.SnapshotHook()
......
...@@ -9,12 +9,15 @@ import ( ...@@ -9,12 +9,15 @@ import (
func ConfigurePeerScoring(gossipConf GossipSetupConfigurables, scorer Scorer, log log.Logger) []pubsub.Option { func ConfigurePeerScoring(gossipConf GossipSetupConfigurables, scorer Scorer, log log.Logger) []pubsub.Option {
// If we want to completely disable scoring config here, we can use the [peerScoringParams] // If we want to completely disable scoring config here, we can use the [peerScoringParams]
// to return early without returning any [pubsub.Option]. // to return early without returning any [pubsub.Option].
peerScoreParams := gossipConf.PeerScoringParams() scoreParams := gossipConf.PeerScoringParams()
peerScoreThresholds := NewPeerScoreThresholds()
opts := []pubsub.Option{} opts := []pubsub.Option{}
if peerScoreParams != nil { if scoreParams != nil {
peerScoreThresholds := NewPeerScoreThresholds()
// Create copy of params before modifying the AppSpecificScore
params := scoreParams.PeerScoring
params.AppSpecificScore = scorer.ApplicationScore
opts = []pubsub.Option{ opts = []pubsub.Option{
pubsub.WithPeerScore(peerScoreParams, &peerScoreThresholds), pubsub.WithPeerScore(&params, &peerScoreThresholds),
pubsub.WithPeerScoreInspect(scorer.SnapshotHook(), peerScoreInspectFrequency), pubsub.WithPeerScoreInspect(scorer.SnapshotHook(), peerScoreInspectFrequency),
} }
} else { } else {
......
...@@ -82,6 +82,18 @@ func getNetHosts(testSuite *PeerScoresTestSuite, ctx context.Context, n int) []h ...@@ -82,6 +82,18 @@ func getNetHosts(testSuite *PeerScoresTestSuite, ctx context.Context, n int) []h
return out return out
} }
type discriminatingAppScorer struct {
badPeer peer.ID
NoopApplicationScorer
}
func (d *discriminatingAppScorer) ApplicationScore(id peer.ID) float64 {
if id == d.badPeer {
return -1000
}
return 0
}
func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []host.Host) []*pubsub.PubSub { func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []host.Host) []*pubsub.PubSub {
var psubs []*pubsub.PubSub var psubs []*pubsub.PubSub
...@@ -100,17 +112,10 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts [] ...@@ -100,17 +112,10 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []
scorer := NewScorer( scorer := NewScorer(
&rollup.Config{L2ChainID: big.NewInt(123)}, &rollup.Config{L2ChainID: big.NewInt(123)},
extPeerStore, testSuite.mockMetricer, logger) extPeerStore, testSuite.mockMetricer, &discriminatingAppScorer{badPeer: hosts[0].ID()}, logger)
opts = append(opts, ConfigurePeerScoring(&Config{ opts = append(opts, ConfigurePeerScoring(&Config{
ScoringParams: &ScoringParams{ ScoringParams: &ScoringParams{
PeerScoring: pubsub.PeerScoreParams{ PeerScoring: pubsub.PeerScoreParams{
AppSpecificScore: func(p peer.ID) float64 {
if p == hosts[0].ID() {
return -1000
} else {
return 0
}
},
AppSpecificWeight: 1, AppSpecificWeight: 1,
DecayInterval: time.Second, DecayInterval: time.Second,
DecayToZero: 0.01, DecayToZero: 0.01,
......
...@@ -69,7 +69,7 @@ func (p *Prepared) ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option { ...@@ -69,7 +69,7 @@ func (p *Prepared) ConfigureGossip(rollupCfg *rollup.Config) []pubsub.Option {
} }
} }
func (p *Prepared) PeerScoringParams() *pubsub.PeerScoreParams { func (p *Prepared) PeerScoringParams() *ScoringParams {
return nil return nil
} }
......
...@@ -111,6 +111,12 @@ type SyncClientMetrics interface { ...@@ -111,6 +111,12 @@ type SyncClientMetrics interface {
PayloadsQuarantineSize(n int) PayloadsQuarantineSize(n int)
} }
type SyncPeerScorer interface {
onValidResponse(id peer.ID)
onResponseError(id peer.ID)
onRejectedPayload(id peer.ID)
}
// SyncClient implements a reverse chain sync with a minimal interface: // SyncClient implements a reverse chain sync with a minimal interface:
// signal the desired range, and receive blocks within this range back. // signal the desired range, and receive blocks within this range back.
// Through parent-hash verification, received blocks are all ensured to be part of the canonical chain at one point, // Through parent-hash verification, received blocks are all ensured to be part of the canonical chain at one point,
...@@ -181,6 +187,7 @@ type SyncClient struct { ...@@ -181,6 +187,7 @@ type SyncClient struct {
cfg *rollup.Config cfg *rollup.Config
metrics SyncClientMetrics metrics SyncClientMetrics
appScorer SyncPeerScorer
newStreamFn newStreamFn newStreamFn newStreamFn
payloadByNumber protocol.ID payloadByNumber protocol.ID
...@@ -227,13 +234,14 @@ type SyncClient struct { ...@@ -227,13 +234,14 @@ type SyncClient struct {
closingPeers bool closingPeers bool
} }
func NewSyncClient(log log.Logger, cfg *rollup.Config, newStream newStreamFn, rcv receivePayloadFn, metrics SyncClientMetrics) *SyncClient { func NewSyncClient(log log.Logger, cfg *rollup.Config, newStream newStreamFn, rcv receivePayloadFn, metrics SyncClientMetrics, appScorer SyncPeerScorer) *SyncClient {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c := &SyncClient{ c := &SyncClient{
log: log, log: log,
cfg: cfg, cfg: cfg,
metrics: metrics, metrics: metrics,
appScorer: appScorer,
newStreamFn: newStream, newStreamFn: newStream,
payloadByNumber: PayloadByNumberProtocolID(cfg.L2ChainID), payloadByNumber: PayloadByNumberProtocolID(cfg.L2ChainID),
peers: make(map[peer.ID]context.CancelFunc), peers: make(map[peer.ID]context.CancelFunc),
...@@ -268,11 +276,11 @@ func (s *SyncClient) Start() { ...@@ -268,11 +276,11 @@ func (s *SyncClient) Start() {
func (s *SyncClient) AddPeer(id peer.ID) { func (s *SyncClient) AddPeer(id peer.ID) {
s.peersLock.Lock() s.peersLock.Lock()
defer s.peersLock.Unlock() defer s.peersLock.Unlock()
if _, ok := s.peers[id]; ok { if s.closingPeers {
s.log.Warn("cannot register peer for sync duties, peer was already registered", "peer", id)
return return
} }
if s.closingPeers { if _, ok := s.peers[id]; ok {
s.log.Warn("cannot register peer for sync duties, peer was already registered", "peer", id)
return return
} }
s.wg.Add(1) s.wg.Add(1)
...@@ -424,7 +432,8 @@ func (s *SyncClient) onQuarantineEvict(key common.Hash, value syncResult) { ...@@ -424,7 +432,8 @@ func (s *SyncClient) onQuarantineEvict(key common.Hash, value syncResult) {
s.metrics.PayloadsQuarantineSize(s.quarantine.Len()) s.metrics.PayloadsQuarantineSize(s.quarantine.Len())
if !s.trusted.Contains(key) { if !s.trusted.Contains(key) {
s.log.Debug("evicting untrusted payload from quarantine", "id", value.payload.ID(), "peer", value.peer) s.log.Debug("evicting untrusted payload from quarantine", "id", value.payload.ID(), "peer", value.peer)
// TODO(CLI-3732): downscore peer for having provided us a bad block that never turned out to be canonical // Down-score peer for having provided us a bad block that never turned out to be canonical
s.appScorer.onRejectedPayload(value.peer)
} else { } else {
s.log.Debug("evicting trusted payload from quarantine", "id", value.payload.ID(), "peer", value.peer) s.log.Debug("evicting trusted payload from quarantine", "id", value.payload.ID(), "peer", value.peer)
} }
...@@ -492,9 +501,9 @@ func (s *SyncClient) peerLoop(ctx context.Context, id peer.ID) { ...@@ -492,9 +501,9 @@ func (s *SyncClient) peerLoop(ctx context.Context, id peer.ID) {
defer func() { defer func() {
s.peersLock.Lock() s.peersLock.Lock()
delete(s.peers, id) // clean up delete(s.peers, id) // clean up
s.log.Debug("stopped syncing loop of peer", "id", id)
s.wg.Done() s.wg.Done()
s.peersLock.Unlock() s.peersLock.Unlock()
s.log.Debug("stopped syncing loop of peer", "id", id)
}() }()
log := s.log.New("peer", id) log := s.log.New("peer", id)
...@@ -525,6 +534,7 @@ func (s *SyncClient) peerLoop(ctx context.Context, id peer.ID) { ...@@ -525,6 +534,7 @@ func (s *SyncClient) peerLoop(ctx context.Context, id peer.ID) {
// mark as complete if there's an error: we are not sending any result and can complete immediately. // mark as complete if there's an error: we are not sending any result and can complete immediately.
pr.complete.Store(true) pr.complete.Store(true)
log.Warn("failed p2p sync request", "num", pr.num, "err", err) log.Warn("failed p2p sync request", "num", pr.num, "err", err)
s.appScorer.onResponseError(id)
// If we hit an error, then count it as many requests. // If we hit an error, then count it as many requests.
// We'd like to avoid making more requests for a while, to back off. // We'd like to avoid making more requests for a while, to back off.
if err := rl.WaitN(ctx, clientErrRateCost); err != nil { if err := rl.WaitN(ctx, clientErrRateCost); err != nil {
...@@ -532,11 +542,9 @@ func (s *SyncClient) peerLoop(ctx context.Context, id peer.ID) { ...@@ -532,11 +542,9 @@ func (s *SyncClient) peerLoop(ctx context.Context, id peer.ID) {
} }
} else { } else {
log.Debug("completed p2p sync request", "num", pr.num) log.Debug("completed p2p sync request", "num", pr.num)
s.appScorer.onValidResponse(id)
} }
took := time.Since(start) took := time.Since(start)
// TODO(CLI-3732): update scores: depending on the speed of the result,
// increase the p2p-sync part of the peer score
// (don't allow the score to grow indefinitely only based on this factor though)
resultCode := byte(0) resultCode := byte(0)
if err != nil { if err != nil {
......
...@@ -137,7 +137,7 @@ func TestSinglePeerSync(t *testing.T) { ...@@ -137,7 +137,7 @@ func TestSinglePeerSync(t *testing.T) {
hostA.SetStreamHandler(PayloadByNumberProtocolID(cfg.L2ChainID), payloadByNumber) hostA.SetStreamHandler(PayloadByNumberProtocolID(cfg.L2ChainID), payloadByNumber)
// Setup host B as the client // Setup host B as the client
cl := NewSyncClient(log.New("role", "client"), cfg, hostB.NewStream, receivePayload, metrics.NoopMetrics) cl := NewSyncClient(log.New("role", "client"), cfg, hostB.NewStream, receivePayload, metrics.NoopMetrics, &NoopApplicationScorer{})
// Setup host B (client) to sync from its peer Host A (server) // Setup host B (client) to sync from its peer Host A (server)
cl.AddPeer(hostA.ID()) cl.AddPeer(hostA.ID())
...@@ -190,7 +190,7 @@ func TestMultiPeerSync(t *testing.T) { ...@@ -190,7 +190,7 @@ func TestMultiPeerSync(t *testing.T) {
payloadByNumber := MakeStreamHandler(ctx, log.New("serve", "payloads_by_number"), srv.HandleSyncRequest) payloadByNumber := MakeStreamHandler(ctx, log.New("serve", "payloads_by_number"), srv.HandleSyncRequest)
h.SetStreamHandler(PayloadByNumberProtocolID(cfg.L2ChainID), payloadByNumber) h.SetStreamHandler(PayloadByNumberProtocolID(cfg.L2ChainID), payloadByNumber)
cl := NewSyncClient(log.New("role", "client"), cfg, h.NewStream, receivePayload, metrics.NoopMetrics) cl := NewSyncClient(log.New("role", "client"), cfg, h.NewStream, receivePayload, metrics.NoopMetrics, &NoopApplicationScorer{})
return cl, received return cl, received
} }
......
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -64,27 +64,27 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -64,27 +64,27 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
Rollup: *rollupConfig, Rollup: *rollupConfig,
Driver: *driverConfig, Driver: *driverConfig,
RPC: node.RPCConfig{ RPC: node.RPCConfig{
ListenAddr: ctx.GlobalString(flags.RPCListenAddr.Name), ListenAddr: ctx.String(flags.RPCListenAddr.Name),
ListenPort: ctx.GlobalInt(flags.RPCListenPort.Name), ListenPort: ctx.Int(flags.RPCListenPort.Name),
EnableAdmin: ctx.GlobalBool(flags.RPCEnableAdmin.Name), EnableAdmin: ctx.Bool(flags.RPCEnableAdmin.Name),
}, },
Metrics: node.MetricsConfig{ Metrics: node.MetricsConfig{
Enabled: ctx.GlobalBool(flags.MetricsEnabledFlag.Name), Enabled: ctx.Bool(flags.MetricsEnabledFlag.Name),
ListenAddr: ctx.GlobalString(flags.MetricsAddrFlag.Name), ListenAddr: ctx.String(flags.MetricsAddrFlag.Name),
ListenPort: ctx.GlobalInt(flags.MetricsPortFlag.Name), ListenPort: ctx.Int(flags.MetricsPortFlag.Name),
}, },
Pprof: oppprof.CLIConfig{ Pprof: oppprof.CLIConfig{
Enabled: ctx.GlobalBool(flags.PprofEnabledFlag.Name), Enabled: ctx.Bool(flags.PprofEnabledFlag.Name),
ListenAddr: ctx.GlobalString(flags.PprofAddrFlag.Name), ListenAddr: ctx.String(flags.PprofAddrFlag.Name),
ListenPort: ctx.GlobalInt(flags.PprofPortFlag.Name), ListenPort: ctx.Int(flags.PprofPortFlag.Name),
}, },
P2P: p2pConfig, P2P: p2pConfig,
P2PSigner: p2pSignerSetup, P2PSigner: p2pSignerSetup,
L1EpochPollInterval: ctx.GlobalDuration(flags.L1EpochPollIntervalFlag.Name), L1EpochPollInterval: ctx.Duration(flags.L1EpochPollIntervalFlag.Name),
Heartbeat: node.HeartbeatConfig{ Heartbeat: node.HeartbeatConfig{
Enabled: ctx.GlobalBool(flags.HeartbeatEnabledFlag.Name), Enabled: ctx.Bool(flags.HeartbeatEnabledFlag.Name),
Moniker: ctx.GlobalString(flags.HeartbeatMonikerFlag.Name), Moniker: ctx.String(flags.HeartbeatMonikerFlag.Name),
URL: ctx.GlobalString(flags.HeartbeatURLFlag.Name), URL: ctx.String(flags.HeartbeatURLFlag.Name),
}, },
} }
if err := cfg.Check(); err != nil { if err := cfg.Check(); err != nil {
...@@ -95,18 +95,18 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -95,18 +95,18 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
func NewL1EndpointConfig(ctx *cli.Context) *node.L1EndpointConfig { func NewL1EndpointConfig(ctx *cli.Context) *node.L1EndpointConfig {
return &node.L1EndpointConfig{ return &node.L1EndpointConfig{
L1NodeAddr: ctx.GlobalString(flags.L1NodeAddr.Name), L1NodeAddr: ctx.String(flags.L1NodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name), L1TrustRPC: ctx.Bool(flags.L1TrustRPC.Name),
L1RPCKind: sources.RPCProviderKind(strings.ToLower(ctx.GlobalString(flags.L1RPCProviderKind.Name))), L1RPCKind: sources.RPCProviderKind(strings.ToLower(ctx.String(flags.L1RPCProviderKind.Name))),
RateLimit: ctx.GlobalFloat64(flags.L1RPCRateLimit.Name), RateLimit: ctx.Float64(flags.L1RPCRateLimit.Name),
BatchSize: ctx.GlobalInt(flags.L1RPCMaxBatchSize.Name), BatchSize: ctx.Int(flags.L1RPCMaxBatchSize.Name),
HttpPollInterval: ctx.Duration(flags.L1HTTPPollInterval.Name), HttpPollInterval: ctx.Duration(flags.L1HTTPPollInterval.Name),
} }
} }
func NewL2EndpointConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointConfig, error) { func NewL2EndpointConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointConfig, error) {
l2Addr := ctx.GlobalString(flags.L2EngineAddr.Name) l2Addr := ctx.String(flags.L2EngineAddr.Name)
fileName := ctx.GlobalString(flags.L2EngineJWTSecret.Name) fileName := ctx.String(flags.L2EngineJWTSecret.Name)
var secret [32]byte var secret [32]byte
fileName = strings.TrimSpace(fileName) fileName = strings.TrimSpace(fileName)
if fileName == "" { if fileName == "" {
...@@ -138,23 +138,23 @@ func NewL2EndpointConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointConf ...@@ -138,23 +138,23 @@ func NewL2EndpointConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointConf
// flag is set, otherwise nil. // flag is set, otherwise nil.
func NewL2SyncEndpointConfig(ctx *cli.Context) *node.L2SyncEndpointConfig { func NewL2SyncEndpointConfig(ctx *cli.Context) *node.L2SyncEndpointConfig {
return &node.L2SyncEndpointConfig{ return &node.L2SyncEndpointConfig{
L2NodeAddr: ctx.GlobalString(flags.BackupL2UnsafeSyncRPC.Name), L2NodeAddr: ctx.String(flags.BackupL2UnsafeSyncRPC.Name),
TrustRPC: ctx.GlobalBool(flags.BackupL2UnsafeSyncRPCTrustRPC.Name), TrustRPC: ctx.Bool(flags.BackupL2UnsafeSyncRPCTrustRPC.Name),
} }
} }
func NewDriverConfig(ctx *cli.Context) *driver.Config { func NewDriverConfig(ctx *cli.Context) *driver.Config {
return &driver.Config{ return &driver.Config{
VerifierConfDepth: ctx.GlobalUint64(flags.VerifierL1Confs.Name), VerifierConfDepth: ctx.Uint64(flags.VerifierL1Confs.Name),
SequencerConfDepth: ctx.GlobalUint64(flags.SequencerL1Confs.Name), SequencerConfDepth: ctx.Uint64(flags.SequencerL1Confs.Name),
SequencerEnabled: ctx.GlobalBool(flags.SequencerEnabledFlag.Name), SequencerEnabled: ctx.Bool(flags.SequencerEnabledFlag.Name),
SequencerStopped: ctx.GlobalBool(flags.SequencerStoppedFlag.Name), SequencerStopped: ctx.Bool(flags.SequencerStoppedFlag.Name),
SequencerMaxSafeLag: ctx.GlobalUint64(flags.SequencerMaxSafeLagFlag.Name), SequencerMaxSafeLag: ctx.Uint64(flags.SequencerMaxSafeLagFlag.Name),
} }
} }
func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) { func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) {
network := ctx.GlobalString(flags.Network.Name) network := ctx.String(flags.Network.Name)
if network != "" { if network != "" {
config, err := chaincfg.GetRollupConfig(network) config, err := chaincfg.GetRollupConfig(network)
if err != nil { if err != nil {
...@@ -164,7 +164,7 @@ func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) { ...@@ -164,7 +164,7 @@ func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) {
return &config, nil return &config, nil
} }
rollupConfigPath := ctx.GlobalString(flags.RollupConfig.Name) rollupConfigPath := ctx.String(flags.RollupConfig.Name)
file, err := os.Open(rollupConfigPath) file, err := os.Open(rollupConfigPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read rollup config: %w", err) return nil, fmt.Errorf("failed to read rollup config: %w", err)
...@@ -179,7 +179,7 @@ func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) { ...@@ -179,7 +179,7 @@ func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) {
} }
func NewSnapshotLogger(ctx *cli.Context) (log.Logger, error) { func NewSnapshotLogger(ctx *cli.Context) (log.Logger, error) {
snapshotFile := ctx.GlobalString(flags.SnapshotLog.Name) snapshotFile := ctx.String(flags.SnapshotLog.Name)
handler := log.DiscardHandler() handler := log.DiscardHandler()
if snapshotFile != "" { if snapshotFile != "" {
var err error var err error
......
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-program/host/version" "github.com/ethereum-optimism/optimism/op-program/host/version"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
...@@ -118,23 +118,23 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -118,23 +118,23 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
l2Head := common.HexToHash(ctx.GlobalString(flags.L2Head.Name)) l2Head := common.HexToHash(ctx.String(flags.L2Head.Name))
if l2Head == (common.Hash{}) { if l2Head == (common.Hash{}) {
return nil, ErrInvalidL2Head return nil, ErrInvalidL2Head
} }
l2Claim := common.HexToHash(ctx.GlobalString(flags.L2Claim.Name)) l2Claim := common.HexToHash(ctx.String(flags.L2Claim.Name))
if l2Claim == (common.Hash{}) { if l2Claim == (common.Hash{}) {
return nil, ErrInvalidL2Claim return nil, ErrInvalidL2Claim
} }
l2ClaimBlockNum := ctx.GlobalUint64(flags.L2BlockNumber.Name) l2ClaimBlockNum := ctx.Uint64(flags.L2BlockNumber.Name)
l1Head := common.HexToHash(ctx.GlobalString(flags.L1Head.Name)) l1Head := common.HexToHash(ctx.String(flags.L1Head.Name))
if l1Head == (common.Hash{}) { if l1Head == (common.Hash{}) {
return nil, ErrInvalidL1Head return nil, ErrInvalidL1Head
} }
l2GenesisPath := ctx.GlobalString(flags.L2GenesisPath.Name) l2GenesisPath := ctx.String(flags.L2GenesisPath.Name)
var l2ChainConfig *params.ChainConfig var l2ChainConfig *params.ChainConfig
if l2GenesisPath == "" { if l2GenesisPath == "" {
networkName := ctx.GlobalString(flags.Network.Name) networkName := ctx.String(flags.Network.Name)
l2ChainConfig = L2ChainConfigsByName[networkName] l2ChainConfig = L2ChainConfigsByName[networkName]
if l2ChainConfig == nil { if l2ChainConfig == nil {
return nil, fmt.Errorf("flag %s is required for network %s", flags.L2GenesisPath.Name, networkName) return nil, fmt.Errorf("flag %s is required for network %s", flags.L2GenesisPath.Name, networkName)
...@@ -147,18 +147,18 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -147,18 +147,18 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
} }
return &Config{ return &Config{
Rollup: rollupCfg, Rollup: rollupCfg,
DataDir: ctx.GlobalString(flags.DataDir.Name), DataDir: ctx.String(flags.DataDir.Name),
L2URL: ctx.GlobalString(flags.L2NodeAddr.Name), L2URL: ctx.String(flags.L2NodeAddr.Name),
L2ChainConfig: l2ChainConfig, L2ChainConfig: l2ChainConfig,
L2Head: l2Head, L2Head: l2Head,
L2Claim: l2Claim, L2Claim: l2Claim,
L2ClaimBlockNumber: l2ClaimBlockNum, L2ClaimBlockNumber: l2ClaimBlockNum,
L1Head: l1Head, L1Head: l1Head,
L1URL: ctx.GlobalString(flags.L1NodeAddr.Name), L1URL: ctx.String(flags.L1NodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name), L1TrustRPC: ctx.Bool(flags.L1TrustRPC.Name),
L1RPCKind: sources.RPCProviderKind(ctx.GlobalString(flags.L1RPCProviderKind.Name)), L1RPCKind: sources.RPCProviderKind(ctx.String(flags.L1RPCProviderKind.Name)),
ExecCmd: ctx.GlobalString(flags.Exec.Name), ExecCmd: ctx.String(flags.Exec.Name),
ServerMode: ctx.GlobalBool(flags.Server.Name), ServerMode: ctx.Bool(flags.Server.Name),
}, nil }, nil
} }
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
...@@ -15,81 +15,85 @@ import ( ...@@ -15,81 +15,85 @@ import (
const EnvVarPrefix = "OP_PROGRAM" const EnvVarPrefix = "OP_PROGRAM"
func prefixEnvVars(name string) []string {
return service.PrefixEnvVar(EnvVarPrefix, name)
}
var ( var (
RollupConfig = cli.StringFlag{ RollupConfig = &cli.StringFlag{
Name: "rollup.config", Name: "rollup.config",
Usage: "Rollup chain parameters", Usage: "Rollup chain parameters",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "ROLLUP_CONFIG"), EnvVars: prefixEnvVars("ROLLUP_CONFIG"),
} }
Network = cli.StringFlag{ Network = &cli.StringFlag{
Name: "network", Name: "network",
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")), Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "NETWORK"), EnvVars: prefixEnvVars("NETWORK"),
} }
DataDir = cli.StringFlag{ DataDir = &cli.StringFlag{
Name: "datadir", Name: "datadir",
Usage: "Directory to use for preimage data storage. Default uses in-memory storage", Usage: "Directory to use for preimage data storage. Default uses in-memory storage",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "DATADIR"), EnvVars: prefixEnvVars("DATADIR"),
} }
L2NodeAddr = cli.StringFlag{ L2NodeAddr = &cli.StringFlag{
Name: "l2", Name: "l2",
Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)", Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_RPC"), EnvVars: prefixEnvVars("L2_RPC"),
} }
L1Head = cli.StringFlag{ L1Head = &cli.StringFlag{
Name: "l1.head", Name: "l1.head",
Usage: "Hash of the L1 head block. Derivation stops after this block is processed.", Usage: "Hash of the L1 head block. Derivation stops after this block is processed.",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_HEAD"), EnvVars: prefixEnvVars("L1_HEAD"),
} }
L2Head = cli.StringFlag{ L2Head = &cli.StringFlag{
Name: "l2.head", Name: "l2.head",
Usage: "Hash of the agreed L2 block to start derivation from", Usage: "Hash of the agreed L2 block to start derivation from",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_HEAD"), EnvVars: prefixEnvVars("L2_HEAD"),
} }
L2Claim = cli.StringFlag{ L2Claim = &cli.StringFlag{
Name: "l2.claim", Name: "l2.claim",
Usage: "Claimed L2 output root to validate", Usage: "Claimed L2 output root to validate",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_CLAIM"), EnvVars: prefixEnvVars("L2_CLAIM"),
} }
L2BlockNumber = cli.Uint64Flag{ L2BlockNumber = &cli.Uint64Flag{
Name: "l2.blocknumber", Name: "l2.blocknumber",
Usage: "Number of the L2 block that the claim is from", Usage: "Number of the L2 block that the claim is from",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_BLOCK_NUM"), EnvVars: prefixEnvVars("L2_BLOCK_NUM"),
} }
L2GenesisPath = cli.StringFlag{ L2GenesisPath = &cli.StringFlag{
Name: "l2.genesis", Name: "l2.genesis",
Usage: "Path to the op-geth genesis file", Usage: "Path to the op-geth genesis file",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_GENESIS"), EnvVars: prefixEnvVars("L2_GENESIS"),
} }
L1NodeAddr = cli.StringFlag{ L1NodeAddr = &cli.StringFlag{
Name: "l1", Name: "l1",
Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)", Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_RPC"), EnvVars: prefixEnvVars("L1_RPC"),
} }
L1TrustRPC = cli.BoolFlag{ L1TrustRPC = &cli.BoolFlag{
Name: "l1.trustrpc", Name: "l1.trustrpc",
Usage: "Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data", Usage: "Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_TRUST_RPC"), EnvVars: prefixEnvVars("L1_TRUST_RPC"),
} }
L1RPCProviderKind = cli.GenericFlag{ L1RPCProviderKind = &cli.GenericFlag{
Name: "l1.rpckind", Name: "l1.rpckind",
Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " + Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " +
openum.EnumString(sources.RPCProviderKinds), openum.EnumString(sources.RPCProviderKinds),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_RPC_KIND"), EnvVars: prefixEnvVars("L1_RPC_KIND"),
Value: func() *sources.RPCProviderKind { Value: func() *sources.RPCProviderKind {
out := sources.RPCKindBasic out := sources.RPCKindBasic
return &out return &out
}(), }(),
} }
Exec = cli.StringFlag{ Exec = &cli.StringFlag{
Name: "exec", Name: "exec",
Usage: "Run the specified client program as a separate process detached from the host. Default is to run the client program in the host process.", Usage: "Run the specified client program as a separate process detached from the host. Default is to run the client program in the host process.",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "EXEC"), EnvVars: prefixEnvVars("EXEC"),
} }
Server = cli.BoolFlag{ Server = &cli.BoolFlag{
Name: "server", Name: "server",
Usage: "Run in pre-image server mode without executing any client program.", Usage: "Run in pre-image server mode without executing any client program.",
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "SERVER"), EnvVars: prefixEnvVars("SERVER"),
} }
) )
...@@ -122,20 +126,20 @@ func init() { ...@@ -122,20 +126,20 @@ func init() {
} }
func CheckRequired(ctx *cli.Context) error { func CheckRequired(ctx *cli.Context) error {
rollupConfig := ctx.GlobalString(RollupConfig.Name) rollupConfig := ctx.String(RollupConfig.Name)
network := ctx.GlobalString(Network.Name) network := ctx.String(Network.Name)
if rollupConfig == "" && network == "" { if rollupConfig == "" && network == "" {
return fmt.Errorf("flag %s or %s is required", RollupConfig.Name, Network.Name) return fmt.Errorf("flag %s or %s is required", RollupConfig.Name, Network.Name)
} }
if rollupConfig != "" && network != "" { if rollupConfig != "" && network != "" {
return fmt.Errorf("cannot specify both %s and %s", RollupConfig.Name, Network.Name) return fmt.Errorf("cannot specify both %s and %s", RollupConfig.Name, Network.Name)
} }
if network == "" && ctx.GlobalString(L2GenesisPath.Name) == "" { if network == "" && ctx.String(L2GenesisPath.Name) == "" {
return fmt.Errorf("flag %s is required for custom networks", L2GenesisPath.Name) return fmt.Errorf("flag %s is required for custom networks", L2GenesisPath.Name)
} }
for _, flag := range requiredFlags { for _, flag := range requiredFlags {
if !ctx.IsSet(flag.GetName()) { if !ctx.IsSet(flag.Names()[0]) {
return fmt.Errorf("flag %s is required", flag.GetName()) return fmt.Errorf("flag %s is required", flag.Names()[0])
} }
} }
return nil return nil
......
...@@ -5,14 +5,14 @@ import ( ...@@ -5,14 +5,14 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags. // TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags.
func TestUniqueFlags(t *testing.T) { func TestUniqueFlags(t *testing.T) {
seenCLI := make(map[string]struct{}) seenCLI := make(map[string]struct{})
for _, flag := range Flags { for _, flag := range Flags {
name := flag.GetName() name := flag.Names()[0]
if _, ok := seenCLI[name]; ok { if _, ok := seenCLI[name]; ok {
t.Errorf("duplicate flag %s", name) t.Errorf("duplicate flag %s", name)
continue continue
...@@ -38,22 +38,22 @@ func TestCorrectEnvVarPrefix(t *testing.T) { ...@@ -38,22 +38,22 @@ func TestCorrectEnvVarPrefix(t *testing.T) {
for _, flag := range Flags { for _, flag := range Flags {
envVar := envVarForFlag(flag) envVar := envVarForFlag(flag)
if envVar == "" { if envVar == "" {
t.Errorf("Failed to find EnvVar for flag %v", flag.GetName()) t.Errorf("Failed to find EnvVar for flag %v", flag.Names()[0])
} }
if !strings.HasPrefix(envVar, "OP_PROGRAM_") { if !strings.HasPrefix(envVar, "OP_PROGRAM_") {
t.Errorf("Flag %v env var (%v) does not start with OP_PROGRAM_", flag.GetName(), envVar) t.Errorf("Flag %v env var (%v) does not start with OP_PROGRAM_", flag.Names()[0], envVar)
} }
if strings.Contains(envVar, "__") { if strings.Contains(envVar, "__") {
t.Errorf("Flag %v env var (%v) has duplicate underscores", flag.GetName(), envVar) t.Errorf("Flag %v env var (%v) has duplicate underscores", flag.Names()[0], envVar)
} }
} }
} }
func envVarForFlag(flag cli.Flag) string { func envVarForFlag(flag cli.Flag) string {
values := reflect.ValueOf(flag) values := reflect.ValueOf(flag)
envVarValue := values.FieldByName("EnvVar") envVarValue := values.Elem().FieldByName("EnvVars")
if envVarValue == (reflect.Value{}) { if envVarValue == (reflect.Value{}) || envVarValue.Len() == 0 {
return "" return ""
} }
return envVarValue.String() return envVarValue.Index(0).String()
} }
...@@ -6,7 +6,6 @@ import ( ...@@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
"math"
"os" "os"
"os/exec" "os/exec"
...@@ -22,13 +21,10 @@ import ( ...@@ -22,13 +21,10 @@ import (
"github.com/ethereum-optimism/optimism/op-program/host/prefetcher" "github.com/ethereum-optimism/optimism/op-program/host/prefetcher"
oppio "github.com/ethereum-optimism/optimism/op-program/io" oppio "github.com/ethereum-optimism/optimism/op-program/io"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
opclient "github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
const maxRPCRetries = math.MaxInt
type L2Source struct { type L2Source struct {
*sources.L2Client *sources.L2Client
*sources.DebugClient *sources.DebugClient
...@@ -192,41 +188,31 @@ func PreimageServer(ctx context.Context, logger log.Logger, cfg *config.Config, ...@@ -192,41 +188,31 @@ func PreimageServer(ctx context.Context, logger log.Logger, cfg *config.Config,
func makePrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV, cfg *config.Config) (*prefetcher.Prefetcher, error) { func makePrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV, cfg *config.Config) (*prefetcher.Prefetcher, error) {
logger.Info("Connecting to L1 node", "l1", cfg.L1URL) logger.Info("Connecting to L1 node", "l1", cfg.L1URL)
l1RPC, err := createRetryingRPC(ctx, logger, cfg.L1URL) l1RPC, err := client.NewRPC(ctx, logger, cfg.L1URL)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to setup L1 RPC: %w", err) return nil, fmt.Errorf("failed to setup L1 RPC: %w", err)
} }
logger.Info("Connecting to L2 node", "l2", cfg.L2URL) logger.Info("Connecting to L2 node", "l2", cfg.L2URL)
l2RPC, err := createRetryingRPC(ctx, logger, cfg.L2URL) l2RPC, err := client.NewRPC(ctx, logger, cfg.L2URL)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to setup L2 RPC: %w", err) return nil, fmt.Errorf("failed to setup L2 RPC: %w", err)
} }
l1ClCfg := sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind) l1ClCfg := sources.L1ClientDefaultConfig(cfg.Rollup, cfg.L1TrustRPC, cfg.L1RPCKind)
l2ClCfg := sources.L2ClientDefaultConfig(cfg.Rollup, true)
l1Cl, err := sources.NewL1Client(l1RPC, logger, nil, l1ClCfg) l1Cl, err := sources.NewL1Client(l1RPC, logger, nil, l1ClCfg)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create L1 client: %w", err) return nil, fmt.Errorf("failed to create L1 client: %w", err)
} }
l2ClCfg := sources.L2ClientDefaultConfig(cfg.Rollup, true)
l2Cl, err := sources.NewL2Client(l2RPC, logger, nil, l2ClCfg) l2Cl, err := sources.NewL2Client(l2RPC, logger, nil, l2ClCfg)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create L2 client: %w", err) return nil, fmt.Errorf("failed to create L2 client: %w", err)
} }
l2DebugCl := &L2Source{L2Client: l2Cl, DebugClient: sources.NewDebugClient(l2RPC.CallContext)} l2DebugCl := &L2Source{L2Client: l2Cl, DebugClient: sources.NewDebugClient(l2RPC.CallContext)}
return prefetcher.NewPrefetcher(logger, l1Cl, l2DebugCl, kv), nil return prefetcher.NewPrefetcher(logger, l1Cl, l2DebugCl, kv), nil
} }
func createRetryingRPC(ctx context.Context, logger log.Logger, url string) (client.RPC, error) {
rpc, err := client.NewRPC(ctx, logger, url)
if err != nil {
return nil, err
}
return opclient.NewRetryingClient(logger, rpc, maxRPCRetries), nil
}
func routeHints(logger log.Logger, hHostRW io.ReadWriter, hinter preimage.HintHandler) chan error { func routeHints(logger log.Logger, hHostRW io.ReadWriter, hinter preimage.HintHandler) chan error {
chErr := make(chan error) chErr := make(chan error)
hintReader := preimage.NewHintReader(hHostRW) hintReader := preimage.NewHintReader(hHostRW)
......
...@@ -42,8 +42,8 @@ type Prefetcher struct { ...@@ -42,8 +42,8 @@ type Prefetcher struct {
func NewPrefetcher(logger log.Logger, l1Fetcher L1Source, l2Fetcher L2Source, kvStore kvstore.KV) *Prefetcher { func NewPrefetcher(logger log.Logger, l1Fetcher L1Source, l2Fetcher L2Source, kvStore kvstore.KV) *Prefetcher {
return &Prefetcher{ return &Prefetcher{
logger: logger, logger: logger,
l1Fetcher: l1Fetcher, l1Fetcher: NewRetryingL1Source(logger, l1Fetcher),
l2Fetcher: l2Fetcher, l2Fetcher: NewRetryingL2Source(logger, l2Fetcher),
kvStore: kvStore, kvStore: kvStore,
} }
} }
......
package prefetcher
import (
"context"
"math"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
const maxAttempts = math.MaxInt // Succeed or die trying
type RetryingL1Source struct {
logger log.Logger
source L1Source
strategy backoff.Strategy
}
func NewRetryingL1Source(logger log.Logger, source L1Source) *RetryingL1Source {
return &RetryingL1Source{
logger: logger,
source: source,
strategy: backoff.Exponential(),
}
}
func (s *RetryingL1Source) InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error) {
var info eth.BlockInfo
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
res, err := s.source.InfoByHash(ctx, blockHash)
if err != nil {
s.logger.Warn("Failed to retrieve info", "hash", blockHash, "err", err)
return err
}
info = res
return nil
})
return info, err
}
func (s *RetryingL1Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) {
var info eth.BlockInfo
var txs types.Transactions
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
i, t, err := s.source.InfoAndTxsByHash(ctx, blockHash)
if err != nil {
s.logger.Warn("Failed to retrieve l1 info and txs", "hash", blockHash, "err", err)
return err
}
info = i
txs = t
return nil
})
return info, txs, err
}
func (s *RetryingL1Source) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) {
var info eth.BlockInfo
var rcpts types.Receipts
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
i, r, err := s.source.FetchReceipts(ctx, blockHash)
if err != nil {
s.logger.Warn("Failed to fetch receipts", "hash", blockHash, "err", err)
return err
}
info = i
rcpts = r
return nil
})
return info, rcpts, err
}
var _ L1Source = (*RetryingL1Source)(nil)
type RetryingL2Source struct {
logger log.Logger
source L2Source
strategy backoff.Strategy
}
func (s *RetryingL2Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) {
var info eth.BlockInfo
var txs types.Transactions
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
i, t, err := s.source.InfoAndTxsByHash(ctx, blockHash)
if err != nil {
s.logger.Warn("Failed to retrieve l2 info and txs", "hash", blockHash, "err", err)
return err
}
info = i
txs = t
return nil
})
return info, txs, err
}
func (s *RetryingL2Source) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
var node []byte
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
n, err := s.source.NodeByHash(ctx, hash)
if err != nil {
s.logger.Warn("Failed to retrieve node", "hash", hash, "err", err)
return err
}
node = n
return nil
})
return node, err
}
func (s *RetryingL2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
var code []byte
err := backoff.DoCtx(ctx, maxAttempts, s.strategy, func() error {
c, err := s.source.CodeByHash(ctx, hash)
if err != nil {
s.logger.Warn("Failed to retrieve code", "hash", hash, "err", err)
return err
}
code = c
return nil
})
return code, err
}
func NewRetryingL2Source(logger log.Logger, source L2Source) *RetryingL2Source {
return &RetryingL2Source{
logger: logger,
source: source,
strategy: backoff.Exponential(),
}
}
var _ L2Source = (*RetryingL2Source)(nil)
package prefetcher
import (
"context"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestRetryingL1Source(t *testing.T) {
ctx := context.Background()
hash := common.Hash{0xab}
info := &testutils.MockBlockInfo{InfoHash: hash}
// The mock really doesn't like returning nil for a eth.BlockInfo so return a value we expect to be ignored instead
wrongInfo := &testutils.MockBlockInfo{InfoHash: common.Hash{0x99}}
txs := types.Transactions{
&types.Transaction{},
}
rcpts := types.Receipts{
&types.Receipt{},
}
t.Run("InfoByHash Success", func(t *testing.T) {
source, mock := createL1Source(t)
defer mock.AssertExpectations(t)
mock.ExpectInfoByHash(hash, info, nil)
result, err := source.InfoByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, info, result)
})
t.Run("InfoByHash Error", func(t *testing.T) {
source, mock := createL1Source(t)
defer mock.AssertExpectations(t)
expectedErr := errors.New("boom")
mock.ExpectInfoByHash(hash, wrongInfo, expectedErr)
mock.ExpectInfoByHash(hash, info, nil)
result, err := source.InfoByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, info, result)
})
t.Run("InfoAndTxsByHash Success", func(t *testing.T) {
source, mock := createL1Source(t)
defer mock.AssertExpectations(t)
mock.ExpectInfoAndTxsByHash(hash, info, txs, nil)
actualInfo, actualTxs, err := source.InfoAndTxsByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, info, actualInfo)
require.Equal(t, txs, actualTxs)
})
t.Run("InfoAndTxsByHash Error", func(t *testing.T) {
source, mock := createL1Source(t)
defer mock.AssertExpectations(t)
expectedErr := errors.New("boom")
mock.ExpectInfoAndTxsByHash(hash, wrongInfo, nil, expectedErr)
mock.ExpectInfoAndTxsByHash(hash, info, txs, nil)
actualInfo, actualTxs, err := source.InfoAndTxsByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, info, actualInfo)
require.Equal(t, txs, actualTxs)
})
t.Run("FetchReceipts Success", func(t *testing.T) {
source, mock := createL1Source(t)
defer mock.AssertExpectations(t)
mock.ExpectFetchReceipts(hash, info, rcpts, nil)
actualInfo, actualRcpts, err := source.FetchReceipts(ctx, hash)
require.NoError(t, err)
require.Equal(t, info, actualInfo)
require.Equal(t, rcpts, actualRcpts)
})
t.Run("FetchReceipts Error", func(t *testing.T) {
source, mock := createL1Source(t)
defer mock.AssertExpectations(t)
expectedErr := errors.New("boom")
mock.ExpectFetchReceipts(hash, wrongInfo, nil, expectedErr)
mock.ExpectFetchReceipts(hash, info, rcpts, nil)
actualInfo, actualRcpts, err := source.FetchReceipts(ctx, hash)
require.NoError(t, err)
require.Equal(t, info, actualInfo)
require.Equal(t, rcpts, actualRcpts)
})
}
func createL1Source(t *testing.T) (*RetryingL1Source, *testutils.MockL1Source) {
logger := testlog.Logger(t, log.LvlDebug)
mock := &testutils.MockL1Source{}
source := NewRetryingL1Source(logger, mock)
// Avoid sleeping in tests by using a fixed backoff strategy with no delay
source.strategy = backoff.Fixed(0)
return source, mock
}
func TestRetryingL2Source(t *testing.T) {
ctx := context.Background()
hash := common.Hash{0xab}
info := &testutils.MockBlockInfo{InfoHash: hash}
// The mock really doesn't like returning nil for a eth.BlockInfo so return a value we expect to be ignored instead
wrongInfo := &testutils.MockBlockInfo{InfoHash: common.Hash{0x99}}
txs := types.Transactions{
&types.Transaction{},
}
data := []byte{1, 2, 3, 4, 5}
t.Run("InfoAndTxsByHash Success", func(t *testing.T) {
source, mock := createL2Source(t)
defer mock.AssertExpectations(t)
mock.ExpectInfoAndTxsByHash(hash, info, txs, nil)
actualInfo, actualTxs, err := source.InfoAndTxsByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, info, actualInfo)
require.Equal(t, txs, actualTxs)
})
t.Run("InfoAndTxsByHash Error", func(t *testing.T) {
source, mock := createL2Source(t)
defer mock.AssertExpectations(t)
expectedErr := errors.New("boom")
mock.ExpectInfoAndTxsByHash(hash, wrongInfo, nil, expectedErr)
mock.ExpectInfoAndTxsByHash(hash, info, txs, nil)
actualInfo, actualTxs, err := source.InfoAndTxsByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, info, actualInfo)
require.Equal(t, txs, actualTxs)
})
t.Run("NodeByHash Success", func(t *testing.T) {
source, mock := createL2Source(t)
defer mock.AssertExpectations(t)
mock.ExpectNodeByHash(hash, data, nil)
actual, err := source.NodeByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, data, actual)
})
t.Run("NodeByHash Error", func(t *testing.T) {
source, mock := createL2Source(t)
defer mock.AssertExpectations(t)
expectedErr := errors.New("boom")
mock.ExpectNodeByHash(hash, nil, expectedErr)
mock.ExpectNodeByHash(hash, data, nil)
actual, err := source.NodeByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, data, actual)
})
t.Run("CodeByHash Success", func(t *testing.T) {
source, mock := createL2Source(t)
defer mock.AssertExpectations(t)
mock.ExpectCodeByHash(hash, data, nil)
actual, err := source.CodeByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, data, actual)
})
t.Run("CodeByHash Error", func(t *testing.T) {
source, mock := createL2Source(t)
defer mock.AssertExpectations(t)
expectedErr := errors.New("boom")
mock.ExpectCodeByHash(hash, nil, expectedErr)
mock.ExpectCodeByHash(hash, data, nil)
actual, err := source.CodeByHash(ctx, hash)
require.NoError(t, err)
require.Equal(t, data, actual)
})
}
func createL2Source(t *testing.T) (*RetryingL2Source, *MockL2Source) {
logger := testlog.Logger(t, log.LvlDebug)
mock := &MockL2Source{}
source := NewRetryingL2Source(logger, mock)
// Avoid sleeping in tests by using a fixed backoff strategy with no delay
source.strategy = backoff.Fixed(0)
return source, mock
}
type MockL2Source struct {
mock.Mock
}
func (m *MockL2Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) {
out := m.Mock.MethodCalled("InfoAndTxsByHash", blockHash)
return out[0].(eth.BlockInfo), out[1].(types.Transactions), *out[2].(*error)
}
func (m *MockL2Source) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
out := m.Mock.MethodCalled("NodeByHash", hash)
return out[0].([]byte), *out[1].(*error)
}
func (m *MockL2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
out := m.Mock.MethodCalled("CodeByHash", hash)
return out[0].([]byte), *out[1].(*error)
}
func (m *MockL2Source) ExpectInfoAndTxsByHash(blockHash common.Hash, info eth.BlockInfo, txs types.Transactions, err error) {
m.Mock.On("InfoAndTxsByHash", blockHash).Once().Return(info, txs, &err)
}
func (m *MockL2Source) ExpectNodeByHash(hash common.Hash, node []byte, err error) {
m.Mock.On("NodeByHash", hash).Once().Return(node, &err)
}
func (m *MockL2Source) ExpectCodeByHash(hash common.Hash, code []byte, err error) {
m.Mock.On("CodeByHash", hash).Once().Return(code, &err)
}
var _ L2Source = (*MockL2Source)(nil)
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-proposer/metrics" "github.com/ethereum-optimism/optimism/op-proposer/metrics"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var Subcommands = cli.Commands{ var Subcommands = cli.Commands{
...@@ -16,7 +16,7 @@ var Subcommands = cli.Commands{ ...@@ -16,7 +16,7 @@ var Subcommands = cli.Commands{
Name: "metrics", Name: "metrics",
Usage: "Dumps a list of supported metrics to stdout", Usage: "Dumps a list of supported metrics to stdout",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "format", Name: "format",
Value: "markdown", Value: "markdown",
Usage: "Output format (json|markdown)", Usage: "Output format (json|markdown)",
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-proposer/cmd/doc" "github.com/ethereum-optimism/optimism/op-proposer/cmd/doc"
"github.com/ethereum-optimism/optimism/op-proposer/flags" "github.com/ethereum-optimism/optimism/op-proposer/flags"
...@@ -29,7 +29,7 @@ func main() { ...@@ -29,7 +29,7 @@ func main() {
app.Usage = "L2Output Submitter" app.Usage = "L2Output Submitter"
app.Description = "Service for generating and submitting L2 Output checkpoints to the L2OutputOracle contract" app.Description = "Service for generating and submitting L2 Output checkpoints to the L2OutputOracle contract"
app.Action = curryMain(Version) app.Action = curryMain(Version)
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "doc", Name: "doc",
Subcommands: doc.Subcommands, Subcommands: doc.Subcommands,
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
...@@ -16,35 +16,39 @@ import ( ...@@ -16,35 +16,39 @@ import (
const EnvVarPrefix = "OP_PROPOSER" const EnvVarPrefix = "OP_PROPOSER"
func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(EnvVarPrefix, name)
}
var ( var (
// Required Flags // Required Flags
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = &cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1", Usage: "HTTP provider URL for L1",
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L1_ETH_RPC"), EnvVars: prefixEnvVars("L1_ETH_RPC"),
} }
RollupRpcFlag = cli.StringFlag{ RollupRpcFlag = &cli.StringFlag{
Name: "rollup-rpc", Name: "rollup-rpc",
Usage: "HTTP provider URL for the rollup node", Usage: "HTTP provider URL for the rollup node",
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_RPC"), EnvVars: prefixEnvVars("ROLLUP_RPC"),
} }
L2OOAddressFlag = cli.StringFlag{ L2OOAddressFlag = &cli.StringFlag{
Name: "l2oo-address", Name: "l2oo-address",
Usage: "Address of the L2OutputOracle contract", Usage: "Address of the L2OutputOracle contract",
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L2OO_ADDRESS"), EnvVars: prefixEnvVars("L2OO_ADDRESS"),
} }
// Optional flags // Optional flags
PollIntervalFlag = cli.DurationFlag{ PollIntervalFlag = &cli.DurationFlag{
Name: "poll-interval", Name: "poll-interval",
Usage: "How frequently to poll L2 for new blocks", Usage: "How frequently to poll L2 for new blocks",
Value: 6 * time.Second, Value: 6 * time.Second,
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "POLL_INTERVAL"), EnvVars: prefixEnvVars("POLL_INTERVAL"),
} }
AllowNonFinalizedFlag = cli.BoolFlag{ AllowNonFinalizedFlag = &cli.BoolFlag{
Name: "allow-non-finalized", Name: "allow-non-finalized",
Usage: "Allow the proposer to submit proposals for L2 blocks derived from non-finalized L1 blocks.", Usage: "Allow the proposer to submit proposals for L2 blocks derived from non-finalized L1 blocks.",
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ALLOW_NON_FINALIZED"), EnvVars: prefixEnvVars("ALLOW_NON_FINALIZED"),
} }
// Legacy Flags // Legacy Flags
L2OutputHDPathFlag = txmgr.L2OutputHDPathFlag L2OutputHDPathFlag = txmgr.L2OutputHDPathFlag
...@@ -77,8 +81,8 @@ var Flags []cli.Flag ...@@ -77,8 +81,8 @@ var Flags []cli.Flag
func CheckRequired(ctx *cli.Context) error { func CheckRequired(ctx *cli.Context) error {
for _, f := range requiredFlags { for _, f := range requiredFlags {
if !ctx.GlobalIsSet(f.GetName()) { if !ctx.IsSet(f.Names()[0]) {
return fmt.Errorf("flag %s is required", f.GetName()) return fmt.Errorf("flag %s is required", f.Names()[0])
} }
} }
return nil return nil
......
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-proposer/flags" "github.com/ethereum-optimism/optimism/op-proposer/flags"
...@@ -86,13 +86,13 @@ func (c CLIConfig) Check() error { ...@@ -86,13 +86,13 @@ func (c CLIConfig) Check() error {
func NewConfig(ctx *cli.Context) CLIConfig { func NewConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
// Required Flags // Required Flags
L1EthRpc: ctx.GlobalString(flags.L1EthRpcFlag.Name), L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name),
RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name), RollupRpc: ctx.String(flags.RollupRpcFlag.Name),
L2OOAddress: ctx.GlobalString(flags.L2OOAddressFlag.Name), L2OOAddress: ctx.String(flags.L2OOAddressFlag.Name),
PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name), PollInterval: ctx.Duration(flags.PollIntervalFlag.Name),
TxMgrConfig: txmgr.ReadCLIConfig(ctx), TxMgrConfig: txmgr.ReadCLIConfig(ctx),
// Optional Flags // Optional Flags
AllowNonFinalized: ctx.GlobalBool(flags.AllowNonFinalizedFlag.Name), AllowNonFinalized: ctx.Bool(flags.AllowNonFinalizedFlag.Name),
RPCConfig: oprpc.ReadCLIConfig(ctx), RPCConfig: oprpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx), LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx), MetricsConfig: opmetrics.ReadCLIConfig(ctx),
......
...@@ -14,7 +14,7 @@ import ( ...@@ -14,7 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
......
package client
import (
"context"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/hashicorp/go-multierror"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-service/backoff"
)
var (
// ExponentialBackoff is the default backoff strategy.
ExponentialBackoff = backoff.Exponential()
)
// retryingClient wraps a [client.RPC] with a backoff strategy.
type retryingClient struct {
log log.Logger
c client.RPC
retryAttempts int
strategy backoff.Strategy
}
// NewRetryingClient creates a new retrying client.
// The backoff strategy is optional, if not provided, the default exponential backoff strategy is used.
func NewRetryingClient(logger log.Logger, c client.RPC, retries int, strategy ...backoff.Strategy) *retryingClient {
if len(strategy) == 0 {
strategy = []backoff.Strategy{ExponentialBackoff}
}
return &retryingClient{
log: logger,
c: c,
retryAttempts: retries,
strategy: strategy[0],
}
}
// BackoffStrategy returns the [backoff.Strategy] used by the client.
func (b *retryingClient) BackoffStrategy() backoff.Strategy {
return b.strategy
}
func (b *retryingClient) Close() {
b.c.Close()
}
func (b *retryingClient) CallContext(ctx context.Context, result any, method string, args ...any) error {
return backoff.DoCtx(ctx, b.retryAttempts, b.strategy, func() error {
cCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
err := b.c.CallContext(cCtx, result, method, args...)
if err != nil {
b.log.Warn("RPC request failed", "method", method, "err", err)
}
return err
})
}
// pendingReq combines BatchElem information with the index of this request in the original []rpc.BatchElem
type pendingReq struct {
// req is a copy of the BatchElem individual request to make.
// It never has Result or Error set as it gets copied again as part of being passed to the underlying client.
req rpc.BatchElem
// idx tracks the index of the original BatchElem in the supplied input array
// This can then be used to set the result on the original input
idx int
}
func (b *retryingClient) BatchCallContext(ctx context.Context, input []rpc.BatchElem) error {
// Add all BatchElem to the initial pending set
// Each time we retry, we'll remove successful BatchElem for this list so we only retry ones that fail.
pending := make([]*pendingReq, len(input))
for i, req := range input {
pending[i] = &pendingReq{
req: req,
idx: i,
}
}
return backoff.DoCtx(ctx, b.retryAttempts, b.strategy, func() error {
cCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
batch := make([]rpc.BatchElem, len(pending))
for i, req := range pending {
batch[i] = req.req
}
err := b.c.BatchCallContext(cCtx, batch)
if err != nil {
b.log.Warn("Batch request failed", "err", err)
// Whole call failed, retry all pending elems again
return err
}
var failed []*pendingReq
var combinedErr error
for i, elem := range batch {
req := pending[i]
idx := req.idx // Index into input of the original BatchElem
// Set the result on the original batch to pass back to the caller in case we stop retrying
input[idx].Error = elem.Error
input[idx].Result = elem.Result
// If the individual request failed, add it to the list to retry
if elem.Error != nil {
// Need to retry this request
failed = append(failed, req)
combinedErr = multierror.Append(elem.Error, combinedErr)
}
}
if len(failed) > 0 {
pending = failed
b.log.Warn("Batch request returned errors", "err", combinedErr)
return combinedErr
}
return nil
})
}
func (b *retryingClient) EthSubscribe(ctx context.Context, channel any, args ...any) (ethereum.Subscription, error) {
var sub ethereum.Subscription
err := backoff.DoCtx(ctx, b.retryAttempts, b.strategy, func() error {
var err error
sub, err = b.c.EthSubscribe(ctx, channel, args...)
if err != nil {
b.log.Warn("Subscription request failed", "err", err)
}
return err
})
return sub, err
}
package client_test
import (
"context"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-service/backoff"
"github.com/ethereum-optimism/optimism/op-service/client"
opclient "github.com/ethereum-optimism/optimism/op-node/client"
)
type MockRPC struct {
mock.Mock
}
func (m *MockRPC) Close() {
m.Called()
}
func (m *MockRPC) CallContext(ctx context.Context, result any, method string, args ...any) error {
out := m.Mock.MethodCalled("CallContext", ctx, result, method, args)
return *out[0].(*error)
}
func (m *MockRPC) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
out := m.Mock.MethodCalled("BatchCallContext", ctx, b)
err, ok := out[0].(*error)
if ok {
return *err
}
return nil
}
func (m *MockRPC) EthSubscribe(ctx context.Context, channel any, args ...any) (ethereum.Subscription, error) {
out := m.Mock.MethodCalled("EthSubscribe", ctx, channel, args)
return *out[0].(*ethereum.Subscription), *out[1].(*error)
}
func (m *MockRPC) ExpectCallContext(err error, result any, method string, arg string) {
m.On("CallContext", mock.Anything, result, method, []interface{}{arg}).Return(&err)
}
func (m *MockRPC) ExpectBatchCallContext(err error, b []rpc.BatchElem) {
m.On("BatchCallContext", mock.Anything, b).Return(&err)
}
func (m *MockRPC) OnBatchCallContext(err error, b []rpc.BatchElem, action func(callBatches []rpc.BatchElem)) {
m.On("BatchCallContext", mock.Anything, b).Return(err).Run(func(args mock.Arguments) {
action(args[1].([]rpc.BatchElem))
})
}
func (m *MockRPC) ExpectEthSubscribe(sub ethereum.Subscription, err error, channel any, args ...any) {
m.On("EthSubscribe", mock.Anything, channel, args).Return(&sub, &err)
}
var _ opclient.RPC = (*MockRPC)(nil)
func TestClient_BackoffClient_Strategy(t *testing.T) {
mockRpc := &MockRPC{}
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 0)
require.Equal(t, backoffClient.BackoffStrategy(), client.ExponentialBackoff)
fixedStrategy := &backoff.FixedStrategy{}
backoffClient = client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 0, fixedStrategy)
require.Equal(t, backoffClient.BackoffStrategy(), fixedStrategy)
}
func TestClient_BackoffClient_Close(t *testing.T) {
mockRpc := &MockRPC{}
mockRpc.On("Close").Return()
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 0)
backoffClient.Close()
require.True(t, mockRpc.AssertCalled(t, "Close"))
}
func TestClient_BackoffClient_CallContext(t *testing.T) {
mockRpc := &MockRPC{}
mockRpc.ExpectCallContext(nil, nil, "foo", "bar")
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 1)
err := backoffClient.CallContext(context.Background(), nil, "foo", "bar")
require.NoError(t, err)
require.True(t, mockRpc.AssertCalled(t, "CallContext", mock.Anything, nil, "foo", []interface{}{"bar"}))
}
func TestClient_BackoffClient_CallContext_WithRetries(t *testing.T) {
mockRpc := &MockRPC{}
mockRpc.ExpectCallContext(errors.New("foo"), nil, "foo", "bar")
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 2, backoff.Fixed(0))
err := backoffClient.CallContext(context.Background(), nil, "foo", "bar")
require.Error(t, err)
require.True(t, mockRpc.AssertNumberOfCalls(t, "CallContext", 2))
}
func TestClient_BackoffClient_BatchCallContext(t *testing.T) {
mockRpc := &MockRPC{}
mockRpc.ExpectBatchCallContext(nil, []rpc.BatchElem{})
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 1)
err := backoffClient.BatchCallContext(context.Background(), nil)
require.NoError(t, err)
require.True(t, mockRpc.AssertCalled(t, "BatchCallContext", mock.Anything, []rpc.BatchElem{}))
}
func TestClient_BackoffClient_BatchCallContext_WithRetries(t *testing.T) {
mockRpc := &MockRPC{}
mockRpc.ExpectBatchCallContext(errors.New("foo"), []rpc.BatchElem{})
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 2, backoff.Fixed(0))
err := backoffClient.BatchCallContext(context.Background(), nil)
require.Error(t, err)
require.True(t, mockRpc.AssertNumberOfCalls(t, "BatchCallContext", 2))
}
func TestClient_BackoffClient_BatchCallContext_WithPartialRetries(t *testing.T) {
batches := []rpc.BatchElem{
{Method: "0"},
{Method: "1"},
{Method: "2"},
}
mockRpc := &MockRPC{}
mockRpc.OnBatchCallContext(nil, batches, func(batch []rpc.BatchElem) {
batch[0].Result = batch[0].Method
batch[1].Error = errors.New("boom")
batch[2].Error = errors.New("boom")
})
mockRpc.OnBatchCallContext(nil, []rpc.BatchElem{batches[1], batches[2]}, func(batch []rpc.BatchElem) {
batch[0].Error = errors.New("boom again")
batch[1].Result = batch[1].Method
})
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 2, backoff.Fixed(0))
err := backoffClient.BatchCallContext(context.Background(), batches)
require.Error(t, err)
require.True(t, mockRpc.AssertNumberOfCalls(t, "BatchCallContext", 2))
// Check our original batches got updated correctly
require.Equal(t, rpc.BatchElem{Method: "0", Result: "0"}, batches[0])
require.Equal(t, rpc.BatchElem{Method: "1", Result: nil, Error: errors.New("boom again")}, batches[1])
require.Equal(t, rpc.BatchElem{Method: "2", Result: "2"}, batches[2])
}
func TestClient_BackoffClient_BatchCallContext_WithPartialRetriesUntilSuccess(t *testing.T) {
batches := []rpc.BatchElem{
{Method: "0"},
{Method: "1"},
{Method: "2"},
}
mockRpc := &MockRPC{}
mockRpc.OnBatchCallContext(nil, batches, func(batch []rpc.BatchElem) {
batch[0].Result = batch[0].Method
batch[1].Error = errors.New("boom")
batch[2].Error = errors.New("boom")
})
mockRpc.OnBatchCallContext(nil, []rpc.BatchElem{batches[1], batches[2]}, func(batch []rpc.BatchElem) {
batch[0].Error = errors.New("boom again")
batch[1].Result = batch[1].Method
})
mockRpc.OnBatchCallContext(nil, []rpc.BatchElem{batches[1]}, func(batch []rpc.BatchElem) {
batch[0].Result = batch[0].Method
})
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 4, backoff.Fixed(0))
err := backoffClient.BatchCallContext(context.Background(), batches)
require.NoError(t, err)
require.True(t, mockRpc.AssertNumberOfCalls(t, "BatchCallContext", 3))
// Check our original batches got updated correctly
require.Equal(t, rpc.BatchElem{Method: "0", Result: "0"}, batches[0])
require.Equal(t, rpc.BatchElem{Method: "1", Result: "1"}, batches[1])
require.Equal(t, rpc.BatchElem{Method: "2", Result: "2"}, batches[2])
}
func TestClient_BackoffClient_EthSubscribe(t *testing.T) {
mockRpc := &MockRPC{}
mockRpc.ExpectEthSubscribe(ethereum.Subscription(nil), nil, nil, "foo", "bar")
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 1)
_, err := backoffClient.EthSubscribe(context.Background(), nil, "foo", "bar")
require.NoError(t, err)
require.True(t, mockRpc.AssertCalled(t, "EthSubscribe", mock.Anything, nil, []interface{}{"foo", "bar"}))
}
func TestClient_BackoffClient_EthSubscribe_WithRetries(t *testing.T) {
mockRpc := &MockRPC{}
mockRpc.ExpectEthSubscribe(ethereum.Subscription(nil), errors.New("foo"), nil, "foo", "bar")
backoffClient := client.NewRetryingClient(testlog.Logger(t, log.LvlInfo), mockRpc, 2, backoff.Fixed(0))
_, err := backoffClient.EthSubscribe(context.Background(), nil, "foo", "bar")
require.Error(t, err)
require.True(t, mockRpc.AssertNumberOfCalls(t, "EthSubscribe", 2))
}
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"golang.org/x/term" "golang.org/x/term"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
...@@ -20,22 +20,22 @@ const ( ...@@ -20,22 +20,22 @@ const (
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: LevelFlagName, Name: LevelFlagName,
Usage: "The lowest log level that will be output", Usage: "The lowest log level that will be output",
Value: "info", Value: "info",
EnvVar: opservice.PrefixEnvVar(envPrefix, "LOG_LEVEL"), EnvVars: opservice.PrefixEnvVar(envPrefix, "LOG_LEVEL"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: FormatFlagName, Name: FormatFlagName,
Usage: "Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty',", Usage: "Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty',",
Value: "text", Value: "text",
EnvVar: opservice.PrefixEnvVar(envPrefix, "LOG_FORMAT"), EnvVars: opservice.PrefixEnvVar(envPrefix, "LOG_FORMAT"),
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: ColorFlagName, Name: ColorFlagName,
Usage: "Color the log output if in terminal mode", Usage: "Color the log output if in terminal mode",
EnvVar: opservice.PrefixEnvVar(envPrefix, "LOG_COLOR"), EnvVars: opservice.PrefixEnvVar(envPrefix, "LOG_COLOR"),
}, },
} }
} }
...@@ -81,7 +81,7 @@ func DefaultCLIConfig() CLIConfig { ...@@ -81,7 +81,7 @@ func DefaultCLIConfig() CLIConfig {
} }
} }
func ReadLocalCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
cfg := DefaultCLIConfig() cfg := DefaultCLIConfig()
cfg.Level = ctx.String(LevelFlagName) cfg.Level = ctx.String(LevelFlagName)
cfg.Format = ctx.String(FormatFlagName) cfg.Format = ctx.String(FormatFlagName)
...@@ -91,16 +91,6 @@ func ReadLocalCLIConfig(ctx *cli.Context) CLIConfig { ...@@ -91,16 +91,6 @@ func ReadLocalCLIConfig(ctx *cli.Context) CLIConfig {
return cfg return cfg
} }
func ReadCLIConfig(ctx *cli.Context) CLIConfig {
cfg := DefaultCLIConfig()
cfg.Level = ctx.GlobalString(LevelFlagName)
cfg.Format = ctx.GlobalString(FormatFlagName)
if ctx.IsSet(ColorFlagName) {
cfg.Color = ctx.GlobalBool(ColorFlagName)
}
return cfg
}
// Format turns a string and color into a structured Format object // Format turns a string and color into a structured Format object
func Format(lf string, color bool) log.Format { func Format(lf string, color bool) log.Format {
switch lf { switch lf {
......
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const ( const (
...@@ -17,22 +17,22 @@ const ( ...@@ -17,22 +17,22 @@ const (
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: EnabledFlagName, Name: EnabledFlagName,
Usage: "Enable the metrics server", Usage: "Enable the metrics server",
EnvVar: opservice.PrefixEnvVar(envPrefix, "METRICS_ENABLED"), EnvVars: opservice.PrefixEnvVar(envPrefix, "METRICS_ENABLED"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: ListenAddrFlagName, Name: ListenAddrFlagName,
Usage: "Metrics listening address", Usage: "Metrics listening address",
Value: "0.0.0.0", Value: "0.0.0.0",
EnvVar: opservice.PrefixEnvVar(envPrefix, "METRICS_ADDR"), EnvVars: opservice.PrefixEnvVar(envPrefix, "METRICS_ADDR"),
}, },
cli.IntFlag{ &cli.IntFlag{
Name: PortFlagName, Name: PortFlagName,
Usage: "Metrics listening port", Usage: "Metrics listening port",
Value: 7300, Value: 7300,
EnvVar: opservice.PrefixEnvVar(envPrefix, "METRICS_PORT"), EnvVars: opservice.PrefixEnvVar(envPrefix, "METRICS_PORT"),
}, },
} }
} }
...@@ -56,14 +56,6 @@ func (m CLIConfig) Check() error { ...@@ -56,14 +56,6 @@ func (m CLIConfig) Check() error {
} }
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{
Enabled: ctx.GlobalBool(EnabledFlagName),
ListenAddr: ctx.GlobalString(ListenAddrFlagName),
ListenPort: ctx.GlobalInt(PortFlagName),
}
}
func ReadLocalCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
Enabled: ctx.Bool(EnabledFlagName), Enabled: ctx.Bool(EnabledFlagName),
ListenAddr: ctx.String(ListenAddrFlagName), ListenAddr: ctx.String(ListenAddrFlagName),
......
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"math" "math"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const ( const (
...@@ -16,22 +16,22 @@ const ( ...@@ -16,22 +16,22 @@ const (
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: EnabledFlagName, Name: EnabledFlagName,
Usage: "Enable the pprof server", Usage: "Enable the pprof server",
EnvVar: opservice.PrefixEnvVar(envPrefix, "PPROF_ENABLED"), EnvVars: opservice.PrefixEnvVar(envPrefix, "PPROF_ENABLED"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: ListenAddrFlagName, Name: ListenAddrFlagName,
Usage: "pprof listening address", Usage: "pprof listening address",
Value: "0.0.0.0", Value: "0.0.0.0",
EnvVar: opservice.PrefixEnvVar(envPrefix, "PPROF_ADDR"), EnvVars: opservice.PrefixEnvVar(envPrefix, "PPROF_ADDR"),
}, },
cli.IntFlag{ &cli.IntFlag{
Name: PortFlagName, Name: PortFlagName,
Usage: "pprof listening port", Usage: "pprof listening port",
Value: 6060, Value: 6060,
EnvVar: opservice.PrefixEnvVar(envPrefix, "PPROF_PORT"), EnvVars: opservice.PrefixEnvVar(envPrefix, "PPROF_PORT"),
}, },
} }
} }
...@@ -56,8 +56,8 @@ func (m CLIConfig) Check() error { ...@@ -56,8 +56,8 @@ func (m CLIConfig) Check() error {
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
Enabled: ctx.GlobalBool(EnabledFlagName), Enabled: ctx.Bool(EnabledFlagName),
ListenAddr: ctx.GlobalString(ListenAddrFlagName), ListenAddr: ctx.String(ListenAddrFlagName),
ListenPort: ctx.GlobalInt(PortFlagName), ListenPort: ctx.Int(PortFlagName),
} }
} }
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"math" "math"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const ( const (
...@@ -15,17 +15,17 @@ const ( ...@@ -15,17 +15,17 @@ const (
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: ListenAddrFlagName, Name: ListenAddrFlagName,
Usage: "rpc listening address", Usage: "rpc listening address",
Value: "0.0.0.0", Value: "0.0.0.0",
EnvVar: opservice.PrefixEnvVar(envPrefix, "RPC_ADDR"), EnvVars: opservice.PrefixEnvVar(envPrefix, "RPC_ADDR"),
}, },
cli.IntFlag{ &cli.IntFlag{
Name: PortFlagName, Name: PortFlagName,
Usage: "rpc listening port", Usage: "rpc listening port",
Value: 8545, Value: 8545,
EnvVar: opservice.PrefixEnvVar(envPrefix, "RPC_PORT"), EnvVars: opservice.PrefixEnvVar(envPrefix, "RPC_PORT"),
}, },
} }
} }
...@@ -45,7 +45,7 @@ func (c CLIConfig) Check() error { ...@@ -45,7 +45,7 @@ func (c CLIConfig) Check() error {
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
ListenAddr: ctx.GlobalString(ListenAddrFlagName), ListenAddr: ctx.String(ListenAddrFlagName),
ListenPort: ctx.GlobalInt(PortFlagName), ListenPort: ctx.Int(PortFlagName),
} }
} }
...@@ -6,7 +6,7 @@ import ( ...@@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
) )
...@@ -29,24 +29,27 @@ func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string) []cli.Flag { ...@@ -29,24 +29,27 @@ func CLIFlagsWithFlagPrefix(envPrefix string, flagPrefix string) []cli.Flag {
prefixFunc := func(flagName string) string { prefixFunc := func(flagName string) string {
return strings.Trim(fmt.Sprintf("%s.%s", flagPrefix, flagName), ".") return strings.Trim(fmt.Sprintf("%s.%s", flagPrefix, flagName), ".")
} }
prefixEnvVars := func(name string) []string {
return opservice.PrefixEnvVar(envPrefix, name)
}
return []cli.Flag{ return []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: prefixFunc(TLSCaCertFlagName), Name: prefixFunc(TLSCaCertFlagName),
Usage: "tls ca cert path", Usage: "tls ca cert path",
Value: "tls/ca.crt", Value: "tls/ca.crt",
EnvVar: opservice.PrefixEnvVar(envPrefix, "TLS_CA"), EnvVars: prefixEnvVars("TLS_CA"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: prefixFunc(TLSCertFlagName), Name: prefixFunc(TLSCertFlagName),
Usage: "tls cert path", Usage: "tls cert path",
Value: "tls/tls.crt", Value: "tls/tls.crt",
EnvVar: opservice.PrefixEnvVar(envPrefix, "TLS_CERT"), EnvVars: prefixEnvVars("TLS_CERT"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: prefixFunc(TLSKeyFlagName), Name: prefixFunc(TLSKeyFlagName),
Usage: "tls key", Usage: "tls key",
Value: "tls/tls.key", Value: "tls/tls.key",
EnvVar: opservice.PrefixEnvVar(envPrefix, "TLS_KEY"), EnvVars: prefixEnvVars("TLS_KEY"),
}, },
} }
} }
...@@ -73,9 +76,9 @@ func (c CLIConfig) TLSEnabled() bool { ...@@ -73,9 +76,9 @@ func (c CLIConfig) TLSEnabled() bool {
// This should be used for server TLS configs, or when client and server tls configs are the same // This should be used for server TLS configs, or when client and server tls configs are the same
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
TLSCaCert: ctx.GlobalString(TLSCaCertFlagName), TLSCaCert: ctx.String(TLSCaCertFlagName),
TLSCert: ctx.GlobalString(TLSCertFlagName), TLSCert: ctx.String(TLSCertFlagName),
TLSKey: ctx.GlobalString(TLSKeyFlagName), TLSKey: ctx.String(TLSKeyFlagName),
} }
} }
...@@ -86,8 +89,8 @@ func ReadCLIConfigWithPrefix(ctx *cli.Context, flagPrefix string) CLIConfig { ...@@ -86,8 +89,8 @@ func ReadCLIConfigWithPrefix(ctx *cli.Context, flagPrefix string) CLIConfig {
return strings.Trim(fmt.Sprintf("%s.%s", flagPrefix, flagName), ".") return strings.Trim(fmt.Sprintf("%s.%s", flagPrefix, flagName), ".")
} }
return CLIConfig{ return CLIConfig{
TLSCaCert: ctx.GlobalString(prefixFunc(TLSCaCertFlagName)), TLSCaCert: ctx.String(prefixFunc(TLSCaCertFlagName)),
TLSCert: ctx.GlobalString(prefixFunc(TLSCertFlagName)), TLSCert: ctx.String(prefixFunc(TLSCertFlagName)),
TLSKey: ctx.GlobalString(prefixFunc(TLSKeyFlagName)), TLSKey: ctx.String(prefixFunc(TLSKeyFlagName)),
} }
} }
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const ( const (
...@@ -34,78 +34,81 @@ const ( ...@@ -34,78 +34,81 @@ const (
) )
var ( var (
SequencerHDPathFlag = cli.StringFlag{ SequencerHDPathFlag = &cli.StringFlag{
Name: "sequencer-hd-path", Name: "sequencer-hd-path",
Usage: "DEPRECATED: The HD path used to derive the sequencer wallet from the " + Usage: "DEPRECATED: The HD path used to derive the sequencer wallet from the " +
"mnemonic. The mnemonic flag must also be set.", "mnemonic. The mnemonic flag must also be set.",
EnvVar: "OP_BATCHER_SEQUENCER_HD_PATH", EnvVars: []string{"OP_BATCHER_SEQUENCER_HD_PATH"},
} }
L2OutputHDPathFlag = cli.StringFlag{ L2OutputHDPathFlag = &cli.StringFlag{
Name: "l2-output-hd-path", Name: "l2-output-hd-path",
Usage: "DEPRECATED:The HD path used to derive the l2output wallet from the " + Usage: "DEPRECATED:The HD path used to derive the l2output wallet from the " +
"mnemonic. The mnemonic flag must also be set.", "mnemonic. The mnemonic flag must also be set.",
EnvVar: "OP_PROPOSER_L2_OUTPUT_HD_PATH", EnvVars: []string{"OP_PROPOSER_L2_OUTPUT_HD_PATH"},
} }
) )
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
prefixEnvVars := func(name string) []string {
return opservice.PrefixEnvVar(envPrefix, name)
}
return append([]cli.Flag{ return append([]cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: MnemonicFlagName, Name: MnemonicFlagName,
Usage: "The mnemonic used to derive the wallets for either the service", Usage: "The mnemonic used to derive the wallets for either the service",
EnvVar: opservice.PrefixEnvVar(envPrefix, "MNEMONIC"), EnvVars: prefixEnvVars("MNEMONIC"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: HDPathFlagName, Name: HDPathFlagName,
Usage: "The HD path used to derive the sequencer wallet from the mnemonic. The mnemonic flag must also be set.", Usage: "The HD path used to derive the sequencer wallet from the mnemonic. The mnemonic flag must also be set.",
EnvVar: opservice.PrefixEnvVar(envPrefix, "HD_PATH"), EnvVars: prefixEnvVars("HD_PATH"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: PrivateKeyFlagName, Name: PrivateKeyFlagName,
Usage: "The private key to use with the service. Must not be used with mnemonic.", Usage: "The private key to use with the service. Must not be used with mnemonic.",
EnvVar: opservice.PrefixEnvVar(envPrefix, "PRIVATE_KEY"), EnvVars: prefixEnvVars("PRIVATE_KEY"),
}, },
cli.Uint64Flag{ &cli.Uint64Flag{
Name: NumConfirmationsFlagName, Name: NumConfirmationsFlagName,
Usage: "Number of confirmations which we will wait after sending a transaction", Usage: "Number of confirmations which we will wait after sending a transaction",
Value: 10, Value: 10,
EnvVar: opservice.PrefixEnvVar(envPrefix, "NUM_CONFIRMATIONS"), EnvVars: prefixEnvVars("NUM_CONFIRMATIONS"),
}, },
cli.Uint64Flag{ &cli.Uint64Flag{
Name: SafeAbortNonceTooLowCountFlagName, Name: SafeAbortNonceTooLowCountFlagName,
Usage: "Number of ErrNonceTooLow observations required to give up on a tx at a particular nonce without receiving confirmation", Usage: "Number of ErrNonceTooLow observations required to give up on a tx at a particular nonce without receiving confirmation",
Value: 3, Value: 3,
EnvVar: opservice.PrefixEnvVar(envPrefix, "SAFE_ABORT_NONCE_TOO_LOW_COUNT"), EnvVars: prefixEnvVars("SAFE_ABORT_NONCE_TOO_LOW_COUNT"),
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: ResubmissionTimeoutFlagName, Name: ResubmissionTimeoutFlagName,
Usage: "Duration we will wait before resubmitting a transaction to L1", Usage: "Duration we will wait before resubmitting a transaction to L1",
Value: 48 * time.Second, Value: 48 * time.Second,
EnvVar: opservice.PrefixEnvVar(envPrefix, "RESUBMISSION_TIMEOUT"), EnvVars: prefixEnvVars("RESUBMISSION_TIMEOUT"),
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: NetworkTimeoutFlagName, Name: NetworkTimeoutFlagName,
Usage: "Timeout for all network operations", Usage: "Timeout for all network operations",
Value: 2 * time.Second, Value: 2 * time.Second,
EnvVar: opservice.PrefixEnvVar(envPrefix, "NETWORK_TIMEOUT"), EnvVars: prefixEnvVars("NETWORK_TIMEOUT"),
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: TxSendTimeoutFlagName, Name: TxSendTimeoutFlagName,
Usage: "Timeout for sending transactions. If 0 it is disabled.", Usage: "Timeout for sending transactions. If 0 it is disabled.",
Value: 0, Value: 0,
EnvVar: opservice.PrefixEnvVar(envPrefix, "TXMGR_TX_SEND_TIMEOUT"), EnvVars: prefixEnvVars("TXMGR_TX_SEND_TIMEOUT"),
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: TxNotInMempoolTimeoutFlagName, Name: TxNotInMempoolTimeoutFlagName,
Usage: "Timeout for aborting a tx send if the tx does not make it to the mempool.", Usage: "Timeout for aborting a tx send if the tx does not make it to the mempool.",
Value: 2 * time.Minute, Value: 2 * time.Minute,
EnvVar: opservice.PrefixEnvVar(envPrefix, "TXMGR_TX_NOT_IN_MEMPOOL_TIMEOUT"), EnvVars: prefixEnvVars("TXMGR_TX_NOT_IN_MEMPOOL_TIMEOUT"),
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: ReceiptQueryIntervalFlagName, Name: ReceiptQueryIntervalFlagName,
Usage: "Frequency to poll for receipts", Usage: "Frequency to poll for receipts",
Value: 12 * time.Second, Value: 12 * time.Second,
EnvVar: opservice.PrefixEnvVar(envPrefix, "TXMGR_RECEIPT_QUERY_INTERVAL"), EnvVars: prefixEnvVars("TXMGR_RECEIPT_QUERY_INTERVAL"),
}, },
}, client.CLIFlags(envPrefix)...) }, client.CLIFlags(envPrefix)...)
} }
...@@ -157,20 +160,20 @@ func (m CLIConfig) Check() error { ...@@ -157,20 +160,20 @@ func (m CLIConfig) Check() error {
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
L1RPCURL: ctx.GlobalString(L1RPCFlagName), L1RPCURL: ctx.String(L1RPCFlagName),
Mnemonic: ctx.GlobalString(MnemonicFlagName), Mnemonic: ctx.String(MnemonicFlagName),
HDPath: ctx.GlobalString(HDPathFlagName), HDPath: ctx.String(HDPathFlagName),
SequencerHDPath: ctx.GlobalString(SequencerHDPathFlag.Name), SequencerHDPath: ctx.String(SequencerHDPathFlag.Name),
L2OutputHDPath: ctx.GlobalString(L2OutputHDPathFlag.Name), L2OutputHDPath: ctx.String(L2OutputHDPathFlag.Name),
PrivateKey: ctx.GlobalString(PrivateKeyFlagName), PrivateKey: ctx.String(PrivateKeyFlagName),
SignerCLIConfig: client.ReadCLIConfig(ctx), SignerCLIConfig: client.ReadCLIConfig(ctx),
NumConfirmations: ctx.GlobalUint64(NumConfirmationsFlagName), NumConfirmations: ctx.Uint64(NumConfirmationsFlagName),
SafeAbortNonceTooLowCount: ctx.GlobalUint64(SafeAbortNonceTooLowCountFlagName), SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName),
ResubmissionTimeout: ctx.GlobalDuration(ResubmissionTimeoutFlagName), ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName),
ReceiptQueryInterval: ctx.GlobalDuration(ReceiptQueryIntervalFlagName), ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName),
NetworkTimeout: ctx.GlobalDuration(NetworkTimeoutFlagName), NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName),
TxSendTimeout: ctx.GlobalDuration(TxSendTimeoutFlagName), TxSendTimeout: ctx.Duration(TxSendTimeoutFlagName),
TxNotInMempoolTimeout: ctx.GlobalDuration(TxNotInMempoolTimeoutFlagName), TxNotInMempoolTimeout: ctx.Duration(TxNotInMempoolTimeoutFlagName),
} }
} }
......
...@@ -13,11 +13,13 @@ import ( ...@@ -13,11 +13,13 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func PrefixEnvVar(prefix, suffix string) string { // PrefixEnvVar adds a prefix to the environment variable,
return prefix + "_" + suffix // and returns the env-var wrapped in a slice for usage with urfave CLI v2.
func PrefixEnvVar(prefix, suffix string) []string {
return []string{prefix + "_" + suffix}
} }
// ValidateEnvVars logs all env vars that are found where the env var is // ValidateEnvVars logs all env vars that are found where the env var is
...@@ -33,8 +35,9 @@ func ValidateEnvVars(prefix string, flags []cli.Flag, log log.Logger) { ...@@ -33,8 +35,9 @@ func ValidateEnvVars(prefix string, flags []cli.Flag, log log.Logger) {
func cliFlagsToEnvVars(flags []cli.Flag) map[string]struct{} { func cliFlagsToEnvVars(flags []cli.Flag) map[string]struct{} {
definedEnvVars := make(map[string]struct{}) definedEnvVars := make(map[string]struct{})
for _, flag := range flags { for _, flag := range flags {
envVarField := reflect.ValueOf(flag).FieldByName("EnvVar") envVars := reflect.ValueOf(flag).Elem().FieldByName("EnvVars")
if envVarField.IsValid() { for i := 0; i < envVars.Len(); i++ {
envVarField := envVars.Index(i)
definedEnvVars[envVarField.String()] = struct{}{} definedEnvVars[envVarField.String()] = struct{}{}
} }
} }
......
...@@ -4,16 +4,16 @@ import ( ...@@ -4,16 +4,16 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func TestCLIFlagsToEnvVars(t *testing.T) { func TestCLIFlagsToEnvVars(t *testing.T) {
flags := []cli.Flag{ flags := []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "test", Name: "test",
EnvVar: "OP_NODE_TEST_VAR", EnvVars: []string{"OP_NODE_TEST_VAR"},
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "no env var", Name: "no env var",
}, },
} }
......
...@@ -3,7 +3,7 @@ package client ...@@ -3,7 +3,7 @@ package client
import ( import (
"errors" "errors"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
optls "github.com/ethereum-optimism/optimism/op-service/tls" optls "github.com/ethereum-optimism/optimism/op-service/tls"
...@@ -17,15 +17,15 @@ const ( ...@@ -17,15 +17,15 @@ const (
func CLIFlags(envPrefix string) []cli.Flag { func CLIFlags(envPrefix string) []cli.Flag {
envPrefix += "_SIGNER" envPrefix += "_SIGNER"
flags := []cli.Flag{ flags := []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: EndpointFlagName, Name: EndpointFlagName,
Usage: "Signer endpoint the client will connect to", Usage: "Signer endpoint the client will connect to",
EnvVar: opservice.PrefixEnvVar(envPrefix, "ENDPOINT"), EnvVars: opservice.PrefixEnvVar(envPrefix, "ENDPOINT"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: AddressFlagName, Name: AddressFlagName,
Usage: "Address the signer is signing transactions for", Usage: "Address the signer is signing transactions for",
EnvVar: opservice.PrefixEnvVar(envPrefix, "ADDRESS"), EnvVars: opservice.PrefixEnvVar(envPrefix, "ADDRESS"),
}, },
} }
flags = append(flags, optls.CLIFlagsWithFlagPrefix(envPrefix, "signer")...) flags = append(flags, optls.CLIFlagsWithFlagPrefix(envPrefix, "signer")...)
......
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -29,7 +29,7 @@ func main() { ...@@ -29,7 +29,7 @@ func main() {
app.Before = func(c *cli.Context) error { app.Before = func(c *cli.Context) error {
log.Root().SetHandler( log.Root().SetHandler(
log.LvlFilterHandler( log.LvlFilterHandler(
oplog.Level(c.GlobalString(wheel.GlobalGethLogLvlFlag.Name)), oplog.Level(c.String(wheel.GlobalGethLogLvlFlag.Name)),
log.StreamHandler(os.Stdout, log.TerminalFormat(true)), log.StreamHandler(os.Stdout, log.TerminalFormat(true)),
), ),
) )
...@@ -40,7 +40,7 @@ func main() { ...@@ -40,7 +40,7 @@ func main() {
}) })
app.Writer = os.Stdout app.Writer = os.Stdout
app.ErrWriter = os.Stderr app.ErrWriter = os.Stderr
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
wheel.CheatCmd, wheel.CheatCmd,
wheel.EngineCmd, wheel.EngineCmd,
} }
......
...@@ -16,7 +16,7 @@ import ( ...@@ -16,7 +16,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/client" "github.com/ethereum-optimism/optimism/op-node/client"
opservice "github.com/ethereum-optimism/optimism/op-service" opservice "github.com/ethereum-optimism/optimism/op-service"
...@@ -28,61 +28,65 @@ import ( ...@@ -28,61 +28,65 @@ import (
const envVarPrefix = "OP_WHEEL" const envVarPrefix = "OP_WHEEL"
func prefixEnvVars(name string) []string {
return []string{envVarPrefix + "_" + name}
}
var ( var (
GlobalGethLogLvlFlag = cli.StringFlag{ GlobalGethLogLvlFlag = &cli.StringFlag{
Name: "geth-log-level", Name: "geth-log-level",
Usage: "Set the global geth logging level", Usage: "Set the global geth logging level",
EnvVar: opservice.PrefixEnvVar("OP_WHEEL", "GETH_LOG_LEVEL"), EnvVars: prefixEnvVars("GETH_LOG_LEVEL"),
Value: "error", Value: "error",
} }
DataDirFlag = cli.StringFlag{ DataDirFlag = &cli.StringFlag{
Name: "data-dir", Name: "data-dir",
Usage: "Geth data dir location.", Usage: "Geth data dir location.",
Required: true, Required: true,
TakesFile: true, TakesFile: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "DATA_DIR"), EnvVars: prefixEnvVars("DATA_DIR"),
} }
EngineEndpoint = cli.StringFlag{ EngineEndpoint = &cli.StringFlag{
Name: "engine", Name: "engine",
Usage: "Engine API RPC endpoint, can be HTTP/WS/IPC", Usage: "Engine API RPC endpoint, can be HTTP/WS/IPC",
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ENGINE"), EnvVars: prefixEnvVars("ENGINE"),
} }
EngineJWTPath = cli.StringFlag{ EngineJWTPath = &cli.StringFlag{
Name: "engine.jwt-secret", Name: "engine.jwt-secret",
Usage: "Path to JWT secret file used to authenticate Engine API communication with.", Usage: "Path to JWT secret file used to authenticate Engine API communication with.",
Required: true, Required: true,
TakesFile: true, TakesFile: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ENGINE_JWT_SECRET"), EnvVars: prefixEnvVars("ENGINE_JWT_SECRET"),
} }
FeeRecipientFlag = cli.GenericFlag{ FeeRecipientFlag = &cli.GenericFlag{
Name: "fee-recipient", Name: "fee-recipient",
Usage: "fee-recipient of the block building", Usage: "fee-recipient of the block building",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "FEE_RECIPIENT"), EnvVars: prefixEnvVars("FEE_RECIPIENT"),
Value: &TextFlag[*common.Address]{Value: &common.Address{1: 0x13, 2: 0x37}}, Value: &TextFlag[*common.Address]{Value: &common.Address{1: 0x13, 2: 0x37}},
} }
RandaoFlag = cli.GenericFlag{ RandaoFlag = &cli.GenericFlag{
Name: "randao", Name: "randao",
Usage: "randao value of the block building", Usage: "randao value of the block building",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "RANDAO"), EnvVars: prefixEnvVars("RANDAO"),
Value: &TextFlag[*common.Hash]{Value: &common.Hash{1: 0x13, 2: 0x37}}, Value: &TextFlag[*common.Hash]{Value: &common.Hash{1: 0x13, 2: 0x37}},
} }
BlockTimeFlag = cli.Uint64Flag{ BlockTimeFlag = &cli.Uint64Flag{
Name: "block-time", Name: "block-time",
Usage: "block time, interval of timestamps between blocks to build, in seconds", Usage: "block time, interval of timestamps between blocks to build, in seconds",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "BLOCK_TIME"), EnvVars: prefixEnvVars("BLOCK_TIME"),
Value: 12, Value: 12,
} }
BuildingTime = cli.DurationFlag{ BuildingTime = &cli.DurationFlag{
Name: "building-time", Name: "building-time",
Usage: "duration of of block building, this should be set to something lower than the block time.", Usage: "duration of of block building, this should be set to something lower than the block time.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "BUILDING_TIME"), EnvVars: prefixEnvVars("BUILDING_TIME"),
Value: time.Second * 6, Value: time.Second * 6,
} }
AllowGaps = cli.BoolFlag{ AllowGaps = &cli.BoolFlag{
Name: "allow-gaps", Name: "allow-gaps",
Usage: "allow gaps in block building, like missed slots on the beacon chain.", Usage: "allow gaps in block building, like missed slots on the beacon chain.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ALLOW_GAPS"), EnvVars: prefixEnvVars("ALLOW_GAPS"),
} }
) )
...@@ -166,29 +170,29 @@ func (a *TextFlag[T]) Get() T { ...@@ -166,29 +170,29 @@ func (a *TextFlag[T]) Get() T {
var _ cli.Generic = (*TextFlag[*common.Address])(nil) var _ cli.Generic = (*TextFlag[*common.Address])(nil)
func textFlag[T Text](name string, usage string, value T) cli.GenericFlag { func textFlag[T Text](name string, usage string, value T) *cli.GenericFlag {
return cli.GenericFlag{ return &cli.GenericFlag{
Name: name, Name: name,
Usage: usage, Usage: usage,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, strings.ToUpper(name)), EnvVars: prefixEnvVars(strings.ToUpper(name)),
Required: true, Required: true,
Value: &TextFlag[T]{Value: value}, Value: &TextFlag[T]{Value: value},
} }
} }
func addrFlag(name string, usage string) cli.GenericFlag { func addrFlag(name string, usage string) *cli.GenericFlag {
return textFlag[*common.Address](name, usage, new(common.Address)) return textFlag[*common.Address](name, usage, new(common.Address))
} }
func bytesFlag(name string, usage string) cli.GenericFlag { func bytesFlag(name string, usage string) *cli.GenericFlag {
return textFlag[*hexutil.Bytes](name, usage, new(hexutil.Bytes)) return textFlag[*hexutil.Bytes](name, usage, new(hexutil.Bytes))
} }
func hashFlag(name string, usage string) cli.GenericFlag { func hashFlag(name string, usage string) *cli.GenericFlag {
return textFlag[*common.Hash](name, usage, new(common.Hash)) return textFlag[*common.Hash](name, usage, new(common.Hash))
} }
func bigFlag(name string, usage string) cli.GenericFlag { func bigFlag(name string, usage string) *cli.GenericFlag {
return textFlag[*big.Int](name, usage, new(big.Int)) return textFlag[*big.Int](name, usage, new(big.Int))
} }
...@@ -209,7 +213,7 @@ func bigFlagValue(name string, ctx *cli.Context) *big.Int { ...@@ -209,7 +213,7 @@ func bigFlagValue(name string, ctx *cli.Context) *big.Int {
} }
var ( var (
CheatStorageGetCmd = cli.Command{ CheatStorageGetCmd = &cli.Command{
Name: "get", Name: "get",
Aliases: []string{"read"}, Aliases: []string{"read"},
Flags: []cli.Flag{ Flags: []cli.Flag{
...@@ -221,7 +225,7 @@ var ( ...@@ -221,7 +225,7 @@ var (
return ch.RunAndClose(cheat.StorageGet(addrFlagValue("address", ctx), hashFlagValue("key", ctx), ctx.App.Writer)) return ch.RunAndClose(cheat.StorageGet(addrFlagValue("address", ctx), hashFlagValue("key", ctx), ctx.App.Writer))
}), }),
} }
CheatStorageSetCmd = cli.Command{ CheatStorageSetCmd = &cli.Command{
Name: "set", Name: "set",
Aliases: []string{"write"}, Aliases: []string{"write"},
Flags: []cli.Flag{ Flags: []cli.Flag{
...@@ -234,7 +238,7 @@ var ( ...@@ -234,7 +238,7 @@ var (
return ch.RunAndClose(cheat.StorageSet(addrFlagValue("address", ctx), hashFlagValue("key", ctx), hashFlagValue("value", ctx))) return ch.RunAndClose(cheat.StorageSet(addrFlagValue("address", ctx), hashFlagValue("key", ctx), hashFlagValue("value", ctx)))
}), }),
} }
CheatStorageReadAll = cli.Command{ CheatStorageReadAll = &cli.Command{
Name: "read-all", Name: "read-all",
Aliases: []string{"get-all"}, Aliases: []string{"get-all"},
Usage: "Read all storage of the given account", Usage: "Read all storage of the given account",
...@@ -243,7 +247,7 @@ var ( ...@@ -243,7 +247,7 @@ var (
return ch.RunAndClose(cheat.StorageReadAll(addrFlagValue("address", ctx), ctx.App.Writer)) return ch.RunAndClose(cheat.StorageReadAll(addrFlagValue("address", ctx), ctx.App.Writer))
}), }),
} }
CheatStorageDiffCmd = cli.Command{ CheatStorageDiffCmd = &cli.Command{
Name: "diff", Name: "diff",
Usage: "Diff the storage of accounts A and B", Usage: "Diff the storage of accounts A and B",
Flags: []cli.Flag{DataDirFlag, hashFlag("a", "address of account A"), hashFlag("b", "address of account B")}, Flags: []cli.Flag{DataDirFlag, hashFlag("a", "address of account A"), hashFlag("b", "address of account B")},
...@@ -251,7 +255,7 @@ var ( ...@@ -251,7 +255,7 @@ var (
return ch.RunAndClose(cheat.StorageDiff(ctx.App.Writer, addrFlagValue("a", ctx), addrFlagValue("b", ctx))) return ch.RunAndClose(cheat.StorageDiff(ctx.App.Writer, addrFlagValue("a", ctx), addrFlagValue("b", ctx)))
}), }),
} }
CheatStoragePatchCmd = cli.Command{ CheatStoragePatchCmd = &cli.Command{
Name: "patch", Name: "patch",
Usage: "Apply storage patch from STDIN to the given account address", Usage: "Apply storage patch from STDIN to the given account address",
Flags: []cli.Flag{DataDirFlag, addrFlag("address", "Address to patch storage of")}, Flags: []cli.Flag{DataDirFlag, addrFlag("address", "Address to patch storage of")},
...@@ -259,9 +263,9 @@ var ( ...@@ -259,9 +263,9 @@ var (
return ch.RunAndClose(cheat.StoragePatch(os.Stdin, addrFlagValue("address", ctx))) return ch.RunAndClose(cheat.StoragePatch(os.Stdin, addrFlagValue("address", ctx)))
}), }),
} }
CheatStorageCmd = cli.Command{ CheatStorageCmd = &cli.Command{
Name: "storage", Name: "storage",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
CheatStorageGetCmd, CheatStorageGetCmd,
CheatStorageSetCmd, CheatStorageSetCmd,
CheatStorageReadAll, CheatStorageReadAll,
...@@ -269,7 +273,7 @@ var ( ...@@ -269,7 +273,7 @@ var (
CheatStoragePatchCmd, CheatStoragePatchCmd,
}, },
} }
CheatSetBalanceCmd = cli.Command{ CheatSetBalanceCmd = &cli.Command{
Name: "balance", Name: "balance",
Flags: []cli.Flag{ Flags: []cli.Flag{
DataDirFlag, DataDirFlag,
...@@ -280,7 +284,7 @@ var ( ...@@ -280,7 +284,7 @@ var (
return ch.RunAndClose(cheat.SetBalance(addrFlagValue("address", ctx), bigFlagValue("balance", ctx))) return ch.RunAndClose(cheat.SetBalance(addrFlagValue("address", ctx), bigFlagValue("balance", ctx)))
}), }),
} }
CheatSetCodeCmd = cli.Command{ CheatSetCodeCmd = &cli.Command{
Name: "code", Name: "code",
Flags: []cli.Flag{ Flags: []cli.Flag{
DataDirFlag, DataDirFlag,
...@@ -291,7 +295,7 @@ var ( ...@@ -291,7 +295,7 @@ var (
return ch.RunAndClose(cheat.SetCode(addrFlagValue("address", ctx), bytesFlagValue("code", ctx))) return ch.RunAndClose(cheat.SetCode(addrFlagValue("address", ctx), bytesFlagValue("code", ctx)))
}), }),
} }
CheatSetNonceCmd = cli.Command{ CheatSetNonceCmd = &cli.Command{
Name: "nonce", Name: "nonce",
Flags: []cli.Flag{ Flags: []cli.Flag{
DataDirFlag, DataDirFlag,
...@@ -302,15 +306,15 @@ var ( ...@@ -302,15 +306,15 @@ var (
return ch.RunAndClose(cheat.SetNonce(addrFlagValue("address", ctx), bigFlagValue("balance", ctx).Uint64())) return ch.RunAndClose(cheat.SetNonce(addrFlagValue("address", ctx), bigFlagValue("balance", ctx).Uint64()))
}), }),
} }
CheatOvmOwnersCmd = cli.Command{ CheatOvmOwnersCmd = &cli.Command{
Name: "ovm-owners", Name: "ovm-owners",
Flags: []cli.Flag{ Flags: []cli.Flag{
DataDirFlag, DataDirFlag,
cli.StringFlag{ &cli.StringFlag{
Name: "config", Name: "config",
Usage: "Path to JSON config of OVM address replacements to apply.", Usage: "Path to JSON config of OVM address replacements to apply.",
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "OVM_OWNERS"), EnvVars: prefixEnvVars("OVM_OWNERS"),
Value: "ovm-owners.json", Value: "ovm-owners.json",
}, },
}, },
...@@ -326,7 +330,7 @@ var ( ...@@ -326,7 +330,7 @@ var (
return ch.RunAndClose(cheat.OvmOwners(&conf)) return ch.RunAndClose(cheat.OvmOwners(&conf))
}), }),
} }
CheatPrintHeadBlock = cli.Command{ CheatPrintHeadBlock = &cli.Command{
Name: "head-block", Name: "head-block",
Usage: "dump head block as JSON", Usage: "dump head block as JSON",
Flags: []cli.Flag{ Flags: []cli.Flag{
...@@ -345,7 +349,7 @@ var ( ...@@ -345,7 +349,7 @@ var (
}) })
}), }),
} }
CheatPrintHeadHeader = cli.Command{ CheatPrintHeadHeader = &cli.Command{
Name: "head-header", Name: "head-header",
Usage: "dump head header as JSON", Usage: "dump head header as JSON",
Flags: []cli.Flag{ Flags: []cli.Flag{
...@@ -357,7 +361,7 @@ var ( ...@@ -357,7 +361,7 @@ var (
return enc.Encode(rawdb.ReadHeadHeader(db)) return enc.Encode(rawdb.ReadHeadHeader(db))
}), }),
} }
EngineBlockCmd = cli.Command{ EngineBlockCmd = &cli.Command{
Name: "block", Name: "block",
Usage: "build the next block using the Engine API", Usage: "build the next block using the Engine API",
Flags: []cli.Flag{ Flags: []cli.Flag{
...@@ -382,7 +386,7 @@ var ( ...@@ -382,7 +386,7 @@ var (
return err return err
}), }),
} }
EngineAutoCmd = cli.Command{ EngineAutoCmd = &cli.Command{
Name: "auto", Name: "auto",
Usage: "Run a proof-of-nothing chain with fixed block time.", Usage: "Run a proof-of-nothing chain with fixed block time.",
Description: "The block time can be changed. The execution engine must be synced to a post-Merge state first.", Description: "The block time can be changed. The execution engine must be synced to a post-Merge state first.",
...@@ -391,7 +395,7 @@ var ( ...@@ -391,7 +395,7 @@ var (
FeeRecipientFlag, RandaoFlag, BlockTimeFlag, BuildingTime, AllowGaps, FeeRecipientFlag, RandaoFlag, BlockTimeFlag, BuildingTime, AllowGaps,
}, oplog.CLIFlags(envVarPrefix)...), opmetrics.CLIFlags(envVarPrefix)...), }, oplog.CLIFlags(envVarPrefix)...), opmetrics.CLIFlags(envVarPrefix)...),
Action: EngineAction(func(ctx *cli.Context, client client.RPC) error { Action: EngineAction(func(ctx *cli.Context, client client.RPC) error {
logCfg := oplog.ReadLocalCLIConfig(ctx) logCfg := oplog.ReadCLIConfig(ctx)
if err := logCfg.Check(); err != nil { if err := logCfg.Check(); err != nil {
return fmt.Errorf("failed to parse log configuration: %w", err) return fmt.Errorf("failed to parse log configuration: %w", err)
} }
...@@ -400,7 +404,7 @@ var ( ...@@ -400,7 +404,7 @@ var (
settings := ParseBuildingArgs(ctx) settings := ParseBuildingArgs(ctx)
// TODO: finalize/safe flag // TODO: finalize/safe flag
metricsCfg := opmetrics.ReadLocalCLIConfig(ctx) metricsCfg := opmetrics.ReadCLIConfig(ctx)
return opservice.CloseAction(func(ctx context.Context, shutdown <-chan struct{}) error { return opservice.CloseAction(func(ctx context.Context, shutdown <-chan struct{}) error {
registry := opmetrics.NewRegistry() registry := opmetrics.NewRegistry()
...@@ -417,7 +421,7 @@ var ( ...@@ -417,7 +421,7 @@ var (
}) })
}), }),
} }
EngineStatusCmd = cli.Command{ EngineStatusCmd = &cli.Command{
Name: "status", Name: "status",
Flags: []cli.Flag{EngineEndpoint, EngineJWTPath}, Flags: []cli.Flag{EngineEndpoint, EngineJWTPath},
Action: EngineAction(func(ctx *cli.Context, client client.RPC) error { Action: EngineAction(func(ctx *cli.Context, client client.RPC) error {
...@@ -430,15 +434,15 @@ var ( ...@@ -430,15 +434,15 @@ var (
return enc.Encode(stat) return enc.Encode(stat)
}), }),
} }
EngineCopyCmd = cli.Command{ EngineCopyCmd = &cli.Command{
Name: "copy", Name: "copy",
Flags: []cli.Flag{ Flags: []cli.Flag{
EngineEndpoint, EngineJWTPath, EngineEndpoint, EngineJWTPath,
cli.StringFlag{ &cli.StringFlag{
Name: "source", Name: "source",
Usage: "Unauthenticated regular eth JSON RPC to pull block data from, can be HTTP/WS/IPC.", Usage: "Unauthenticated regular eth JSON RPC to pull block data from, can be HTTP/WS/IPC.",
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ENGINE"), EnvVars: prefixEnvVars("ENGINE"),
}, },
}, },
Action: EngineAction(func(ctx *cli.Context, dest client.RPC) error { Action: EngineAction(func(ctx *cli.Context, dest client.RPC) error {
...@@ -452,12 +456,12 @@ var ( ...@@ -452,12 +456,12 @@ var (
} }
) )
var CheatCmd = cli.Command{ var CheatCmd = &cli.Command{
Name: "cheat", Name: "cheat",
Usage: "Cheating commands to modify a Geth database.", Usage: "Cheating commands to modify a Geth database.",
Description: "Each sub-command opens a Geth database, applies the cheat, and then saves and closes the database." + Description: "Each sub-command opens a Geth database, applies the cheat, and then saves and closes the database." +
"The Geth node will live in its own false reality, other nodes cannot sync the cheated state if they process the blocks.", "The Geth node will live in its own false reality, other nodes cannot sync the cheated state if they process the blocks.",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
CheatStorageCmd, CheatStorageCmd,
CheatSetBalanceCmd, CheatSetBalanceCmd,
CheatSetCodeCmd, CheatSetCodeCmd,
...@@ -468,11 +472,11 @@ var CheatCmd = cli.Command{ ...@@ -468,11 +472,11 @@ var CheatCmd = cli.Command{
}, },
} }
var EngineCmd = cli.Command{ var EngineCmd = &cli.Command{
Name: "engine", Name: "engine",
Usage: "Engine API commands to build/reorg/finalize blocks.", Usage: "Engine API commands to build/reorg/finalize blocks.",
Description: "Each sub-command dials the engine API endpoint (with provided JWT secret) and then runs the action", Description: "Each sub-command dials the engine API endpoint (with provided JWT secret) and then runs the action",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
EngineBlockCmd, EngineBlockCmd,
EngineAutoCmd, EngineAutoCmd,
EngineStatusCmd, EngineStatusCmd,
......
Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9413) Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9413)
Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1430) Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1430)
Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17240) Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17240)
Bytes_slice_Test:test_slice_fromZeroIdx_works() (gas: 20826) Bytes_slice_Test:test_slice_fromZeroIdx_works() (gas: 20804)
Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 78882) Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 78882)
Bytes_toNibbles_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 3992) Bytes_toNibbles_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 3992)
Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 823) Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 823)
...@@ -32,25 +32,30 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert ...@@ -32,25 +32,30 @@ DisputeGameFactory_SetImplementation_Test:test_setImplementation_notOwner_revert
DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243) DisputeGameFactory_SetImplementation_Test:test_setImplementation_succeeds() (gas: 44243)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950) DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_notOwner_reverts() (gas: 15950)
DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642) DisputeGameFactory_TransferOwnership_Test:test_transferOwnership_succeeds() (gas: 18642)
FaultDisputeGame_Test:test_clockTimeExceeded_reverts() (gas: 26496) FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 497198)
FaultDisputeGame_Test:test_defendRoot_reverts() (gas: 13236) FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 499064)
FaultDisputeGame_Test:test_duplicateClaim_reverts() (gas: 103425) FaultDisputeGame_ResolvesCorrectly_CorrectRoot:test_resolvesCorrectly_succeeds() (gas: 489092)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17478) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 494067)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17859) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 495933)
FaultDisputeGame_Test:test_gameDepthExceeded_reverts() (gas: 5907231) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot:test_resolvesCorrectly_succeeds() (gas: 485961)
FaultDisputeGame_Test:test_defendRoot_invalidMove_reverts() (gas: 13250)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 17409)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 17834)
FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10337) FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10337)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8259) FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8216)
FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17624) FaultDisputeGame_Test:test_initialRootClaimData_succeeds() (gas: 17691)
FaultDisputeGame_Test:test_moveAgainstNonexistentParent_reverts() (gas: 24632) FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26410)
FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10945) FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103231)
FaultDisputeGame_Test:test_resolve_challengeContested() (gas: 222383) FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 407967)
FaultDisputeGame_Test:test_resolve_reverts() (gas: 27121) FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 10923)
FaultDisputeGame_Test:test_resolve_rootContested() (gas: 107225) FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24632)
FaultDisputeGame_Test:test_resolve_rootUncontested() (gas: 24459) FaultDisputeGame_Test:test_resolve_challengeContested() (gas: 221074)
FaultDisputeGame_Test:test_resolve_teamDeathmatch() (gas: 393609) FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9657)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8169) FaultDisputeGame_Test:test_resolve_rootContested() (gas: 106120)
FaultDisputeGame_Test:test_simpleAttack_succeeds() (gas: 107389) FaultDisputeGame_Test:test_resolve_rootUncontested() (gas: 23630)
FaultDisputeGame_Test:test_version_succeeds() (gas: 9780) FaultDisputeGame_Test:test_resolve_teamDeathmatch() (gas: 391731)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8203)
FaultDisputeGame_Test:test_simpleAttack_succeeds() (gas: 107322)
FeeVault_Test:test_constructor_succeeds() (gas: 18185) FeeVault_Test:test_constructor_succeeds() (gas: 18185)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2950342) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2950342)
...@@ -225,6 +230,7 @@ LegacyERC20ETH_Test:test_mint_doesNotExist_reverts() (gas: 10627) ...@@ -225,6 +230,7 @@ LegacyERC20ETH_Test:test_mint_doesNotExist_reverts() (gas: 10627)
LegacyERC20ETH_Test:test_transferFrom_doesNotExist_reverts() (gas: 12957) LegacyERC20ETH_Test:test_transferFrom_doesNotExist_reverts() (gas: 12957)
LegacyERC20ETH_Test:test_transfer_doesNotExist_reverts() (gas: 10755) LegacyERC20ETH_Test:test_transfer_doesNotExist_reverts() (gas: 10755)
LegacyMessagePasser_Test:test_passMessageToL1_succeeds() (gas: 34524) LegacyMessagePasser_Test:test_passMessageToL1_succeeds() (gas: 34524)
LibPosition_Test:test_pos_correctness_succeeds() (gas: 38711)
MerkleTrie_get_Test:test_get_corruptedProof_reverts() (gas: 5736) MerkleTrie_get_Test:test_get_corruptedProof_reverts() (gas: 5736)
MerkleTrie_get_Test:test_get_extraProofElements_reverts() (gas: 58975) MerkleTrie_get_Test:test_get_extraProofElements_reverts() (gas: 58975)
MerkleTrie_get_Test:test_get_invalidDataRemainder_reverts() (gas: 35852) MerkleTrie_get_Test:test_get_invalidDataRemainder_reverts() (gas: 35852)
......
...@@ -13,73 +13,53 @@ import { IDisputeGame } from "./interfaces/IDisputeGame.sol"; ...@@ -13,73 +13,53 @@ import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol"; import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import { IVersioned } from "./interfaces/IVersioned.sol"; import { IVersioned } from "./interfaces/IVersioned.sol";
/** /// @title DisputeGameFactory
* @title DisputeGameFactory /// @notice A factory contract for creating `IDisputeGame` contracts. All created dispute games
* @notice A factory contract for creating `IDisputeGame` contracts. All created dispute games /// are stored in both a mapping and an append only array. The timestamp of the creation
* are stored in both a mapping and an append only array. The timestamp of the creation /// time of the dispute game is packed tightly into the storage slot with the address of
* time of the dispute game is packed tightly into the storage slot with the address of /// the dispute game. This is to make offchain discoverability of playable dispute games
* the dispute game. This is to make offchain discoverability of playable dispute games /// easier.
* easier.
*/
contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersioned { contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersioned {
/** /// @dev Allows for the creation of clone proxies with immutable arguments.
* @dev Allows for the creation of clone proxies with immutable arguments.
*/
using ClonesWithImmutableArgs for address; using ClonesWithImmutableArgs for address;
/** /// @inheritdoc IDisputeGameFactory
* @inheritdoc IDisputeGameFactory
*/
mapping(GameType => IDisputeGame) public gameImpls; mapping(GameType => IDisputeGame) public gameImpls;
/** /// @notice Mapping of a hash of `gameType || rootClaim || extraData` to
* @notice Mapping of a hash of `gameType || rootClaim || extraData` to /// the deployed `IDisputeGame` clone.
* the deployed `IDisputeGame` clone. /// @dev Note: `||` denotes concatenation.
* @dev Note: `||` denotes concatenation.
*/
mapping(Hash => GameId) internal _disputeGames; mapping(Hash => GameId) internal _disputeGames;
/** /// @notice an append-only array of disputeGames that have been created.
* @notice An append-only array of disputeGames that have been created. /// @dev this accessor is used by offchain game solvers to efficiently
* @dev This accessor is used by offchain game solvers to efficiently /// track dispute games
* track dispute games
*/
GameId[] internal _disputeGameList; GameId[] internal _disputeGameList;
/** /// @notice constructs a new DisputeGameFactory contract.
* @notice Constructs a new DisputeGameFactory contract.
*/
constructor() OwnableUpgradeable() { constructor() OwnableUpgradeable() {
initialize(address(0)); initialize(address(0));
} }
/** /// @notice Initializes the contract.
* @notice Initializes the contract. /// @param _owner The owner of the contract.
* @param _owner The owner of the contract.
*/
function initialize(address _owner) public initializer { function initialize(address _owner) public initializer {
__Ownable_init(); __Ownable_init();
_transferOwnership(_owner); _transferOwnership(_owner);
} }
/** /// @inheritdoc IVersioned
* @inheritdoc IVersioned /// @custom:semver 0.0.2
* @custom:semver 0.0.2
*/
function version() external pure returns (string memory) { function version() external pure returns (string memory) {
return "0.0.2"; return "0.0.2";
} }
/** /// @inheritdoc IDisputeGameFactory
* @inheritdoc IDisputeGameFactory
*/
function gameCount() external view returns (uint256 gameCount_) { function gameCount() external view returns (uint256 gameCount_) {
gameCount_ = _disputeGameList.length; gameCount_ = _disputeGameList.length;
} }
/** /// @inheritdoc IDisputeGameFactory
* @inheritdoc IDisputeGameFactory
*/
function games( function games(
GameType _gameType, GameType _gameType,
Claim _rootClaim, Claim _rootClaim,
...@@ -92,9 +72,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion ...@@ -92,9 +72,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion
timestamp_ = timestamp; timestamp_ = timestamp;
} }
/** /// @inheritdoc IDisputeGameFactory
* @inheritdoc IDisputeGameFactory
*/
function gameAtIndex(uint256 _index) function gameAtIndex(uint256 _index)
external external
view view
...@@ -106,9 +84,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion ...@@ -106,9 +84,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion
timestamp_ = timestamp; timestamp_ = timestamp;
} }
/** /// @inheritdoc IDisputeGameFactory
* @inheritdoc IDisputeGameFactory
*/
function create( function create(
GameType gameType, GameType gameType,
Claim rootClaim, Claim rootClaim,
...@@ -142,9 +118,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion ...@@ -142,9 +118,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion
emit DisputeGameCreated(address(proxy), gameType, rootClaim); emit DisputeGameCreated(address(proxy), gameType, rootClaim);
} }
/** /// @inheritdoc IDisputeGameFactory
* @inheritdoc IDisputeGameFactory
*/
function getGameUUID( function getGameUUID(
GameType gameType, GameType gameType,
Claim rootClaim, Claim rootClaim,
...@@ -181,27 +155,21 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion ...@@ -181,27 +155,21 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion
} }
} }
/** /// @inheritdoc IDisputeGameFactory
* @inheritdoc IDisputeGameFactory
*/
function setImplementation(GameType gameType, IDisputeGame impl) external onlyOwner { function setImplementation(GameType gameType, IDisputeGame impl) external onlyOwner {
gameImpls[gameType] = impl; gameImpls[gameType] = impl;
emit ImplementationSet(address(impl), gameType); emit ImplementationSet(address(impl), gameType);
} }
/** /// @dev Packs an address and a uint256 into a single bytes32 slot. This
* @dev Packs an address and a uint256 into a single bytes32 slot. This /// is only safe for up to uint96 values.
* is only safe for up to uint96 values.
*/
function _packSlot(address _addr, uint256 _num) internal pure returns (GameId slot_) { function _packSlot(address _addr, uint256 _num) internal pure returns (GameId slot_) {
assembly { assembly {
slot_ := or(shl(0xa0, _num), _addr) slot_ := or(shl(0xa0, _num), _addr)
} }
} }
/** /// @dev Unpacks an address and a uint256 from a single bytes32 slot.
* @dev Unpacks an address and a uint256 from a single bytes32 slot.
*/
function _unpackSlot(GameId _slot) internal pure returns (address addr_, uint256 num_) { function _unpackSlot(GameId _slot) internal pure returns (address addr_, uint256 num_) {
assembly { assembly {
addr_ := and(_slot, 0xffffffffffffffffffffffffffffffffffffffff) addr_ := and(_slot, 0xffffffffffffffffffffffffffffffffffffffff)
......
...@@ -15,101 +15,147 @@ import { LibClock } from "./lib/LibClock.sol"; ...@@ -15,101 +15,147 @@ import { LibClock } from "./lib/LibClock.sol";
import "../libraries/DisputeTypes.sol"; import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol"; import "../libraries/DisputeErrors.sol";
/** /// @title FaultDisputeGame
* @title FaultDisputeGame /// @notice An implementation of the `IFaultDisputeGame` interface.
* @notice An implementation of the `IFaultDisputeGame` interface.
*/
contract FaultDisputeGame is IFaultDisputeGame, Clone { contract FaultDisputeGame is IFaultDisputeGame, Clone {
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// State Vars // // State Vars //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @notice The absolute prestate of the instruction trace. This is a constant that is defined
* @notice The current Semver of the FaultDisputeGame implementation. /// by the program that is being used to execute the trace.
*/ Claim public immutable ABSOLUTE_PRESTATE;
string internal constant VERSION = "0.0.1";
/** /// @notice The max depth of the game.
* @notice The max depth of the game. uint256 public immutable MAX_GAME_DEPTH;
*/
uint256 internal constant MAX_GAME_DEPTH = 63;
/** /// @notice The duration of the game.
* @notice The duration of the game. /// @dev TODO: Account for resolution buffer. (?)
* @dev TODO: Account for resolution buffer. (?)
*/
Duration internal constant GAME_DURATION = Duration.wrap(7 days); Duration internal constant GAME_DURATION = Duration.wrap(7 days);
/** /// @notice The root claim's position is always at gindex 1.
* @notice The root claim's position is always at gindex 1.
*/
Position internal constant ROOT_POSITION = Position.wrap(1); Position internal constant ROOT_POSITION = Position.wrap(1);
/** /// @notice The current Semver of the FaultDisputeGame implementation.
* @notice The starting timestamp of the game string internal constant VERSION = "0.0.2";
*/
/// @notice The starting timestamp of the game
Timestamp public gameStart; Timestamp public gameStart;
/** /// @inheritdoc IDisputeGame
* @inheritdoc IDisputeGame
*/
GameStatus public status; GameStatus public status;
/** /// @inheritdoc IDisputeGame
* @inheritdoc IDisputeGame
*/
IBondManager public bondManager; IBondManager public bondManager;
/** /// @notice An append-only array of all claims made during the dispute game.
* @notice An append-only array of all claims made during the dispute game.
*/
ClaimData[] public claimData; ClaimData[] public claimData;
/** /// @notice An internal mapping to allow for constant-time lookups of existing claims.
* @notice An internal mapping to allow for constant-time lookups of existing claims.
*/
mapping(ClaimHash => bool) internal claims; mapping(ClaimHash => bool) internal claims;
/// @param _absolutePrestate The absolute prestate of the instruction trace.
constructor(Claim _absolutePrestate, uint256 _maxGameDepth) {
ABSOLUTE_PRESTATE = _absolutePrestate;
MAX_GAME_DEPTH = _maxGameDepth;
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// External Logic // // External Logic //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @inheritdoc IFaultDisputeGame
* @inheritdoc IFaultDisputeGame
*/
function attack(uint256 _parentIndex, Claim _pivot) external payable { function attack(uint256 _parentIndex, Claim _pivot) external payable {
_move(_parentIndex, _pivot, true); _move(_parentIndex, _pivot, true);
} }
/** /// @inheritdoc IFaultDisputeGame
* @inheritdoc IFaultDisputeGame
*/
function defend(uint256 _parentIndex, Claim _pivot) external payable { function defend(uint256 _parentIndex, Claim _pivot) external payable {
_move(_parentIndex, _pivot, false); _move(_parentIndex, _pivot, false);
} }
/** /// @inheritdoc IFaultDisputeGame
* @inheritdoc IFaultDisputeGame
*/
function step( function step(
uint256 _prestateIndex, uint256 _stateIndex,
uint256 _parentIndex, uint256 _claimIndex,
bytes calldata _stateData, bool _isAttack,
bytes calldata _proof bytes calldata,
bytes calldata
) external { ) external {
// TODO - Call the VM to perform the execution step. // Steps cannot be made unless the game is currently in progress.
if (status != GameStatus.IN_PROGRESS) {
revert GameNotInProgress();
}
// Get the parent. If it does not exist, the call will revert with OOB.
ClaimData storage parent = claimData[_claimIndex];
// Pull the parent position out of storage.
Position parentPos = parent.position;
// Determine the position of the step.
Position stepPos = parentPos.move(_isAttack);
// Ensure that the step position is 1 deeper than the maximum game depth.
if (stepPos.depth() != MAX_GAME_DEPTH + 1) {
revert InvalidParent();
}
// Determine the expected pre & post states of the step.
Claim preStateClaim;
Claim postStateClaim;
if (stepPos.indexAtDepth() == 0) {
// If the step position's index at depth is 0, the prestate is the absolute prestate
// and the post state is the parent claim.
preStateClaim = ABSOLUTE_PRESTATE;
postStateClaim = claimData[_claimIndex].claim;
} else {
Position preStatePos;
Position postStatePos;
if (_isAttack) {
// If the step is an attack, the prestate exists elsewhere in the game state,
// and the parent claim is the expected post-state.
preStatePos = claimData[_stateIndex].position;
preStateClaim = claimData[_stateIndex].claim;
postStatePos = parentPos;
postStateClaim = parent.claim;
} else {
// If the step is a defense, the poststate exists elsewhere in the game state,
// and the parent claim is the expected pre-state.
preStatePos = parent.position;
preStateClaim = parent.claim;
postStatePos = claimData[_stateIndex].position;
postStateClaim = claimData[_stateIndex].claim;
}
// Assert that the given prestate commits to the instruction at `gindex - 1`.
if (
Position.unwrap(preStatePos.rightIndex(MAX_GAME_DEPTH)) !=
Position.unwrap(postStatePos.rightIndex(MAX_GAME_DEPTH)) - 1
) {
revert InvalidPrestate();
}
}
// TODO: Call `MIPS.sol#step` to verify the step.
// For now, we just use a simple state transition function that increments the prestate,
// `s_p`, by 1.
if (uint256(Claim.unwrap(preStateClaim)) + 1 == uint256(Claim.unwrap(postStateClaim))) {
revert ValidStep();
}
// Set the parent claim as countered. We do not need to append a new claim to the game;
// instead, we can just set the existing parent as countered.
parent.countered = true;
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// Internal Logic // // Internal Logic //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @notice Internal move function, used by both `attack` and `defend`.
* @notice Internal move function, used by both `attack` and `defend`. /// @param _challengeIndex The index of the claim being moved against.
* @param _challengeIndex The index of the claim being moved against. /// @param _pivot The claim at the next logical position in the game.
* @param _pivot The claim at the next logical position in the game. /// @param _isAttack Whether or not the move is an attack or defense.
* @param _isAttack Whether or not the move is an attack or defense.
*/
function _move( function _move(
uint256 _challengeIndex, uint256 _challengeIndex,
Claim _pivot, Claim _pivot,
...@@ -136,12 +182,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -136,12 +182,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
// Compute the position that the claim commits to. Because the parent's position is already // Compute the position that the claim commits to. Because the parent's position is already
// known, we can compute the next position by moving left or right depending on whether // known, we can compute the next position by moving left or right depending on whether
// or not the move is an attack or defense. // or not the move is an attack or defense.
Position nextPosition = _isAttack ? parent.position.attack() : parent.position.defend(); Position nextPosition = parent.position.move(_isAttack);
// At the leaf nodes of the game, the only option is to run a step to prove or disprove // At the leaf nodes of the game, the only option is to run a step to prove or disprove
// the above claim. At this depth, the parent claim commits to the state after a single // the above claim. At this depth, the parent claim commits to the state after a single
// instruction step. // instruction step.
if (nextPosition.depth() >= MAX_GAME_DEPTH) { if (nextPosition.depth() > MAX_GAME_DEPTH) {
revert GameDepthExceeded(); revert GameDepthExceeded();
} }
...@@ -197,39 +243,38 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -197,39 +243,38 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
emit Move(_challengeIndex, _pivot, msg.sender); emit Move(_challengeIndex, _pivot, msg.sender);
} }
/// @inheritdoc IFaultDisputeGame
function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) {
l2BlockNumber_ = _getArgUint256(0x20);
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// `IDisputeGame` impl // // `IDisputeGame` impl //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @inheritdoc IDisputeGame
* @inheritdoc IDisputeGame
*/
function gameType() public pure override returns (GameType gameType_) { function gameType() public pure override returns (GameType gameType_) {
gameType_ = GameTypes.FAULT; gameType_ = GameTypes.FAULT;
} }
/** /// @inheritdoc IDisputeGame
* @inheritdoc IDisputeGame
*/
function createdAt() external view returns (Timestamp createdAt_) { function createdAt() external view returns (Timestamp createdAt_) {
createdAt_ = gameStart; createdAt_ = gameStart;
} }
/** /// @inheritdoc IDisputeGame
* @inheritdoc IDisputeGame
*/
function resolve() external returns (GameStatus status_) { function resolve() external returns (GameStatus status_) {
// TODO: Do not allow resolution before clocks run out.
if (status != GameStatus.IN_PROGRESS) { if (status != GameStatus.IN_PROGRESS) {
// If the game is not in progress, it cannot be resolved.
revert GameNotInProgress(); revert GameNotInProgress();
} }
// Search for the left-most dangling non-bottom node // Search for the left-most dangling non-bottom node
// The most recent claim is always a dangling, non-bottom node so we start with that // The most recent claim is always a dangling, non-bottom node so we start with that
uint256 leftMostIndex = claimData.length - 1; uint256 leftMostIndex = claimData.length - 1;
uint256 leftMostTraceIndex = LibPosition.rightIndex( Position leftMostTraceIndex = Position.wrap(type(uint128).max);
claimData[leftMostIndex].position,
MAX_GAME_DEPTH
);
for (uint256 i = leftMostIndex; i < type(uint64).max; ) { for (uint256 i = leftMostIndex; i < type(uint64).max; ) {
// Fetch the claim at the current index. // Fetch the claim at the current index.
ClaimData storage claim = claimData[i]; ClaimData storage claim = claimData[i];
...@@ -243,24 +288,29 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -243,24 +288,29 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
// If the claim is not a dangling node above the bottom of the tree, // If the claim is not a dangling node above the bottom of the tree,
// we can skip over it. These nodes are not relevant to the game resolution. // we can skip over it. These nodes are not relevant to the game resolution.
Position claimPos = claim.position; Position claimPos = claim.position;
if (LibPosition.depth(claimPos) == MAX_GAME_DEPTH || claim.countered) { if (claim.countered) {
continue; continue;
} }
// If the claim is a dangling node, we can check if it is the left-most // If the claim is a dangling node, we can check if it is the left-most
// dangling node we've come across so far. If it is, we can update the // dangling node we've come across so far. If it is, we can update the
// left-most trace index. // left-most trace index.
uint256 traceIndex = LibPosition.rightIndex(claimPos, MAX_GAME_DEPTH); Position traceIndex = claimPos.rightIndex(MAX_GAME_DEPTH);
if (traceIndex < leftMostTraceIndex) { if (Position.unwrap(traceIndex) < Position.unwrap(leftMostTraceIndex)) {
leftMostTraceIndex = traceIndex; leftMostTraceIndex = traceIndex;
unchecked {
leftMostIndex = i + 1; leftMostIndex = i + 1;
} }
} }
}
// If the left-most dangling node is at an even depth, the defender wins. // If the left-most dangling node is at an even depth, the defender wins.
// Otherwise, the challenger wins and the root claim is deemed invalid. // Otherwise, the challenger wins and the root claim is deemed invalid.
if (
// slither-disable-next-line weak-prng // slither-disable-next-line weak-prng
if (LibPosition.depth(claimData[leftMostIndex].position) % 2 == 0) { claimData[leftMostIndex].position.depth() % 2 == 0 &&
Position.unwrap(leftMostTraceIndex) != type(uint128).max
) {
status_ = GameStatus.DEFENDER_WINS; status_ = GameStatus.DEFENDER_WINS;
} else { } else {
status_ = GameStatus.CHALLENGER_WINS; status_ = GameStatus.CHALLENGER_WINS;
...@@ -271,16 +321,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -271,16 +321,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
emit Resolved(status_); emit Resolved(status_);
} }
/** /// @inheritdoc IDisputeGame
* @inheritdoc IDisputeGame
*/
function rootClaim() public pure returns (Claim rootClaim_) { function rootClaim() public pure returns (Claim rootClaim_) {
rootClaim_ = Claim.wrap(_getArgFixedBytes(0x00)); rootClaim_ = Claim.wrap(_getArgFixedBytes(0x00));
} }
/** /// @inheritdoc IDisputeGame
* @inheritdoc IDisputeGame
*/
function extraData() public pure returns (bytes memory extraData_) { function extraData() public pure returns (bytes memory extraData_) {
// The extra data starts at the second word within the cwia calldata. // The extra data starts at the second word within the cwia calldata.
// TODO: What data do we need to pass along to this contract from the factory? // TODO: What data do we need to pass along to this contract from the factory?
...@@ -288,9 +334,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -288,9 +334,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
extraData_ = _getArgDynBytes(0x20, 0x20); extraData_ = _getArgDynBytes(0x20, 0x20);
} }
/** /// @inheritdoc IDisputeGame
* @inheritdoc IDisputeGame
*/
function gameData() function gameData()
external external
pure pure
...@@ -305,9 +349,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -305,9 +349,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
extraData_ = extraData(); extraData_ = extraData();
} }
/** /// @inheritdoc IInitializable
* @inheritdoc IInitializable
*/
function initialize() external { function initialize() external {
// Set the game start // Set the game start
gameStart = Timestamp.wrap(uint64(block.timestamp)); gameStart = Timestamp.wrap(uint64(block.timestamp));
...@@ -326,9 +368,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone { ...@@ -326,9 +368,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
); );
} }
/** /// @inheritdoc IVersioned
* @inheritdoc IVersioned
*/
function version() external pure override returns (string memory version_) { function version() external pure override returns (string memory version_) {
version_ = VERSION; version_ = VERSION;
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
/** /// @title IBondManager
* @title IBondManager /// @notice The Bond Manager holds ether posted as a bond for a bond id.
* @notice The Bond Manager holds ether posted as a bond for a bond id.
*/
interface IBondManager { interface IBondManager {
/** /// @notice BondPosted is emitted when a bond is posted.
* @notice BondPosted is emitted when a bond is posted. /// @param bondId is the id of the bond.
* @param bondId is the id of the bond. /// @param owner is the address that owns the bond.
* @param owner is the address that owns the bond. /// @param expiration is the time at which the bond expires.
* @param expiration is the time at which the bond expires. /// @param amount is the amount of the bond.
* @param amount is the amount of the bond.
*/
event BondPosted(bytes32 bondId, address owner, uint256 expiration, uint256 amount); event BondPosted(bytes32 bondId, address owner, uint256 expiration, uint256 amount);
/** /// @notice BondSeized is emitted when a bond is seized.
* @notice BondSeized is emitted when a bond is seized. /// @param bondId is the id of the bond.
* @param bondId is the id of the bond. /// @param owner is the address that owns the bond.
* @param owner is the address that owns the bond. /// @param seizer is the address that seized the bond.
* @param seizer is the address that seized the bond. /// @param amount is the amount of the bond.
* @param amount is the amount of the bond.
*/
event BondSeized(bytes32 bondId, address owner, address seizer, uint256 amount); event BondSeized(bytes32 bondId, address owner, address seizer, uint256 amount);
/** /// @notice BondReclaimed is emitted when a bond is reclaimed by the owner.
* @notice BondReclaimed is emitted when a bond is reclaimed by the owner. /// @param bondId is the id of the bond.
* @param bondId is the id of the bond. /// @param claiment is the address that reclaimed the bond.
* @param claiment is the address that reclaimed the bond. /// @param amount is the amount of the bond.
* @param amount is the amount of the bond.
*/
event BondReclaimed(bytes32 bondId, address claiment, uint256 amount); event BondReclaimed(bytes32 bondId, address claiment, uint256 amount);
/** /// @notice Post a bond with a given id and owner.
* @notice Post a bond with a given id and owner. /// @dev This function will revert if the provided bondId is already in use.
* @dev This function will revert if the provided bondId is already in use. /// @param _bondId is the id of the bond.
* @param _bondId is the id of the bond. /// @param _bondOwner is the address that owns the bond.
* @param _bondOwner is the address that owns the bond. /// @param _minClaimHold is the minimum amount of time the owner
* @param _minClaimHold is the minimum amount of time the owner /// must wait before reclaiming their bond.
* must wait before reclaiming their bond.
*/
function post( function post(
bytes32 _bondId, bytes32 _bondId,
address _bondOwner, address _bondOwner,
uint128 _minClaimHold uint128 _minClaimHold
) external payable; ) external payable;
/** /// @notice Seizes the bond with the given id.
* @notice Seizes the bond with the given id. /// @dev This function will revert if there is no bond at the given id.
* @dev This function will revert if there is no bond at the given id. /// @param _bondId is the id of the bond.
* @param _bondId is the id of the bond.
*/
function seize(bytes32 _bondId) external; function seize(bytes32 _bondId) external;
/** /// @notice Seizes the bond with the given id and distributes it to recipients.
* @notice Seizes the bond with the given id and distributes it to recipients. /// @dev This function will revert if there is no bond at the given id.
* @dev This function will revert if there is no bond at the given id. /// @param _bondId is the id of the bond.
* @param _bondId is the id of the bond. /// @param _claimRecipients is a set of addresses to split the bond amongst.
* @param _claimRecipients is a set of addresses to split the bond amongst. ///
*/
function seizeAndSplit(bytes32 _bondId, address[] calldata _claimRecipients) external; function seizeAndSplit(bytes32 _bondId, address[] calldata _claimRecipients) external;
/** /// @notice Reclaims the bond of the bond owner.
* @notice Reclaims the bond of the bond owner. /// @dev This function will revert if there is no bond at the given id.
* @dev This function will revert if there is no bond at the given id. /// @param _bondId is the id of the bond.
* @param _bondId is the id of the bond.
*/
function reclaim(bytes32 _bondId) external; function reclaim(bytes32 _bondId) external;
} }
...@@ -7,77 +7,57 @@ import { IVersioned } from "./IVersioned.sol"; ...@@ -7,77 +7,57 @@ import { IVersioned } from "./IVersioned.sol";
import { IBondManager } from "./IBondManager.sol"; import { IBondManager } from "./IBondManager.sol";
import { IInitializable } from "./IInitializable.sol"; import { IInitializable } from "./IInitializable.sol";
/** /// @title IDisputeGame
* @title IDisputeGame /// @notice The generic interface for a DisputeGame contract.
* @notice The generic interface for a DisputeGame contract.
*/
interface IDisputeGame is IInitializable, IVersioned { interface IDisputeGame is IInitializable, IVersioned {
/** /// @notice Emitted when the game is resolved.
* @notice Emitted when the game is resolved. /// @param status The status of the game after resolution.
* @param status The status of the game after resolution.
*/
event Resolved(GameStatus indexed status); event Resolved(GameStatus indexed status);
/** /// @notice Returns the timestamp that the DisputeGame contract was created at.
* @notice Returns the timestamp that the DisputeGame contract was created at. /// @return createdAt_ The timestamp that the DisputeGame contract was created at.
* @return createdAt_ The timestamp that the DisputeGame contract was created at.
*/
function createdAt() external view returns (Timestamp createdAt_); function createdAt() external view returns (Timestamp createdAt_);
/** /// @notice Returns the current status of the game.
* @notice Returns the current status of the game. /// @return status_ The current status of the game.
* @return status_ The current status of the game.
*/
function status() external view returns (GameStatus status_); function status() external view returns (GameStatus status_);
/** /// @notice Getter for the game type.
* @notice Getter for the game type. /// @dev `clones-with-immutable-args` argument #1
* @dev `clones-with-immutable-args` argument #1 /// @dev The reference impl should be entirely different depending on the type (fault, validity)
* @dev The reference impl should be entirely different depending on the type (fault, validity) /// i.e. The game type should indicate the security model.
* i.e. The game type should indicate the security model. /// @return gameType_ The type of proof system being used.
* @return gameType_ The type of proof system being used.
*/
function gameType() external pure returns (GameType gameType_); function gameType() external pure returns (GameType gameType_);
/** /// @notice Getter for the root claim.
* @notice Getter for the root claim. /// @dev `clones-with-immutable-args` argument #2
* @dev `clones-with-immutable-args` argument #2 /// @return rootClaim_ The root claim of the DisputeGame.
* @return rootClaim_ The root claim of the DisputeGame.
*/
function rootClaim() external pure returns (Claim rootClaim_); function rootClaim() external pure returns (Claim rootClaim_);
/** /// @notice Getter for the extra data.
* @notice Getter for the extra data. /// @dev `clones-with-immutable-args` argument #3
* @dev `clones-with-immutable-args` argument #3 /// @return extraData_ Any extra data supplied to the dispute game contract by the creator.
* @return extraData_ Any extra data supplied to the dispute game contract by the creator.
*/
function extraData() external pure returns (bytes memory extraData_); function extraData() external pure returns (bytes memory extraData_);
/** /// @notice Returns the address of the `BondManager` used.
* @notice Returns the address of the `BondManager` used. /// @return bondManager_ The address of the `BondManager` used.
* @return bondManager_ The address of the `BondManager` used.
*/
function bondManager() external view returns (IBondManager bondManager_); function bondManager() external view returns (IBondManager bondManager_);
/** /// @notice If all necessary information has been gathered, this function should mark the game
* @notice If all necessary information has been gathered, this function should mark the game /// status as either `CHALLENGER_WINS` or `DEFENDER_WINS` and return the status of
* status as either `CHALLENGER_WINS` or `DEFENDER_WINS` and return the status of /// the resolved game. It is at this stage that the bonds should be awarded to the
* the resolved game. It is at this stage that the bonds should be awarded to the /// necessary parties.
* necessary parties. /// @dev May only be called if the `status` is `IN_PROGRESS`.
* @dev May only be called if the `status` is `IN_PROGRESS`. /// @return status_ The status of the game after resolution.
* @return status_ The status of the game after resolution.
*/
function resolve() external returns (GameStatus status_); function resolve() external returns (GameStatus status_);
/** /// @notice A compliant implementation of this interface should return the components of the
* @notice A compliant implementation of this interface should return the components of the /// game UUID's preimage provided in the cwia payload. The preimage of the UUID is
* game UUID's preimage provided in the cwia payload. The preimage of the UUID is /// constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes
* constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes /// concatenation.
* concatenation. /// @return gameType_ The type of proof system being used.
* @return gameType_ The type of proof system being used. /// @return rootClaim_ The root claim of the DisputeGame.
* @return rootClaim_ The root claim of the DisputeGame. /// @return extraData_ Any extra data supplied to the dispute game contract by the creator.
* @return extraData_ Any extra data supplied to the dispute game contract by the creator.
*/
function gameData() function gameData()
external external
pure pure
......
...@@ -5,105 +5,85 @@ import "../../libraries/DisputeTypes.sol"; ...@@ -5,105 +5,85 @@ import "../../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol"; import { IDisputeGame } from "./IDisputeGame.sol";
/** /// @title IDisputeGameFactory
* @title IDisputeGameFactory /// @notice The interface for a DisputeGameFactory contract.
* @notice The interface for a DisputeGameFactory contract.
*/
interface IDisputeGameFactory { interface IDisputeGameFactory {
/** /// @notice Emitted when a new dispute game is created
* @notice Emitted when a new dispute game is created /// @param disputeProxy The address of the dispute game proxy
* @param disputeProxy The address of the dispute game proxy /// @param gameType The type of the dispute game proxy's implementation
* @param gameType The type of the dispute game proxy's implementation /// @param rootClaim The root claim of the dispute game
* @param rootClaim The root claim of the dispute game
*/
event DisputeGameCreated( event DisputeGameCreated(
address indexed disputeProxy, address indexed disputeProxy,
GameType indexed gameType, GameType indexed gameType,
Claim indexed rootClaim Claim indexed rootClaim
); );
/** /// @notice Emitted when a new game implementation added to the factory
* @notice Emitted when a new game implementation added to the factory /// @param impl The implementation contract for the given `GameType`.
* @param impl The implementation contract for the given `GameType`. /// @param gameType The type of the DisputeGame.
* @param gameType The type of the DisputeGame.
*/
event ImplementationSet(address indexed impl, GameType indexed gameType); event ImplementationSet(address indexed impl, GameType indexed gameType);
/** /// @notice The total number of dispute games created by this factory.
* @notice the total number of dispute games created by this factory. /// @return _gameCount The total number of dispute games created by this factory.
* @return _gameCount The total number of dispute games created by this factory.
*/
function gameCount() external view returns (uint256 _gameCount); function gameCount() external view returns (uint256 _gameCount);
/** /// @notice `games` queries an internal mapping that maps the hash of
* @notice `games` queries an internal a mapping that maps the hash of /// `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone.
* `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone. /// @dev `++` equates to concatenation.
* @dev `++` equates to concatenation. /// @param gameType The type of the DisputeGame - used to decide the proxy implementation
* @param gameType The type of the DisputeGame - used to decide the proxy implementation /// @param rootClaim The root claim of the DisputeGame.
* @param rootClaim The root claim of the DisputeGame. /// @param extraData Any extra data that should be provided to the created dispute game.
* @param extraData Any extra data that should be provided to the created dispute game. /// @return _proxy The clone of the `DisputeGame` created with the given parameters.
* @return _proxy The clone of the `DisputeGame` created with the given parameters. /// Returns `address(0)` if nonexistent.
* Returns `address(0)` if nonexistent. /// @return _timestamp The timestamp of the creation of the dispute game.
* @return _timestamp The timestamp of the creation of the dispute game.
*/
function games( function games(
GameType gameType, GameType gameType,
Claim rootClaim, Claim rootClaim,
bytes calldata extraData bytes calldata extraData
) external view returns (IDisputeGame _proxy, uint256 _timestamp); ) external view returns (IDisputeGame _proxy, uint256 _timestamp);
/** /// @notice `gameAtIndex` returns the dispute game contract address and its creation timestamp
* @notice `gameAtIndex` returns the dispute game contract address and its creation timestamp /// at the given index. Each created dispute game increments the underlying index.
* at the given index. Each created dispute game increments the underlying index. /// @param _index The index of the dispute game.
* @param _index The index of the dispute game. /// @return _proxy The clone of the `DisputeGame` created with the given parameters.
* @return _proxy The clone of the `DisputeGame` created with the given parameters. /// Returns `address(0)` if nonexistent.
* Returns `address(0)` if nonexistent. /// @return _timestamp The timestamp of the creation of the dispute game.
* @return _timestamp The timestamp of the creation of the dispute game.
*/
function gameAtIndex(uint256 _index) function gameAtIndex(uint256 _index)
external external
view view
returns (IDisputeGame _proxy, uint256 _timestamp); returns (IDisputeGame _proxy, uint256 _timestamp);
/** /// @notice `gameImpls` is a mapping that maps `GameType`s to their respective
* @notice `gameImpls` is a mapping that maps `GameType`s to their respective /// `IDisputeGame` implementations.
* `IDisputeGame` implementations. /// @param gameType The type of the dispute game.
* @param gameType The type of the dispute game. /// @return _impl The address of the implementation of the game type.
* @return _impl The address of the implementation of the game type. /// Will be cloned on creation of a new dispute game with the given `gameType`.
* Will be cloned on creation of a new dispute game with the given `gameType`.
*/
function gameImpls(GameType gameType) external view returns (IDisputeGame _impl); function gameImpls(GameType gameType) external view returns (IDisputeGame _impl);
/** /// @notice Creates a new DisputeGame proxy contract.
* @notice Creates a new DisputeGame proxy contract. /// @param gameType The type of the DisputeGame - used to decide the proxy implementation.
* @param gameType The type of the DisputeGame - used to decide the proxy implementation /// @param rootClaim The root claim of the DisputeGame.
* @param rootClaim The root claim of the DisputeGame. /// @param extraData Any extra data that should be provided to the created dispute game.
* @param extraData Any extra data that should be provided to the created dispute game. /// @return proxy The address of the created DisputeGame proxy.
* @return proxy The address of the created DisputeGame proxy.
*/
function create( function create(
GameType gameType, GameType gameType,
Claim rootClaim, Claim rootClaim,
bytes calldata extraData bytes calldata extraData
) external returns (IDisputeGame proxy); ) external returns (IDisputeGame proxy);
/** /// @notice Sets the implementation contract for a specific `GameType`.
* @notice Sets the implementation contract for a specific `GameType`. /// @dev May only be called by the `owner`.
* @dev May only be called by the `owner`. /// @param gameType The type of the DisputeGame.
* @param gameType The type of the DisputeGame. /// @param impl The implementation contract for the given `GameType`.
* @param impl The implementation contract for the given `GameType`.
*/
function setImplementation(GameType gameType, IDisputeGame impl) external; function setImplementation(GameType gameType, IDisputeGame impl) external;
/** /// @notice Returns a unique identifier for the given dispute game parameters.
* @notice Returns a unique identifier for the given dispute game parameters. /// @dev Hashes the concatenation of `gameType . rootClaim . extraData`
* @dev Hashes the concatenation of `gameType . rootClaim . extraData` /// without expanding memory.
* without expanding memory. /// @param gameType The type of the DisputeGame.
* @param gameType The type of the DisputeGame. /// @param rootClaim The root claim of the DisputeGame.
* @param rootClaim The root claim of the DisputeGame. /// @param extraData Any extra data that should be provided to the created dispute game.
* @param extraData Any extra data that should be provided to the created dispute game. /// @return _uuid The unique identifier for the given dispute game parameters.
* @return _uuid The unique identifier for the given dispute game parameters.
*/
function getGameUUID( function getGameUUID(
GameType gameType, GameType gameType,
Claim rootClaim, Claim rootClaim,
......
...@@ -5,16 +5,11 @@ import "../../libraries/DisputeTypes.sol"; ...@@ -5,16 +5,11 @@ import "../../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol"; import { IDisputeGame } from "./IDisputeGame.sol";
/** /// @title IFaultDisputeGame
* @title IFaultDisputeGame /// @notice The interface for a fault proof backed dispute game.
* @notice The interface for a fault proof backed dispute game.
*/
interface IFaultDisputeGame is IDisputeGame { interface IFaultDisputeGame is IDisputeGame {
/** /// @notice The `ClaimData` struct represents the data associated with a Claim.
* @notice The `ClaimData` struct represents the data associated with a Claim. /// @dev TODO: Add bond ID information.
* @dev TODO: Pack `Clock` and `Position` into the same slot. Should require 4 64 bit arms.
* @dev TODO: Add bond ID information.
*/
struct ClaimData { struct ClaimData {
uint32 parentIndex; uint32 parentIndex;
bool countered; bool countered;
...@@ -23,43 +18,42 @@ interface IFaultDisputeGame is IDisputeGame { ...@@ -23,43 +18,42 @@ interface IFaultDisputeGame is IDisputeGame {
Clock clock; Clock clock;
} }
/** /// @notice Emitted when a new claim is added to the DAG by `claimant`
* @notice Emitted when a new claim is added to the DAG by `claimant` /// @param parentIndex The index within the `claimData` array of the parent claim
* @param parentIndex The index within the `claimData` array of the parent claim /// @param pivot The claim being added
* @param pivot The claim being added /// @param claimant The address of the claimant
* @param claimant The address of the claimant
*/
event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant); event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant);
/** /// @notice Attack a disagreed upon `Claim`.
* @notice Attack a disagreed upon `Claim`. /// @param _parentIndex Index of the `Claim` to attack in `claimData`.
* @param _parentIndex Index of the `Claim` to attack in `claimData`. /// @param _pivot The `Claim` at the relative attack position.
* @param _pivot The `Claim` at the relative attack position.
*/
function attack(uint256 _parentIndex, Claim _pivot) external payable; function attack(uint256 _parentIndex, Claim _pivot) external payable;
/** /// @notice Defend an agreed upon `Claim`.
* @notice Defend an agreed upon `Claim`. /// @param _parentIndex Index of the claim to defend in `claimData`.
* @param _parentIndex Index of the claim to defend in `claimData`. /// @param _pivot The `Claim` at the relative defense position.
* @param _pivot The `Claim` at the relative defense position.
*/
function defend(uint256 _parentIndex, Claim _pivot) external payable; function defend(uint256 _parentIndex, Claim _pivot) external payable;
/** /// @notice Perform the final step via an on-chain fault proof processor
* @notice Perform the final step via an on-chain fault proof processor /// @dev This function should point to a fault proof processor in order to execute
* @dev This function should point to a fault proof processor in order to execute /// a step in the fault proof program on-chain. The interface of the fault proof
* a step in the fault proof program on-chain. The interface of the fault proof /// processor contract should be generic enough such that we can use different
* processor contract should be generic enough such that we can use different /// fault proof VMs (MIPS, RiscV5, etc.)
* fault proof VMs (MIPS, RiscV5, etc.) /// @param _stateIndex The index of the pre/post state of the step within `claimData`.
* @param _prestateIndex The index of the prestate of the step within `claimData`. /// @param _claimIndex The index of the challenged claim within `claimData`.
* @param _parentIndex The index of the parent claim within `claimData`. /// @param _isAttack Whether or not the step is an attack or a defense.
* @param _stateData The stateData of the step is the preimage of the claim @ `prestateIndex` /// @param _stateData The stateData of the step is the preimage of the claim @ `prestateIndex`
* @param _proof Proof to access memory leaf nodes in the VM. /// @param _proof Proof to access memory leaf nodes in the VM.
*/
function step( function step(
uint256 _prestateIndex, uint256 _stateIndex,
uint256 _parentIndex, uint256 _claimIndex,
bool _isAttack,
bytes calldata _stateData, bytes calldata _stateData,
bytes calldata _proof bytes calldata _proof
) external; ) external;
/// @notice The l2BlockNumber that the `rootClaim` commits to. The trace being bisected within
/// the game is from `l2BlockNumber - 1` -> `l2BlockNumber`.
/// @return l2BlockNumber_ The l2BlockNumber that the `rootClaim` commits to.
function l2BlockNumber() external view returns (uint256 l2BlockNumber_);
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
/** /// @title IInitializable
* @title IInitializable /// @notice An interface for initializable contracts.
* @notice An interface for initializable contracts.
*/
interface IInitializable { interface IInitializable {
/** /// @notice Initializes the contract.
* @notice Initializes the contract. /// @dev This function may only be called once.
* @dev This function may only be called once.
*/
function initialize() external; function initialize() external;
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
/** /// @title IVersioned
* @title IVersioned /// @notice An interface for semantically versioned contracts.
* @notice An interface for semantically versioned contracts.
*/
interface IVersioned { interface IVersioned {
/** /// @notice Returns the semantic version of the contract
* @notice Returns the semantic version of the contract /// @return _version The semantic version of the contract
* @return _version The semantic version of the contract
*/
function version() external pure returns (string memory _version); function version() external pure returns (string memory _version);
} }
...@@ -3,28 +3,22 @@ pragma solidity ^0.8.15; ...@@ -3,28 +3,22 @@ pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol"; import "../../libraries/DisputeTypes.sol";
/** /// @title LibClock
* @title LibClock /// @notice This library contains helper functions for working with the `Clock` type.
* @notice This library contains helper functions for working with the `Clock` type.
*/
library LibClock { library LibClock {
/** /// @notice Packs a `Duration` and `Timestamp` into a `Clock` type.
* @notice Packs a `Duration` and `Timestamp` into a `Clock` type. /// @param _duration The `Duration` to pack into the `Clock` type.
* @param _duration The `Duration` to pack into the `Clock` type. /// @param _timestamp The `Timestamp` to pack into the `Clock` type.
* @param _timestamp The `Timestamp` to pack into the `Clock` type. /// @return clock_ The `Clock` containing the `_duration` and `_timestamp`.
* @return clock_ The `Clock` containing the `_duration` and `_timestamp`.
*/
function wrap(Duration _duration, Timestamp _timestamp) internal pure returns (Clock clock_) { function wrap(Duration _duration, Timestamp _timestamp) internal pure returns (Clock clock_) {
assembly { assembly {
clock_ := or(shl(0x40, _duration), _timestamp) clock_ := or(shl(0x40, _duration), _timestamp)
} }
} }
/** /// @notice Pull the `Duration` out of a `Clock` type.
* @notice Pull the `Duration` out of a `Clock` type. /// @param _clock The `Clock` type to pull the `Duration` out of.
* @param _clock The `Clock` type to pull the `Duration` out of. /// @return duration_ The `Duration` pulled out of `_clock`.
* @return duration_ The `Duration` pulled out of `_clock`.
*/
function duration(Clock _clock) internal pure returns (Duration duration_) { function duration(Clock _clock) internal pure returns (Duration duration_) {
// Shift the high-order 64 bits into the low-order 64 bits, leaving only the `duration`. // Shift the high-order 64 bits into the low-order 64 bits, leaving only the `duration`.
assembly { assembly {
...@@ -32,11 +26,9 @@ library LibClock { ...@@ -32,11 +26,9 @@ library LibClock {
} }
} }
/** /// @notice Pull the `Timestamp` out of a `Clock` type.
* @notice Pull the `Timestamp` out of a `Clock` type. /// @param _clock The `Clock` type to pull the `Timestamp` out of.
* @param _clock The `Clock` type to pull the `Timestamp` out of. /// @return timestamp_ The `Timestamp` pulled out of `_clock`.
* @return timestamp_ The `Timestamp` pulled out of `_clock`.
*/
function timestamp(Clock _clock) internal pure returns (Timestamp timestamp_) { function timestamp(Clock _clock) internal pure returns (Timestamp timestamp_) {
// Clean the high-order 192 bits by shifting the clock left and then right again, leaving // Clean the high-order 192 bits by shifting the clock left and then right again, leaving
// only the `timestamp`. // only the `timestamp`.
......
...@@ -3,17 +3,14 @@ pragma solidity ^0.8.15; ...@@ -3,17 +3,14 @@ pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol"; import "../../libraries/DisputeTypes.sol";
/** /// @title Hashing
* @title Hashing /// @notice This library contains all of the hashing utilities used in the Cannon contracts.
* @notice This library contains all of the hashing utilities used in the Cannon contracts.
*/
library LibHashing { library LibHashing {
/**
* @notice Hashes a claim and a position together. /// @notice Hashes a claim and a position together.
* @param _claim A Claim type. /// @param _claim A Claim type.
* @param _position The position of `claim`. /// @param _position The position of `claim`.
* @return claimHash_ A hash of abi.encodePacked(claim, position); /// @return claimHash_ A hash of abi.encodePacked(claim, position);
*/
function hashClaimPos(Claim _claim, Position _position) internal pure returns (ClaimHash claimHash_) { function hashClaimPos(Claim _claim, Position _position) internal pure returns (ClaimHash claimHash_) {
assembly { assembly {
mstore(0x00, _claim) mstore(0x00, _claim)
......
...@@ -3,25 +3,27 @@ pragma solidity ^0.8.15; ...@@ -3,25 +3,27 @@ pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol"; import "../../libraries/DisputeTypes.sol";
/** /// @title LibPosition
* @title LibPosition /// @notice This library contains helper functions for working with the `Position` type.
* @notice This library contains helper functions for working with the `Position` type.
*/
library LibPosition { library LibPosition {
/// @notice Computes a generalized index (2^{depth} + indexAtDepth).
/// @param _depth The depth of the position.
/// @param _indexAtDepth The index at the depth of the position.
/// @return position_ The computed generalized index.
function wrap(uint64 _depth, uint64 _indexAtDepth) internal pure returns (Position position_) { function wrap(uint64 _depth, uint64 _indexAtDepth) internal pure returns (Position position_) {
assembly { assembly {
// gindex = 2^{_depth} + _indexAtDepth
position_ := add(shl(_depth, 1), _indexAtDepth) position_ := add(shl(_depth, 1), _indexAtDepth)
} }
} }
/** /// @notice Pulls the `depth` out of a `Position` type.
* @notice Pulls the `depth` out of a packed `Position` type. /// @param _position The generalized index to get the `depth` of.
* @param _position The position to get the `depth` of. /// @return depth_ The `depth` of the `position` gindex.
* @return depth_ The `depth` of the `position`. /// @custom:attribution Solady <https://github.com/Vectorized/Solady>
* @custom:attribution Solady <https://github.com/Vectorized/Solady>
*/
function depth(Position _position) internal pure returns (uint64 depth_) { function depth(Position _position) internal pure returns (uint64 depth_) {
// Return the most significant bit position // Return the most significant bit offset, which signifies the depth of the gindex.
assembly { assembly {
depth_ := or(depth_, shl(6, lt(0xffffffffffffffff, shr(depth_, _position)))) depth_ := or(depth_, shl(6, lt(0xffffffffffffffff, shr(depth_, _position))))
depth_ := or(depth_, shl(5, lt(0xffffffff, shr(depth_, _position)))) depth_ := or(depth_, shl(5, lt(0xffffffff, shr(depth_, _position))))
...@@ -44,93 +46,73 @@ library LibPosition { ...@@ -44,93 +46,73 @@ library LibPosition {
} }
} }
/** /// @notice Pulls the `indexAtDepth` out of a `Position` type.
* @notice Pulls the `indexAtDepth` out of a packed `Position` type. /// The `indexAtDepth` is the left/right index of a position at a specific depth within
* @param _position The position to get the `indexAtDepth` of. /// the binary tree, starting from index 0. For example, at gindex 2, the `depth` = 1
* @return indexAtDepth_ The `indexAtDepth` of the `position`. /// and the `indexAtDepth` = 0.
*/ /// @param _position The generalized index to get the `indexAtDepth` of.
/// @return indexAtDepth_ The `indexAtDepth` of the `position` gindex.
function indexAtDepth(Position _position) internal pure returns (uint64 indexAtDepth_) { function indexAtDepth(Position _position) internal pure returns (uint64 indexAtDepth_) {
// Return bits p_{msb-1}...p_{0} // Return bits p_{msb-1}...p_{0}. This effectively pulls the 2^{depth} out of the gindex,
// leaving only the `indexAtDepth`.
uint256 msb = depth(_position); uint256 msb = depth(_position);
assembly { assembly {
indexAtDepth_ := sub(_position, shl(msb, 1)) indexAtDepth_ := sub(_position, shl(msb, 1))
} }
} }
/** /// @notice Get the left child of `_position`.
* @notice Get the position to the left of `position`. /// @param _position The position to get the left position of.
* @param _position The position to get the left position of. /// @return left_ The position to the left of `position`.
* @return left_ The position to the left of `position`.
*/
function left(Position _position) internal pure returns (Position left_) { function left(Position _position) internal pure returns (Position left_) {
assembly { assembly {
left_ := shl(1, _position) left_ := shl(1, _position)
} }
} }
/** /// @notice Get the right child of `_position`
* @notice Get the position to the right of `position`. /// @param _position The position to get the right position of.
* @param _position The position to get the right position of. /// @return right_ The position to the right of `position`.
* @return right_ The position to the right of `position`.
*/
function right(Position _position) internal pure returns (Position right_) { function right(Position _position) internal pure returns (Position right_) {
assembly { assembly {
right_ := add(1, shl(1, _position)) right_ := or(1, shl(1, _position))
} }
} }
/** /// @notice Get the parent position of `_position`.
* @notice Get the parent position of `position`. /// @param _position The position to get the parent position of.
* @param _position The position to get the parent position of. /// @return parent_ The parent position of `position`.
* @return parent_ The parent position of `position`.
*/
function parent(Position _position) internal pure returns (Position parent_) { function parent(Position _position) internal pure returns (Position parent_) {
assembly { assembly {
parent_ := shr(1, _position) parent_ := shr(1, _position)
} }
} }
/** /// @notice Get the deepest, right most gindex relative to the `position`. This is equivalent to
* @notice Get the deepest, right most index relative to the `position`. /// calling `right` on a position until the maximum depth is reached.
* @param _position The position to get the relative deepest, right most index of. /// @param _position The position to get the relative deepest, right most gindex of.
* @param _maxDepth The maximum depth of the game. /// @param _maxDepth The maximum depth of the game.
* @return rightIndex_ The deepest, right most index relative to the `position`. /// @return rightIndex_ The deepest, right most gindex relative to the `position`.
*/
function rightIndex( function rightIndex(
Position _position, Position _position,
uint256 _maxDepth uint256 _maxDepth
) internal pure returns (uint64 rightIndex_) { ) internal pure returns (Position rightIndex_) {
uint256 msb = depth(_position); uint256 msb = depth(_position);
assembly { assembly {
switch eq(msb, _maxDepth)
case true {
rightIndex_ := _position
}
default {
let remaining := sub(_maxDepth, msb) let remaining := sub(_maxDepth, msb)
rightIndex_ := or(shl(remaining, _position), sub(shl(remaining, 1), 1)) rightIndex_ := or(shl(remaining, _position), sub(shl(remaining, 1), 1))
} }
rightIndex_ := sub(rightIndex_, shl(_maxDepth, 1))
}
}
/**
* @notice Get the attack position relative to `position`.
* @param _position The position to get the relative attack position of.
* @return attack_ The attack position relative to `position`.
*/
function attack(Position _position) internal pure returns (Position attack_) {
return left(_position);
} }
/** /// @notice Get the move position of `_position`, which is the left child of:
* @notice Get the defense position relative to `position`. /// 1. `_position + 1` if `_isAttack` is true.
* @param _position The position to get the relative defense position of. /// 1. `_position` if `_isAttack` is false.
* @return defense_ The defense position relative to `position`. /// @param _position The position to get the relative attack/defense position of.
*/ /// @param _isAttack Whether or not the move is an attack move.
function defend(Position _position) internal pure returns (Position defense_) { /// @return move_ The move position relative to `position`.
function move(Position _position, bool _isAttack) internal pure returns (Position move_) {
assembly { assembly {
defense_ := shl(1, add(1, shl(1, shr(1, _position)))) move_ := shl(1, or(iszero(_isAttack), _position))
} }
} }
} }
...@@ -7,80 +7,65 @@ import "./DisputeTypes.sol"; ...@@ -7,80 +7,65 @@ import "./DisputeTypes.sol";
// `DisputeGameFactory` Errors // // `DisputeGameFactory` Errors //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @notice Thrown when a dispute game is attempted to be created with an unsupported game type.
* @notice Thrown when a dispute game is attempted to be created with an unsupported game type. /// @param gameType The unsupported game type.
* @param gameType The unsupported game type.
*/
error NoImplementation(GameType gameType); error NoImplementation(GameType gameType);
/** /// @notice Thrown when a dispute game that already exists is attempted to be created.
* @notice Thrown when a dispute game that already exists is attempted to be created. /// @param uuid The UUID of the dispute game that already exists.
* @param uuid The UUID of the dispute game that already exists.
*/
error GameAlreadyExists(Hash uuid); error GameAlreadyExists(Hash uuid);
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// `DisputeGame_Fault.sol` Errors // // `DisputeGame_Fault.sol` Errors //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @notice Thrown when a supplied bond is too low to cover the
* @notice Thrown when a supplied bond is too low to cover the /// cost of the next possible counter claim.
* cost of the next possible counter claim.
*/
error BondTooLow(); error BondTooLow();
/** /// @notice Thrown when a defense against the root claim is attempted.
* @notice Thrown when a defense against the root claim is attempted.
*/
error CannotDefendRootClaim(); error CannotDefendRootClaim();
/** /// @notice Thrown when a claim is attempting to be made that already exists.
* @notice Thrown when a claim is attempting to be made that already exists.
*/
error ClaimAlreadyExists(); error ClaimAlreadyExists();
/** /// @notice Thrown when a given claim is invalid (0).
* @notice Thrown when a given claim is invalid (0).
*/
error InvalidClaim(); error InvalidClaim();
/** /// @notice Thrown when an action that requires the game to be `IN_PROGRESS` is invoked when
* @notice Thrown when an action that requires the game to be `IN_PROGRESS` is invoked when /// the game is not in progress.
* the game is not in progress.
*/
error GameNotInProgress(); error GameNotInProgress();
/** /// @notice Thrown when a move is attempted to be made after the clock has timed out.
* @notice Thrown when a move is attempted to be made after the clock has timed out.
*/
error ClockTimeExceeded(); error ClockTimeExceeded();
/** /// @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
* @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
*/
error GameDepthExceeded(); error GameDepthExceeded();
/// @notice Thrown when a step is attempted above the maximum game depth.
error InvalidParent();
/// @notice Thrown when an invalid prestate is supplied to `step`.
error InvalidPrestate();
/// @notice Thrown when a step is made that computes the expected post state correctly.
error ValidStep();
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// `AttestationDisputeGame` Errors // // `AttestationDisputeGame` Errors //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @notice Thrown when an invalid signature is submitted to `challenge`.
* @notice Thrown when an invalid signature is submitted to `challenge`.
*/
error InvalidSignature(); error InvalidSignature();
/** /// @notice Thrown when a signature that has already been used to support the
* @notice Thrown when a signature that has already been used to support the /// `rootClaim` is submitted to `challenge`.
* `rootClaim` is submitted to `challenge`.
*/
error AlreadyChallenged(); error AlreadyChallenged();
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// `Ownable` Errors // // `Ownable` Errors //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @notice Thrown when a function that is protected by the `onlyOwner` modifier
* @notice Thrown when a function that is protected by the `onlyOwner` modifier /// is called from an account other than the owner.
* is called from an account other than the owner.
*/
error NotOwner(); error NotOwner();
...@@ -9,79 +9,57 @@ using LibHashing for Claim global; ...@@ -9,79 +9,57 @@ using LibHashing for Claim global;
using LibPosition for Position global; using LibPosition for Position global;
using LibClock for Clock global; using LibClock for Clock global;
/** /// @notice A custom type for a generic hash.
* @notice A custom type for a generic hash.
*/
type Hash is bytes32; type Hash is bytes32;
/** /// @notice A claim represents an MPT root representing the state of the fault proof program.
* @notice A claim represents an MPT root representing the state of the fault proof program.
*/
type Claim is bytes32; type Claim is bytes32;
/** /// @notice A claim hash represents a hash of a claim and a position within the game tree.
* @notice A claim hash represents a hash of a claim and a position within the game tree. /// @dev Keccak hash of abi.encodePacked(Claim, Position);
* @dev Keccak hash of abi.encodePacked(Claim, Position);
*/
type ClaimHash is bytes32; type ClaimHash is bytes32;
/** /// @notice A bondamount represents the amount of collateral that a user has locked up in a claim.
* @notice A bondamount represents the amount of collateral that a user has locked up in a claim.
*/
type BondAmount is uint256; type BondAmount is uint256;
/** /// @notice A dedicated timestamp type.
* @notice A dedicated timestamp type.
*/
type Timestamp is uint64; type Timestamp is uint64;
/** /// @notice A dedicated duration type.
* @notice A dedicated duration type. /// @dev Unit: seconds
* @dev Unit: seconds
*/
type Duration is uint64; type Duration is uint64;
/** /// @notice A `GameId` represents a packed 12 byte timestamp and a 20 byte address.
* @notice A `GameId` represents a packed 12 byte timestamp and a 20 byte address. /// @dev The packed layout of this type is as follows:
* @dev The packed layout of this type is as follows: /// ┌────────────┬────────────────┐
* ┌────────────┬────────────────┐ /// │ Bits │ Value │
* │ Bits │ Value │ /// ├────────────┼────────────────┤
* ├────────────┼────────────────┤ /// │ [0, 96) │ Timestamp │
* │ [0, 96) │ Timestamp │ /// │ [96, 256) │ Address │
* │ [96, 256) │ Address │ /// └────────────┴────────────────┘
* └────────────┴────────────────┘
*/
type GameId is bytes32; type GameId is bytes32;
/** /// @notice A `Clock` represents a packed `Duration` and `Timestamp`
* @notice A `Clock` represents a packed `Duration` and `Timestamp` /// @dev The packed layout of this type is as follows:
* @dev The packed layout of this type is as follows: /// ┌────────────┬────────────────┐
* ┌────────────┬────────────────┐ /// │ Bits │ Value │
* │ Bits │ Value │ /// ├────────────┼────────────────┤
* ├────────────┼────────────────┤ /// │ [0, 64) │ Duration │
* │ [0, 64) │ Duration │ /// │ [64, 128) │ Timestamp │
* │ [64, 128) │ Timestamp │ /// └────────────┴────────────────┘
* └────────────┴────────────────┘
*/
type Clock is uint128; type Clock is uint128;
/** /// @notice A `Position` represents a position of a claim within the game tree.
* @notice A `Position` represents a position of a claim within the game tree. /// @dev This is represented as a "generalized index" where the high-order bit
* @dev This is represented as a "generalized index" where the high-order bit /// is the level in the tree and the remaining bits is a unique bit pattern, allowing
* is the level in the tree and the remaining bits is a unique bit pattern, allowing /// a unique identifier for each node in the tree. Mathematically, it is calculated
* a unique identifier for each node in the tree. Mathematically, it is calculated /// as 2^{depth} + indexAtDepth.
* as 2^{depth} + indexAtDepth.
*/
type Position is uint128; type Position is uint128;
/** /// @notice A `GameType` represents the type of game being played.
* @notice A `GameType` represents the type of game being played.
*/
type GameType is uint8; type GameType is uint8;
/** /// @notice The current status of the dispute game.
* @notice The current status of the dispute game.
*/
enum GameStatus { enum GameStatus {
// The game is currently in progress, and has not been resolved. // The game is currently in progress, and has not been resolved.
IN_PROGRESS, IN_PROGRESS,
...@@ -91,23 +69,15 @@ enum GameStatus { ...@@ -91,23 +69,15 @@ enum GameStatus {
DEFENDER_WINS DEFENDER_WINS
} }
/** /// @title GameTypes
* @title GameTypes /// @notice A library that defines the IDs of games that can be played.
* @notice A library that defines the IDs of games that can be played.
*/
library GameTypes { library GameTypes {
/** /// @dev The game will use a `IDisputeGame` implementation that utilizes fault proofs.
* @dev The game will use a `IDisputeGame` implementation that utilizes fault proofs.
*/
GameType internal constant FAULT = GameType.wrap(0); GameType internal constant FAULT = GameType.wrap(0);
/** /// @dev The game will use a `IDisputeGame` implementation that utilizes validity proofs.
* @dev The game will use a `IDisputeGame` implementation that utilizes validity proofs.
*/
GameType internal constant VALIDITY = GameType.wrap(1); GameType internal constant VALIDITY = GameType.wrap(1);
/** /// @dev The game will use a `IDisputeGame` implementation that utilizes attestation proofs.
* @dev The game will use a `IDisputeGame` implementation that utilizes attestation proofs.
*/
GameType internal constant ATTESTATION = GameType.wrap(2); GameType internal constant ATTESTATION = GameType.wrap(2);
} }
...@@ -70,59 +70,6 @@ contract Bytes_slice_Test is Test { ...@@ -70,59 +70,6 @@ contract Bytes_slice_Test is Test {
assertEq(Bytes.slice(input, 31, 34), expected); assertEq(Bytes.slice(input, 31, 34), expected);
} }
/**
* @notice Tests that, when given an input bytes array of length `n`, the `slice` function will
* always revert if `_start + _length > n`.
*/
function testFuzz_slice_outOfBounds_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// We want a valid start index and a length that will not overflow.
vm.assume(_start < _input.length && _length < type(uint256).max - 31);
// But, we want an invalid slice length.
vm.assume(_start + _length > _input.length);
vm.expectRevert("slice_outOfBounds");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that, when given a length `n` that is greater than `type(uint256).max - 31`,
* the `slice` function reverts.
*/
function testFuzz_slice_lengthOverflows_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// Ensure that the `_length` will overflow if a number >= 31 is added to it.
vm.assume(_length > type(uint256).max - 31);
vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that, when given a start index `n` that is greater than
* `type(uint256).max - n`, the `slice` function reverts.
*/
function testFuzz_slice_rangeOverflows_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// Ensure that `_length` is a realistic length of a slice. This is to make sure
// we revert on the correct require statement.
vm.assume(_length < _input.length);
// Ensure that `_start` will overflow if `_length` is added to it.
vm.assume(_start > type(uint256).max - _length);
vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length);
}
/** /**
* @notice Tests that the `slice` function correctly updates the free memory pointer depending * @notice Tests that the `slice` function correctly updates the free memory pointer depending
* on the length of the slice. * on the length of the slice.
...@@ -181,6 +128,61 @@ contract Bytes_slice_Test is Test { ...@@ -181,6 +128,61 @@ contract Bytes_slice_Test is Test {
} }
} }
contract Bytes_slice_TestFail is Test {
/**
* @notice Tests that, when given an input bytes array of length `n`, the `slice` function will
* always revert if `_start + _length > n`.
*/
function testFuzz_slice_outOfBounds_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// We want a valid start index and a length that will not overflow.
vm.assume(_start < _input.length && _length < type(uint256).max - 31);
// But, we want an invalid slice length.
vm.assume(_start + _length > _input.length);
vm.expectRevert("slice_outOfBounds");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that, when given a length `n` that is greater than `type(uint256).max - 31`,
* the `slice` function reverts.
*/
function testFuzz_slice_lengthOverflows_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// Ensure that the `_length` will overflow if a number >= 31 is added to it.
vm.assume(_length > type(uint256).max - 31);
vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length);
}
/**
* @notice Tests that, when given a start index `n` that is greater than
* `type(uint256).max - n`, the `slice` function reverts.
*/
function testFuzz_slice_rangeOverflows_reverts(
bytes memory _input,
uint256 _start,
uint256 _length
) public {
// Ensure that `_length` is a realistic length of a slice. This is to make sure
// we revert on the correct require statement.
vm.assume(_length < _input.length);
// Ensure that `_start` will overflow if `_length` is added to it.
vm.assume(_start > type(uint256).max - _length);
vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length);
}
}
contract Bytes_toNibbles_Test is Test { contract Bytes_toNibbles_Test is Test {
/** /**
* @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of * @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of
......
...@@ -37,10 +37,8 @@ contract DisputeGameFactory_Init is Test { ...@@ -37,10 +37,8 @@ contract DisputeGameFactory_Init is Test {
} }
contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init { contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
/** /// @dev Tests that the `create` function succeeds when creating a new dispute game
* @dev Tests that the `create` function succeeds when creating a new dispute game /// with a `GameType` that has an implementation set.
* with a `GameType` that has an implementation set.
*/
function testFuzz_create_succeeds( function testFuzz_create_succeeds(
uint8 gameType, uint8 gameType,
Claim rootClaim, Claim rootClaim,
...@@ -70,10 +68,8 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init { ...@@ -70,10 +68,8 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
assertEq(timestamp2, block.timestamp); assertEq(timestamp2, block.timestamp);
} }
/** /// @dev Tests that the `create` function reverts when there is no implementation
* @dev Tests that the `create` function reverts when there is no implementation /// set for the given `GameType`.
* set for the given `GameType`.
*/
function testFuzz_create_noImpl_reverts( function testFuzz_create_noImpl_reverts(
uint8 gameType, uint8 gameType,
Claim rootClaim, Claim rootClaim,
...@@ -86,9 +82,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init { ...@@ -86,9 +82,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
factory.create(gt, rootClaim, extraData); factory.create(gt, rootClaim, extraData);
} }
/** /// @dev Tests that the `create` function reverts when there exists a dispute game with the same UUID.
* @dev Tests that the `create` function reverts when there exists a dispute game with the same UUID.
*/
function testFuzz_create_sameUUID_reverts( function testFuzz_create_sameUUID_reverts(
uint8 gameType, uint8 gameType,
Claim rootClaim, Claim rootClaim,
...@@ -124,9 +118,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init { ...@@ -124,9 +118,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
} }
contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init { contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init {
/** /// @dev Tests that the `setImplementation` function properly sets the implementation for a given `GameType`.
* @dev Tests that the `setImplementation` function properly sets the implementation for a given `GameType`.
*/
function test_setImplementation_succeeds() public { function test_setImplementation_succeeds() public {
// There should be no implementation for the `GameTypes.FAULT` enum value, it has not been set. // There should be no implementation for the `GameTypes.FAULT` enum value, it has not been set.
assertEq(address(factory.gameImpls(GameTypes.FAULT)), address(0)); assertEq(address(factory.gameImpls(GameTypes.FAULT)), address(0));
...@@ -141,9 +133,7 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init { ...@@ -141,9 +133,7 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init {
assertEq(address(factory.gameImpls(GameTypes.FAULT)), address(1)); assertEq(address(factory.gameImpls(GameTypes.FAULT)), address(1));
} }
/** /// @dev Tests that the `setImplementation` function reverts when called by a non-owner.
* @dev Tests that the `setImplementation` function reverts when called by a non-owner.
*/
function test_setImplementation_notOwner_reverts() public { function test_setImplementation_notOwner_reverts() public {
// Ensure that the `setImplementation` function reverts when called by a non-owner. // Ensure that the `setImplementation` function reverts when called by a non-owner.
vm.prank(address(0)); vm.prank(address(0));
...@@ -153,10 +143,8 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init { ...@@ -153,10 +143,8 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init {
} }
contract DisputeGameFactory_GetGameUUID_Test is DisputeGameFactory_Init { contract DisputeGameFactory_GetGameUUID_Test is DisputeGameFactory_Init {
/** /// @dev Tests that the `getGameUUID` function returns the correct hash when comparing
* @dev Tests that the `getGameUUID` function returns the correct hash when comparing /// against the keccak256 hash of the abi-encoded parameters.
* against the keccak256 hash of the abi-encoded parameters.
*/
function testDiff_getGameUUID_succeeds( function testDiff_getGameUUID_succeeds(
uint8 gameType, uint8 gameType,
Claim rootClaim, Claim rootClaim,
...@@ -173,26 +161,20 @@ contract DisputeGameFactory_GetGameUUID_Test is DisputeGameFactory_Init { ...@@ -173,26 +161,20 @@ contract DisputeGameFactory_GetGameUUID_Test is DisputeGameFactory_Init {
} }
contract DisputeGameFactory_Owner_Test is DisputeGameFactory_Init { contract DisputeGameFactory_Owner_Test is DisputeGameFactory_Init {
/** /// @dev Tests that the `owner` function returns the correct address after deployment.
* @dev Tests that the `owner` function returns the correct address after deployment.
*/
function test_owner_succeeds() public { function test_owner_succeeds() public {
assertEq(factory.owner(), address(this)); assertEq(factory.owner(), address(this));
} }
} }
contract DisputeGameFactory_TransferOwnership_Test is DisputeGameFactory_Init { contract DisputeGameFactory_TransferOwnership_Test is DisputeGameFactory_Init {
/** /// @dev Tests that the `transferOwnership` function succeeds when called by the owner.
* @dev Tests that the `transferOwnership` function succeeds when called by the owner.
*/
function test_transferOwnership_succeeds() public { function test_transferOwnership_succeeds() public {
factory.transferOwnership(address(1)); factory.transferOwnership(address(1));
assertEq(factory.owner(), address(1)); assertEq(factory.owner(), address(1));
} }
/** /// @dev Tests that the `transferOwnership` function reverts when called by a non-owner.
* @dev Tests that the `transferOwnership` function reverts when called by a non-owner.
*/
function test_transferOwnership_notOwner_reverts() public { function test_transferOwnership_notOwner_reverts() public {
vm.prank(address(0)); vm.prank(address(0));
vm.expectRevert("Ownable: caller is not the owner"); vm.expectRevert("Ownable: caller is not the owner");
...@@ -200,11 +182,9 @@ contract DisputeGameFactory_TransferOwnership_Test is DisputeGameFactory_Init { ...@@ -200,11 +182,9 @@ contract DisputeGameFactory_TransferOwnership_Test is DisputeGameFactory_Init {
} }
} }
/** /// @title PackingTester
* @title PackingTester /// @notice Exposes the internal packing functions so that they can be fuzzed
* @notice Exposes the internal packing functions so that they can be fuzzed /// in a roundtrip manner.
* in a roundtrip manner.
*/
contract PackingTester is DisputeGameFactory { contract PackingTester is DisputeGameFactory {
function packSlot(address _addr, uint256 _num) external pure returns (GameId) { function packSlot(address _addr, uint256 _num) external pure returns (GameId) {
return _packSlot(_addr, _num); return _packSlot(_addr, _num);
...@@ -215,10 +195,8 @@ contract PackingTester is DisputeGameFactory { ...@@ -215,10 +195,8 @@ contract PackingTester is DisputeGameFactory {
} }
} }
/** /// @title DisputeGameFactory_PackSlot_Test
* @title DisputeGameFactory_PackSlot_Test /// @notice Fuzzes the PackingTester contract
* @notice Fuzzes the PackingTester contract
*/
contract DisputeGameFactory_PackSlot_Test is Test { contract DisputeGameFactory_PackSlot_Test is Test {
PackingTester tester; PackingTester tester;
...@@ -226,9 +204,7 @@ contract DisputeGameFactory_PackSlot_Test is Test { ...@@ -226,9 +204,7 @@ contract DisputeGameFactory_PackSlot_Test is Test {
tester = new PackingTester(); tester = new PackingTester();
} }
/** /// @dev Tests that the `packSlot` and `unpackSlot` functions roundtrip correctly.
* @dev Tests that the `packSlot` and `unpackSlot` functions roundtrip correctly.
*/
function testFuzz_packSlot_succeeds(address _addr, uint96 _num) public { function testFuzz_packSlot_succeeds(address _addr, uint96 _num) public {
GameId slot = tester.packSlot(_addr, uint256(_num)); GameId slot = tester.packSlot(_addr, uint256(_num));
(address addr, uint256 num) = tester.unpackSlot(slot); (address addr, uint256 num) = tester.unpackSlot(slot);
...@@ -237,9 +213,7 @@ contract DisputeGameFactory_PackSlot_Test is Test { ...@@ -237,9 +213,7 @@ contract DisputeGameFactory_PackSlot_Test is Test {
} }
} }
/** /// @dev A fake clone used for testing the `DisputeGameFactory` contract's `create` function.
* @dev A fake clone used for testing the `DisputeGameFactory` contract's `create` function.
*/
contract FakeClone { contract FakeClone {
function initialize() external { function initialize() external {
// noop // noop
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { Vm } from "forge-std/Vm.sol";
import { DisputeGameFactory_Init } from "./DisputeGameFactory.t.sol"; import { DisputeGameFactory_Init } from "./DisputeGameFactory.t.sol";
import { DisputeGameFactory } from "../dispute/DisputeGameFactory.sol"; import { DisputeGameFactory } from "../dispute/DisputeGameFactory.sol";
import { FaultDisputeGame } from "../dispute/FaultDisputeGame.sol"; import { FaultDisputeGame } from "../dispute/FaultDisputeGame.sol";
...@@ -11,90 +12,68 @@ import "../libraries/DisputeErrors.sol"; ...@@ -11,90 +12,68 @@ import "../libraries/DisputeErrors.sol";
import { LibClock } from "../dispute/lib/LibClock.sol"; import { LibClock } from "../dispute/lib/LibClock.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol"; import { LibPosition } from "../dispute/lib/LibPosition.sol";
contract FaultDisputeGame_Test is DisputeGameFactory_Init { contract FaultDisputeGame_Init is DisputeGameFactory_Init {
/** /// @dev The extra data passed to the game for initialization.
* @dev The root claim of the game.
*/
Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10)));
/**
* @dev The extra data passed to the game for initialization.
*/
bytes internal constant EXTRA_DATA = abi.encode(1); bytes internal constant EXTRA_DATA = abi.encode(1);
/** /// @dev The type of the game being tested.
* @dev The type of the game being tested.
*/
GameType internal constant GAME_TYPE = GameType.wrap(0); GameType internal constant GAME_TYPE = GameType.wrap(0);
/**
* @dev The current version of the `FaultDisputeGame` contract. /// @dev The implementation of the game.
*/
string internal constant VERSION = "0.0.1";
/**
* @dev The implementation of the game.
*/
FaultDisputeGame internal gameImpl; FaultDisputeGame internal gameImpl;
/** /// @dev The `Clone` proxy of the game.
* @dev The `Clone` proxy of the game.
*/
FaultDisputeGame internal gameProxy; FaultDisputeGame internal gameProxy;
event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant); event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant);
function setUp() public override { function init(Claim rootClaim, Claim absolutePrestate) public {
super.setUp(); super.setUp();
// Deploy an implementation of the fault game // Deploy an implementation of the fault game
gameImpl = new FaultDisputeGame(); gameImpl = new FaultDisputeGame(absolutePrestate, 4);
// Register the game implementation with the factory. // Register the game implementation with the factory.
factory.setImplementation(GAME_TYPE, gameImpl); factory.setImplementation(GAME_TYPE, gameImpl);
// Create a new game. // Create a new game.
gameProxy = FaultDisputeGame(address(factory.create(GAME_TYPE, ROOT_CLAIM, EXTRA_DATA))); gameProxy = FaultDisputeGame(address(factory.create(GAME_TYPE, rootClaim, EXTRA_DATA)));
// Label the proxy // Label the proxy
vm.label(address(gameProxy), "FaultDisputeGame_Clone"); vm.label(address(gameProxy), "FaultDisputeGame_Clone");
} }
}
contract FaultDisputeGame_Test is FaultDisputeGame_Init {
/// @dev The root claim of the game.
Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10)));
/// @dev The absolute prestate of the trace.
Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32(uint256(0)));
function setUp() public override {
super.init(ROOT_CLAIM, ABSOLUTE_PRESTATE);
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// `IDisputeGame` Implementation Tests // // `IDisputeGame` Implementation Tests //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @dev Tests that the game's root claim is set correctly.
* @dev Tests that the game's root claim is set correctly.
*/
function test_rootClaim_succeeds() public { function test_rootClaim_succeeds() public {
assertEq(Claim.unwrap(gameProxy.rootClaim()), Claim.unwrap(ROOT_CLAIM)); assertEq(Claim.unwrap(gameProxy.rootClaim()), Claim.unwrap(ROOT_CLAIM));
} }
/** /// @dev Tests that the game's extra data is set correctly.
* @dev Tests that the game's extra data is set correctly.
*/
function test_extraData_succeeds() public { function test_extraData_succeeds() public {
assertEq(gameProxy.extraData(), EXTRA_DATA); assertEq(gameProxy.extraData(), EXTRA_DATA);
} }
/** /// @dev Tests that the game's status is set correctly.
* @dev Tests that the game's version is set correctly.
*/
function test_version_succeeds() public {
assertEq(gameProxy.version(), VERSION);
}
/**
* @dev Tests that the game's status is set correctly.
*/
function test_gameStart_succeeds() public { function test_gameStart_succeeds() public {
assertEq(Timestamp.unwrap(gameProxy.gameStart()), block.timestamp); assertEq(Timestamp.unwrap(gameProxy.gameStart()), block.timestamp);
} }
/** /// @dev Tests that the game's type is set correctly.
* @dev Tests that the game's type is set correctly.
*/
function test_gameType_succeeds() public { function test_gameType_succeeds() public {
assertEq(GameType.unwrap(gameProxy.gameType()), GameType.unwrap(GAME_TYPE)); assertEq(GameType.unwrap(gameProxy.gameType()), GameType.unwrap(GAME_TYPE));
} }
/** /// @dev Tests that the game's data is set correctly.
* @dev Tests that the game's data is set correctly.
*/
function test_gameData_succeeds() public { function test_gameData_succeeds() public {
(GameType gameType, Claim rootClaim, bytes memory extraData) = gameProxy.gameData(); (GameType gameType, Claim rootClaim, bytes memory extraData) = gameProxy.gameData();
...@@ -107,9 +86,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -107,9 +86,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
// `IFaultDisputeGame` Implementation Tests // // `IFaultDisputeGame` Implementation Tests //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
/** /// @dev Tests that the root claim's data is set correctly when the game is initialized.
* @dev Tests that the root claim's data is set correctly when the game is initialized.
*/
function test_initialRootClaimData_succeeds() public { function test_initialRootClaimData_succeeds() public {
( (
uint32 parentIndex, uint32 parentIndex,
...@@ -129,10 +106,8 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -129,10 +106,8 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
); );
} }
/** /// @dev Tests that a move while the game status is not `IN_PROGRESS` causes the call to revert
* @dev Tests that a move while the game status is not `IN_PROGRESS` causes the call to revert /// with the `GameNotInProgress` error
* with the `GameNotInProgress` error
*/
function test_move_gameNotInProgress_reverts() public { function test_move_gameNotInProgress_reverts() public {
uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS); uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS);
...@@ -153,19 +128,15 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -153,19 +128,15 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
gameProxy.attack(0, Claim.wrap(0)); gameProxy.attack(0, Claim.wrap(0));
} }
/** /// @dev Tests that an attempt to defend the root claim reverts with the `CannotDefendRootClaim` error.
* @dev Tests that an attempt to defend the root claim reverts with the `CannotDefendRootClaim` error. function test_defendRoot_invalidMove_reverts() public {
*/
function test_defendRoot_reverts() public {
vm.expectRevert(CannotDefendRootClaim.selector); vm.expectRevert(CannotDefendRootClaim.selector);
gameProxy.defend(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.defend(0, Claim.wrap(bytes32(uint256(5))));
} }
/** /// @dev Tests that an attempt to move against a claim that does not exist reverts with the
* @dev Tests that an attempt to move against a claim that does not exist reverts with the /// `ParentDoesNotExist` error.
* `ParentDoesNotExist` error. function test_move_nonExistentParent_reverts() public {
*/
function test_moveAgainstNonexistentParent_reverts() public {
Claim claim = Claim.wrap(bytes32(uint256(5))); Claim claim = Claim.wrap(bytes32(uint256(5)));
// Expect an out of bounds revert for an attack // Expect an out of bounds revert for an attack
...@@ -177,39 +148,35 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -177,39 +148,35 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
gameProxy.defend(1, claim); gameProxy.defend(1, claim);
} }
/** /// @dev Tests that an attempt to move at the maximum game depth reverts with the
* @dev Tests that an attempt to move at the maximum game depth reverts with the /// `GameDepthExceeded` error.
* `GameDepthExceeded` error. function test_move_gameDepthExceeded_reverts() public {
*/
function test_gameDepthExceeded_reverts() public {
Claim claim = Claim.wrap(bytes32(uint256(5))); Claim claim = Claim.wrap(bytes32(uint256(5)));
for (uint256 i = 0; i < 63; i++) { uint256 maxDepth = gameProxy.MAX_GAME_DEPTH();
for (uint256 i = 0; i <= maxDepth; i++) {
// At the max game depth, the `_move` function should revert with // At the max game depth, the `_move` function should revert with
// the `GameDepthExceeded` error. // the `GameDepthExceeded` error.
if (i == 62) { if (i == maxDepth) {
vm.expectRevert(GameDepthExceeded.selector); vm.expectRevert(GameDepthExceeded.selector);
} }
gameProxy.attack(i, claim); gameProxy.attack(i, claim);
} }
} }
/** /// @dev Tests that a move made after the clock time has exceeded reverts with the
* @dev Tests that a move made after the clock time has exceeded reverts with the /// `ClockTimeExceeded` error.
* `ClockTimeExceeded` error. function test_move_clockTimeExceeded_reverts() public {
*/
function test_clockTimeExceeded_reverts() public {
// Warp ahead past the clock time for the first move (3 1/2 days) // Warp ahead past the clock time for the first move (3 1/2 days)
vm.warp(block.timestamp + 3 days + 12 hours + 1); vm.warp(block.timestamp + 3 days + 12 hours + 1);
vm.expectRevert(ClockTimeExceeded.selector); vm.expectRevert(ClockTimeExceeded.selector);
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
} }
/** /// @dev Tests that an identical claim cannot be made twice. The duplicate claim attempt should
* @dev Tests that an identical claim cannot be made twice. The duplicate claim attempt should /// revert with the `ClaimAlreadyExists` error.
* revert with the `ClaimAlreadyExists` error. function test_move_duplicateClaim_reverts() public {
*/
function test_duplicateClaim_reverts() public {
Claim claim = Claim.wrap(bytes32(uint256(5))); Claim claim = Claim.wrap(bytes32(uint256(5)));
// Make the first move. This should succeed. // Make the first move. This should succeed.
...@@ -220,9 +187,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -220,9 +187,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
gameProxy.attack(0, claim); gameProxy.attack(0, claim);
} }
/** /// @dev Static unit test for the correctness of an opening attack.
* @dev Static unit test for the correctness of an opening attack.
*/
function test_simpleAttack_succeeds() public { function test_simpleAttack_succeeds() public {
// Warp ahead 5 seconds. // Warp ahead 5 seconds.
vm.warp(block.timestamp + 5); vm.warp(block.timestamp + 5);
...@@ -247,7 +212,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -247,7 +212,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(parentIndex, 0); assertEq(parentIndex, 0);
assertEq(countered, false); assertEq(countered, false);
assertEq(Claim.unwrap(claim), Claim.unwrap(counter)); assertEq(Claim.unwrap(claim), Claim.unwrap(counter));
assertEq(Position.unwrap(position), Position.unwrap(Position.wrap(1).attack())); assertEq(Position.unwrap(position), Position.unwrap(Position.wrap(1).move(true)));
assertEq( assertEq(
Clock.unwrap(clock), Clock.unwrap(clock),
Clock.unwrap(LibClock.wrap(Duration.wrap(5), Timestamp.wrap(uint64(block.timestamp)))) Clock.unwrap(LibClock.wrap(Duration.wrap(5), Timestamp.wrap(uint64(block.timestamp))))
...@@ -269,27 +234,31 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -269,27 +234,31 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
); );
} }
/** /// @dev Static unit test for the correctness an uncontested root resolution.
* @dev Static unit test for the correctness an uncontested root resolution.
*/
function test_resolve_rootUncontested() public { function test_resolve_rootUncontested() public {
GameStatus status = gameProxy.resolve(); GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
} }
/** /// @dev Static unit test asserting that resolve reverts when the game state is
* @dev Static unit test asserting that resolve reverts when the game is not in progress. /// not in progress.
*/ function test_resolve_notInProgress_reverts() public {
function test_resolve_reverts() public { uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS);
gameProxy.resolve();
// Replace the game status in storage. It exists in slot 0 at offset 8.
uint256 slot = uint256(vm.load(address(gameProxy), bytes32(0)));
uint256 offset = (8 << 3);
uint256 mask = 0xFF << offset;
// Replace the byte in the slot value with the challenger wins status.
slot = (slot & ~mask) | (chalWins << offset);
vm.store(address(gameProxy), bytes32(uint256(0)), bytes32(slot));
vm.expectRevert(GameNotInProgress.selector); vm.expectRevert(GameNotInProgress.selector);
gameProxy.resolve(); gameProxy.resolve();
} }
/** /// @dev Static unit test for the correctness of resolving a single attack game state.
* @dev Static unit test for the correctness of resolving a single attack game state.
*/
function test_resolve_rootContested() public { function test_resolve_rootContested() public {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
...@@ -298,9 +267,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -298,9 +267,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
} }
/** /// @dev Static unit test for the correctness of resolving a game with a contested challenge claim.
* @dev Static unit test for the correctness of resolving a game with a contested challenge claim.
*/
function test_resolve_challengeContested() public { function test_resolve_challengeContested() public {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(6)))); gameProxy.defend(1, Claim.wrap(bytes32(uint256(6))));
...@@ -310,9 +277,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -310,9 +277,7 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
} }
/** /// @dev Static unit test for the correctness of resolving a game with multiplayer moves.
* @dev Static unit test for the correctness of resolving a game with multiplayer moves.
*/
function test_resolve_teamDeathmatch() public { function test_resolve_teamDeathmatch() public {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5)))); gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.attack(0, Claim.wrap(bytes32(uint256(4)))); gameProxy.attack(0, Claim.wrap(bytes32(uint256(4))));
...@@ -325,35 +290,370 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init { ...@@ -325,35 +290,370 @@ contract FaultDisputeGame_Test is DisputeGameFactory_Init {
} }
} }
/** /// @notice A generic game player actor with a configurable trace.
* @title BigStepper /// @dev This actor always responds rationally with respect to their trace. The
* @notice A mock fault proof processor contract for testing purposes. /// `play` function can be overridden to change this behavior.
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ contract GamePlayer {
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠶⢅⠒⢄⢔⣶⡦⣤⡤⠄⣀⠀⠀⠀⠀⠀⠀⠀ bool public failedToStep;
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠨⡏⠀⠀⠈⠢⣙⢯⣄⠀⢨⠯⡺⡘⢄⠀⠀⠀⠀⠀ FaultDisputeGame public gameProxy;
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣶⡆⠀⠀⠀⠀⠈⠓⠬⡒⠡⣀⢙⡜⡀⠓⠄⠀⠀⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡷⠿⣧⣀⡀⠀⠀⠀⠀⠀⠀⠉⠣⣞⠩⠥⠀⠼⢄⠀⠀ GamePlayer internal counterParty;
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠉⢹⣶⠒⠒⠂⠈⠉⠁⠘⡆⠀⣿⣿⠫⡄⠀ Vm internal vm;
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢶⣤⣀⡀⠀⠀⢸⡿⠀⠀⠀⠀⠀⢀⠞⠀⠀⢡⢨⢀⡄⠀ bytes internal trace;
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡒⣿⢿⡤⠝⡣⠉⠁⠚⠛⠀⠤⠤⣄⡰⠁⠀⠀⠀⠉⠙⢸⠀⠀ uint256 internal maxDepth;
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⢯⡌⡿⡇⠘⡷⠀⠁⠀⠀⢀⣰⠢⠲⠛⣈⣸⠦⠤⠶⠴⢬⣐⣊⡂⠀
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡪⡗⢫⠞⠀⠆⣀⠻⠤⠴⠐⠚⣉⢀⠦⠂⠋⠁⠀⠁⠀⠀⠀⠀⢋⠉⠇⠀ /// @notice Initializes the player
*⠀⠀⠀⠀⣀⡤⠐⠒⠘⡹⠉⢸⠇⠸⠀⠀⠀⠀⣀⣤⠴⠚⠉⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⠀⣾⠀ function init(
*⠀⠀⠀⡰⠀⠉⠉⠀⠁⠀⠀⠈⢇⠈⠒⠒⠘⠈⢀⢡⡂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⢸⡄ FaultDisputeGame _gameProxy,
*⠀⠀⠸⣿⣆⠤⢀⡀⠀⠀⠀⠀⢘⡌⠀⠀⣀⣀⣀⡈⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⢸⡇ GamePlayer _counterParty,
*⠀⠀⢸⣀⠀⠉⠒⠐⠛⠋⠭⠭⠍⠉⠛⠒⠒⠒⠀⠒⠚⠛⠛⠛⠩⠭⠭⠭⠭⠤⠤⠤⠤⠤⠭⠭⠉⠓⡆ Vm _vm
*⠀⠀⠘⠿⣷⣶⣤⣤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ ) public virtual {
*⠀⠀⠀⠀⠀⠉⠙⠛⠛⠻⠿⢿⣿⣿⣷⣶⣶⣶⣤⣤⣀⣁⣛⣃⣒⠿⠿⠿⠤⠠⠄⠤⠤⢤⣛⣓⣂⣻⡇ gameProxy = _gameProxy;
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠙⠛⠻⠿⠿⠿⢿⣿⣿⣿⣷⣶⣶⣾⣿⣿⣿⣿⠿⠟⠁ counterParty = _counterParty;
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀ vm = _vm;
*/ maxDepth = _gameProxy.MAX_GAME_DEPTH();
contract BigStepper { }
/**
* @notice Steps from the `preState` to the `postState` by adding 1 to the `preState`. /// @notice Perform the next move in the game.
* @param preState The pre state to start from function play(uint256 _parentIndex) public virtual {
* @return postState The state stepped to // Grab the claim data at the parent index.
*/ (uint32 grandparentIndex, , Claim parentClaim, Position parentPos, ) = gameProxy.claimData(
function step(Claim preState) external pure returns (Claim postState) { _parentIndex
postState = Claim.wrap(bytes32(uint256(Claim.unwrap(preState)) + 1)); );
// The position to move to.
Position movePos;
// May or may not be used.
Position movePos2;
// Signifies whether the move is an attack or not.
bool isAttack;
if (grandparentIndex == type(uint32).max) {
// If the parent claim is the root claim, begin by attacking.
movePos = parentPos.move(true);
// Flag the move as an attack.
isAttack = true;
} else {
// If the parent claim is not the root claim, check if we disagree with it and/or its grandparent
// to determine our next move(s).
// Fetch our claim at the parent's position.
Claim ourParentClaim = claimAt(parentPos);
// Fetch our claim at the grandparent's position.
(, , Claim grandparentClaim, Position grandparentPos, ) = gameProxy.claimData(
grandparentIndex
);
Claim ourGrandparentClaim = claimAt(grandparentPos);
if (Claim.unwrap(ourParentClaim) != Claim.unwrap(parentClaim)) {
// Attack parent.
movePos = parentPos.move(true);
// If we also disagree with the grandparent, attack it as well.
if (Claim.unwrap(ourGrandparentClaim) != Claim.unwrap(grandparentClaim)) {
movePos2 = grandparentPos.move(true);
}
// Flag the move as an attack.
isAttack = true;
} else if (
Claim.unwrap(ourParentClaim) == Claim.unwrap(parentClaim) &&
Claim.unwrap(ourGrandparentClaim) == Claim.unwrap(grandparentClaim)
) {
movePos = parentPos.move(false);
}
}
// If we are past the maximum depth, break the recursion and step.
if (movePos.depth() > maxDepth) {
// Perform a step.
uint256 stateIndex;
// First, we need to find the pre/post state index depending on whether we
// are making an attack step or a defense step. If the index at depth of the
// move position is 0, the prestate is the absolute prestate and we need to
// do nothing.
if (movePos.indexAtDepth() > 0) {
Position leafPos = isAttack
? Position.wrap(Position.unwrap(parentPos) - 1)
: Position.wrap(Position.unwrap(parentPos) + 1);
Position statePos = leafPos;
// Walk up until the valid position that commits to the prestate's
// trace index is found.
while (
Position.unwrap(statePos.parent().rightIndex(maxDepth)) ==
Position.unwrap(leafPos)
) {
statePos = statePos.parent();
}
// Now, search for the index of the claim that commits to the prestate's trace
// index.
uint256 len = claimDataLen();
for (uint256 i = 0; i < len; i++) {
(, , , Position pos, ) = gameProxy.claimData(i);
if (Position.unwrap(pos) == Position.unwrap(statePos)) {
stateIndex = i;
break;
}
}
}
// Perform the step and halt recursion.
try gameProxy.step(stateIndex, _parentIndex, isAttack, hex"", hex"") {
// Do nothing, step succeeded.
} catch {
failedToStep = true;
}
} else {
// Find the trace index that our next claim must commit to.
uint256 traceIndex = movePos.rightIndex(maxDepth).indexAtDepth();
// Grab the claim that we need to make from the helper.
Claim ourClaim = claimAt(traceIndex);
if (isAttack) {
// Attack the parent claim.
gameProxy.attack(_parentIndex, ourClaim);
// Call out to our counter party to respond.
counterParty.play(claimDataLen() - 1);
// If we have a second move position, attack the grandparent.
if (Position.unwrap(movePos2) != 0) {
(, , , Position grandparentPos, ) = gameProxy.claimData(grandparentIndex);
Claim ourGrandparentClaim = claimAt(grandparentPos.move(true));
gameProxy.attack(grandparentIndex, ourGrandparentClaim);
counterParty.play(claimDataLen() - 1);
}
} else {
// Defend the parent claim.
gameProxy.defend(_parentIndex, ourClaim);
// Call out to our counter party to respond.
counterParty.play(claimDataLen() - 1);
}
}
}
/// @notice Returns the length of the claim data array.
function claimDataLen() internal view returns (uint256 len_) {
return uint256(vm.load(address(gameProxy), bytes32(uint256(1))));
}
/// @notice Returns the player's claim that commits to a given gindex.
function claimAt(Position _position) internal view returns (Claim claim_) {
return claimAt(_position.rightIndex(maxDepth).indexAtDepth());
}
/// @notice Returns the player's claim that commits to a given trace index.
function claimAt(uint256 _traceIndex) public view returns (Claim claim_) {
return Claim.wrap(bytes32(uint256(bytes32(trace[_traceIndex]) >> 248)));
}
}
contract OneVsOne_Arena is FaultDisputeGame_Init {
/// @dev The absolute prestate of the trace.
Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32(uint256(15)));
/// @dev The honest participant.
GamePlayer internal honest;
/// @dev The dishonest participant.
GamePlayer internal dishonest;
function init(
GamePlayer _honest,
GamePlayer _dishonest,
Claim _rootClaim
) public {
super.init(_rootClaim, ABSOLUTE_PRESTATE);
// Deploy a new honest player.
honest = _honest;
// Deploy a new dishonest player.
dishonest = _dishonest;
// Set the counterparties.
honest.init(gameProxy, dishonest, vm);
dishonest.init(gameProxy, honest, vm);
// Label actors for trace.
vm.label(address(honest), "HonestPlayer");
vm.label(address(dishonest), "DishonestPlayer");
}
}
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new FullyDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(30))));
}
function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced.
honest.play(0);
// Resolve the game and assert that the honest player challenged the root
// claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
assertFalse(honest.failedToStep());
}
}
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new FullyDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31))));
}
function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced.
dishonest.play(0);
// Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
assertTrue(dishonest.failedToStep());
}
}
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2 is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new HalfDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(15))));
}
function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced.
honest.play(0);
// Resolve the game and assert that the honest player challenged the root
// claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
assertFalse(honest.failedToStep());
}
}
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot2 is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new HalfDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31))));
}
function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced.
dishonest.play(0);
// Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
assertTrue(dishonest.failedToStep());
}
}
contract FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3 is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new EarlyDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(15))));
}
function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced.
honest.play(0);
// Resolve the game and assert that the honest player challenged the root
// claim successfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.CHALLENGER_WINS));
assertFalse(honest.failedToStep());
}
}
contract FaultDisputeGame_ResolvesCorrectly_CorrectRoot4 is OneVsOne_Arena {
function setUp() public override {
GamePlayer honest = new HonestPlayer();
GamePlayer dishonest = new EarlyDivergentPlayer();
super.init(honest, dishonest, Claim.wrap(bytes32(uint256(31))));
}
function test_resolvesCorrectly_succeeds() public {
// Play the game until a step is forced.
dishonest.play(0);
// Resolve the game and assert that the dishonest player challenged the root
// claim unsuccessfully.
assertEq(uint8(gameProxy.resolve()), uint8(GameStatus.DEFENDER_WINS));
assertTrue(dishonest.failedToStep());
}
}
////////////////////////////////////////////////////////////////
// ACTORS //
////////////////////////////////////////////////////////////////
contract HonestPlayer is GamePlayer {
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
bytes memory honestTrace = new bytes(16);
for (uint8 i = 0; i < honestTrace.length; i++) {
honestTrace[i] = bytes1(absolutePrestate + i + 1);
}
trace = honestTrace;
}
}
contract FullyDivergentPlayer is GamePlayer {
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the honest trace by 1.
dishonestTrace[i] = bytes1(absolutePrestate + i);
}
trace = dishonestTrace;
}
}
contract HalfDivergentPlayer is GamePlayer {
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the trace after the first half.
dishonestTrace[i] = i > 7 ? bytes1(i) : bytes1(absolutePrestate + i + 1);
}
trace = dishonestTrace;
}
}
contract EarlyDivergentPlayer is GamePlayer {
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
Vm _vm
) public virtual override {
super.init(_gameProxy, _counterParty, _vm);
uint8 absolutePrestate = uint8(uint256(Claim.unwrap(_gameProxy.ABSOLUTE_PRESTATE())));
bytes memory dishonestTrace = new bytes(16);
for (uint8 i = 0; i < dishonestTrace.length; i++) {
// Offset the trace after the first half.
dishonestTrace[i] = i > 2 ? bytes1(i) : bytes1(absolutePrestate + i + 1);
}
trace = dishonestTrace;
} }
} }
...@@ -5,21 +5,15 @@ import { Test } from "forge-std/Test.sol"; ...@@ -5,21 +5,15 @@ import { Test } from "forge-std/Test.sol";
import { LibClock } from "../dispute/lib/LibClock.sol"; import { LibClock } from "../dispute/lib/LibClock.sol";
import "../libraries/DisputeTypes.sol"; import "../libraries/DisputeTypes.sol";
/** /// @notice Tests for `LibClock`
* @notice Tests for `LibClock`
*/
contract LibClock_Test is Test { contract LibClock_Test is Test {
/** /// @notice Tests that the `duration` function correctly shifts out the `Duration` from a packed `Clock` type.
* @notice Tests that the `duration` function correctly shifts out the `Duration` from a packed `Clock` type.
*/
function testFuzz_duration_succeeds(Duration _duration, Timestamp _timestamp) public { function testFuzz_duration_succeeds(Duration _duration, Timestamp _timestamp) public {
Clock clock = LibClock.wrap(_duration, _timestamp); Clock clock = LibClock.wrap(_duration, _timestamp);
assertEq(Duration.unwrap(clock.duration()), Duration.unwrap(_duration)); assertEq(Duration.unwrap(clock.duration()), Duration.unwrap(_duration));
} }
/** /// @notice Tests that the `timestamp` function correctly shifts out the `Timestamp` from a packed `Clock` type.
* @notice Tests that the `timestamp` function correctly shifts out the `Timestamp` from a packed `Clock` type.
*/
function testFuzz_timestamp_succeeds(Duration _duration, Timestamp _timestamp) public { function testFuzz_timestamp_succeeds(Duration _duration, Timestamp _timestamp) public {
Clock clock = LibClock.wrap(_duration, _timestamp); Clock clock = LibClock.wrap(_duration, _timestamp);
assertEq(Timestamp.unwrap(clock.timestamp()), Timestamp.unwrap(_timestamp)); assertEq(Timestamp.unwrap(clock.timestamp()), Timestamp.unwrap(_timestamp));
......
...@@ -5,15 +5,11 @@ import { Test } from "forge-std/Test.sol"; ...@@ -5,15 +5,11 @@ import { Test } from "forge-std/Test.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol"; import { LibPosition } from "../dispute/lib/LibPosition.sol";
import "../libraries/DisputeTypes.sol"; import "../libraries/DisputeTypes.sol";
/** /// @notice Tests for `LibPosition`
* @notice Tests for `LibPosition`
*/
contract LibPosition_Test is Test { contract LibPosition_Test is Test {
/** /// @dev Assumes a MAX depth of 63 for the Position type. Any greater depth can cause overflows.
* @dev Assumes a MAX depth of 63 for the Position type. Any greater depth can cause overflows. /// @dev At the lowest level of the tree, this allows for 2 ** 63 leaves. In reality, the max game depth
* @dev At the lowest level of the tree, this allows for 2 ** 63 leaves. In reality, the max game depth /// will likely be much lower.
* will likely be much lower.
*/
uint8 internal constant MAX_DEPTH = 63; uint8 internal constant MAX_DEPTH = 63;
function boundIndexAtDepth(uint8 _depth, uint64 _indexAtDepth) internal view returns (uint64) { function boundIndexAtDepth(uint8 _depth, uint64 _indexAtDepth) internal view returns (uint64) {
...@@ -25,30 +21,24 @@ contract LibPosition_Test is Test { ...@@ -25,30 +21,24 @@ contract LibPosition_Test is Test {
} }
} }
/** /// @notice Tests that the `depth` function correctly shifts out the `depth` from a packed `Position` type.
* @notice Tests that the `depth` function correctly shifts out the `depth` from a packed `Position` type. function testFuzz_depth_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
*/
function testFuzz_depth_correctness(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 0, MAX_DEPTH)); _depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
assertEq(position.depth(), _depth); assertEq(position.depth(), _depth);
} }
/** /// @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from a packed `Position` type.
* @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from a packed `Position` type. function testFuzz_indexAtDepth_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
*/
function testFuzz_indexAtDepth_correctness(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 0, MAX_DEPTH)); _depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
assertEq(position.indexAtDepth(), _indexAtDepth); assertEq(position.indexAtDepth(), _indexAtDepth);
} }
/** /// @notice Tests that the `left` function correctly computes the position of the left child.
* @notice Tests that the `left` function correctly computes the position of the left child. function testFuzz_left_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
*/
function testFuzz_left_correctness(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 0, MAX_DEPTH)); _depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
...@@ -59,10 +49,8 @@ contract LibPosition_Test is Test { ...@@ -59,10 +49,8 @@ contract LibPosition_Test is Test {
assertEq(left.indexAtDepth(), _indexAtDepth * 2); assertEq(left.indexAtDepth(), _indexAtDepth * 2);
} }
/** /// @notice Tests that the `right` function correctly computes the position of the right child.
* @notice Tests that the `right` function correctly computes the position of the right child. function testFuzz_right_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
*/
function testFuzz_right_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63] // Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH)); _depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
...@@ -74,10 +62,8 @@ contract LibPosition_Test is Test { ...@@ -74,10 +62,8 @@ contract LibPosition_Test is Test {
assertEq(right.indexAtDepth(), _indexAtDepth * 2 + 1); assertEq(right.indexAtDepth(), _indexAtDepth * 2 + 1);
} }
/** /// @notice Tests that the `parent` function correctly computes the position of the parent.
* @notice Tests that the `parent` function correctly computes the position of the parent. function testFuzz_parent_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
*/
function testFuzz_parent_correctness(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 1, MAX_DEPTH)); _depth = uint8(bound(_depth, 1, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
...@@ -88,11 +74,9 @@ contract LibPosition_Test is Test { ...@@ -88,11 +74,9 @@ contract LibPosition_Test is Test {
assertEq(parent.indexAtDepth(), _indexAtDepth / 2); assertEq(parent.indexAtDepth(), _indexAtDepth / 2);
} }
/** /// @notice Tests that the `rightIndex` function correctly computes the deepest, right most index relative
* @notice Tests that the `rightIndex` function correctly computes the deepest, right most index relative /// to a given position.
* to a given position. function testFuzz_rightIndex_correctness_suceeds(
*/
function testFuzz_rightIndex_correctness(
uint64 _maxDepth, uint64 _maxDepth,
uint8 _depth, uint8 _depth,
uint64 _indexAtDepth uint64 _indexAtDepth
...@@ -105,49 +89,298 @@ contract LibPosition_Test is Test { ...@@ -105,49 +89,298 @@ contract LibPosition_Test is Test {
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
uint64 rightIndex = position.rightIndex(_maxDepth); Position rightIndex = position.rightIndex(_maxDepth);
// Find the deepest, rightmost index in Solidity rather than Yul // Find the deepest, rightmost index in Solidity rather than Yul
for (uint256 i = _depth; i < _maxDepth; ++i) { for (uint256 i = _depth; i < _maxDepth; ++i) {
position = position.right(); position = position.right();
} }
uint64 _rightIndex = position.indexAtDepth();
assertEq(rightIndex, _rightIndex); assertEq(Position.unwrap(rightIndex), Position.unwrap(position));
} }
/** /// @notice Tests that the `attack` function correctly computes the position of the attack relative to
* @notice Tests that the `attack` function correctly computes the position of the attack relative to /// a given position.
* a given position. /// @dev `attack` is an alias for `left`, but we test it separately for completeness.
* @dev `attack` is an alias for `left`, but we test it separately for completeness. function testFuzz_attack_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
*/
function testFuzz_attack_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63] // Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH)); _depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position attack = position.attack(); Position attack = position.move(true);
assertEq(attack.depth(), _depth + 1); assertEq(attack.depth(), _depth + 1);
assertEq(attack.indexAtDepth(), _indexAtDepth * 2); assertEq(attack.indexAtDepth(), _indexAtDepth * 2);
} }
/** /// @notice Tests that the `defend` function correctly computes the position of the defense relative to
* @notice Tests that the `defend` function correctly computes the position of the defense relative to /// a given position.
* a given position. /// @dev A defense can only be given if the position does not belong to the root claim, hence the bound of [1, 127]
* @dev A defense can only be given if the position does not belong to the root claim, hence the bound of [1, 127] /// on the depth.
* on the depth. function testFuzz_defend_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
*/
function testFuzz_defend_correctness(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [1, 63] // Depth bound: [1, 63]
_depth = uint8(bound(_depth, 1, MAX_DEPTH)); _depth = uint8(bound(_depth, 1, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth); _indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
Position position = LibPosition.wrap(_depth, _indexAtDepth); Position position = LibPosition.wrap(_depth, _indexAtDepth);
Position defend = position.defend(); Position defend = position.move(false);
assertEq(defend.depth(), _depth + 1); assertEq(defend.depth(), _depth + 1);
assertEq(defend.indexAtDepth(), ((_indexAtDepth / 2) * 2 + 1) * 2); assertEq(defend.indexAtDepth(), ((_indexAtDepth / 2) * 2 + 1) * 2);
} }
/// @notice A static unit test for the correctness of all gindicies, (depth, index) combos,
/// and the trace index in a tree of max depth = 4.
function test_pos_correctness_succeeds() public {
uint256 maxDepth = 4;
Position p = LibPosition.wrap(0, 0);
assertEq(Position.unwrap(p), 1); // gindex = 1
assertEq(p.depth(), 0); // depth = 0
assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0
Position r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 31); // right gindex = 31
assertEq(r.indexAtDepth(), 15); // trace index = 15
p = LibPosition.wrap(1, 0);
assertEq(Position.unwrap(p), 2); // gindex = 2
assertEq(p.depth(), 1); // depth = 1
assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 23); // right gindex = 23
assertEq(r.indexAtDepth(), 7); // trace index = 7
p = LibPosition.wrap(1, 1);
assertEq(Position.unwrap(p), 3); // gindex = 3
assertEq(p.depth(), 1); // depth = 1
assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 31); // right gindex = 31
assertEq(r.indexAtDepth(), 15); // trace index = 15
p = LibPosition.wrap(2, 0);
assertEq(Position.unwrap(p), 4); // gindex = 4
assertEq(p.depth(), 2); // depth = 2
assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 19); // right gindex = 19
assertEq(r.indexAtDepth(), 3); // trace index = 3
p = LibPosition.wrap(2, 1);
assertEq(Position.unwrap(p), 5); // gindex = 5
assertEq(p.depth(), 2); // depth = 2
assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 23); // right gindex = 23
assertEq(r.indexAtDepth(), 7); // trace index = 7
p = LibPosition.wrap(2, 2);
assertEq(Position.unwrap(p), 6); // gindex = 6
assertEq(p.depth(), 2); // depth = 2
assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 27); // right gindex = 27
assertEq(r.indexAtDepth(), 11); // trace index = 11
p = LibPosition.wrap(2, 3);
assertEq(Position.unwrap(p), 7); // gindex = 7
assertEq(p.depth(), 2); // depth = 2
assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 31); // right gindex = 31
assertEq(r.indexAtDepth(), 15); // trace index = 15
p = LibPosition.wrap(3, 0);
assertEq(Position.unwrap(p), 8); // gindex = 8
assertEq(p.depth(), 3); // depth = 3
assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 17); // right gindex = 17
assertEq(r.indexAtDepth(), 1); // trace index = 1
p = LibPosition.wrap(3, 1);
assertEq(Position.unwrap(p), 9); // gindex = 9
assertEq(p.depth(), 3); // depth = 3
assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 19); // right gindex = 19
assertEq(r.indexAtDepth(), 3); // trace index = 3
p = LibPosition.wrap(3, 2);
assertEq(Position.unwrap(p), 10); // gindex = 10
assertEq(p.depth(), 3); // depth = 3
assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 21); // right gindex = 21
assertEq(r.indexAtDepth(), 5); // trace index = 5
p = LibPosition.wrap(3, 3);
assertEq(Position.unwrap(p), 11); // gindex = 11
assertEq(p.depth(), 3); // depth = 3
assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 23); // right gindex = 23
assertEq(r.indexAtDepth(), 7); // trace index = 7
p = LibPosition.wrap(3, 4);
assertEq(Position.unwrap(p), 12); // gindex = 12
assertEq(p.depth(), 3); // depth = 3
assertEq(p.indexAtDepth(), 4); // indexAtDepth = 4
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 25); // right gindex = 25
assertEq(r.indexAtDepth(), 9); // trace index = 9
p = LibPosition.wrap(3, 5);
assertEq(Position.unwrap(p), 13); // gindex = 13
assertEq(p.depth(), 3); // depth = 3
assertEq(p.indexAtDepth(), 5); // indexAtDepth = 5
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 27); // right gindex = 27
assertEq(r.indexAtDepth(), 11); // trace index = 11
p = LibPosition.wrap(3, 6);
assertEq(Position.unwrap(p), 14); // gindex = 14
assertEq(p.depth(), 3); // depth = 3
assertEq(p.indexAtDepth(), 6); // indexAtDepth = 6
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 29); // right gindex = 29
assertEq(r.indexAtDepth(), 13); // trace index = 13
p = LibPosition.wrap(3, 7);
assertEq(Position.unwrap(p), 15); // gindex = 15
assertEq(p.depth(), 3); // depth = 3
assertEq(p.indexAtDepth(), 7); // indexAtDepth = 7
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 31); // right gindex = 31
assertEq(r.indexAtDepth(), 15); // trace index = 15
p = LibPosition.wrap(4, 0);
assertEq(Position.unwrap(p), 16); // gindex = 16
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 0); // indexAtDepth = 0
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 16); // right gindex = 16
assertEq(r.indexAtDepth(), 0); // trace index = 0
p = LibPosition.wrap(4, 1);
assertEq(Position.unwrap(p), 17); // gindex = 17
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 1); // indexAtDepth = 1
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 17); // right gindex = 17
assertEq(r.indexAtDepth(), 1); // trace index = 1
p = LibPosition.wrap(4, 2);
assertEq(Position.unwrap(p), 18); // gindex = 18
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 2); // indexAtDepth = 2
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 18); // right gindex = 18
assertEq(r.indexAtDepth(), 2); // trace index = 2
p = LibPosition.wrap(4, 3);
assertEq(Position.unwrap(p), 19); // gindex = 19
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 3); // indexAtDepth = 3
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 19); // right gindex = 19
assertEq(r.indexAtDepth(), 3); // trace index = 3
p = LibPosition.wrap(4, 4);
assertEq(Position.unwrap(p), 20); // gindex = 20
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 4); // indexAtDepth = 4
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 20); // right gindex = 20
assertEq(r.indexAtDepth(), 4); // trace index = 4
p = LibPosition.wrap(4, 5);
assertEq(Position.unwrap(p), 21); // gindex = 21
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 5); // indexAtDepth = 5
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 21); // right gindex = 21
assertEq(r.indexAtDepth(), 5); // trace index = 5
p = LibPosition.wrap(4, 6);
assertEq(Position.unwrap(p), 22); // gindex = 22
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 6); // indexAtDepth = 6
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 22); // right gindex = 22
assertEq(r.indexAtDepth(), 6); // trace index = 6
p = LibPosition.wrap(4, 7);
assertEq(Position.unwrap(p), 23); // gindex = 23
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 7); // indexAtDepth = 7
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 23); // right gindex = 23
assertEq(r.indexAtDepth(), 7); // trace index = 7
p = LibPosition.wrap(4, 8);
assertEq(Position.unwrap(p), 24); // gindex = 24
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 8); // indexAtDepth = 8
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 24); // right gindex = 24
assertEq(r.indexAtDepth(), 8); // trace index = 8
p = LibPosition.wrap(4, 9);
assertEq(Position.unwrap(p), 25); // gindex = 25
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 9); // indexAtDepth = 9
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 25); // right gindex = 25
assertEq(r.indexAtDepth(), 9); // trace index = 9
p = LibPosition.wrap(4, 10);
assertEq(Position.unwrap(p), 26); // gindex = 26
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 10); // indexAtDepth = 10
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 26); // right gindex = 26
assertEq(r.indexAtDepth(), 10); // trace index = 10
p = LibPosition.wrap(4, 11);
assertEq(Position.unwrap(p), 27); // gindex = 27
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 11); // indexAtDepth = 11
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 27); // right gindex = 27
assertEq(r.indexAtDepth(), 11); // trace index = 11
p = LibPosition.wrap(4, 12);
assertEq(Position.unwrap(p), 28); // gindex = 28
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 12); // indexAtDepth = 12
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 28); // right gindex = 28
assertEq(r.indexAtDepth(), 12); // trace index = 12
p = LibPosition.wrap(4, 13);
assertEq(Position.unwrap(p), 29); // gindex = 29
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 13); // indexAtDepth = 13
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 29); // right gindex = 29
assertEq(r.indexAtDepth(), 13); // trace index = 13
p = LibPosition.wrap(4, 14);
assertEq(Position.unwrap(p), 30); // gindex = 30
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 14); // indexAtDepth = 14
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 30); // right gindex = 30
assertEq(r.indexAtDepth(), 14); // trace index = 14
p = LibPosition.wrap(4, 15);
assertEq(Position.unwrap(p), 31); // gindex = 31
assertEq(p.depth(), 4); // depth = 4
assertEq(p.indexAtDepth(), 15); // indexAtDepth = 15
r = p.rightIndex(maxDepth);
assertEq(Position.unwrap(r), 31); // right gindex = 31
assertEq(r.indexAtDepth(), 15); // trace index = 15
}
} }
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