Commit 97a9a1c6 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into jg/limit_p2p_unsafe_sync_reqs

parents 2eb66797 fd86a57e
...@@ -1504,7 +1504,7 @@ workflows: ...@@ -1504,7 +1504,7 @@ workflows:
type: approval type: approval
filters: filters:
tags: tags:
only: /^op-[a-z0-9\-]*\/v.*/ only: /^(proxyd|op-[a-z0-9\-]*)\/v.*/
branches: branches:
ignore: /.*/ ignore: /.*/
- docker-release: - docker-release:
...@@ -1586,7 +1586,7 @@ workflows: ...@@ -1586,7 +1586,7 @@ workflows:
- oplabs-gcr-release - oplabs-gcr-release
requires: requires:
- hold - hold
- docker-build: - docker-release:
name: proxyd-docker-release name: proxyd-docker-release
filters: filters:
tags: tags:
......
...@@ -13,13 +13,13 @@ ...@@ -13,13 +13,13 @@
/packages/core-utils @ethereum-optimism/legacy-reviewers /packages/core-utils @ethereum-optimism/legacy-reviewers
/packages/data-transport-layer @ethereum-optimism/legacy-reviewers /packages/data-transport-layer @ethereum-optimism/legacy-reviewers
/packages/chain-mon @smartcontracts /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/hardhat-deploy-config @ethereum-optimism/legacy-reviewers
/packages/message-relayer @ethereum-optimism/legacy-reviewers /packages/message-relayer @ethereum-optimism/legacy-reviewers
/packages/migration-data @ethereum-optimism/legacy-reviewers /packages/migration-data @ethereum-optimism/legacy-reviewers
/packages/replica-healthcheck @ethereum-optimism/legacy-reviewers /packages/replica-healthcheck @ethereum-optimism/legacy-reviewers
/packages/sdk @ethereum-optimism/ecopod /packages/sdk @ethereum-optimism/devxpod
/packages/atst @ethereum-optimism/ecopod /packages/atst @ethereum-optimism/devxpod
# Bedrock codebases # Bedrock codebases
/bedrock-devnet @ethereum-optimism/go-reviewers /bedrock-devnet @ethereum-optimism/go-reviewers
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
# Misc # Misc
/proxyd @ethereum-optimism/infra-reviewers /proxyd @ethereum-optimism/infra-reviewers
/indexer @ethereum-optimism/infra-reviewers /indexer @ethereum-optimism/devxpod
/infra @ethereum-optimism/infra-reviewers /infra @ethereum-optimism/infra-reviewers
/specs @ethereum-optimism/contract-reviewers @ethereum-optimism/go-reviewers /specs @ethereum-optimism/contract-reviewers @ethereum-optimism/go-reviewers
/endpoint-monitor @ethereum-optimism/infra-reviewers /endpoint-monitor @ethereum-optimism/infra-reviewers
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-batcher/rpc" "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" oplog "github.com/ethereum-optimism/optimism/op-service/log"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
...@@ -33,6 +34,7 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -33,6 +34,7 @@ func Main(version string, cliCtx *cli.Context) error {
} }
l := oplog.NewLogger(cfg.LogConfig) l := oplog.NewLogger(cfg.LogConfig)
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)
m := metrics.NewMetrics("default") m := metrics.NewMetrics("default")
l.Info("Initializing Batch Submitter") l.Info("Initializing Batch Submitter")
......
...@@ -15,24 +15,24 @@ import ( ...@@ -15,24 +15,24 @@ import (
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
const envVarPrefix = "OP_BATCHER" const EnvVarPrefix = "OP_BATCHER"
var ( var (
// Required flags // Required flags
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1", Usage: "HTTP provider URL for L1",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L1_ETH_RPC"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L1_ETH_RPC"),
} }
L2EthRpcFlag = cli.StringFlag{ L2EthRpcFlag = cli.StringFlag{
Name: "l2-eth-rpc", Name: "l2-eth-rpc",
Usage: "HTTP provider URL for L2 execution engine", Usage: "HTTP provider URL for L2 execution engine",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L2_ETH_RPC"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L2_ETH_RPC"),
} }
RollupRpcFlag = cli.StringFlag{ RollupRpcFlag = cli.StringFlag{
Name: "rollup-rpc", Name: "rollup-rpc",
Usage: "HTTP provider URL for Rollup node", Usage: "HTTP provider URL for Rollup node",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_RPC"),
} }
// Optional flags // Optional flags
SubSafetyMarginFlag = cli.Uint64Flag{ SubSafetyMarginFlag = cli.Uint64Flag{
...@@ -41,54 +41,54 @@ var ( ...@@ -41,54 +41,54 @@ var (
"from a channel's timeout and sequencing window, to guarantee safe inclusion " + "from a channel's timeout and sequencing window, to guarantee safe inclusion " +
"of a channel on L1.", "of a channel on L1.",
Value: 10, Value: 10,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SUB_SAFETY_MARGIN"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "SUB_SAFETY_MARGIN"),
} }
PollIntervalFlag = cli.DurationFlag{ PollIntervalFlag = cli.DurationFlag{
Name: "poll-interval", Name: "poll-interval",
Usage: "How frequently to poll L2 for new blocks", Usage: "How frequently to poll L2 for new blocks",
Value: 6 * time.Second, Value: 6 * time.Second,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "POLL_INTERVAL"),
} }
MaxPendingTransactionsFlag = cli.Uint64Flag{ MaxPendingTransactionsFlag = cli.Uint64Flag{
Name: "max-pending-tx", Name: "max-pending-tx",
Usage: "The maximum number of pending transactions. 0 for no limit.", Usage: "The maximum number of pending transactions. 0 for no limit.",
Value: 1, Value: 1,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MAX_PENDING_TX"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_PENDING_TX"),
} }
MaxChannelDurationFlag = cli.Uint64Flag{ MaxChannelDurationFlag = cli.Uint64Flag{
Name: "max-channel-duration", Name: "max-channel-duration",
Usage: "The maximum duration of L1-blocks to keep a channel open. 0 to disable.", Usage: "The maximum duration of L1-blocks to keep a channel open. 0 to disable.",
Value: 0, Value: 0,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MAX_CHANNEL_DURATION"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_CHANNEL_DURATION"),
} }
MaxL1TxSizeBytesFlag = cli.Uint64Flag{ MaxL1TxSizeBytesFlag = cli.Uint64Flag{
Name: "max-l1-tx-size-bytes", Name: "max-l1-tx-size-bytes",
Usage: "The maximum size of a batch tx submitted to L1.", Usage: "The maximum size of a batch tx submitted to L1.",
Value: 120_000, Value: 120_000,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MAX_L1_TX_SIZE_BYTES"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "MAX_L1_TX_SIZE_BYTES"),
} }
TargetL1TxSizeBytesFlag = cli.Uint64Flag{ TargetL1TxSizeBytesFlag = cli.Uint64Flag{
Name: "target-l1-tx-size-bytes", Name: "target-l1-tx-size-bytes",
Usage: "The target size of a batch tx submitted to L1.", Usage: "The target size of a batch tx submitted to L1.",
Value: 100_000, Value: 100_000,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "TARGET_L1_TX_SIZE_BYTES"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "TARGET_L1_TX_SIZE_BYTES"),
} }
TargetNumFramesFlag = cli.IntFlag{ TargetNumFramesFlag = cli.IntFlag{
Name: "target-num-frames", Name: "target-num-frames",
Usage: "The target number of frames to create per channel", Usage: "The target number of frames to create per channel",
Value: 1, Value: 1,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "TARGET_NUM_FRAMES"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "TARGET_NUM_FRAMES"),
} }
ApproxComprRatioFlag = cli.Float64Flag{ ApproxComprRatioFlag = cli.Float64Flag{
Name: "approx-compr-ratio", Name: "approx-compr-ratio",
Usage: "The approximate compression ratio (<= 1.0)", Usage: "The approximate compression ratio (<= 1.0)",
Value: 0.4, Value: 0.4,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "APPROX_COMPR_RATIO"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "APPROX_COMPR_RATIO"),
} }
StoppedFlag = cli.BoolFlag{ StoppedFlag = cli.BoolFlag{
Name: "stopped", Name: "stopped",
Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC", Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "STOPPED"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "STOPPED"),
} }
// Legacy Flags // Legacy Flags
SequencerHDPathFlag = txmgr.SequencerHDPathFlag SequencerHDPathFlag = txmgr.SequencerHDPathFlag
...@@ -114,12 +114,12 @@ var optionalFlags = []cli.Flag{ ...@@ -114,12 +114,12 @@ var optionalFlags = []cli.Flag{
} }
func init() { func init() {
optionalFlags = append(optionalFlags, oprpc.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oprpc.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, rpc.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, rpc.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, txmgr.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...) Flags = append(requiredFlags, optionalFlags...)
} }
......
This diff is collapsed.
...@@ -13,7 +13,7 @@ const WETH9StorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"con ...@@ -13,7 +13,7 @@ const WETH9StorageLayoutJSON = "{\"storage\":[{\"astId\":1000,\"contract\":\"con
var WETH9StorageLayout = new(solc.StorageLayout) var WETH9StorageLayout = new(solc.StorageLayout)
var WETH9DeployedBin = "0x6080604052600436106100bc5760003560e01c8063313ce56711610074578063a9059cbb1161004e578063a9059cbb146102cb578063d0e30db0146100bc578063dd62ed3e14610311576100bc565b8063313ce5671461024b57806370a082311461027657806395d89b41146102b6576100bc565b806318160ddd116100a557806318160ddd146101aa57806323b872dd146101d15780632e1a7d4d14610221576100bc565b806306fdde03146100c6578063095ea7b314610150575b6100c4610359565b005b3480156100d257600080fd5b506100db6103a8565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101155781810151838201526020016100fd565b50505050905090810190601f1680156101425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015c57600080fd5b506101966004803603604081101561017357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610454565b604080519115158252519081900360200190f35b3480156101b657600080fd5b506101bf6104c7565b60408051918252519081900360200190f35b3480156101dd57600080fd5b50610196600480360360608110156101f457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356104cb565b34801561022d57600080fd5b506100c46004803603602081101561024457600080fd5b503561066b565b34801561025757600080fd5b50610260610700565b6040805160ff9092168252519081900360200190f35b34801561028257600080fd5b506101bf6004803603602081101561029957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610709565b3480156102c257600080fd5b506100db61071b565b3480156102d757600080fd5b50610196600480360360408110156102ee57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610793565b34801561031d57600080fd5b506101bf6004803603604081101561033457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160200135166107a7565b33600081815260036020908152604091829020805434908101909155825190815291517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9281900390910190a2565b6000805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b820191906000526020600020905b81548152906001019060200180831161042f57829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b4790565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120548211156104fd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff84163314801590610573575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14155b156105ed5773ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020548211156105b557600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020805483900390555b73ffffffffffffffffffffffffffffffffffffffff808516600081815260036020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060019392505050565b3360009081526003602052604090205481111561068757600080fd5b33600081815260036020526040808220805485900390555183156108fc0291849190818181858888f193505050501580156106c6573d6000803e3d6000fd5b5060408051828152905133917f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65919081900360200190a250565b60025460ff1681565b60036020526000908152604090205481565b60018054604080516020600284861615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b60006107a03384846104cb565b9392505050565b60046020908152600092835260408084209091529082529020548156fea265627a7a72315820fd69d075d01838a66174ca9cefc98bf8f255e049a94475b253ffc70bf383f90564736f6c63430005110032" var WETH9DeployedBin = "0x6080604052600436106100bc5760003560e01c8063313ce56711610074578063a9059cbb1161004e578063a9059cbb146102cb578063d0e30db0146100bc578063dd62ed3e14610311576100bc565b8063313ce5671461024b57806370a082311461027657806395d89b41146102b6576100bc565b806318160ddd116100a557806318160ddd146101aa57806323b872dd146101d15780632e1a7d4d14610221576100bc565b806306fdde03146100c6578063095ea7b314610150575b6100c4610359565b005b3480156100d257600080fd5b506100db6103a8565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101155781810151838201526020016100fd565b50505050905090810190601f1680156101425780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015c57600080fd5b506101966004803603604081101561017357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610454565b604080519115158252519081900360200190f35b3480156101b657600080fd5b506101bf6104c7565b60408051918252519081900360200190f35b3480156101dd57600080fd5b50610196600480360360608110156101f457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356104cb565b34801561022d57600080fd5b506100c46004803603602081101561024457600080fd5b503561066b565b34801561025757600080fd5b50610260610700565b6040805160ff9092168252519081900360200190f35b34801561028257600080fd5b506101bf6004803603602081101561029957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610709565b3480156102c257600080fd5b506100db61071b565b3480156102d757600080fd5b50610196600480360360408110156102ee57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610793565b34801561031d57600080fd5b506101bf6004803603604081101561033457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160200135166107a7565b33600081815260036020908152604091829020805434908101909155825190815291517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9281900390910190a2565b6000805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b820191906000526020600020905b81548152906001019060200180831161042f57829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b4790565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120548211156104fd57600080fd5b73ffffffffffffffffffffffffffffffffffffffff84163314801590610573575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14155b156105ed5773ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020548211156105b557600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020805483900390555b73ffffffffffffffffffffffffffffffffffffffff808516600081815260036020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060019392505050565b3360009081526003602052604090205481111561068757600080fd5b33600081815260036020526040808220805485900390555183156108fc0291849190818181858888f193505050501580156106c6573d6000803e3d6000fd5b5060408051828152905133917f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65919081900360200190a250565b60025460ff1681565b60036020526000908152604090205481565b60018054604080516020600284861615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561044c5780601f106104215761010080835404028352916020019161044c565b60006107a03384846104cb565b9392505050565b60046020908152600092835260408084209091529082529020548156fea265627a7a72315820e496abb80c5983b030f680d0bd88f66bf44e261bc3be070d612dd72f9f1f5e9a64736f6c63430005110032"
func init() { func init() {
if err := json.Unmarshal([]byte(WETH9StorageLayoutJSON), WETH9StorageLayout); err != nil { if err := json.Unmarshal([]byte(WETH9StorageLayoutJSON), WETH9StorageLayout); err != nil {
......
...@@ -10,6 +10,43 @@ import ( ...@@ -10,6 +10,43 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup" "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{ var Goerli = rollup.Config{
Genesis: rollup.Genesis{ Genesis: rollup.Genesis{
L1: eth.BlockID{ L1: eth.BlockID{
...@@ -42,6 +79,8 @@ var Goerli = rollup.Config{ ...@@ -42,6 +79,8 @@ var Goerli = rollup.Config{
var NetworksByName = map[string]rollup.Config{ var NetworksByName = map[string]rollup.Config{
"goerli": Goerli, "goerli": Goerli,
// moose: Update this during migration
// "mainnet": Mainnet,
} }
var L2ChainIDToNetworkName = func() map[string]string { var L2ChainIDToNetworkName = func() map[string]string {
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/node" "github.com/ethereum-optimism/optimism/op-node/node"
"github.com/ethereum-optimism/optimism/op-node/version" "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" oplog "github.com/ethereum-optimism/optimism/op-service/log"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
) )
...@@ -88,6 +89,7 @@ func RollupNodeMain(ctx *cli.Context) error { ...@@ -88,6 +89,7 @@ func RollupNodeMain(ctx *cli.Context) error {
return err return err
} }
log := oplog.NewLogger(logCfg) log := oplog.NewLogger(logCfg)
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, log)
m := metrics.NewMetrics("default") m := metrics.NewMetrics("default")
cfg, err := opnode.NewConfig(ctx, log) cfg, err := opnode.NewConfig(ctx, log)
......
...@@ -14,10 +14,10 @@ import ( ...@@ -14,10 +14,10 @@ import (
// Flags // Flags
const envVarPrefix = "OP_NODE" const EnvVarPrefix = "OP_NODE"
func prefixEnvVar(name string) string { func prefixEnvVar(name string) string {
return envVarPrefix + "_" + name return EnvVarPrefix + "_" + name
} }
var ( var (
...@@ -251,7 +251,7 @@ var Flags []cli.Flag ...@@ -251,7 +251,7 @@ var Flags []cli.Flag
func init() { func init() {
optionalFlags = append(optionalFlags, p2pFlags...) optionalFlags = append(optionalFlags, p2pFlags...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...) Flags = append(requiredFlags, optionalFlags...)
} }
......
...@@ -103,6 +103,8 @@ type Metrics struct { ...@@ -103,6 +103,8 @@ type Metrics struct {
SequencerInconsistentL1Origin *EventMetrics SequencerInconsistentL1Origin *EventMetrics
SequencerResets *EventMetrics SequencerResets *EventMetrics
L1RequestDurationSeconds *prometheus.HistogramVec
SequencerBuildingDiffDurationSeconds prometheus.Histogram SequencerBuildingDiffDurationSeconds prometheus.Histogram
SequencerBuildingDiffTotal prometheus.Counter SequencerBuildingDiffTotal prometheus.Counter
...@@ -378,6 +380,14 @@ func NewMetrics(procName string) *Metrics { ...@@ -378,6 +380,14 @@ func NewMetrics(procName string) *Metrics {
Help: "number of unverified execution payloads buffered in quarantine", 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{ SequencerBuildingDiffDurationSeconds: factory.NewHistogram(prometheus.HistogramOpts{
Namespace: ns, Namespace: ns,
Name: "sequencer_building_diff_seconds", Name: "sequencer_building_diff_seconds",
...@@ -589,6 +599,11 @@ func (m *Metrics) RecordBandwidth(ctx context.Context, bwc *libp2pmetrics.Bandwi ...@@ -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 // RecordSequencerBuildingDiffTime tracks the amount of time the sequencer was allowed between
// start to finish, incl. sealing, minus the block time. // 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. // Ideally this is 0, realistically the sequencer scheduler may be busy with other jobs like syncing sometimes.
......
...@@ -30,6 +30,7 @@ type Metrics interface { ...@@ -30,6 +30,7 @@ type Metrics interface {
RecordL1ReorgDepth(d uint64) RecordL1ReorgDepth(d uint64)
EngineMetrics EngineMetrics
L1FetcherMetrics
SequencerMetrics SequencerMetrics
} }
...@@ -103,6 +104,7 @@ type AltSync interface { ...@@ -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. // 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 { 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) l1State := NewL1State(log, metrics)
sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1) sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1)
findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth) 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()
}
...@@ -13,69 +13,69 @@ import ( ...@@ -13,69 +13,69 @@ import (
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
) )
const envVarPrefix = "OP_PROGRAM" const EnvVarPrefix = "OP_PROGRAM"
var ( var (
RollupConfig = cli.StringFlag{ RollupConfig = cli.StringFlag{
Name: "rollup.config", Name: "rollup.config",
Usage: "Rollup chain parameters", Usage: "Rollup chain parameters",
EnvVar: service.PrefixEnvVar(envVarPrefix, "ROLLUP_CONFIG"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "ROLLUP_CONFIG"),
} }
Network = cli.StringFlag{ Network = cli.StringFlag{
Name: "network", Name: "network",
Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")), Usage: fmt.Sprintf("Predefined network selection. Available networks: %s", strings.Join(chaincfg.AvailableNetworks(), ", ")),
EnvVar: service.PrefixEnvVar(envVarPrefix, "NETWORK"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "NETWORK"),
} }
DataDir = cli.StringFlag{ DataDir = cli.StringFlag{
Name: "datadir", Name: "datadir",
Usage: "Directory to use for preimage data storage. Default uses in-memory storage", Usage: "Directory to use for preimage data storage. Default uses in-memory storage",
EnvVar: service.PrefixEnvVar(envVarPrefix, "DATADIR"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "DATADIR"),
} }
L2NodeAddr = cli.StringFlag{ L2NodeAddr = cli.StringFlag{
Name: "l2", Name: "l2",
Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)", Usage: "Address of L2 JSON-RPC endpoint to use (eth and debug namespace required)",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_RPC"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_RPC"),
} }
L1Head = cli.StringFlag{ L1Head = cli.StringFlag{
Name: "l1.head", Name: "l1.head",
Usage: "Hash of the L1 head block. Derivation stops after this block is processed.", Usage: "Hash of the L1 head block. Derivation stops after this block is processed.",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_HEAD"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_HEAD"),
} }
L2Head = cli.StringFlag{ L2Head = cli.StringFlag{
Name: "l2.head", Name: "l2.head",
Usage: "Hash of the agreed L2 block to start derivation from", Usage: "Hash of the agreed L2 block to start derivation from",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_HEAD"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_HEAD"),
} }
L2Claim = cli.StringFlag{ L2Claim = cli.StringFlag{
Name: "l2.claim", Name: "l2.claim",
Usage: "Claimed L2 output root to validate", Usage: "Claimed L2 output root to validate",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_CLAIM"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_CLAIM"),
} }
L2BlockNumber = cli.Uint64Flag{ L2BlockNumber = cli.Uint64Flag{
Name: "l2.blocknumber", Name: "l2.blocknumber",
Usage: "Number of the L2 block that the claim is from", Usage: "Number of the L2 block that the claim is from",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_BLOCK_NUM"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_BLOCK_NUM"),
} }
L2GenesisPath = cli.StringFlag{ L2GenesisPath = cli.StringFlag{
Name: "l2.genesis", Name: "l2.genesis",
Usage: "Path to the op-geth genesis file", Usage: "Path to the op-geth genesis file",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L2_GENESIS"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L2_GENESIS"),
} }
L1NodeAddr = cli.StringFlag{ L1NodeAddr = cli.StringFlag{
Name: "l1", Name: "l1",
Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)", Usage: "Address of L1 JSON-RPC endpoint to use (eth namespace required)",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_RPC"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_RPC"),
} }
L1TrustRPC = cli.BoolFlag{ L1TrustRPC = cli.BoolFlag{
Name: "l1.trustrpc", Name: "l1.trustrpc",
Usage: "Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data", Usage: "Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data",
EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_TRUST_RPC"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_TRUST_RPC"),
} }
L1RPCProviderKind = cli.GenericFlag{ L1RPCProviderKind = cli.GenericFlag{
Name: "l1.rpckind", Name: "l1.rpckind",
Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " + Usage: "The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: " +
nodeflags.EnumString[sources.RPCProviderKind](sources.RPCProviderKinds), nodeflags.EnumString[sources.RPCProviderKind](sources.RPCProviderKinds),
EnvVar: service.PrefixEnvVar(envVarPrefix, "L1_RPC_KIND"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "L1_RPC_KIND"),
Value: func() *sources.RPCProviderKind { Value: func() *sources.RPCProviderKind {
out := sources.RPCKindBasic out := sources.RPCKindBasic
return &out return &out
...@@ -84,12 +84,12 @@ var ( ...@@ -84,12 +84,12 @@ var (
Exec = cli.StringFlag{ Exec = cli.StringFlag{
Name: "exec", Name: "exec",
Usage: "Run the specified client program as a separate process detached from the host. Default is to run the client program in the host process.", Usage: "Run the specified client program as a separate process detached from the host. Default is to run the client program in the host process.",
EnvVar: service.PrefixEnvVar(envVarPrefix, "EXEC"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "EXEC"),
} }
Server = cli.BoolFlag{ Server = cli.BoolFlag{
Name: "server", Name: "server",
Usage: "Run in pre-image server mode without executing any client program.", Usage: "Run in pre-image server mode without executing any client program.",
EnvVar: service.PrefixEnvVar(envVarPrefix, "SERVER"), EnvVar: service.PrefixEnvVar(EnvVarPrefix, "SERVER"),
} }
) )
...@@ -116,7 +116,7 @@ var programFlags = []cli.Flag{ ...@@ -116,7 +116,7 @@ var programFlags = []cli.Flag{
} }
func init() { func init() {
Flags = append(Flags, oplog.CLIFlags(envVarPrefix)...) Flags = append(Flags, oplog.CLIFlags(EnvVarPrefix)...)
Flags = append(Flags, requiredFlags...) Flags = append(Flags, requiredFlags...)
Flags = append(Flags, programFlags...) Flags = append(Flags, programFlags...)
} }
......
...@@ -15,10 +15,12 @@ import ( ...@@ -15,10 +15,12 @@ import (
cl "github.com/ethereum-optimism/optimism/op-program/client" 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/client/driver"
"github.com/ethereum-optimism/optimism/op-program/host/config" "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/kvstore"
"github.com/ethereum-optimism/optimism/op-program/host/prefetcher" "github.com/ethereum-optimism/optimism/op-program/host/prefetcher"
oppio "github.com/ethereum-optimism/optimism/op-program/io" oppio "github.com/ethereum-optimism/optimism/op-program/io"
"github.com/ethereum-optimism/optimism/op-program/preimage" "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/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -32,6 +34,7 @@ func Main(logger log.Logger, cfg *config.Config) error { ...@@ -32,6 +34,7 @@ func Main(logger log.Logger, cfg *config.Config) error {
if err := cfg.Check(); err != nil { if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid config: %w", err) return fmt.Errorf("invalid config: %w", err)
} }
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, logger)
cfg.Rollup.LogDescription(logger, chaincfg.L2ChainIDToNetworkName) cfg.Rollup.LogDescription(logger, chaincfg.L2ChainIDToNetworkName)
ctx := context.Background() ctx := context.Background()
......
...@@ -14,24 +14,24 @@ import ( ...@@ -14,24 +14,24 @@ import (
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
) )
const envVarPrefix = "OP_PROPOSER" const EnvVarPrefix = "OP_PROPOSER"
var ( var (
// Required Flags // Required Flags
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
Usage: "HTTP provider URL for L1", Usage: "HTTP provider URL for L1",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L1_ETH_RPC"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L1_ETH_RPC"),
} }
RollupRpcFlag = cli.StringFlag{ RollupRpcFlag = cli.StringFlag{
Name: "rollup-rpc", Name: "rollup-rpc",
Usage: "HTTP provider URL for the rollup node", Usage: "HTTP provider URL for the rollup node",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_RPC"),
} }
L2OOAddressFlag = cli.StringFlag{ L2OOAddressFlag = cli.StringFlag{
Name: "l2oo-address", Name: "l2oo-address",
Usage: "Address of the L2OutputOracle contract", Usage: "Address of the L2OutputOracle contract",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "L2OO_ADDRESS"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "L2OO_ADDRESS"),
} }
// Optional flags // Optional flags
...@@ -39,12 +39,12 @@ var ( ...@@ -39,12 +39,12 @@ var (
Name: "poll-interval", Name: "poll-interval",
Usage: "How frequently to poll L2 for new blocks", Usage: "How frequently to poll L2 for new blocks",
Value: 6 * time.Second, Value: 6 * time.Second,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "POLL_INTERVAL"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "POLL_INTERVAL"),
} }
AllowNonFinalizedFlag = cli.BoolFlag{ AllowNonFinalizedFlag = cli.BoolFlag{
Name: "allow-non-finalized", Name: "allow-non-finalized",
Usage: "Allow the proposer to submit proposals for L2 blocks derived from non-finalized L1 blocks.", Usage: "Allow the proposer to submit proposals for L2 blocks derived from non-finalized L1 blocks.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ALLOW_NON_FINALIZED"), EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ALLOW_NON_FINALIZED"),
} }
// Legacy Flags // Legacy Flags
L2OutputHDPathFlag = txmgr.L2OutputHDPathFlag L2OutputHDPathFlag = txmgr.L2OutputHDPathFlag
...@@ -63,11 +63,11 @@ var optionalFlags = []cli.Flag{ ...@@ -63,11 +63,11 @@ var optionalFlags = []cli.Flag{
} }
func init() { func init() {
optionalFlags = append(optionalFlags, oprpc.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oprpc.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(envVarPrefix)...) optionalFlags = append(optionalFlags, txmgr.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...) Flags = append(requiredFlags, optionalFlags...)
} }
......
...@@ -46,6 +46,7 @@ func Main(version string, cliCtx *cli.Context) error { ...@@ -46,6 +46,7 @@ func Main(version string, cliCtx *cli.Context) error {
} }
l := oplog.NewLogger(cfg.LogConfig) l := oplog.NewLogger(cfg.LogConfig)
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)
m := metrics.NewMetrics("default") m := metrics.NewMetrics("default")
l.Info("Initializing L2 Output Submitter") l.Info("Initializing L2 Output Submitter")
......
...@@ -6,16 +6,59 @@ import ( ...@@ -6,16 +6,59 @@ import (
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
"reflect"
"strings"
"syscall" "syscall"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli"
) )
func PrefixEnvVar(prefix, suffix string) string { func PrefixEnvVar(prefix, suffix string) string {
return prefix + "_" + suffix 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 // ParseAddress parses an ETH address from a hex string. This method will fail if
// the address is not a valid hexadecimal address. // the address is not a valid hexadecimal address.
func ParseAddress(address string) (common.Address, error) { 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 ...@@ -6,7 +6,7 @@ DOCKER_REPO=$1
GIT_TAG=$2 GIT_TAG=$2
GIT_SHA=$3 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 if [ -z "$IMAGE_NAME" ]; then
echo "image name could not be parsed from git tag '$GIT_TAG'" echo "image name could not be parsed from git tag '$GIT_TAG'"
exit 1 exit 1
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"@eth-optimism/contracts-bedrock/ds-test", "@eth-optimism/contracts-bedrock/ds-test",
"@eth-optimism/contracts-bedrock/forge-std", "@eth-optimism/contracts-bedrock/forge-std",
"@eth-optimism/contracts-bedrock/@rari-capital/solmate", "@eth-optimism/contracts-bedrock/@rari-capital/solmate",
"@eth-optimism/contracts-bedrock/clones-with-immutable-args",
"**/@openzeppelin/*", "**/@openzeppelin/*",
"@eth-optimism/contracts-periphery/ds-test", "@eth-optimism/contracts-periphery/ds-test",
"@eth-optimism/contracts-periphery/forge-std", "@eth-optimism/contracts-periphery/forge-std",
......
...@@ -27,6 +27,11 @@ CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597) ...@@ -27,6 +27,11 @@ CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597)
CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883) CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883)
DeployerWhitelist_Test:test_owner_succeeds() (gas: 7582) DeployerWhitelist_Test:test_owner_succeeds() (gas: 7582)
DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395) 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_constructor_succeeds() (gas: 10736)
FeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 10713) FeeVault_Test:test_minWithdrawalAmount_succeeds() (gas: 10713)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352135)
......
// 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 { IBondManager } from "./IBondManager.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)
}
}
}
...@@ -4,13 +4,12 @@ pragma solidity ^0.8.15; ...@@ -4,13 +4,12 @@ pragma solidity ^0.8.15;
import { Claim, GameType } from "../libraries/DisputeTypes.sol"; import { Claim, GameType } from "../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol"; import { IDisputeGame } from "./IDisputeGame.sol";
import { IOwnable } from "./IOwnable.sol";
/** /**
* @title IDisputeGameFactory * @title IDisputeGameFactory
* @notice The interface for a DisputeGameFactory contract. * @notice The interface for a DisputeGameFactory contract.
*/ */
interface IDisputeGameFactory is IOwnable { interface IDisputeGameFactory {
/** /**
* @notice Emitted when a new dispute game is created * @notice Emitted when a new dispute game is created
* @param disputeProxy The address of the dispute game proxy * @param disputeProxy The address of the dispute game proxy
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @title IOwnable
* @notice An interface for ownable contracts.
*/
interface IOwnable {
/**
* @notice Returns the owner of the contract
* @return _owner The address of the owner
*/
function owner() external view returns (address _owner);
/**
* @notice Transfers ownership of the contract to a new address
* @dev May only be called by the contract owner
* @param newOwner The address to transfer ownership to
*/
function transferOwnership(address newOwner) external;
}
// 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;
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
}
}
...@@ -8,6 +8,7 @@ remappings = [ ...@@ -8,6 +8,7 @@ remappings = [
'@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/', '@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/',
'@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/', '@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/',
'@rari-capital/solmate/=node_modules/@rari-capital/solmate', '@rari-capital/solmate/=node_modules/@rari-capital/solmate',
"@cwia/=node_modules/clones-with-immutable-args/src",
'forge-std/=node_modules/forge-std/src', 'forge-std/=node_modules/forge-std/src',
'ds-test/=node_modules/ds-test/src' 'ds-test/=node_modules/ds-test/src'
] ]
......
...@@ -70,6 +70,7 @@ ...@@ -70,6 +70,7 @@
"@nomiclabs/hardhat-ethers": "^2.0.0", "@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-waffle": "^2.0.0", "@nomiclabs/hardhat-waffle": "^2.0.0",
"@rari-capital/solmate": "https://github.com/rari-capital/solmate.git#8f9b23f8838670afda0fd8983f2c41e8037ae6bc", "@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", "@typechain/ethers-v5": "^10.1.0",
"@typescript-eslint/eslint-plugin": "^5.45.1", "@typescript-eslint/eslint-plugin": "^5.45.1",
"@typescript-eslint/parser": "^5.45.1", "@typescript-eslint/parser": "^5.45.1",
......
...@@ -80,6 +80,46 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { ...@@ -80,6 +80,46 @@ func TestRPCCacheImmutableRPCs(t *testing.T) {
}, },
name: "eth_getBlockByNumber earliest", name: "eth_getBlockByNumber earliest",
}, },
{
req: &RPCReq{
JSONRPC: "2.0",
Method: "eth_getBlockByNumber",
Params: []byte(`["safe", false]`),
ID: ID,
},
res: nil,
name: "eth_getBlockByNumber safe",
},
{
req: &RPCReq{
JSONRPC: "2.0",
Method: "eth_getBlockByNumber",
Params: []byte(`["finalized", false]`),
ID: ID,
},
res: nil,
name: "eth_getBlockByNumber finalized",
},
{
req: &RPCReq{
JSONRPC: "2.0",
Method: "eth_getBlockByNumber",
Params: []byte(`["pending", false]`),
ID: ID,
},
res: nil,
name: "eth_getBlockByNumber pending",
},
{
req: &RPCReq{
JSONRPC: "2.0",
Method: "eth_getBlockByNumber",
Params: []byte(`["latest", false]`),
ID: ID,
},
res: nil,
name: "eth_getBlockByNumber latest",
},
{ {
req: &RPCReq{ req: &RPCReq{
JSONRPC: "2.0", JSONRPC: "2.0",
......
...@@ -203,23 +203,22 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller ...@@ -203,23 +203,22 @@ func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller
// UpdateBackend refreshes the consensus state of a single backend // UpdateBackend refreshes the consensus state of a single backend
func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) {
bs := cp.backendState[be] if cp.IsBanned(be) {
if time.Now().Before(bs.bannedUntil) { log.Debug("skipping backend banned", "backend", be.Name)
log.Debug("skipping backend banned", "backend", be.Name, "bannedUntil", bs.bannedUntil)
return return
} }
// if backend it not online or not in a health state we'll only resume checkin it after ban // if backend it not online or not in a health state we'll only resume checkin it after ban
if !be.Online() || !be.IsHealthy() { if !be.Online() || !be.IsHealthy() {
log.Warn("backend banned - not online or not healthy", "backend", be.Name, "bannedUntil", bs.bannedUntil) log.Warn("backend banned - not online or not healthy", "backend", be.Name)
bs.bannedUntil = time.Now().Add(cp.banPeriod) cp.Ban(be)
} }
// if backend it not in sync we'll check again after ban // if backend it not in sync we'll check again after ban
inSync, err := cp.isInSync(ctx, be) inSync, err := cp.isInSync(ctx, be)
if err != nil || !inSync { if err != nil || !inSync {
log.Warn("backend banned - not in sync", "backend", be.Name, "bannedUntil", bs.bannedUntil) log.Warn("backend banned - not in sync", "backend", be.Name)
bs.bannedUntil = time.Now().Add(cp.banPeriod) cp.Ban(be)
} }
// if backend exhausted rate limit we'll skip it for now // if backend exhausted rate limit we'll skip it for now
...@@ -246,7 +245,11 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { ...@@ -246,7 +245,11 @@ func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) {
if changed { if changed {
RecordBackendLatestBlock(be, latestBlockNumber) RecordBackendLatestBlock(be, latestBlockNumber)
log.Debug("backend state updated", "name", be.Name, "state", bs) log.Debug("backend state updated",
"name", be.Name,
"peerCount", peerCount,
"latestBlockNumber", latestBlockNumber,
"latestBlockHash", latestBlockHash)
} }
} }
...@@ -258,7 +261,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { ...@@ -258,7 +261,7 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) {
currentConsensusBlockNumber := cp.GetConsensusBlockNumber() currentConsensusBlockNumber := cp.GetConsensusBlockNumber()
for _, be := range cp.backendGroup.Backends { for _, be := range cp.backendGroup.Backends {
peerCount, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate := cp.getBackendState(be) peerCount, backendLatestBlockNumber, backendLatestBlockHash, lastUpdate, _ := cp.getBackendState(be)
if !be.skipPeerCountCheck && peerCount < cp.minPeerCount { if !be.skipPeerCountCheck && peerCount < cp.minPeerCount {
continue continue
...@@ -306,10 +309,10 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { ...@@ -306,10 +309,10 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) {
- with minimum peer count - with minimum peer count
- updated recently - updated recently
*/ */
bs := cp.backendState[be] peerCount, _, _, lastUpdate, bannedUntil := cp.getBackendState(be)
notUpdated := bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) notUpdated := lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now())
isBanned := time.Now().Before(bs.bannedUntil) isBanned := time.Now().Before(bannedUntil)
notEnoughPeers := !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount notEnoughPeers := !be.skipPeerCountCheck && peerCount < cp.minPeerCount
if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers { if !be.IsHealthy() || be.IsRateLimited() || !be.Online() || notUpdated || isBanned || notEnoughPeers {
filteredBackendsNames = append(filteredBackendsNames, be.Name) filteredBackendsNames = append(filteredBackendsNames, be.Name)
continue continue
...@@ -359,6 +362,22 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { ...@@ -359,6 +362,22 @@ func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) {
log.Debug("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", ")) log.Debug("group state", "proposedBlock", proposedBlock, "consensusBackends", strings.Join(consensusBackendsNames, ", "), "filteredBackends", strings.Join(filteredBackendsNames, ", "))
} }
// IsBanned checks if a specific backend is banned
func (cp *ConsensusPoller) IsBanned(be *Backend) bool {
bs := cp.backendState[be]
defer bs.backendStateMux.Unlock()
bs.backendStateMux.Lock()
return time.Now().Before(bs.bannedUntil)
}
// Ban bans a specific backend
func (cp *ConsensusPoller) Ban(be *Backend) {
bs := cp.backendState[be]
defer bs.backendStateMux.Unlock()
bs.backendStateMux.Lock()
bs.bannedUntil = time.Now().Add(cp.banPeriod)
}
// Unban remove any bans from the backends // Unban remove any bans from the backends
func (cp *ConsensusPoller) Unban() { func (cp *ConsensusPoller) Unban() {
for _, be := range cp.backendGroup.Backends { for _, be := range cp.backendGroup.Backends {
...@@ -432,13 +451,14 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo ...@@ -432,13 +451,14 @@ func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bo
return res, nil return res, nil
} }
func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, blockNumber hexutil.Uint64, blockHash string, lastUpdate time.Time) { func (cp *ConsensusPoller) getBackendState(be *Backend) (peerCount uint64, blockNumber hexutil.Uint64, blockHash string, lastUpdate time.Time, bannedUntil time.Time) {
bs := cp.backendState[be] bs := cp.backendState[be]
bs.backendStateMux.Lock() bs.backendStateMux.Lock()
peerCount = bs.peerCount peerCount = bs.peerCount
blockNumber = bs.latestBlockNumber blockNumber = bs.latestBlockNumber
blockHash = bs.latestBlockHash blockHash = bs.latestBlockHash
lastUpdate = bs.lastUpdate lastUpdate = bs.lastUpdate
bannedUntil = bs.bannedUntil
bs.backendStateMux.Unlock() bs.backendStateMux.Unlock()
return return
} }
......
...@@ -271,7 +271,10 @@ func (e *EthGasPriceMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRe ...@@ -271,7 +271,10 @@ func (e *EthGasPriceMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRe
} }
func isBlockDependentParam(s string) bool { func isBlockDependentParam(s string) bool {
return s == "latest" || s == "pending" return s == "latest" ||
s == "pending" ||
s == "finalized" ||
s == "safe"
} }
func decodeGetBlockByNumberParams(params json.RawMessage) (string, bool, error) { func decodeGetBlockByNumberParams(params json.RawMessage) (string, bool, error) {
...@@ -355,7 +358,11 @@ func decodeEthCallParams(req *RPCReq) (*ethCallParams, string, error) { ...@@ -355,7 +358,11 @@ func decodeEthCallParams(req *RPCReq) (*ethCallParams, string, error) {
} }
func validBlockInput(input string) bool { func validBlockInput(input string) bool {
if input == "earliest" || input == "pending" || input == "latest" { if input == "earliest" ||
input == "latest" ||
input == "pending" ||
input == "finalized" ||
input == "safe" {
return true return true
} }
_, err := decodeBlockInput(input) _, err := decodeBlockInput(input)
......
...@@ -576,7 +576,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ...@@ -576,7 +576,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
return nil return nil
} }
ctx = context.WithValue(r.Context(), ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck ctx = context.WithValue(ctx, ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck
} }
return context.WithValue( return context.WithValue(
......
# [Public] 4/26 Transaction Delays Post-Mortem
# Incident Summary
On April 26, 2023 between the hours of 1900 and 2100 UTC, OP Mainnet experienced degraded service following a ~10x increase in the rate of `eth_sendRawTransaction` requests.
While the sequencer remained online and continued to process transactions, users experienced transaction inclusion delays, rate limit errors, problems syncing nodes, and other symptoms of degraded performance.
The issue resolved itself once the rate of `eth_sendRawTransaction` requests subsided. However, we did not communicate the status of the network to our users, nor did we execute on mitigations quickly enough that could have reduced the impact of the degraded service. We recognize that this was a frustrating experience that caused significant impact to our users, particularly those participating in the DeFi ecosystem. We are sorry for this user experience, and hope that this retrospective provides insight into what happened and what we are doing to prevent similar issues from happening again.
# Leadup
OP Mainnet has not yet been upgraded to Bedrock. As a result, all OP Mainnet nodes run two components to sync the L2 chain:
- The `data-transport-layer` (or DTL), which indexes transactions from L1 or another L2 node.
- `l2geth`, which executes the transactions indexed by the DTL and maintains the L2 chain’s state.
The DTL and `l2geth` retrieve new data by polling for it. The DTL polls L1 or L2 depending on how it is configured, and `l2geth` polls the DTL. The higher the transaction throughput is, the more transactions will have to be processed between each “tick” of the polling loop. When throughput is too high, it is possible for the number of transactions between each tick to exceed what can be processed in a single tick. In this case, multiple ticks are necessary to catch up to the tip of the chain.
To protect the sequencer against traffic spikes, we route read requests - specifically `eth_getBlockRange`, which the DTL uses to sync from L2 - to a read-only replica rather than to the sequencer itself.
At 1915 UTC, sequencer throughput jumped from the usual ~8 transactions per second to a peak of 95 transactions per second over the course of 15 minutes.
# Causes
As a result of the increased throughput, our read-only replica started to fall behind. The graph below shows the delay, in seconds, between the sequencer creating new blocks and them being indexed by the read-only replica:
![outage.png](2023-04-26-transaction-delays/outage.png)
This meant that while the sequencer was processing transactions normally, users were unable to see their transactions confirmed on chain for several minutes. For DeFi apps relying on an accurate view of on-chain data, this caused transactions to be reverted and positions to be liquidated. It also made it difficult to retry transactions, since the user’s wallet nonce may have increased on the sequencer but not on the replica.
This issue was ecosystem wide. Infrastructure providers run replicas of their own, which use the same polling-based mechanism to sync data. This likely further increased the delay between when transactions were processed, and when they appeared as confirmed. This is not an error on the part of infrastructure providers, but rather a flaw in how the pre-Bedrock system is designed.
# Recovery and Lessons Learned
The issue resolved itself once the transaction volume dropped back down to normal levels. However, we did not communicate with our community for the duration of the outage. This is a significant miss, and for that we apologize. Going forward, we will do the following in an effort to avoid similar issues:
- We will add monitoring for replica lag, so that we can proactively route traffic directly to the sequencer when throughput increases beyond what the sync mechanism can handle.
- Though rate limits were not the direct cause of this incident, we will increase rate limits so that node operators can poll for new blocks more frequently.
Lastly, we will upgrade mainnet to Bedrock later this year. Bedrock fixes these issues from an architectural perspective. Specifically:
- There will be no more DTL, or polling-based sync mechanism. Blocks are either derived from L1, or gossipped over a peer-to-peer network.
- Blocks will be created every two seconds rather than on every transaction. This allows data to propagate across the network more efficiently and predictably.
- The sequencer will have a private mempool. This will allow fee-replacement transactions to work properly, and eliminate the need for aggressive rate limiting on the sequencer.
We recognize how frustrating an issue like this can be, and that is compounded when we are not proactively communicating. We’re sorry our users had this experience. We’re committed to applying these learnings moving forward and appreciate our community holding us accountable.
...@@ -7915,6 +7915,10 @@ clone@^1.0.2: ...@@ -7915,6 +7915,10 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
"clones-with-immutable-args@https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args.git#105efee1b9127ed7f6fedf139e1fc796ce8791f2":
version "2.0.0"
resolved "https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args.git#105efee1b9127ed7f6fedf139e1fc796ce8791f2"
clsx@^1.1.0: clsx@^1.1.0:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
......
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