Commit 64a6649a authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into hamdi/fault.detector

parents d26c0b38 e5fc8961
---
'@eth-optimism/contracts-periphery': patch
---
Add faucet contract
......@@ -1504,7 +1504,7 @@ workflows:
type: approval
filters:
tags:
only: /^op-[a-z0-9\-]*\/v.*/
only: /^(proxyd|op-[a-z0-9\-]*)\/v.*/
branches:
ignore: /.*/
- docker-release:
......@@ -1586,7 +1586,7 @@ workflows:
- oplabs-gcr-release
requires:
- hold
- docker-build:
- docker-release:
name: proxyd-docker-release
filters:
tags:
......
......@@ -13,13 +13,13 @@
/packages/core-utils @ethereum-optimism/legacy-reviewers
/packages/data-transport-layer @ethereum-optimism/legacy-reviewers
/packages/chain-mon @smartcontracts
/packages/fault-detector @ethereum-optimism/legacy-reviewers
/packages/fault-detector @ethereum-optimism/devxpod
/packages/hardhat-deploy-config @ethereum-optimism/legacy-reviewers
/packages/message-relayer @ethereum-optimism/legacy-reviewers
/packages/migration-data @ethereum-optimism/legacy-reviewers
/packages/replica-healthcheck @ethereum-optimism/legacy-reviewers
/packages/sdk @ethereum-optimism/ecopod
/packages/atst @ethereum-optimism/ecopod
/packages/sdk @ethereum-optimism/devxpod
/packages/atst @ethereum-optimism/devxpod
# Bedrock codebases
/bedrock-devnet @ethereum-optimism/go-reviewers
......@@ -42,7 +42,7 @@
# Misc
/proxyd @ethereum-optimism/infra-reviewers
/indexer @ethereum-optimism/infra-reviewers
/indexer @ethereum-optimism/devxpod
/infra @ethereum-optimism/infra-reviewers
/specs @ethereum-optimism/contract-reviewers @ethereum-optimism/go-reviewers
/endpoint-monitor @ethereum-optimism/infra-reviewers
......@@ -122,4 +122,4 @@ update-op-geth:
.PHONY: update-op-geth
bedrock-markdown-links:
docker run --init -it -v `pwd`:/input lycheeverse/lychee --verbose --no-progress --exclude-loopback --exclude twitter.com --exclude-mail /input/README.md "/input/specs/**/*.md"
docker run --init -it -v `pwd`:/input lycheeverse/lychee --verbose --no-progress --exclude-loopback --exclude twitter.com --exclude explorer.optimism.io --exclude-mail /input/README.md "/input/specs/**/*.md"
......@@ -62,7 +62,7 @@ require (
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
......
......@@ -124,8 +124,8 @@ github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQ
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE=
github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
......
......@@ -14,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-batcher/rpc"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
......@@ -33,6 +34,7 @@ func Main(version string, cliCtx *cli.Context) error {
}
l := oplog.NewLogger(cfg.LogConfig)
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)
m := metrics.NewMetrics("default")
l.Info("Initializing Batch Submitter")
......
......@@ -15,24 +15,24 @@ import (
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
const envVarPrefix = "OP_BATCHER"
const EnvVarPrefix = "OP_BATCHER"
var (
// Required flags
L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L1_ETH_RPC"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L1_ETH_RPC"),
}
L2EthRpcFlag = cli.StringFlag{
Name: "l2-eth-rpc",
Usage: "HTTP provider URL for L2 execution engine",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L2_ETH_RPC"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L2_ETH_RPC"),
}
RollupRpcFlag = cli.StringFlag{
Name: "rollup-rpc",
Usage: "HTTP provider URL for Rollup node",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_RPC"),
}
// Optional flags
SubSafetyMarginFlag = cli.Uint64Flag{
......@@ -41,54 +41,54 @@ var (
"from a channel's timeout and sequencing window, to guarantee safe inclusion " +
"of a channel on L1.",
Value: 10,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SUB_SAFETY_MARGIN"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "SUB_SAFETY_MARGIN"),
}
PollIntervalFlag = cli.DurationFlag{
Name: "poll-interval",
Usage: "How frequently to poll L2 for new blocks",
Value: 6 * time.Second,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "POLL_INTERVAL"),
}
MaxPendingTransactionsFlag = cli.Uint64Flag{
Name: "max-pending-tx",
Usage: "The maximum number of pending transactions. 0 for no limit.",
Value: 1,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MAX_PENDING_TX"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_PENDING_TX"),
}
MaxChannelDurationFlag = cli.Uint64Flag{
Name: "max-channel-duration",
Usage: "The maximum duration of L1-blocks to keep a channel open. 0 to disable.",
Value: 0,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MAX_CHANNEL_DURATION"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_CHANNEL_DURATION"),
}
MaxL1TxSizeBytesFlag = cli.Uint64Flag{
Name: "max-l1-tx-size-bytes",
Usage: "The maximum size of a batch tx submitted to L1.",
Value: 120_000,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MAX_L1_TX_SIZE_BYTES"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_L1_TX_SIZE_BYTES"),
}
TargetL1TxSizeBytesFlag = cli.Uint64Flag{
Name: "target-l1-tx-size-bytes",
Usage: "The target size of a batch tx submitted to L1.",
Value: 100_000,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "TARGET_L1_TX_SIZE_BYTES"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "TARGET_L1_TX_SIZE_BYTES"),
}
TargetNumFramesFlag = cli.IntFlag{
Name: "target-num-frames",
Usage: "The target number of frames to create per channel",
Value: 1,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "TARGET_NUM_FRAMES"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "TARGET_NUM_FRAMES"),
}
ApproxComprRatioFlag = cli.Float64Flag{
Name: "approx-compr-ratio",
Usage: "The approximate compression ratio (<= 1.0)",
Value: 0.4,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "APPROX_COMPR_RATIO"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "APPROX_COMPR_RATIO"),
}
StoppedFlag = cli.BoolFlag{
Name: "stopped",
Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "STOPPED"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "STOPPED"),
}
// Legacy Flags
SequencerHDPathFlag = txmgr.SequencerHDPathFlag
......@@ -114,12 +114,12 @@ var optionalFlags = []cli.Flag{
}
func init() {
optionalFlags = append(optionalFlags, oprpc.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, rpc.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oprpc.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, rpc.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...)
}
......
This diff is collapsed.
......@@ -13,7 +13,7 @@ const WETH9StorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"con
var WETH9StorageLayout = new(solc.StorageLayout)
var WETH9DeployedBin = "0x6080604052600436106100bc5760003560e01c8063313ce56711610074578063a9059cbb1161004e578063a9059cbb146102cb578063d0e30db0146100bc578063dd62ed3e14610311576100bc565b8063313ce5671461024b57806370a082311461027657806395d89b41146102b6576100bc565b806318160ddd116100a557806318160ddd146101aa57806323b872dd146101d15780632e1a7d4d14610221576100bc565b806306fdde03146100c6578063095ea7b314610150575b6100c4610359565b005b3480156100d257600080fd5b506100db6103a8565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101155781810151838201526020016100fd565b50505050905090810190601f1680156101425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015c57600080fd5b506101966004803603604081101561017357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610454565b604080519115158252519081900360200190f35b3480156101b657600080fd5b506101bf6104c7565b60408051918252519081900360200190f35b3480156101dd57600080fd5b50610196600480360360608110156101f457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356104cb565b34801561022d57600080fd5b506100c46004803603602081101561024457600080fd5b503561066b565b34801561025757600080fd5b50610260610700565b6040805160ff9092168252519081900360200190f35b34801561028257600080fd5b506101bf6004803603602081101561029957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610709565b3480156102c257600080fd5b506100db61071b565b3480156102d757600080fd5b50610196600480360360408110156102ee57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610793565b34801561031d57600080fd5b506101bf6004803603604081101561033457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160200135166107a7565b33600081815260036020908152604091829020805434908101909155825190815291517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9281900390910190a2565b6000805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b820191906000526020600020905b81548152906001019060200180831161042f57829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b4790565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120548211156104fd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff84163314801590610573575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14155b156105ed5773ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020548211156105b557600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020805483900390555b73ffffffffffffffffffffffffffffffffffffffff808516600081815260036020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060019392505050565b3360009081526003602052604090205481111561068757600080fd5b33600081815260036020526040808220805485900390555183156108fc0291849190818181858888f193505050501580156106c6573d6000803e3d6000fd5b5060408051828152905133917f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65919081900360200190a250565b60025460ff1681565b60036020526000908152604090205481565b60018054604080516020600284861615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b60006107a03384846104cb565b9392505050565b60046020908152600092835260408084209091529082529020548156fea265627a7a72315820fd69d075d01838a66174ca9cefc98bf8f255e049a94475b253ffc70bf383f90564736f6c63430005110032"
var WETH9DeployedBin = "0x6080604052600436106100bc5760003560e01c8063313ce56711610074578063a9059cbb1161004e578063a9059cbb146102cb578063d0e30db0146100bc578063dd62ed3e14610311576100bc565b8063313ce5671461024b57806370a082311461027657806395d89b41146102b6576100bc565b806318160ddd116100a557806318160ddd146101aa57806323b872dd146101d15780632e1a7d4d14610221576100bc565b806306fdde03146100c6578063095ea7b314610150575b6100c4610359565b005b3480156100d257600080fd5b506100db6103a8565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101155781810151838201526020016100fd565b50505050905090810190601f1680156101425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015c57600080fd5b506101966004803603604081101561017357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610454565b604080519115158252519081900360200190f35b3480156101b657600080fd5b506101bf6104c7565b60408051918252519081900360200190f35b3480156101dd57600080fd5b50610196600480360360608110156101f457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356104cb565b34801561022d57600080fd5b506100c46004803603602081101561024457600080fd5b503561066b565b34801561025757600080fd5b50610260610700565b6040805160ff9092168252519081900360200190f35b34801561028257600080fd5b506101bf6004803603602081101561029957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610709565b3480156102c257600080fd5b506100db61071b565b3480156102d757600080fd5b50610196600480360360408110156102ee57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610793565b34801561031d57600080fd5b506101bf6004803603604081101561033457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160200135166107a7565b33600081815260036020908152604091829020805434908101909155825190815291517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9281900390910190a2565b6000805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b820191906000526020600020905b81548152906001019060200180831161042f57829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b4790565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120548211156104fd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff84163314801590610573575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14155b156105ed5773ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020548211156105b557600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020805483900390555b73ffffffffffffffffffffffffffffffffffffffff808516600081815260036020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060019392505050565b3360009081526003602052604090205481111561068757600080fd5b33600081815260036020526040808220805485900390555183156108fc0291849190818181858888f193505050501580156106c6573d6000803e3d6000fd5b5060408051828152905133917f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65919081900360200190a250565b60025460ff1681565b60036020526000908152604090205481565b60018054604080516020600284861615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b60006107a03384846104cb565b9392505050565b60046020908152600092835260408084209091529082529020548156fea265627a7a72315820e496abb80c5983b030f680d0bd88f66bf44e261bc3be070d612dd72f9f1f5e9a64736f6c63430005110032"
func init() {
if err := json.Unmarshal([]byte(WETH9StorageLayoutJSON), WETH9StorageLayout); err != nil {
......
......@@ -19,6 +19,17 @@ var (
errLegacyStorageSlotNotFound = errors.New("cannot find storage slot")
)
// Constants used by `CrossDomainMessenger.baseGas`
var (
RelayConstantOverhead uint64 = 200_000
RelayPerByteDataCost uint64 = params.TxDataNonZeroGasEIP2028
MinGasDynamicOverheadNumerator uint64 = 64
MinGasDynamicOverheadDenominator uint64 = 63
RelayCallOverhead uint64 = 40_000
RelayReservedGas uint64 = 40_000
RelayGasCheckBuffer uint64 = 5_000
)
// MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB.
func MigrateWithdrawals(
withdrawals SafeFilteredWithdrawals,
......@@ -112,16 +123,38 @@ func MigrateWithdrawalGasLimit(data []byte, chainID *big.Int) uint64 {
// Compute the upper bound on the gas limit. This could be more
// accurate if individual 0 bytes and non zero bytes were accounted
// for.
dataCost := uint64(len(data)) * params.TxDataNonZeroGasEIP2028
dataCost := uint64(len(data)) * RelayPerByteDataCost
// Goerli has a lower gas limit than other chains.
overhead := uint64(200_000)
if chainID.Cmp(big.NewInt(420)) != 0 {
overhead = 1_000_000
var overhead uint64
if chainID.Cmp(big.NewInt(420)) == 0 {
overhead = uint64(200_000)
} else {
// Mimic `baseGas` from `CrossDomainMessenger.sol`
overhead = uint64(
// Constant overhead
RelayConstantOverhead +
// Dynamic overhead (EIP-150)
// We use a constant 1 million gas limit due to the overhead of simulating all migrated withdrawal
// transactions during the migration. This is a conservative estimate, and if a withdrawal
// uses more than the minimum gas limit, it will fail and need to be replayed with a higher
// gas limit.
(MinGasDynamicOverheadNumerator*1_000_000)/MinGasDynamicOverheadDenominator +
// Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas
// factors. (Conservative)
RelayCallOverhead +
// Relay reserved gas (to ensure execution of `relayMessage` completes after the
// subcontext finishes executing) (Conservative)
RelayReservedGas +
// Gas reserved for the execution between the `hasMinGas` check and the `CALL`
// opcode. (Conservative)
RelayGasCheckBuffer,
)
}
// Set the outer gas limit. This cannot be zero
// Set the outer minimum gas limit. This cannot be zero
gasLimit := dataCost + overhead
// Cap the gas limit to be 25 million to prevent creating withdrawals
// that go over the block gas limit.
if gasLimit > 25_000_000 {
......
......@@ -2,111 +2,26 @@ package challenger
import (
"context"
"fmt"
_ "net/http/pprof"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-node/sources"
opservice "github.com/ethereum-optimism/optimism/op-service"
opclient "github.com/ethereum-optimism/optimism/op-service/client"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
// Main is the entrypoint into the Challenger. This method executes the
// service and blocks until the service exits.
func Main(version string, cliCtx *cli.Context) error {
cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err)
}
l := oplog.NewLogger(cfg.LogConfig)
m := metrics.NewMetrics("default")
l.Info("Initializing Challenger")
challengerConfig, err := NewChallengerConfigFromCLIConfig(cfg, l, m)
if err != nil {
l.Error("Unable to create the Challenger", "error", err)
return err
}
challenger, err := NewChallenger(*challengerConfig, l, m)
if err != nil {
l.Error("Unable to create the Challenger", "error", err)
return err
}
l.Info("Starting Challenger")
ctx, cancel := context.WithCancel(context.Background())
if err := challenger.Start(); err != nil {
cancel()
l.Error("Unable to start Challenger", "error", err)
return err
}
defer challenger.Stop()
l.Info("Challenger started")
pprofConfig := cfg.PprofConfig
if pprofConfig.Enabled {
l.Info("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort)
go func() {
if err := oppprof.ListenAndServe(ctx, pprofConfig.ListenAddr, pprofConfig.ListenPort); err != nil {
l.Error("error starting pprof", "err", err)
}
}()
}
abi "github.com/ethereum/go-ethereum/accounts/abi"
bind "github.com/ethereum/go-ethereum/accounts/abi/bind"
common "github.com/ethereum/go-ethereum/common"
ethclient "github.com/ethereum/go-ethereum/ethclient"
log "github.com/ethereum/go-ethereum/log"
metricsCfg := cfg.MetricsConfig
if metricsCfg.Enabled {
l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
go func() {
if err := m.Serve(ctx, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil {
l.Error("error starting metrics server", err)
}
}()
m.StartBalanceMetrics(ctx, l, challengerConfig.L1Client, challengerConfig.TxManager.From())
}
config "github.com/ethereum-optimism/optimism/op-challenger/config"
metrics "github.com/ethereum-optimism/optimism/op-challenger/metrics"
rpcCfg := cfg.RPCConfig
server := oprpc.NewServer(rpcCfg.ListenAddr, rpcCfg.ListenPort, version, oprpc.WithLogger(l))
if err := server.Start(); err != nil {
cancel()
return fmt.Errorf("error starting RPC server: %w", err)
}
m.RecordInfo(version)
m.RecordUp()
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
cancel()
return nil
}
bindings "github.com/ethereum-optimism/optimism/op-bindings/bindings"
sources "github.com/ethereum-optimism/optimism/op-node/sources"
opclient "github.com/ethereum-optimism/optimism/op-service/client"
txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr"
)
// challenger contests invalid L2OutputOracle outputs
// Challenger contests invalid L2OutputOracle outputs
type Challenger struct {
txMgr txmgr.TxManager
wg sync.WaitGroup
......@@ -128,7 +43,6 @@ type Challenger struct {
l2ooABI *abi.ABI
// dispute game factory contract
// TODO(@refcell): add a binding for this contract
// dgfContract *bindings.DisputeGameFactoryCaller
dgfContractAddr common.Address
// dgfABI *abi.ABI
......@@ -136,59 +50,30 @@ type Challenger struct {
networkTimeout time.Duration
}
// NewChallengerFromCLIConfig creates a new challenger given the CLI Config
func NewChallengerFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metricer) (*Challenger, error) {
challengerConfig, err := NewChallengerConfigFromCLIConfig(cfg, l, m)
if err != nil {
return nil, err
}
return NewChallenger(*challengerConfig, l, m)
}
// NewChallengerConfigFromCLIConfig creates the challenger config from the CLI config.
func NewChallengerConfigFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metricer) (*Config, error) {
l2ooAddress, err := opservice.ParseAddress(cfg.L2OOAddress)
if err != nil {
return nil, err
}
dgfAddress, err := opservice.ParseAddress(cfg.DGFAddress)
if err != nil {
return nil, err
}
// NewChallenger creates a new Challenger
func NewChallenger(cfg config.Config, l log.Logger, m metrics.Metricer) (*Challenger, error) {
ctx, cancel := context.WithCancel(context.Background())
txManager, err := txmgr.NewSimpleTxManager("challenger", l, m, cfg.TxMgrConfig)
txManager, err := txmgr.NewSimpleTxManager("challenger", l, m, *cfg.TxMgrConfig)
if err != nil {
cancel()
return nil, err
}
// Connect to L1 and L2 providers. Perform these last since they are the most expensive.
ctx := context.Background()
l1Client, err := opclient.DialEthClientWithTimeout(ctx, cfg.L1EthRpc, opclient.DefaultDialTimeout)
if err != nil {
cancel()
return nil, err
}
rollupClient, err := opclient.DialRollupClientWithTimeout(ctx, cfg.RollupRpc, opclient.DefaultDialTimeout)
if err != nil {
cancel()
return nil, err
}
return &Config{
L2OutputOracleAddr: l2ooAddress,
DisputeGameFactory: dgfAddress,
NetworkTimeout: cfg.TxMgrConfig.NetworkTimeout,
L1Client: l1Client,
RollupClient: rollupClient,
TxManager: txManager,
}, nil
}
// NewChallenger creates a new Challenger
func NewChallenger(cfg Config, l log.Logger, m metrics.Metricer) (*Challenger, error) {
ctx, cancel := context.WithCancel(context.Background())
l2ooContract, err := bindings.NewL2OutputOracleCaller(cfg.L2OutputOracleAddr, cfg.L1Client)
l2ooContract, err := bindings.NewL2OutputOracleCaller(cfg.L2OOAddress, l1Client)
if err != nil {
cancel()
return nil, err
......@@ -201,7 +86,7 @@ func NewChallenger(cfg Config, l log.Logger, m metrics.Metricer) (*Challenger, e
cancel()
return nil, err
}
log.Info("Connected to L2OutputOracle", "address", cfg.L2OutputOracleAddr, "version", version)
l.Info("Connected to L2OutputOracle", "address", cfg.L2OOAddress, "version", version)
parsed, err := bindings.L2OutputOracleMetaData.GetAbi()
if err != nil {
......@@ -210,7 +95,7 @@ func NewChallenger(cfg Config, l log.Logger, m metrics.Metricer) (*Challenger, e
}
return &Challenger{
txMgr: cfg.TxManager,
txMgr: txManager,
done: make(chan struct{}),
log: l,
......@@ -219,15 +104,15 @@ func NewChallenger(cfg Config, l log.Logger, m metrics.Metricer) (*Challenger, e
ctx: ctx,
cancel: cancel,
rollupClient: cfg.RollupClient,
rollupClient: rollupClient,
l1Client: cfg.L1Client,
l1Client: l1Client,
l2ooContract: l2ooContract,
l2ooContractAddr: cfg.L2OutputOracleAddr,
l2ooContractAddr: cfg.L2OOAddress,
l2ooABI: parsed,
dgfContractAddr: cfg.DisputeGameFactory,
dgfContractAddr: cfg.DGFAddress,
networkTimeout: cfg.NetworkTimeout,
}, nil
......
package challenger
import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli"
flags "github.com/ethereum-optimism/optimism/op-challenger/flags"
sources "github.com/ethereum-optimism/optimism/op-node/sources"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr"
)
// Config contains the well typed fields that are used to initialize the challenger.
// It is intended for programmatic use.
type Config struct {
L2OutputOracleAddr common.Address
DisputeGameFactory common.Address
NetworkTimeout time.Duration
TxManager txmgr.TxManager
L1Client *ethclient.Client
RollupClient *sources.RollupClient
}
// CLIConfig is a well typed config that is parsed from the CLI params.
// This also contains config options for auxiliary services.
// It is transformed into a `Config` before the Challenger is started.
type CLIConfig struct {
// L1EthRpc is the HTTP provider URL for L1.
L1EthRpc string
// RollupRpc is the HTTP provider URL for the rollup node.
RollupRpc string
// L2OOAddress is the L2OutputOracle contract address.
L2OOAddress string
// DGFAddress is the DisputeGameFactory contract address.
DGFAddress string
TxMgrConfig txmgr.CLIConfig
RPCConfig oprpc.CLIConfig
LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig
}
func (c CLIConfig) Check() error {
if err := c.RPCConfig.Check(); err != nil {
return err
}
if err := c.LogConfig.Check(); err != nil {
return err
}
if err := c.MetricsConfig.Check(); err != nil {
return err
}
if err := c.PprofConfig.Check(); err != nil {
return err
}
if err := c.TxMgrConfig.Check(); err != nil {
return err
}
return nil
}
// NewConfig parses the Config from the provided flags or environment variables.
func NewConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{
// Required Flags
L1EthRpc: ctx.GlobalString(flags.L1EthRpcFlag.Name),
RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name),
L2OOAddress: ctx.GlobalString(flags.L2OOAddressFlag.Name),
DGFAddress: ctx.GlobalString(flags.DGFAddressFlag.Name),
TxMgrConfig: txmgr.ReadCLIConfig(ctx),
// Optional Flags
RPCConfig: oprpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx),
}
}
package challenger
import (
"context"
"fmt"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
)
// Main is the entrypoint into the Challenger. This method executes the
// service and blocks until the service exits.
func Main(logger log.Logger, version string, cfg *config.Config) error {
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config: %w", err)
}
m := metrics.NewMetrics("default")
logger.Info("Initializing Challenger")
challenger, err := NewChallenger(*cfg, logger, m)
if err != nil {
logger.Error("Unable to create the Challenger", "error", err)
return err
}
logger.Info("Starting Challenger")
ctx, cancel := context.WithCancel(context.Background())
if err := challenger.Start(); err != nil {
cancel()
logger.Error("Unable to start Challenger", "error", err)
return err
}
defer challenger.Stop()
logger.Info("Challenger started")
pprofConfig := cfg.PprofConfig
if pprofConfig.Enabled {
logger.Info("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort)
go func() {
if err := oppprof.ListenAndServe(ctx, pprofConfig.ListenAddr, pprofConfig.ListenPort); err != nil {
logger.Error("error starting pprof", "err", err)
}
}()
}
metricsCfg := cfg.MetricsConfig
if metricsCfg.Enabled {
log.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
go func() {
if err := m.Serve(ctx, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil {
logger.Error("error starting metrics server", err)
}
}()
m.StartBalanceMetrics(ctx, logger, challenger.l1Client, challenger.txMgr.From())
}
rpcCfg := cfg.RPCConfig
server := oprpc.NewServer(rpcCfg.ListenAddr, rpcCfg.ListenPort, version, oprpc.WithLogger(logger))
if err := server.Start(); err != nil {
cancel()
return fmt.Errorf("error starting RPC server: %w", err)
}
m.RecordInfo(version)
m.RecordUp()
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
cancel()
return nil
}
package main
import (
"fmt"
"os"
challenger "github.com/ethereum-optimism/optimism/op-challenger/challenger"
config "github.com/ethereum-optimism/optimism/op-challenger/config"
flags "github.com/ethereum-optimism/optimism/op-challenger/flags"
version "github.com/ethereum-optimism/optimism/op-challenger/version"
log "github.com/ethereum/go-ethereum/log"
cli "github.com/urfave/cli"
......@@ -12,30 +15,71 @@ import (
oplog "github.com/ethereum-optimism/optimism/op-service/log"
)
const Version = "0.1.0"
var (
GitCommit = ""
GitDate = ""
)
// VersionWithMeta holds the textual version string including the metadata.
var VersionWithMeta = func() string {
v := version.Version
if GitCommit != "" {
v += "-" + GitCommit[:8]
}
if GitDate != "" {
v += "-" + GitDate
}
if version.Meta != "" {
v += "-" + version.Meta
}
return v
}()
func main() {
args := os.Args
if err := run(args, challenger.Main); err != nil {
log.Crit("Application failed", "err", err)
}
}
type ConfigAction func(log log.Logger, version string, config *config.Config) error
// run parses the supplied args to create a config.Config instance, sets up logging
// then calls the supplied ConfigAction.
// This allows testing the translation from CLI arguments to Config
func run(args []string, action ConfigAction) error {
// Set up logger with a default INFO level in case we fail to parse flags,
// otherwise the final critical log won't show what the parsing error was.
oplog.SetupDefaults()
app := cli.NewApp()
app.Version = VersionWithMeta
app.Flags = flags.Flags
app.Version = Version
app.Name = "op-challenger"
app.Usage = "Challenge invalid L2OutputOracle outputs"
app.Usage = "Challenge Invalid L2OutputOracle Outputs"
app.Description = "A modular op-stack challenge agent for dispute games written in golang."
app.Action = curryMain(Version)
app.Commands = []cli.Command{}
app.Action = func(ctx *cli.Context) error {
logger, err := setupLogging(ctx)
if err != nil {
return err
}
logger.Info("Starting challenger", "version", VersionWithMeta)
err := app.Run(os.Args)
cfg, err := config.NewConfigFromCLI(ctx)
if err != nil {
log.Crit("Application failed", "message", err)
return err
}
return action(logger, VersionWithMeta, cfg)
}
return app.Run(args)
}
// curryMain transforms the challenger.Main function into an app.Action
// This is done to capture the Version of the challenger.
func curryMain(version string) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
return challenger.Main(version, ctx)
func setupLogging(ctx *cli.Context) (log.Logger, error) {
logCfg := oplog.ReadCLIConfig(ctx)
if err := logCfg.Check(); err != nil {
return nil, fmt.Errorf("log config error: %w", err)
}
logger := oplog.NewLogger(logCfg)
return logger, nil
}
package config
import (
"errors"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli"
flags "github.com/ethereum-optimism/optimism/op-challenger/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr"
)
var (
ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url")
ErrMissingRollupRpc = errors.New("missing rollup rpc url")
ErrMissingL2OOAddress = errors.New("missing l2 output oracle contract address")
ErrMissingDGFAddress = errors.New("missing dispute game factory contract address")
ErrInvalidNetworkTimeout = errors.New("invalid network timeout")
ErrMissingTxMgrConfig = errors.New("missing tx manager config")
ErrMissingRPCConfig = errors.New("missing rpc config")
ErrMissingLogConfig = errors.New("missing log config")
ErrMissingMetricsConfig = errors.New("missing metrics config")
ErrMissingPprofConfig = errors.New("missing pprof config")
)
// Config is a well typed config that is parsed from the CLI params.
// This also contains config options for auxiliary services.
// It is used to initialize the challenger.
type Config struct {
// L1EthRpc is the HTTP provider URL for L1.
L1EthRpc string
// RollupRpc is the HTTP provider URL for the rollup node.
RollupRpc string
// L2OOAddress is the L2OutputOracle contract address.
L2OOAddress common.Address
// DGFAddress is the DisputeGameFactory contract address.
DGFAddress common.Address
// NetworkTimeout is the timeout for network requests.
NetworkTimeout time.Duration
TxMgrConfig *txmgr.CLIConfig
RPCConfig *oprpc.CLIConfig
LogConfig *oplog.CLIConfig
MetricsConfig *opmetrics.CLIConfig
PprofConfig *oppprof.CLIConfig
}
func (c Config) Check() error {
if c.L1EthRpc == "" {
return ErrMissingL1EthRPC
}
if c.RollupRpc == "" {
return ErrMissingRollupRpc
}
if c.L2OOAddress == (common.Address{}) {
return ErrMissingL2OOAddress
}
if c.DGFAddress == (common.Address{}) {
return ErrMissingDGFAddress
}
if c.NetworkTimeout == 0 {
return ErrInvalidNetworkTimeout
}
if c.TxMgrConfig == nil {
return ErrMissingTxMgrConfig
}
if c.RPCConfig == nil {
return ErrMissingRPCConfig
}
if c.LogConfig == nil {
return ErrMissingLogConfig
}
if c.MetricsConfig == nil {
return ErrMissingMetricsConfig
}
if c.PprofConfig == nil {
return ErrMissingPprofConfig
}
if err := c.RPCConfig.Check(); err != nil {
return err
}
if err := c.LogConfig.Check(); err != nil {
return err
}
if err := c.MetricsConfig.Check(); err != nil {
return err
}
if err := c.PprofConfig.Check(); err != nil {
return err
}
if err := c.TxMgrConfig.Check(); err != nil {
return err
}
return nil
}
// NewConfig creates a Config with all optional values set to the CLI default value
func NewConfig(
L1EthRpc string,
RollupRpc string,
L2OOAddress common.Address,
DGFAddress common.Address,
NetworkTimeout time.Duration,
TxMgrConfig *txmgr.CLIConfig,
RPCConfig *oprpc.CLIConfig,
LogConfig *oplog.CLIConfig,
MetricsConfig *opmetrics.CLIConfig,
PprofConfig *oppprof.CLIConfig,
) *Config {
return &Config{
L1EthRpc: L1EthRpc,
RollupRpc: RollupRpc,
L2OOAddress: L2OOAddress,
DGFAddress: DGFAddress,
NetworkTimeout: NetworkTimeout,
TxMgrConfig: TxMgrConfig,
RPCConfig: RPCConfig,
LogConfig: LogConfig,
MetricsConfig: MetricsConfig,
PprofConfig: PprofConfig,
}
}
// NewConfigFromCLI parses the Config from the provided flags or environment variables.
func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
if err := flags.CheckRequired(ctx); err != nil {
return nil, err
}
l1EthRpc := ctx.GlobalString(flags.L1EthRpcFlag.Name)
if l1EthRpc == "" {
return nil, ErrMissingL1EthRPC
}
rollupRpc := ctx.GlobalString(flags.RollupRpcFlag.Name)
if rollupRpc == "" {
return nil, ErrMissingRollupRpc
}
l2ooAddress := common.HexToAddress(ctx.GlobalString(flags.L2OOAddressFlag.Name))
if l2ooAddress == (common.Address{}) {
return nil, ErrMissingL2OOAddress
}
dgfAddress := common.HexToAddress(ctx.GlobalString(flags.DGFAddressFlag.Name))
if dgfAddress == (common.Address{}) {
return nil, ErrMissingDGFAddress
}
txMgrConfig := txmgr.ReadCLIConfig(ctx)
rpcConfig := oprpc.ReadCLIConfig(ctx)
logConfig := oplog.ReadCLIConfig(ctx)
metricsConfig := opmetrics.ReadCLIConfig(ctx)
pprofConfig := oppprof.ReadCLIConfig(ctx)
return &Config{
// Required Flags
L1EthRpc: l1EthRpc,
RollupRpc: rollupRpc,
L2OOAddress: l2ooAddress,
DGFAddress: dgfAddress,
TxMgrConfig: &txMgrConfig,
// Optional Flags
RPCConfig: &rpcConfig,
LogConfig: &logConfig,
MetricsConfig: &metricsConfig,
PprofConfig: &pprofConfig,
}, nil
}
package config
import (
"testing"
"time"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr"
client "github.com/ethereum-optimism/optimism/op-signer/client"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
var (
validL1EthRpc = "http://localhost:8545"
validRollupRpc = "http://localhost:8546"
validL2OOAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validDGFAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validNetworkTimeout = time.Duration(5) * time.Second
)
var validTxMgrConfig = txmgr.CLIConfig{
L1RPCURL: validL1EthRpc,
NumConfirmations: 10,
NetworkTimeout: validNetworkTimeout,
ResubmissionTimeout: time.Duration(5) * time.Second,
ReceiptQueryInterval: time.Duration(5) * time.Second,
TxNotInMempoolTimeout: time.Duration(5) * time.Second,
SafeAbortNonceTooLowCount: 10,
SignerCLIConfig: client.CLIConfig{
Endpoint: "http://localhost:8547",
// First address for the default hardhat mnemonic
Address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
},
}
var validRPCConfig = oprpc.CLIConfig{
ListenAddr: "localhost:8547",
ListenPort: 8547,
}
var validLogConfig = oplog.DefaultCLIConfig()
var validMetricsConfig = opmetrics.CLIConfig{
Enabled: false,
}
var validPprofConfig = oppprof.CLIConfig{
Enabled: false,
}
func validConfig() *Config {
cfg := NewConfig(
validL1EthRpc,
validRollupRpc,
validL2OOAddress,
validDGFAddress,
validNetworkTimeout,
&validTxMgrConfig,
&validRPCConfig,
&validLogConfig,
&validMetricsConfig,
&validPprofConfig,
)
return cfg
}
// TestValidConfigIsValid checks that the config provided by validConfig is actually valid
func TestValidConfigIsValid(t *testing.T) {
err := validConfig().Check()
require.NoError(t, err)
}
func TestTxMgrConfig(t *testing.T) {
t.Run("Required", func(t *testing.T) {
config := validConfig()
config.TxMgrConfig = nil
err := config.Check()
require.ErrorIs(t, err, ErrMissingTxMgrConfig)
})
t.Run("Invalid", func(t *testing.T) {
config := validConfig()
config.TxMgrConfig = &txmgr.CLIConfig{}
err := config.Check()
require.Equal(t, err.Error(), "must provide a L1 RPC url")
})
}
func TestL1EthRpcRequired(t *testing.T) {
config := validConfig()
config.L1EthRpc = ""
err := config.Check()
require.ErrorIs(t, err, ErrMissingL1EthRPC)
}
func TestRollupRpcRequired(t *testing.T) {
config := validConfig()
config.RollupRpc = ""
err := config.Check()
require.ErrorIs(t, err, ErrMissingRollupRpc)
}
func TestL2OOAddressRequired(t *testing.T) {
config := validConfig()
config.L2OOAddress = common.Address{}
err := config.Check()
require.ErrorIs(t, err, ErrMissingL2OOAddress)
}
func TestDGFAddressRequired(t *testing.T) {
config := validConfig()
config.DGFAddress = common.Address{}
err := config.Check()
require.ErrorIs(t, err, ErrMissingDGFAddress)
}
func TestNetworkTimeoutRequired(t *testing.T) {
config := validConfig()
config.NetworkTimeout = 0
err := config.Check()
require.ErrorIs(t, err, ErrInvalidNetworkTimeout)
}
package flags
import (
"reflect"
"strings"
"testing"
"github.com/urfave/cli"
)
// TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags.
func TestUniqueFlags(t *testing.T) {
seenCLI := make(map[string]struct{})
for _, flag := range Flags {
name := flag.GetName()
if _, ok := seenCLI[name]; ok {
t.Errorf("duplicate flag %s", name)
continue
}
seenCLI[name] = struct{}{}
}
}
// TestUniqueEnvVars asserts that all flag env vars are unique, to avoid accidental conflicts between the many flags.
func TestUniqueEnvVars(t *testing.T) {
seenCLI := make(map[string]struct{})
for _, flag := range Flags {
envVar := envVarForFlag(flag)
if _, ok := seenCLI[envVar]; envVar != "" && ok {
t.Errorf("duplicate flag env var %s", envVar)
continue
}
seenCLI[envVar] = struct{}{}
}
}
func TestCorrectEnvVarPrefix(t *testing.T) {
for _, flag := range Flags {
envVar := envVarForFlag(flag)
if envVar == "" {
t.Errorf("Failed to find EnvVar for flag %v", flag.GetName())
}
if !strings.HasPrefix(envVar, "OP_CHALLENGER_") {
t.Errorf("Flag %v env var (%v) does not start with OP_CHALLENGER_", flag.GetName(), envVar)
}
if strings.Contains(envVar, "__") {
t.Errorf("Flag %v env var (%v) has duplicate underscores", flag.GetName(), envVar)
}
}
}
func envVarForFlag(flag cli.Flag) string {
values := reflect.ValueOf(flag)
envVarValue := values.FieldByName("EnvVar")
if envVarValue == (reflect.Value{}) {
return ""
}
return envVarValue.String()
}
package version
var (
Version = "v0.1.0"
Meta = "dev"
)
......@@ -10,6 +10,43 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup"
)
var Mainnet = rollup.Config{
Genesis: rollup.Genesis{
L1: eth.BlockID{
// moose: Update this during migration
Hash: common.HexToHash("0x"),
// moose: Update this during migration
Number: 0,
},
L2: eth.BlockID{
// moose: Update this during migration
Hash: common.HexToHash("0x"),
// moose: Update this during migration
Number: 0,
},
// moose: Update this during migration
L2Time: 0,
SystemConfig: eth.SystemConfig{
BatcherAddr: common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"),
Overhead: eth.Bytes32(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000834")),
Scalar: eth.Bytes32(common.HexToHash("0x00000000000000000000000000000000000000000000000000000000000f4240")),
GasLimit: 25_000_000,
},
},
BlockTime: 2,
MaxSequencerDrift: 600,
SeqWindowSize: 3600,
ChannelTimeout: 300,
L1ChainID: big.NewInt(1),
L2ChainID: big.NewInt(10),
BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"),
// moose: Update this during migration
DepositContractAddress: common.HexToAddress("0x"),
// moose: Update this during migration
L1SystemConfigAddress: common.HexToAddress("0x"),
RegolithTime: u64Ptr(0),
}
var Goerli = rollup.Config{
Genesis: rollup.Genesis{
L1: eth.BlockID{
......@@ -42,6 +79,8 @@ var Goerli = rollup.Config{
var NetworksByName = map[string]rollup.Config{
"goerli": Goerli,
// moose: Update this during migration
// "mainnet": Mainnet,
}
var L2ChainIDToNetworkName = func() map[string]string {
......
......@@ -23,6 +23,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/node"
"github.com/ethereum-optimism/optimism/op-node/version"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
)
......@@ -88,6 +89,7 @@ func RollupNodeMain(ctx *cli.Context) error {
return err
}
log := oplog.NewLogger(logCfg)
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, log)
m := metrics.NewMetrics("default")
cfg, err := opnode.NewConfig(ctx, log)
......
......@@ -14,10 +14,10 @@ import (
// Flags
const envVarPrefix = "OP_NODE"
const EnvVarPrefix = "OP_NODE"
func prefixEnvVar(name string) string {
return envVarPrefix + "_" + name
return EnvVarPrefix + "_" + name
}
var (
......@@ -251,7 +251,7 @@ var Flags []cli.Flag
func init() {
optionalFlags = append(optionalFlags, p2pFlags...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...)
}
......
......@@ -103,6 +103,8 @@ type Metrics struct {
SequencerInconsistentL1Origin *EventMetrics
SequencerResets *EventMetrics
L1RequestDurationSeconds *prometheus.HistogramVec
SequencerBuildingDiffDurationSeconds prometheus.Histogram
SequencerBuildingDiffTotal prometheus.Counter
......@@ -378,6 +380,14 @@ func NewMetrics(procName string) *Metrics {
Help: "number of unverified execution payloads buffered in quarantine",
}),
L1RequestDurationSeconds: factory.NewHistogramVec(prometheus.HistogramOpts{
Namespace: ns,
Name: "l1_request_seconds",
Buckets: []float64{
.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
Help: "Histogram of L1 request time",
}, []string{"request"}),
SequencerBuildingDiffDurationSeconds: factory.NewHistogram(prometheus.HistogramOpts{
Namespace: ns,
Name: "sequencer_building_diff_seconds",
......@@ -589,6 +599,11 @@ func (m *Metrics) RecordBandwidth(ctx context.Context, bwc *libp2pmetrics.Bandwi
}
}
// RecordL1RequestTime tracks the amount of time the derivation pipeline spent waiting for L1 data requests.
func (m *Metrics) RecordL1RequestTime(method string, duration time.Duration) {
m.L1RequestDurationSeconds.WithLabelValues(method).Observe(float64(duration) / float64(time.Second))
}
// RecordSequencerBuildingDiffTime tracks the amount of time the sequencer was allowed between
// start to finish, incl. sealing, minus the block time.
// Ideally this is 0, realistically the sequencer scheduler may be busy with other jobs like syncing sometimes.
......
......@@ -378,12 +378,21 @@ func (n *OpNode) RequestL2Range(ctx context.Context, start, end eth.L2BlockRef)
return n.rpcSync.RequestL2Range(ctx, start, end)
}
if n.p2pNode != nil && n.p2pNode.AltSyncEnabled() {
if unixTimeStale(start.Time, 12*time.Hour) {
n.log.Debug("ignoring request to sync L2 range, timestamp is too old for p2p", "start", start, "end", end, "start_time", start.Time)
return nil
}
return n.p2pNode.RequestL2Range(ctx, start, end)
}
n.log.Debug("ignoring request to sync L2 range, no sync method available", "start", start, "end", end)
return nil
}
// unixTimeStale returns true if the unix timestamp is before the current time minus the supplied duration.
func unixTimeStale(timestamp uint64, duration time.Duration) bool {
return time.Unix(int64(timestamp), 0).Before(time.Now().Add(-1 * duration))
}
func (n *OpNode) P2P() p2p.Node {
return n.p2pNode
}
......
package node
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestUnixTimeStale(t *testing.T) {
require.True(t, unixTimeStale(1_600_000_000, 1*time.Hour))
require.False(t, unixTimeStale(uint64(time.Now().Unix()), 1*time.Hour))
}
......@@ -30,6 +30,7 @@ type Metrics interface {
RecordL1ReorgDepth(d uint64)
EngineMetrics
L1FetcherMetrics
SequencerMetrics
}
......@@ -103,6 +104,7 @@ type AltSync interface {
// NewDriver composes an events handler that tracks L1 state, triggers L2 derivation, and optionally sequences new L2 blocks.
func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, altSync AltSync, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics) *Driver {
l1 = NewMeteredL1Fetcher(l1, metrics)
l1State := NewL1State(log, metrics)
sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1)
findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth)
......
package driver
import (
"context"
"time"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type L1FetcherMetrics interface {
RecordL1RequestTime(method string, duration time.Duration)
}
type MeteredL1Fetcher struct {
inner derive.L1Fetcher
metrics L1FetcherMetrics
now func() time.Time
}
func NewMeteredL1Fetcher(inner derive.L1Fetcher, metrics L1FetcherMetrics) *MeteredL1Fetcher {
return &MeteredL1Fetcher{
inner: inner,
metrics: metrics,
now: time.Now,
}
}
func (m *MeteredL1Fetcher) L1BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L1BlockRef, error) {
defer m.recordTime("L1BlockRefByLabel")()
return m.inner.L1BlockRefByLabel(ctx, label)
}
func (m *MeteredL1Fetcher) L1BlockRefByNumber(ctx context.Context, num uint64) (eth.L1BlockRef, error) {
defer m.recordTime("L1BlockRefByNumber")()
return m.inner.L1BlockRefByNumber(ctx, num)
}
func (m *MeteredL1Fetcher) L1BlockRefByHash(ctx context.Context, hash common.Hash) (eth.L1BlockRef, error) {
defer m.recordTime("L1BlockRefByHash")()
return m.inner.L1BlockRefByHash(ctx, hash)
}
func (m *MeteredL1Fetcher) InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) {
defer m.recordTime("InfoByHash")()
return m.inner.InfoByHash(ctx, hash)
}
func (m *MeteredL1Fetcher) InfoAndTxsByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, types.Transactions, error) {
defer m.recordTime("InfoAndTxsByHash")()
return m.inner.InfoAndTxsByHash(ctx, hash)
}
func (m *MeteredL1Fetcher) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) {
defer m.recordTime("FetchReceipts")()
return m.inner.FetchReceipts(ctx, blockHash)
}
var _ derive.L1Fetcher = (*MeteredL1Fetcher)(nil)
func (m *MeteredL1Fetcher) recordTime(method string) func() {
start := m.now()
return func() {
end := m.now()
m.metrics.RecordL1RequestTime(method, end.Sub(start))
}
}
package driver
import (
"context"
"errors"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestDurationRecorded(t *testing.T) {
num := uint64(1234)
hash := common.Hash{0xaa}
ref := eth.L1BlockRef{Number: num}
info := &testutils.MockBlockInfo{}
expectedErr := errors.New("test error")
tests := []struct {
method string
expect func(inner *testutils.MockL1Source)
call func(t *testing.T, fetcher *MeteredL1Fetcher, inner *testutils.MockL1Source)
}{
{
method: "L1BlockRefByLabel",
call: func(t *testing.T, fetcher *MeteredL1Fetcher, inner *testutils.MockL1Source) {
inner.ExpectL1BlockRefByLabel(eth.Finalized, ref, expectedErr)
result, err := fetcher.L1BlockRefByLabel(context.Background(), eth.Finalized)
require.Equal(t, ref, result)
require.Equal(t, expectedErr, err)
},
},
{
method: "L1BlockRefByNumber",
call: func(t *testing.T, fetcher *MeteredL1Fetcher, inner *testutils.MockL1Source) {
inner.ExpectL1BlockRefByNumber(num, ref, expectedErr)
result, err := fetcher.L1BlockRefByNumber(context.Background(), num)
require.Equal(t, ref, result)
require.Equal(t, expectedErr, err)
},
},
{
method: "L1BlockRefByHash",
call: func(t *testing.T, fetcher *MeteredL1Fetcher, inner *testutils.MockL1Source) {
inner.ExpectL1BlockRefByHash(hash, ref, expectedErr)
result, err := fetcher.L1BlockRefByHash(context.Background(), hash)
require.Equal(t, ref, result)
require.Equal(t, expectedErr, err)
},
},
{
method: "InfoByHash",
call: func(t *testing.T, fetcher *MeteredL1Fetcher, inner *testutils.MockL1Source) {
inner.ExpectInfoByHash(hash, info, expectedErr)
result, err := fetcher.InfoByHash(context.Background(), hash)
require.Equal(t, info, result)
require.Equal(t, expectedErr, err)
},
},
{
method: "InfoAndTxsByHash",
call: func(t *testing.T, fetcher *MeteredL1Fetcher, inner *testutils.MockL1Source) {
txs := types.Transactions{
&types.Transaction{},
}
inner.ExpectInfoAndTxsByHash(hash, info, txs, expectedErr)
actualInfo, actualTxs, err := fetcher.InfoAndTxsByHash(context.Background(), hash)
require.Equal(t, info, actualInfo)
require.Equal(t, txs, actualTxs)
require.Equal(t, expectedErr, err)
},
},
{
method: "FetchReceipts",
call: func(t *testing.T, fetcher *MeteredL1Fetcher, inner *testutils.MockL1Source) {
rcpts := types.Receipts{
&types.Receipt{},
}
inner.ExpectFetchReceipts(hash, info, rcpts, expectedErr)
actualInfo, actualRcpts, err := fetcher.FetchReceipts(context.Background(), hash)
require.Equal(t, info, actualInfo)
require.Equal(t, rcpts, actualRcpts)
require.Equal(t, expectedErr, err)
},
},
}
for _, test := range tests {
test := test
t.Run(test.method, func(t *testing.T) {
duration := 200 * time.Millisecond
fetcher, inner, metrics := createFetcher(duration)
defer inner.AssertExpectations(t)
defer metrics.AssertExpectations(t)
metrics.ExpectRecordRequestTime(test.method, duration)
test.call(t, fetcher, inner)
})
}
}
// createFetcher creates a MeteredL1Fetcher with a mock inner.
// The clock used to calculate the current time will advance by clockIncrement on each call, making it appear as if
// each request takes that amount of time to execute.
func createFetcher(clockIncrement time.Duration) (*MeteredL1Fetcher, *testutils.MockL1Source, *mockMetrics) {
inner := &testutils.MockL1Source{}
currTime := time.UnixMilli(1294812934000000)
clock := func() time.Time {
currTime = currTime.Add(clockIncrement)
return currTime
}
metrics := &mockMetrics{}
fetcher := MeteredL1Fetcher{
inner: inner,
metrics: metrics,
now: clock,
}
return &fetcher, inner, metrics
}
type mockMetrics struct {
mock.Mock
}
func (m *mockMetrics) RecordL1RequestTime(method string, duration time.Duration) {
m.MethodCalled("RecordL1RequestTime", method, duration)
}
func (m *mockMetrics) ExpectRecordRequestTime(method string, duration time.Duration) {
m.On("RecordL1RequestTime", method, duration).Once()
}
package l2
import (
"fmt"
"math/big"
"testing"
......@@ -54,7 +55,7 @@ func TestGet(t *testing.T) {
db := NewOracleBackedDB(oracle)
key := make([]byte, common.HashLength)
copy(rawdb.CodePrefix, key)
println(key[0])
fmt.Println(key[0])
expected := []byte{1, 2, 3}
oracle.Data[common.BytesToHash(key)] = expected
val, err := db.Get(key)
......
......@@ -13,69 +13,69 @@ import (
oplog "github.com/ethereum-optimism/optimism/op-service/log"
)
const envVarPrefix = "OP_PROGRAM"
const EnvVarPrefix = "OP_PROGRAM"
var (
RollupConfig = cli.StringFlag{
Name: "rollup.config",
Usage: "Rollup chain parameters",
EnvVar: service.PrefixEnvVar(envVarPrefix, "ROLLUP_CONFIG"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "ROLLUP_CONFIG"),
}
Network = cli.StringFlag{
Name: "network",
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVar: service.PrefixEnvVar(envVarPrefix, "NETWORK"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "NETWORK"),
}
DataDir = cli.StringFlag{
Name: "datadir",
Usage: "Directory to use for preimage data storage. Default uses in-memory storage",
EnvVar: service.PrefixEnvVar(envVarPrefix, "DATADIR"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "DATADIR"),
}
L2NodeAddr = cli.StringFlag{
Name: "l2",
Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_RPC"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_RPC"),
}
L1Head = cli.StringFlag{
Name: "l1.head",
Usage: "Hash of the L1 head block. Derivation stops after this block is processed.",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_HEAD"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_HEAD"),
}
L2Head = cli.StringFlag{
Name: "l2.head",
Usage: "Hash of the agreed L2 block to start derivation from",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_HEAD"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_HEAD"),
}
L2Claim = cli.StringFlag{
Name: "l2.claim",
Usage: "Claimed L2 output root to validate",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_CLAIM"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_CLAIM"),
}
L2BlockNumber = cli.Uint64Flag{
Name: "l2.blocknumber",
Usage: "Number of the L2 block that the claim is from",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_BLOCK_NUM"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_BLOCK_NUM"),
}
L2GenesisPath = cli.StringFlag{
Name: "l2.genesis",
Usage: "Path to the op-geth genesis file",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_GENESIS"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_GENESIS"),
}
L1NodeAddr = cli.StringFlag{
Name: "l1",
Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_RPC"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_RPC"),
}
L1TrustRPC = cli.BoolFlag{
Name: "l1.trustrpc",
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"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_TRUST_RPC"),
}
L1RPCProviderKind = cli.GenericFlag{
Name: "l1.rpckind",
Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " +
nodeflags.EnumString[sources.RPCProviderKind](sources.RPCProviderKinds),
EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_RPC_KIND"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_RPC_KIND"),
Value: func() *sources.RPCProviderKind {
out := sources.RPCKindBasic
return &out
......@@ -84,12 +84,12 @@ var (
Exec = cli.StringFlag{
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.",
EnvVar: service.PrefixEnvVar(envVarPrefix, "EXEC"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "EXEC"),
}
Server = cli.BoolFlag{
Name: "server",
Usage: "Run in pre-image server mode without executing any client program.",
EnvVar: service.PrefixEnvVar(envVarPrefix, "SERVER"),
EnvVar: service.PrefixEnvVar(EnvVarPrefix, "SERVER"),
}
)
......@@ -116,7 +116,7 @@ var programFlags = []cli.Flag{
}
func init() {
Flags = append(Flags, oplog.CLIFlags(envVarPrefix)...)
Flags = append(Flags, oplog.CLIFlags(EnvVarPrefix)...)
Flags = append(Flags, requiredFlags...)
Flags = append(Flags, programFlags...)
}
......
......@@ -40,7 +40,7 @@ func TestCorrectEnvVarPrefix(t *testing.T) {
if envVar == "" {
t.Errorf("Failed to find EnvVar for flag %v", flag.GetName())
}
if envVar[:len("OP_PROGRAM_")] != "OP_PROGRAM_" {
if !strings.HasPrefix(envVar, "OP_PROGRAM_") {
t.Errorf("Flag %v env var (%v) does not start with OP_PROGRAM_", flag.GetName(), envVar)
}
if strings.Contains(envVar, "__") {
......
......@@ -15,10 +15,12 @@ import (
cl "github.com/ethereum-optimism/optimism/op-program/client"
"github.com/ethereum-optimism/optimism/op-program/client/driver"
"github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum-optimism/optimism/op-program/host/flags"
"github.com/ethereum-optimism/optimism/op-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-program/host/prefetcher"
oppio "github.com/ethereum-optimism/optimism/op-program/io"
"github.com/ethereum-optimism/optimism/op-program/preimage"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
......@@ -32,6 +34,7 @@ func Main(logger log.Logger, cfg *config.Config) error {
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config: %w", err)
}
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, logger)
cfg.Rollup.LogDescription(logger, chaincfg.L2ChainIDToNetworkName)
ctx := context.Background()
......
......@@ -56,6 +56,7 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
if err != nil {
return fmt.Errorf("get l2 safe head: %w", err)
}
fmt.Printf("Found L2 finalized head number: %v hash: %v\n", l2FinalizedHead.NumberU64(), l2FinalizedHead.Hash())
// Find L1 finalized block. Can't be re-orged and must contain all batches for the L2 finalized block
l1BlockNum := big.NewInt(int64(rpc.FinalizedBlockNumber))
......@@ -63,18 +64,28 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
if err != nil {
return fmt.Errorf("find L1 head: %w", err)
}
fmt.Printf("Found l1 head block number: %v hash: %v\n", l1HeadBlock.NumberU64(), l1HeadBlock.Hash())
// Get the most published L2 output from before the finalized block
callOpts := &bind.CallOpts{Context: ctx}
outputIndex, err := outputOracle.GetL2OutputIndexAfter(callOpts, l2FinalizedHead.Number())
if err != nil {
return fmt.Errorf("get output index after finalized block: %w", err)
fmt.Println("Failed to get output index after finalized block. Checking latest output", "finalized", l2FinalizedHead.Number(), "err", err)
outputIndex, err = outputOracle.LatestOutputIndex(callOpts)
if err != nil {
return fmt.Errorf("get latest output index: %w", err)
}
} else {
outputIndex = outputIndex.Sub(outputIndex, big.NewInt(1))
}
output, err := outputOracle.GetL2Output(callOpts, outputIndex)
if err != nil {
return fmt.Errorf("retrieve latest output: %w", err)
}
// Check we wound up with an output prior to the finalized block
if output.L2BlockNumber.Uint64() > l2FinalizedHead.NumberU64() {
return fmt.Errorf("selected output is after finalized head output block: %v, finalized block: %v", output.L2BlockNumber.Uint64(), l2FinalizedHead.NumberU64())
}
l1Head := l1HeadBlock.Hash()
l2Claim := common.Hash(output.OutputRoot)
......@@ -98,7 +109,7 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
defer func() {
err := os.RemoveAll(temp)
if err != nil {
println("Failed to remove temp dir:" + err.Error())
fmt.Println("Failed to remove temp dir:" + err.Error())
}
}()
fmt.Printf("Using temp dir: %s\n", temp)
......@@ -127,7 +138,7 @@ func Run(l1RpcUrl string, l2RpcUrl string, l2OracleAddr common.Address) error {
}
func runFaultProofProgram(ctx context.Context, args []string) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Minute)
ctx, cancel := context.WithTimeout(ctx, 60*time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, "./bin/op-program", args...)
cmd.Stdout = os.Stdout
......
......@@ -14,24 +14,24 @@ import (
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
const envVarPrefix = "OP_PROPOSER"
const EnvVarPrefix = "OP_PROPOSER"
var (
// Required Flags
L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L1_ETH_RPC"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L1_ETH_RPC"),
}
RollupRpcFlag = cli.StringFlag{
Name: "rollup-rpc",
Usage: "HTTP provider URL for the rollup node",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_RPC"),
}
L2OOAddressFlag = cli.StringFlag{
Name: "l2oo-address",
Usage: "Address of the L2OutputOracle contract",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L2OO_ADDRESS"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L2OO_ADDRESS"),
}
// Optional flags
......@@ -39,12 +39,12 @@ var (
Name: "poll-interval",
Usage: "How frequently to poll L2 for new blocks",
Value: 6 * time.Second,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "POLL_INTERVAL"),
}
AllowNonFinalizedFlag = cli.BoolFlag{
Name: "allow-non-finalized",
Usage: "Allow the proposer to submit proposals for L2 blocks derived from non-finalized L1 blocks.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ALLOW_NON_FINALIZED"),
EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ALLOW_NON_FINALIZED"),
}
// Legacy Flags
L2OutputHDPathFlag = txmgr.L2OutputHDPathFlag
......@@ -63,11 +63,11 @@ var optionalFlags = []cli.Flag{
}
func init() {
optionalFlags = append(optionalFlags, oprpc.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(envVarPrefix)...)
optionalFlags = append(optionalFlags, oprpc.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...)
}
......
......@@ -22,6 +22,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-proposer/flags"
"github.com/ethereum-optimism/optimism/op-proposer/metrics"
opservice "github.com/ethereum-optimism/optimism/op-service"
opclient "github.com/ethereum-optimism/optimism/op-service/client"
......@@ -36,12 +37,16 @@ var supportedL2OutputVersion = eth.Bytes32{}
// Main is the entrypoint into the L2 Output Submitter. This method executes the
// service and blocks until the service exits.
func Main(version string, cliCtx *cli.Context) error {
if err := flags.CheckRequired(cliCtx); err != nil {
return err
}
cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err)
}
l := oplog.NewLogger(cfg.LogConfig)
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)
m := metrics.NewMetrics("default")
l.Info("Initializing L2 Output Submitter")
......
......@@ -6,16 +6,59 @@ import (
"fmt"
"os"
"os/signal"
"reflect"
"strings"
"syscall"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
)
func PrefixEnvVar(prefix, suffix string) string {
return prefix + "_" + suffix
}
// ValidateEnvVars logs all env vars that are found where the env var is
// prefixed with the supplied prefix (like OP_BATCHER) but there is no
// actual env var with that name.
// It helps validate that the supplied env vars are in fact valid.
func ValidateEnvVars(prefix string, flags []cli.Flag, log log.Logger) {
for _, envVar := range validateEnvVars(prefix, os.Environ(), cliFlagsToEnvVars(flags)) {
log.Warn("Unknown env var", "prefix", prefix, "env_var", envVar)
}
}
func cliFlagsToEnvVars(flags []cli.Flag) map[string]struct{} {
definedEnvVars := make(map[string]struct{})
for _, flag := range flags {
envVarField := reflect.ValueOf(flag).FieldByName("EnvVar")
if envVarField.IsValid() {
definedEnvVars[envVarField.String()] = struct{}{}
}
}
return definedEnvVars
}
// validateEnvVars returns a list of the unknown environment variables that match the prefix.
func validateEnvVars(prefix string, providedEnvVars []string, definedEnvVars map[string]struct{}) []string {
var out []string
for _, envVar := range providedEnvVars {
parts := strings.Split(envVar, "=")
if len(parts) == 0 {
continue
}
key := parts[0]
if strings.HasPrefix(key, prefix) {
if _, ok := definedEnvVars[key]; !ok {
out = append(out, envVar)
}
}
}
return out
}
// ParseAddress parses an ETH address from a hex string. This method will fail if
// the address is not a valid hexadecimal address.
func ParseAddress(address string) (common.Address, error) {
......
package op_service
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
)
func TestCLIFlagsToEnvVars(t *testing.T) {
flags := []cli.Flag{
cli.StringFlag{
Name: "test",
EnvVar: "OP_NODE_TEST_VAR",
},
cli.IntFlag{
Name: "no env var",
},
}
res := cliFlagsToEnvVars(flags)
require.Contains(t, res, "OP_NODE_TEST_VAR")
}
func TestValidateEnvVars(t *testing.T) {
provided := []string{"OP_BATCHER_CONFIG=true", "OP_BATCHER_FAKE=false", "LD_PRELOAD=/lib/fake.so"}
defined := map[string]struct{}{
"OP_BATCHER_CONFIG": {},
"OP_BATCHER_OTHER": {},
}
invalids := validateEnvVars("OP_BATCHER", provided, defined)
require.ElementsMatch(t, invalids, []string{"OP_BATCHER_FAKE=false"})
}
......@@ -6,7 +6,7 @@ DOCKER_REPO=$1
GIT_TAG=$2
GIT_SHA=$3
IMAGE_NAME=$(echo "$GIT_TAG" | grep -Eow '^op-[a-z0-9\-]*' || true)
IMAGE_NAME=$(echo "$GIT_TAG" | grep -Eow '^(proxyd|op-[a-z0-9\-]*)' || true)
if [ -z "$IMAGE_NAME" ]; then
echo "image name could not be parsed from git tag '$GIT_TAG'"
exit 1
......
......@@ -25,6 +25,7 @@
"@eth-optimism/contracts-bedrock/ds-test",
"@eth-optimism/contracts-bedrock/forge-std",
"@eth-optimism/contracts-bedrock/@rari-capital/solmate",
"@eth-optimism/contracts-bedrock/clones-with-immutable-args",
"**/@openzeppelin/*",
"@eth-optimism/contracts-periphery/ds-test",
"@eth-optimism/contracts-periphery/forge-std",
......
......@@ -27,6 +27,11 @@ CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597)
CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883)
DeployerWhitelist_Test:test_owner_succeeds() (gas: 7582)
DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395)
DisputeGameFactory_Test:test_owner_succeeds() (gas: 7582)
DisputeGameFactory_Test:test_setImplementation_notOwner_reverts() (gas: 11191)
DisputeGameFactory_Test:test_setImplementation_succeeds() (gas: 32635)
DisputeGameFactory_Test:test_transferOwnership_notOwner_reverts() (gas: 10979)
DisputeGameFactory_Test:test_transferOwnership_succeeds() (gas: 13180)
FeeVault_Test:test_constructor_succeeds() (gas: 10736)
FeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 10713)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135)
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { GameType } from "../libraries/DisputeTypes.sol";
import { GameStatus } from "../libraries/DisputeTypes.sol";
import { SafeCall } from "../libraries/SafeCall.sol";
import { IDisputeGame } from "./IDisputeGame.sol";
import { IDisputeGameFactory } from "./IDisputeGameFactory.sol";
/**
* @title BondManager
* @notice The Bond Manager serves as an escrow for permissionless output proposal bonds.
*/
contract BondManager {
// The Bond Type
struct Bond {
address owner;
uint256 expiration;
bytes32 id;
uint256 amount;
}
/**
* @notice Mapping from bondId to bond.
*/
mapping(bytes32 => Bond) public bonds;
/**
* @notice BondPosted is emitted when a bond is posted.
* @param bondId is the id of the bond.
* @param owner is the address that owns the bond.
* @param expiration is the time at which the bond expires.
* @param amount is the amount of the bond.
*/
event BondPosted(bytes32 bondId, address owner, uint256 expiration, uint256 amount);
/**
* @notice BondSeized is emitted when a bond is seized.
* @param bondId is the id of the bond.
* @param owner is the address that owns the bond.
* @param seizer is the address that seized the bond.
* @param amount is the amount of the bond.
*/
event BondSeized(bytes32 bondId, address owner, address seizer, uint256 amount);
/**
* @notice BondReclaimed is emitted when a bond is reclaimed by the owner.
* @param bondId is the id of the bond.
* @param claiment is the address that reclaimed the bond.
* @param amount is the amount of the bond.
*/
event BondReclaimed(bytes32 bondId, address claiment, uint256 amount);
/**
* @notice The permissioned dispute game factory.
* @dev Used to verify the status of bonds.
*/
IDisputeGameFactory public immutable DISPUTE_GAME_FACTORY;
/**
* @notice Instantiates the bond maanger with the registered dispute game factory.
* @param _disputeGameFactory is the dispute game factory.
*/
constructor(IDisputeGameFactory _disputeGameFactory) {
DISPUTE_GAME_FACTORY = _disputeGameFactory;
}
/**
* @notice Post a bond with a given id and owner.
* @dev This function will revert if the provided bondId is already in use.
* @param _bondId is the id of the bond.
* @param _bondOwner is the address that owns the bond.
* @param _minClaimHold is the minimum amount of time the owner
* must wait before reclaiming their bond.
*/
function post(
bytes32 _bondId,
address _bondOwner,
uint256 _minClaimHold
) external payable {
require(bonds[_bondId].owner == address(0), "BondManager: BondId already posted.");
require(_bondOwner != address(0), "BondManager: Owner cannot be the zero address.");
require(msg.value > 0, "BondManager: Value must be non-zero.");
uint256 expiration = _minClaimHold + block.timestamp;
bonds[_bondId] = Bond({
owner: _bondOwner,
expiration: expiration,
id: _bondId,
amount: msg.value
});
emit BondPosted(_bondId, _bondOwner, expiration, msg.value);
}
/**
* @notice Seizes the bond with 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.
*/
function seize(bytes32 _bondId) external {
Bond memory b = bonds[_bondId];
require(b.owner != address(0), "BondManager: The bond does not exist.");
require(b.expiration >= block.timestamp, "BondManager: Bond expired.");
IDisputeGame caller = IDisputeGame(msg.sender);
IDisputeGame game = DISPUTE_GAME_FACTORY.games(
GameType.ATTESTATION,
caller.rootClaim(),
caller.extraData()
);
require(msg.sender == address(game), "BondManager: Unauthorized seizure.");
require(game.status() == GameStatus.CHALLENGER_WINS, "BondManager: Game incomplete.");
delete bonds[_bondId];
emit BondSeized(_bondId, b.owner, msg.sender, b.amount);
bool success = SafeCall.send(payable(msg.sender), gasleft(), b.amount);
require(success, "BondManager: Failed to send Ether.");
}
/**
* @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.
* @param _bondId is the id of the bond.
* @param _claimRecipients is a set of addresses to split the bond amongst.
*/
function seizeAndSplit(bytes32 _bondId, address[] calldata _claimRecipients) external {
Bond memory b = bonds[_bondId];
require(b.owner != address(0), "BondManager: The bond does not exist.");
require(b.expiration >= block.timestamp, "BondManager: Bond expired.");
IDisputeGame caller = IDisputeGame(msg.sender);
IDisputeGame game = DISPUTE_GAME_FACTORY.games(
GameType.ATTESTATION,
caller.rootClaim(),
caller.extraData()
);
require(msg.sender == address(game), "BondManager: Unauthorized seizure.");
require(game.status() == GameStatus.CHALLENGER_WINS, "BondManager: Game incomplete.");
delete bonds[_bondId];
emit BondSeized(_bondId, b.owner, msg.sender, b.amount);
uint256 len = _claimRecipients.length;
uint256 proportionalAmount = b.amount / len;
for (uint256 i = 0; i < len; i++) {
bool success = SafeCall.send(
payable(_claimRecipients[i]),
gasleft() / len,
proportionalAmount
);
require(success, "BondManager: Failed to send Ether.");
}
}
/**
* @notice Reclaims the bond of the bond owner.
* @dev This function will revert if there is no bond at the given id.
* @param _bondId is the id of the bond.
*/
function reclaim(bytes32 _bondId) external {
Bond memory b = bonds[_bondId];
require(b.owner == msg.sender, "BondManager: Unauthorized claimant.");
require(b.expiration <= block.timestamp, "BondManager: Bond isn't claimable yet.");
delete bonds[_bondId];
emit BondReclaimed(_bondId, msg.sender, b.amount);
bool success = SafeCall.send(payable(msg.sender), gasleft(), b.amount);
require(success, "BondManager: Failed to send Ether.");
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol";
import { Claim } from "../libraries/DisputeTypes.sol";
import { Hash } from "../libraries/DisputeTypes.sol";
import { GameType } from "../libraries/DisputeTypes.sol";
import { NoImplementation } from "../libraries/DisputeErrors.sol";
import { GameAlreadyExists } from "../libraries/DisputeErrors.sol";
import { IDisputeGame } from "./IDisputeGame.sol";
import { IDisputeGameFactory } from "./IDisputeGameFactory.sol";
/**
* @title DisputeGameFactory
* @notice A factory contract for creating `IDisputeGame` contracts.
*/
contract DisputeGameFactory is Ownable, IDisputeGameFactory {
/**
* @dev Allows for the creation of clone proxies with immutable arguments.
*/
using ClonesWithImmutableArgs for address;
/**
* @notice Mapping of `GameType`s to their respective `IDisputeGame` implementations.
*/
mapping(GameType => IDisputeGame) public gameImpls;
/**
* @notice Mapping of a hash of `gameType . rootClaim . extraData` to
* the deployed `IDisputeGame` clone.
* @dev Note: `.` denotes concatenation.
*/
mapping(Hash => IDisputeGame) internal disputeGames;
/**
* @notice Constructs a new DisputeGameFactory contract.
* @param _owner The owner of the contract.
*/
constructor(address _owner) Ownable() {
transferOwnership(_owner);
}
/**
* @notice Retrieves the hash of `gameType . rootClaim . extraData`
* to the deployed `DisputeGame` clone.
* @dev Note: `.` denotes concatenation.
* @param gameType The type of the DisputeGame.
* Used to decide the implementation to clone.
* @param rootClaim The root claim of the DisputeGame.
* @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. `address(0)` if nonexistent.
*/
function games(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
) external view returns (IDisputeGame _proxy) {
return disputeGames[getGameUUID(gameType, rootClaim, extraData)];
}
/**
* @notice Creates a new DisputeGame proxy contract.
* @notice If a dispute game with the given parameters already exists,
* it will be returned.
* @param gameType The type of the DisputeGame.
* Used to decide the proxy implementation.
* @param rootClaim The root claim of the DisputeGame.
* @param extraData Any extra data that should be provided
* to the created dispute game.
* @return proxy The clone of the `DisputeGame`.
*/
function create(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
) external returns (IDisputeGame proxy) {
// Grab the implementation contract for the given `GameType`.
IDisputeGame impl = gameImpls[gameType];
// If there is no implementation to clone for the given `GameType`, revert.
if (address(impl) == address(0)) {
revert NoImplementation(gameType);
}
// Clone the implementation contract and initialize it with the given parameters.
bytes memory data = abi.encodePacked(rootClaim, extraData);
proxy = IDisputeGame(address(impl).clone(data));
proxy.initialize();
// Compute the unique identifier for the dispute game.
Hash uuid = getGameUUID(gameType, rootClaim, extraData);
// If a dispute game with the same UUID already exists, revert.
if (address(disputeGames[uuid]) != address(0)) {
revert GameAlreadyExists(uuid);
}
// Store the dispute game in the mapping & emit the `DisputeGameCreated` event.
disputeGames[uuid] = proxy;
emit DisputeGameCreated(address(proxy), gameType, rootClaim);
}
/**
* @notice Sets the implementation contract for a specific `GameType`.
* @param gameType The type of the DisputeGame.
* @param impl The implementation contract for the given `GameType`.
*/
function setImplementation(GameType gameType, IDisputeGame impl) external onlyOwner {
gameImpls[gameType] = impl;
}
/**
* @notice Returns a unique identifier for the given dispute game parameters.
* @dev Hashes the concatenation of `gameType . rootClaim . extraData`
* without expanding memory.
* @param gameType The type 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.
* @return _uuid The unique identifier for the given dispute game parameters.
*/
function getGameUUID(
GameType gameType,
Claim rootClaim,
bytes memory extraData
) public pure returns (Hash _uuid) {
assembly {
// Grab the offsets of the other memory locations we will need to temporarily overwrite.
let gameTypeOffset := sub(extraData, 0x60)
let rootClaimOffset := add(gameTypeOffset, 0x20)
let pointerOffset := add(rootClaimOffset, 0x20)
// Copy the memory that we will temporarily overwrite onto the stack
// so we can restore it later
let tempA := mload(gameTypeOffset)
let tempB := mload(rootClaimOffset)
let tempC := mload(pointerOffset)
// Overwrite the memory with the data we want to hash
mstore(gameTypeOffset, gameType)
mstore(rootClaimOffset, rootClaim)
mstore(pointerOffset, 0x60)
// Compute the length of the memory to hash
// `0x60 + 0x20 + extraData.length` rounded to the *next* multiple of 32.
let hashLen := and(add(mload(extraData), 0x9F), not(0x1F))
// Hash the memory to produce the UUID digest
_uuid := keccak256(gameTypeOffset, hashLen)
// Restore the memory prior to `extraData`
mstore(gameTypeOffset, tempA)
mstore(rootClaimOffset, tempB)
mstore(pointerOffset, tempC)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { IDisputeGame } from "./IDisputeGame.sol";
/**
* @title IAttestationDisputeGame
* @notice The interface for an attestation-based DisputeGame meant to contest output
* proposals in Optimism's `L2OutputOracle` contract.
*/
interface IAttestationDisputeGame is IDisputeGame {
/**
* @notice A mapping of addresses from the `signerSet` to booleans signifying whether
* or not they have authorized the `rootClaim` to be invalidated.
* @param challenger The address to check for authorization.
* @return _challenged Whether or not the `challenger` has challenged the `rootClaim`.
*/
function challenges(address challenger) external view returns (bool _challenged);
/**
* @notice The signer set consists of authorized public keys that may challenge
* the `rootClaim`.
* @param addr The address to check for authorization.
* @return _isAuthorized Whether or not the `addr` is part of the signer set.
*/
function signerSet(address addr) external view returns (bool _isAuthorized);
/**
* @notice The amount of signatures required to successfully challenge the `rootClaim`
* output proposal. Once this threshold is met by members of the `signerSet`
* calling `challenge`, the game will be resolved to `CHALLENGER_WINS`.
* @custom:invariant The `signatureThreshold` may never be greater than the length
* of the `signerSet`.
* @return _signatureThreshold The amount of signatures required to successfully
* challenge the `rootClaim` output proposal.
*/
function frozenSignatureThreshold() external view returns (uint256 _signatureThreshold);
/**
* @notice Returns the L2 Block Number that the `rootClaim` commits to.
* Exists within the `extraData`.
* @return _l2BlockNumber The L2 Block Number that the `rootClaim` commits to.
*/
function l2BlockNumber() external view returns (uint256 _l2BlockNumber);
/**
* @notice Challenge the `rootClaim`.
* @dev - If the `ecrecover`ed address that created the signature is not a part of
* the signer set returned by `signerSet`, this function should revert.
* - If the `ecrecover`ed address that created the signature is not the
* msg.sender, this function should revert.
* - If the signature provided is the signature that breaches the signature
* threshold, the function should call the `resolve` function to resolve
* the game as `CHALLENGER_WINS`.
* - When the game resolves, the bond attached to the root claim should be
* distributed among the signers who participated in challenging the
* invalid claim.
* @param signature An EIP-712 signature committing to the `rootClaim` and
* `l2BlockNumber` (within the `extraData`) from a key that exists
* within the `signerSet`.
*/
function challenge(bytes calldata signature) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @title IBondManager
* @notice The Bond Manager holds ether posted as a bond for a bond id.
*/
interface IBondManager {
/**
* @notice Post a bond with a given id and owner.
* @dev This function will revert if the provided bondId is already in use.
* @param _bondId is the id of the bond.
* @param _bondOwner is the address that owns the bond.
* @param _minClaimHold is the minimum amount of time the owner
* must wait before reclaiming their bond.
*/
function post(
bytes32 _bondId,
address _bondOwner,
uint256 _minClaimHold
) external payable;
/**
* @notice Seizes the bond with 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.
*/
function seize(bytes32 _bondId) external;
/**
* @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.
* @param _bondId is the id of the bond.
* @param _claimRecipients is a set of addresses to split the bond amongst.
*/
function seizeAndSplit(bytes32 _bondId, address[] calldata _claimRecipients) external;
/**
* @notice Reclaims the bond of the bond owner.
* @dev This function will revert if there is no bond at the given id.
* @param _bondId is the id of the bond.
*/
function reclaim(bytes32 _bondId) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Claim } from "../libraries/DisputeTypes.sol";
import { GameType } from "../libraries/DisputeTypes.sol";
import { GameStatus } from "../libraries/DisputeTypes.sol";
import { Timestamp } from "../libraries/DisputeTypes.sol";
import { IVersioned } from "./IVersioned.sol";
import { IBondManager } from "./IBondManager.sol";
import { IInitializable } from "./IInitializable.sol";
/**
* @title IDisputeGame
* @notice The generic interface for a DisputeGame contract.
*/
interface IDisputeGame is IInitializable, IVersioned {
/**
* @notice Emitted when the game is resolved.
* @param status The status of the game after resolution.
*/
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.
*/
function createdAt() external view returns (Timestamp _createdAt);
/**
* @notice Returns the current status of the game.
* @return _status The current status of the game.
*/
function status() external view returns (GameStatus _status);
/**
* @notice Getter for the game type.
* @dev `clones-with-immutable-args` argument #1
* @dev The reference impl should be entirely different depending on the type (fault, validity)
* i.e. The game type should indicate the security model.
* @return _gameType The type of proof system being used.
*/
function gameType() external view returns (GameType _gameType);
/**
* @notice Getter for the root claim.
* @dev `clones-with-immutable-args` argument #2
* @return _rootClaim The root claim of the DisputeGame.
*/
function rootClaim() external view returns (Claim _rootClaim);
/**
* @notice Getter for the extra data.
* @dev `clones-with-immutable-args` argument #3
* @return _extraData Any extra data supplied to the dispute game contract by the creator.
*/
function extraData() external view returns (bytes memory _extraData);
/**
* @notice Returns the address of the `BondManager` used.
* @return _bondManager The address of the `BondManager` used.
*/
function bondManager() external view returns (IBondManager _bondManager);
/**
* @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
* the resolved game. It is at this stage that the bonds should be awarded to the
* necessary parties.
* @dev May only be called if the `status` is `IN_PROGRESS`.
* @return _status The status of the game after resolution.
*/
function resolve() external returns (GameStatus _status);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Claim } from "../libraries/DisputeTypes.sol";
import { GameType } from "../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol";
/**
* @title IDisputeGameFactory
* @notice The interface for a DisputeGameFactory contract.
*/
interface IDisputeGameFactory {
/**
* @notice Emitted when a new dispute game is created
* @param disputeProxy The address of the dispute game proxy
* @param gameType The type of the dispute game proxy's implementation
* @param rootClaim The root claim of the dispute game
*/
event DisputeGameCreated(
address indexed disputeProxy,
GameType indexed gameType,
Claim indexed rootClaim
);
/**
* @notice `games` queries an internal a mapping that maps the hash of
* `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone.
* @dev `++` equates to concatenation.
* @param gameType The type of the DisputeGame - used to decide the proxy implementation
* @param rootClaim The root claim of the DisputeGame.
* @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.
* Returns `address(0)` if nonexistent.
*/
function games(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
) external view returns (IDisputeGame _proxy);
/**
* @notice `gameImpls` is a mapping that maps `GameType`s to their respective
* `IDisputeGame` implementations.
* @param gameType The type of the dispute game.
* @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`.
*/
function gameImpls(GameType gameType) external view returns (IDisputeGame _impl);
/**
* @notice Creates a new DisputeGame proxy contract.
* @param gameType The type of the DisputeGame - used to decide the proxy implementation
* @param rootClaim The root claim of the DisputeGame.
* @param extraData Any extra data that should be provided to the created dispute game.
* @return proxy The address of the created DisputeGame proxy.
*/
function create(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
) external returns (IDisputeGame proxy);
/**
* @notice Sets the implementation contract for a specific `GameType`.
* @dev May only be called by the `owner`.
* @param gameType The type of the DisputeGame.
* @param impl The implementation contract for the given `GameType`.
*/
function setImplementation(GameType gameType, IDisputeGame impl) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Clock } from "../libraries/DisputeTypes.sol";
import { Claim } from "../libraries/DisputeTypes.sol";
import { Position } from "../libraries/DisputeTypes.sol";
import { Timestamp } from "../libraries/DisputeTypes.sol";
import { ClaimHash } from "../libraries/DisputeTypes.sol";
import { BondAmount } from "../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol";
/**
* @title IFaultDisputeGame
* @notice The interface for a fault proof backed dispute game.
*/
interface IFaultDisputeGame is IDisputeGame {
/**
* @notice Emitted when a subclaim is disagreed upon by `claimant`
* @dev Disagreeing with a subclaim is akin to attacking it.
* @param claimHash The unique ClaimHash that is being disagreed upon
* @param pivot The claim for the following pivot (disagreement = go left)
* @param claimant The address of the claimant
*/
event Attack(ClaimHash indexed claimHash, Claim indexed pivot, address indexed claimant);
/**
* @notice Emitted when a subclaim is agreed upon by `claimant`
* @dev Agreeing with a subclaim is akin to defending it.
* @param claimHash The unique ClaimHash that is being agreed upon
* @param pivot The claim for the following pivot (agreement = go right)
* @param claimant The address of the claimant
*/
event Defend(ClaimHash indexed claimHash, Claim indexed pivot, address indexed claimant);
/**
* @notice State variable of the starting timestamp of the game, set on deployment.
* @return The starting timestamp of the game
*/
function gameStart() external view returns (Timestamp);
/**
* @notice Maps a unique ClaimHash to a Claim.
* @param claimHash The unique ClaimHash
* @return claim The Claim associated with the ClaimHash
*/
function claims(ClaimHash claimHash) external view returns (Claim claim);
/**
* @notice Maps a unique ClaimHash to its parent.
* @param claimHash The unique ClaimHash
* @return parent The parent ClaimHash of the passed ClaimHash
*/
function parents(ClaimHash claimHash) external view returns (ClaimHash parent);
/**
* @notice Maps a unique ClaimHash to its Position.
* @param claimHash The unique ClaimHash
* @return position The Position associated with the ClaimHash
*/
function positions(ClaimHash claimHash) external view returns (Position position);
/**
* @notice Maps a unique ClaimHash to a Bond.
* @param claimHash The unique ClaimHash
* @return bond The Bond associated with the ClaimHash
*/
function bonds(ClaimHash claimHash) external view returns (BondAmount bond);
/**
* @notice Maps a unique ClaimHash its chess clock.
* @param claimHash The unique ClaimHash
* @return clock The chess clock associated with the ClaimHash
*/
function clocks(ClaimHash claimHash) external view returns (Clock clock);
/**
* @notice Maps a unique ClaimHash to its reference counter.
* @param claimHash The unique ClaimHash
* @return _rc The reference counter associated with the ClaimHash
*/
function rc(ClaimHash claimHash) external view returns (uint64 _rc);
/**
* @notice Maps a unique ClaimHash to a boolean indicating whether or not it has been countered.
* @param claimHash The unique claimHash
* @return _countered Whether or not `claimHash` has been countered
*/
function countered(ClaimHash claimHash) external view returns (bool _countered);
/**
* @notice Disagree with a subclaim
* @param disagreement The ClaimHash of the disagreement
* @param pivot The claimed pivot
*/
function attack(ClaimHash disagreement, Claim pivot) external;
/**
* @notice Agree with a subclaim
* @param agreement The ClaimHash of the agreement
* @param pivot The claimed pivot
*/
function defend(ClaimHash agreement, Claim pivot) external;
/**
* @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
* 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
* fault proof VMs (MIPS, RiscV5, etc.)
* @param disagreement The ClaimHash of the disagreement
*/
function step(ClaimHash disagreement) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @title IInitializable
* @notice An interface for initializable contracts.
*/
interface IInitializable {
/**
* @notice Initializes the contract.
* @dev This function may only be called once.
*/
function initialize() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @title IVersioned
* @notice An interface for semantically versioned contracts.
*/
interface IVersioned {
/**
* @notice Returns the semantic version of the contract
* @return _version The semantic version of the contract
*/
function version() external pure returns (string memory _version);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "./DisputeTypes.sol";
////////////////////////////////////////////////////////////////
// `DisputeGameFactory` Errors //
////////////////////////////////////////////////////////////////
/**
* @notice Thrown when a dispute game is attempted to be created with an unsupported game type.
* @param gameType The unsupported game type.
*/
error NoImplementation(GameType gameType);
/**
* @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.
*/
error GameAlreadyExists(Hash uuid);
////////////////////////////////////////////////////////////////
// `DisputeGame_Fault.sol` Errors //
////////////////////////////////////////////////////////////////
/**
* @notice Thrown when a supplied bond is too low to cover the
* cost of the next possible counter claim.
*/
error BondTooLow();
/**
* @notice Thrown when a defense against the root claim is attempted.
*/
error CannotDefendRootClaim();
/**
* @notice Thrown when a claim is attempting to be made that already exists.
*/
error ClaimAlreadyExists();
////////////////////////////////////////////////////////////////
// `AttestationDisputeGame` Errors //
////////////////////////////////////////////////////////////////
/**
* @notice Thrown when an invalid signature is submitted to `challenge`.
*/
error InvalidSignature();
/**
* @notice Thrown when a signature that has already been used to support the
* `rootClaim` is submitted to `challenge`.
*/
error AlreadyChallenged();
////////////////////////////////////////////////////////////////
// `Ownable` Errors //
////////////////////////////////////////////////////////////////
/**
* @notice Thrown when a function that is protected by the `onlyOwner` modifier
* is called from an account other than the owner.
*/
error NotOwner();
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @notice A custom type for a generic hash.
*/
type Hash is bytes32;
/**
* @notice A claim represents an MPT root representing the state of the fault proof program.
*/
type Claim is bytes32;
/**
* @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);
*/
type ClaimHash is bytes32;
/**
* @notice A bondamount represents the amount of collateral that a user has locked up in a claim.
*/
type BondAmount is uint256;
/**
* @notice A dedicated timestamp type.
*/
type Timestamp is uint64;
/**
* @notice A dedicated duration type.
* @dev Unit: seconds
*/
type Duration is uint64;
/**
* @notice A `Clock` represents a packed `Duration` and `Timestamp`
* @dev The packed layout of this type is as follows:
* ┌────────────┬────────────────┐
* │ Bits │ Value │
* ├────────────┼────────────────┤
* │ [0, 128) │ Duration │
* │ [128, 256) │ Timestamp │
* └────────────┴────────────────┘
*/
type Clock is uint256;
/**
* @notice A `Position` represents a position of a claim within the game tree.
* @dev The packed layout of this type is as follows:
* ┌────────────┬────────────────┐
* │ Bits │ Value │
* ├────────────┼────────────────┤
* │ [0, 128) │ Depth │
* │ [128, 256) │ Index at depth │
* └────────────┴────────────────┘
*/
type Position is uint256;
/**
* @notice The current status of the dispute game.
*/
enum GameStatus {
// The game is currently in progress, and has not been resolved.
IN_PROGRESS,
// The game has concluded, and the `rootClaim` was challenged successfully.
CHALLENGER_WINS,
// The game has concluded, and the `rootClaim` could not be contested.
DEFENDER_WINS
}
/**
* @notice The type of proof system being used.
*/
enum GameType {
// The game will use a `IDisputeGame` implementation that utilizes fault proofs.
FAULT,
// The game will use a `IDisputeGame` implementation that utilizes validity proofs.
VALIDITY,
// The game will use a `IDisputeGame` implementation that utilizes attestation proofs.
ATTESTATION
}
......@@ -6,6 +6,34 @@ pragma solidity 0.8.15;
* @notice Perform low level safe calls
*/
library SafeCall {
/**
* @notice Performs a low level call without copying any returndata.
* @dev Passes no calldata to the call context.
*
* @param _target Address to call
* @param _gas Amount of gas to pass to the call
* @param _value Amount of value to pass to the call
*/
function send(
address _target,
uint256 _gas,
uint256 _value
) internal returns (bool) {
bool _success;
assembly {
_success := call(
_gas, // gas
_target, // recipient
_value, // ether value
0, // inloc
0, // inlen
0, // outloc
0 // outlen
)
}
return _success;
}
/**
* @notice Perform a low level call without copying any returndata
*
......
This diff is collapsed.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol";
import { Test } from "forge-std/Test.sol";
import { DisputeGameFactory } from "../dispute/DisputeGameFactory.sol";
import { IDisputeGame } from "../dispute/IDisputeGame.sol";
contract DisputeGameFactory_Test is Test {
DisputeGameFactory factory;
FakeClone fakeClone;
event DisputeGameCreated(
address indexed disputeProxy,
GameType indexed gameType,
Claim indexed rootClaim
);
function setUp() public {
factory = new DisputeGameFactory(address(this));
fakeClone = new FakeClone();
}
/**
* @dev Tests that the `create` function succeeds when creating a new dispute game
* with a `GameType` that has an implementation set.
*/
function testFuzz_create_succeeds(
uint8 gameType,
Claim rootClaim,
bytes calldata extraData
) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType(uint8(bound(gameType, 0, 2)));
// Set all three implementations to the same `FakeClone` contract.
for (uint8 i; i < 3; i++) {
factory.setImplementation(GameType(i), IDisputeGame(address(fakeClone)));
}
vm.expectEmit(false, true, true, false);
emit DisputeGameCreated(address(0), gt, rootClaim);
IDisputeGame proxy = factory.create(gt, rootClaim, extraData);
// Ensure that the dispute game was assigned to the `disputeGames` mapping.
assertEq(address(factory.games(gt, rootClaim, extraData)), address(proxy));
}
/**
* @dev Tests that the `create` function reverts when there is no implementation
* set for the given `GameType`.
*/
function testFuzz_create_noImpl_reverts(
uint8 gameType,
Claim rootClaim,
bytes calldata extraData
) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType(uint8(bound(gameType, 0, 2)));
vm.expectRevert(abi.encodeWithSelector(NoImplementation.selector, gt));
factory.create(gt, rootClaim, extraData);
}
/**
* @dev Tests that the `create` function reverts when there exists a dispute game with the same UUID.
*/
function testFuzz_create_sameUUID_reverts(
uint8 gameType,
Claim rootClaim,
bytes calldata extraData
) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType(uint8(bound(gameType, 0, 2)));
// Set all three implementations to the same `FakeClone` contract.
for (uint8 i; i < 3; i++) {
factory.setImplementation(GameType(i), IDisputeGame(address(fakeClone)));
}
// Create our first dispute game - this should succeed.
vm.expectEmit(false, true, true, false);
emit DisputeGameCreated(address(0), gt, rootClaim);
IDisputeGame proxy = factory.create(gt, rootClaim, extraData);
// Ensure that the dispute game was assigned to the `disputeGames` mapping.
assertEq(address(factory.games(gt, rootClaim, extraData)), address(proxy));
// Ensure that the `create` function reverts when called with parameters that would result in the same UUID.
vm.expectRevert(
abi.encodeWithSelector(
GameAlreadyExists.selector,
factory.getGameUUID(gt, rootClaim, extraData)
)
);
factory.create(gt, rootClaim, extraData);
}
/**
* @dev Tests that the `setImplementation` function properly sets the implementation for a given `GameType`.
*/
function test_setImplementation_succeeds() public {
// There should be no implementation for the `GameType.FAULT` enum value, it has not been set.
assertEq(address(factory.gameImpls(GameType.FAULT)), address(0));
// Set the implementation for the `GameType.FAULT` enum value.
factory.setImplementation(GameType.FAULT, IDisputeGame(address(1)));
// Ensure that the implementation for the `GameType.FAULT` enum value is set.
assertEq(address(factory.gameImpls(GameType.FAULT)), address(1));
}
/**
* @dev Tests that the `setImplementation` function reverts when called by a non-owner.
*/
function test_setImplementation_notOwner_reverts() public {
// Ensure that the `setImplementation` function reverts when called by a non-owner.
vm.prank(address(0));
vm.expectRevert("Ownable: caller is not the owner");
factory.setImplementation(GameType.FAULT, IDisputeGame(address(1)));
}
/**
* @dev Tests that the `getGameUUID` function returns the correct hash when comparing
* against the keccak256 hash of the abi-encoded parameters.
*/
function testDiff_getGameUUID_succeeds(
uint8 gameType,
Claim rootClaim,
bytes calldata extraData
) public {
// Ensure that the `gameType` is within the bounds of the `GameType` enum's possible values.
GameType gt = GameType(uint8(bound(gameType, 0, 2)));
assertEq(
Hash.unwrap(factory.getGameUUID(gt, rootClaim, extraData)),
keccak256(abi.encode(gt, rootClaim, extraData))
);
}
/**
* @dev Tests that the `owner` function returns the correct address after deployment.
*/
function test_owner_succeeds() public {
assertEq(factory.owner(), address(this));
}
/**
* @dev Tests that the `transferOwnership` function succeeds when called by the owner.
*/
function test_transferOwnership_succeeds() public {
factory.transferOwnership(address(1));
assertEq(factory.owner(), address(1));
}
/**
* @dev Tests that the `transferOwnership` function reverts when called by a non-owner.
*/
function test_transferOwnership_notOwner_reverts() public {
vm.prank(address(0));
vm.expectRevert("Ownable: caller is not the owner");
factory.transferOwnership(address(1));
}
}
/**
* @dev A fake clone used for testing the `DisputeGameFactory` contract's `create` function.
*/
contract FakeClone {
function initialize() external {
// noop
}
}
......@@ -5,6 +5,45 @@ import { CommonTest } from "./CommonTest.t.sol";
import { SafeCall } from "../libraries/SafeCall.sol";
contract SafeCall_Test is CommonTest {
function testFuzz_send_succeeds(
address from,
address to,
uint256 gas,
uint64 value
) external {
vm.assume(from.balance == 0);
vm.assume(to.balance == 0);
// no precompiles (mainnet)
assumeNoPrecompiles(to, 1);
// don't call the vm
vm.assume(to != address(vm));
vm.assume(from != address(vm));
// don't call the console
vm.assume(to != address(0x000000000000000000636F6e736F6c652e6c6f67));
// don't call the create2 deployer
vm.assume(to != address(0x4e59b44847b379578588920cA78FbF26c0B4956C));
// don't call the ffi interface
vm.assume(to != address(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f));
assertEq(from.balance, 0, "from balance is 0");
vm.deal(from, value);
assertEq(from.balance, value, "from balance not dealt");
uint256[2] memory balancesBefore = [from.balance, to.balance];
vm.expectCall(to, value, bytes(""));
vm.prank(from);
bool success = SafeCall.send(to, gas, value);
assertTrue(success, "send not successful");
if (from == to) {
assertEq(from.balance, balancesBefore[0], "Self-send did not change balance");
} else {
assertEq(from.balance, balancesBefore[0] - value, "from balance not drained");
assertEq(to.balance, balancesBefore[1] + value, "to balance received");
}
}
function testFuzz_call_succeeds(
address from,
address to,
......
......@@ -8,6 +8,7 @@ remappings = [
'@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/',
'@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/',
'@rari-capital/solmate/=node_modules/@rari-capital/solmate',
"@cwia/=node_modules/clones-with-immutable-args/src",
'forge-std/=node_modules/forge-std/src',
'ds-test/=node_modules/ds-test/src'
]
......
......@@ -70,6 +70,7 @@
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-waffle": "^2.0.0",
"@rari-capital/solmate": "https://github.com/rari-capital/solmate.git#8f9b23f8838670afda0fd8983f2c41e8037ae6bc",
"clones-with-immutable-args": "https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args.git#105efee1b9127ed7f6fedf139e1fc796ce8791f2",
"@typechain/ethers-v5": "^10.1.0",
"@typescript-eslint/eslint-plugin": "^5.45.1",
"@typescript-eslint/parser": "^5.45.1",
......
......@@ -29,6 +29,7 @@ export enum L2ChainID {
OPTIMISM_HARDHAT_DEVNET = 17,
OPTIMISM_BEDROCK_LOCAL_DEVNET = 901,
OPTIMISM_BEDROCK_ALPHA_TESTNET = 28528,
BASE_GOERLI = 84531,
}
/**
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -105,6 +105,7 @@ type BackendGroupConfig struct {
ConsensusBanPeriod TOMLDuration `toml:"consensus_ban_period"`
ConsensusMaxUpdateThreshold TOMLDuration `toml:"consensus_max_update_threshold"`
ConsensusMaxBlockLag uint64 `toml:"consensus_max_block_lag"`
ConsensusMinPeerCount int `toml:"consensus_min_peer_count"`
}
......
This diff is collapsed.
......@@ -93,6 +93,8 @@ backends = ["infura"]
# consensus_ban_period = "1m"
# Maximum delay for update the backend, default 30s
# consensus_max_update_threshold = "20s"
# Maximum block lag, default 50
# consensus_max_block_lag = 10
# Minimum peer count, default 3
# consensus_min_peer_count = 4
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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