Commit 63b952a0 authored by protolambda's avatar protolambda Committed by GitHub

op-chain-ops: Organize deploy-config, improve check-logging (#11189)

* op-chain-ops: Organize deploy-config, improve check-logging

* op-chain-ops: fix DAChallengeProxy check

* op-chain-ops: add godocs
parent 8198bfca
...@@ -46,8 +46,11 @@ func entrypoint(ctx *cli.Context) error { ...@@ -46,8 +46,11 @@ func entrypoint(ctx *cli.Context) error {
return err return err
} }
cfg := oplog.DefaultCLIConfig()
logger := oplog.NewLogger(ctx.App.Writer, cfg)
// Check the config, no need to call `CheckAddresses()` // Check the config, no need to call `CheckAddresses()`
if err := config.Check(); err != nil { if err := config.Check(logger); err != nil {
return err return err
} }
......
...@@ -45,72 +45,46 @@ const ( ...@@ -45,72 +45,46 @@ const (
SystemTxMaxGas = 1_000_000 SystemTxMaxGas = 1_000_000
) )
// DeployConfig represents the deployment configuration for an OP Stack chain. type ConfigChecker interface {
// It is used to deploy the L1 contracts as well as create the L2 genesis state. // Check verifies the contents of a config are correct.
type DeployConfig struct { // Check may log warnings for non-critical configuration remarks.
// L1StartingBlockTag is used to fill in the storage of the L1Block info predeploy. The rollup Check(log log.Logger) error
// config script uses this to fill the L1 genesis info for the rollup. The Output oracle deploy }
// script may use it if the L2 starting timestamp is nil, assuming the L2 genesis is set up
// with this.
L1StartingBlockTag *MarshalableRPCBlockNumberOrHash `json:"l1StartingBlockTag"`
// L1ChainID is the chain ID of the L1 chain.
L1ChainID uint64 `json:"l1ChainID"`
// L2ChainID is the chain ID of the L2 chain.
L2ChainID uint64 `json:"l2ChainID"`
// L2BlockTime is the number of seconds between each L2 block.
L2BlockTime uint64 `json:"l2BlockTime"`
// FinalizationPeriodSeconds represents the number of seconds before an output is considered
// finalized. This impacts the amount of time that withdrawals take to finalize and is
// generally set to 1 week.
FinalizationPeriodSeconds uint64 `json:"finalizationPeriodSeconds"`
// MaxSequencerDrift is the number of seconds after the L1 timestamp of the end of the
// sequencing window that batches must be included, otherwise L2 blocks including
// deposits are force included.
MaxSequencerDrift uint64 `json:"maxSequencerDrift"`
// SequencerWindowSize is the number of L1 blocks per sequencing window.
SequencerWindowSize uint64 `json:"sequencerWindowSize"`
// ChannelTimeout is the number of L1 blocks that a frame stays valid when included in L1.
ChannelTimeout uint64 `json:"channelTimeout"`
// P2PSequencerAddress is the address of the key the sequencer uses to sign blocks on the P2P layer.
P2PSequencerAddress common.Address `json:"p2pSequencerAddress"`
// BatchInboxAddress is the L1 account that batches are sent to.
BatchInboxAddress common.Address `json:"batchInboxAddress"`
// BatchSenderAddress represents the initial sequencer account that authorizes batches.
// Transactions sent from this account to the batch inbox address are considered valid.
BatchSenderAddress common.Address `json:"batchSenderAddress"`
// L2OutputOracleSubmissionInterval is the number of L2 blocks between outputs that are submitted
// to the L2OutputOracle contract located on L1.
L2OutputOracleSubmissionInterval uint64 `json:"l2OutputOracleSubmissionInterval"`
// L2OutputOracleStartingTimestamp is the starting timestamp for the L2OutputOracle.
// MUST be the same as the timestamp of the L2OO start block.
L2OutputOracleStartingTimestamp int `json:"l2OutputOracleStartingTimestamp"`
// L2OutputOracleStartingBlockNumber is the starting block number for the L2OutputOracle.
// Must be greater than or equal to the first Bedrock block. The first L2 output will correspond
// to this value plus the submission interval.
L2OutputOracleStartingBlockNumber uint64 `json:"l2OutputOracleStartingBlockNumber"`
// L2OutputOracleProposer is the address of the account that proposes L2 outputs.
L2OutputOracleProposer common.Address `json:"l2OutputOracleProposer"`
// L2OutputOracleChallenger is the address of the account that challenges L2 outputs.
L2OutputOracleChallenger common.Address `json:"l2OutputOracleChallenger"`
// CliqueSignerAddress represents the signer address for the clique consensus engine. func checkConfigBundle(bundle any, log log.Logger) error {
// It is used in the multi-process devnet to sign blocks. cfgValue := reflect.ValueOf(bundle)
CliqueSignerAddress common.Address `json:"cliqueSignerAddress"` for cfgValue.Kind() == reflect.Interface || cfgValue.Kind() == reflect.Pointer {
// L1UseClique represents whether or not to use the clique consensus engine. cfgValue = cfgValue.Elem()
L1UseClique bool `json:"l1UseClique"` }
if cfgValue.Kind() != reflect.Struct {
return fmt.Errorf("bundle type %s is not a struct", cfgValue.Type().String())
}
for i := 0; i < cfgValue.NumField(); i++ {
field := cfgValue.Field(i)
if field.Kind() != reflect.Pointer { // to call pointer-receiver methods
field = field.Addr()
}
name := cfgValue.Type().Field(i).Name
if v, ok := field.Interface().(ConfigChecker); ok {
if err := v.Check(log.New("config", name)); err != nil {
return fmt.Errorf("config field %s failed checks: %w", name, err)
} else {
log.Debug("Checked config-field", "name", name)
}
} else {
log.Debug("Ignoring config-field", "name", name)
}
}
return nil
}
L1BlockTime uint64 `json:"l1BlockTime"` type DevDeployConfig struct {
L1GenesisBlockTimestamp hexutil.Uint64 `json:"l1GenesisBlockTimestamp"` // FundDevAccounts configures whether to fund the dev accounts.
L1GenesisBlockNonce hexutil.Uint64 `json:"l1GenesisBlockNonce"` // This should only be used during devnet deployments.
L1GenesisBlockGasLimit hexutil.Uint64 `json:"l1GenesisBlockGasLimit"` FundDevAccounts bool `json:"fundDevAccounts"`
L1GenesisBlockDifficulty *hexutil.Big `json:"l1GenesisBlockDifficulty"` }
L1GenesisBlockMixHash common.Hash `json:"l1GenesisBlockMixHash"`
L1GenesisBlockCoinbase common.Address `json:"l1GenesisBlockCoinbase"`
L1GenesisBlockNumber hexutil.Uint64 `json:"l1GenesisBlockNumber"`
L1GenesisBlockGasUsed hexutil.Uint64 `json:"l1GenesisBlockGasUsed"`
L1GenesisBlockParentHash common.Hash `json:"l1GenesisBlockParentHash"`
L1GenesisBlockBaseFeePerGas *hexutil.Big `json:"l1GenesisBlockBaseFeePerGas"`
type L2GenesisBlockDeployConfig struct {
L2GenesisBlockNonce hexutil.Uint64 `json:"l2GenesisBlockNonce"` L2GenesisBlockNonce hexutil.Uint64 `json:"l2GenesisBlockNonce"`
L2GenesisBlockGasLimit hexutil.Uint64 `json:"l2GenesisBlockGasLimit"` L2GenesisBlockGasLimit hexutil.Uint64 `json:"l2GenesisBlockGasLimit"`
L2GenesisBlockDifficulty *hexutil.Big `json:"l2GenesisBlockDifficulty"` L2GenesisBlockDifficulty *hexutil.Big `json:"l2GenesisBlockDifficulty"`
...@@ -119,34 +93,52 @@ type DeployConfig struct { ...@@ -119,34 +93,52 @@ type DeployConfig struct {
L2GenesisBlockGasUsed hexutil.Uint64 `json:"l2GenesisBlockGasUsed"` L2GenesisBlockGasUsed hexutil.Uint64 `json:"l2GenesisBlockGasUsed"`
L2GenesisBlockParentHash common.Hash `json:"l2GenesisBlockParentHash"` L2GenesisBlockParentHash common.Hash `json:"l2GenesisBlockParentHash"`
L2GenesisBlockBaseFeePerGas *hexutil.Big `json:"l2GenesisBlockBaseFeePerGas"` L2GenesisBlockBaseFeePerGas *hexutil.Big `json:"l2GenesisBlockBaseFeePerGas"`
// L2GenesisRegolithTimeOffset is the number of seconds after genesis block that Regolith hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Regolith.
L2GenesisRegolithTimeOffset *hexutil.Uint64 `json:"l2GenesisRegolithTimeOffset,omitempty"`
// L2GenesisCanyonTimeOffset is the number of seconds after genesis block that Canyon hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Canyon.
L2GenesisCanyonTimeOffset *hexutil.Uint64 `json:"l2GenesisCanyonTimeOffset,omitempty"`
// L2GenesisDeltaTimeOffset is the number of seconds after genesis block that Delta hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Delta.
L2GenesisDeltaTimeOffset *hexutil.Uint64 `json:"l2GenesisDeltaTimeOffset,omitempty"`
// L2GenesisEcotoneTimeOffset is the number of seconds after genesis block that Ecotone hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Ecotone.
L2GenesisEcotoneTimeOffset *hexutil.Uint64 `json:"l2GenesisEcotoneTimeOffset,omitempty"`
// L2GenesisFjordTimeOffset is the number of seconds after genesis block that Fjord hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Fjord.
L2GenesisFjordTimeOffset *hexutil.Uint64 `json:"l2GenesisFjordTimeOffset,omitempty"`
// L2GenesisInteropTimeOffset is the number of seconds after genesis block that the Interop hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Interop.
L2GenesisInteropTimeOffset *hexutil.Uint64 `json:"l2GenesisInteropTimeOffset,omitempty"`
// L2GenesisBlockExtraData is configurable extradata. Will default to []byte("BEDROCK") if left unspecified. // L2GenesisBlockExtraData is configurable extradata. Will default to []byte("BEDROCK") if left unspecified.
L2GenesisBlockExtraData []byte `json:"l2GenesisBlockExtraData"` L2GenesisBlockExtraData []byte `json:"l2GenesisBlockExtraData"`
// Note that there is no L2 genesis timestamp:
// This is instead configured based on the timestamp of "l1StartingBlockTag".
}
var _ ConfigChecker = (*L2GenesisBlockDeployConfig)(nil)
func (d *L2GenesisBlockDeployConfig) Check(log log.Logger) error {
if d.L2GenesisBlockGasLimit == 0 {
return fmt.Errorf("%w: L2 genesis block gas limit cannot be 0", ErrInvalidDeployConfig)
}
// When the initial resource config is made to be configurable by the DeployConfig, ensure
// that this check is updated to use the values from the DeployConfig instead of the defaults.
if uint64(d.L2GenesisBlockGasLimit) < uint64(MaxResourceLimit+SystemTxMaxGas) {
return fmt.Errorf("%w: L2 genesis block gas limit is too small", ErrInvalidDeployConfig)
}
if d.L2GenesisBlockBaseFeePerGas == nil {
return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig)
}
return nil
}
// OwnershipDeployConfig defines the ownership of an L2 chain deployment.
// This excludes superchain-wide contracts.
type OwnershipDeployConfig struct {
// ProxyAdminOwner represents the owner of the ProxyAdmin predeploy on L2. // ProxyAdminOwner represents the owner of the ProxyAdmin predeploy on L2.
ProxyAdminOwner common.Address `json:"proxyAdminOwner"` ProxyAdminOwner common.Address `json:"proxyAdminOwner"`
// FinalSystemOwner is the owner of the system on L1. Any L1 contract that is ownable has // FinalSystemOwner is the owner of the system on L1. Any L1 contract that is ownable has
// this account set as its owner. // this account set as its owner.
FinalSystemOwner common.Address `json:"finalSystemOwner"` FinalSystemOwner common.Address `json:"finalSystemOwner"`
// SuperchainConfigGuardian represents the GUARDIAN account in the SuperchainConfig. Has the ability to pause withdrawals. }
SuperchainConfigGuardian common.Address `json:"superchainConfigGuardian"`
var _ ConfigChecker = (*OwnershipDeployConfig)(nil)
func (d *OwnershipDeployConfig) Check(log log.Logger) error {
if d.FinalSystemOwner == (common.Address{}) {
return fmt.Errorf("%w: FinalSystemOwner cannot be address(0)", ErrInvalidDeployConfig)
}
if d.ProxyAdminOwner == (common.Address{}) {
return fmt.Errorf("%w: ProxyAdminOwner cannot be address(0)", ErrInvalidDeployConfig)
}
return nil
}
type L2VaultsDeployConfig struct {
// BaseFeeVaultRecipient represents the recipient of fees accumulated in the BaseFeeVault. // BaseFeeVaultRecipient represents the recipient of fees accumulated in the BaseFeeVault.
// Can be an account on L1 or L2, depending on the BaseFeeVaultWithdrawalNetwork value. // Can be an account on L1 or L2, depending on the BaseFeeVaultWithdrawalNetwork value.
BaseFeeVaultRecipient common.Address `json:"baseFeeVaultRecipient"` BaseFeeVaultRecipient common.Address `json:"baseFeeVaultRecipient"`
...@@ -168,21 +160,68 @@ type DeployConfig struct { ...@@ -168,21 +160,68 @@ type DeployConfig struct {
L1FeeVaultWithdrawalNetwork WithdrawalNetwork `json:"l1FeeVaultWithdrawalNetwork"` L1FeeVaultWithdrawalNetwork WithdrawalNetwork `json:"l1FeeVaultWithdrawalNetwork"`
// SequencerFeeVaultWithdrawalNetwork represents the withdrawal network for the SequencerFeeVault. // SequencerFeeVaultWithdrawalNetwork represents the withdrawal network for the SequencerFeeVault.
SequencerFeeVaultWithdrawalNetwork WithdrawalNetwork `json:"sequencerFeeVaultWithdrawalNetwork"` SequencerFeeVaultWithdrawalNetwork WithdrawalNetwork `json:"sequencerFeeVaultWithdrawalNetwork"`
// L1StandardBridgeProxy represents the address of the L1StandardBridgeProxy on L1 and is used }
// as part of building the L2 genesis state.
L1StandardBridgeProxy common.Address `json:"l1StandardBridgeProxy"` var _ ConfigChecker = (*L2VaultsDeployConfig)(nil)
// L1CrossDomainMessengerProxy represents the address of the L1CrossDomainMessengerProxy on L1 and is used
// as part of building the L2 genesis state. func (d *L2VaultsDeployConfig) Check(log log.Logger) error {
L1CrossDomainMessengerProxy common.Address `json:"l1CrossDomainMessengerProxy"` if d.BaseFeeVaultRecipient == (common.Address{}) {
// L1ERC721BridgeProxy represents the address of the L1ERC721Bridge on L1 and is used return fmt.Errorf("%w: BaseFeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
// as part of building the L2 genesis state. }
L1ERC721BridgeProxy common.Address `json:"l1ERC721BridgeProxy"` if d.L1FeeVaultRecipient == (common.Address{}) {
// SystemConfigProxy represents the address of the SystemConfigProxy on L1 and is used return fmt.Errorf("%w: L1FeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
// as part of the derivation pipeline. }
SystemConfigProxy common.Address `json:"systemConfigProxy"` if d.SequencerFeeVaultRecipient == (common.Address{}) {
// OptimismPortalProxy represents the address of the OptimismPortalProxy on L1 and is used return fmt.Errorf("%w: SequencerFeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
// as part of the derivation pipeline. }
OptimismPortalProxy common.Address `json:"optimismPortalProxy"` if !d.BaseFeeVaultWithdrawalNetwork.Valid() {
return fmt.Errorf("%w: BaseFeeVaultWithdrawalNetwork can only be 0 (L1) or 1 (L2)", ErrInvalidDeployConfig)
}
if !d.L1FeeVaultWithdrawalNetwork.Valid() {
return fmt.Errorf("%w: L1FeeVaultWithdrawalNetwork can only be 0 (L1) or 1 (L2)", ErrInvalidDeployConfig)
}
if !d.SequencerFeeVaultWithdrawalNetwork.Valid() {
return fmt.Errorf("%w: SequencerFeeVaultWithdrawalNetwork can only be 0 (L1) or 1 (L2)", ErrInvalidDeployConfig)
}
return nil
}
// GovernanceDeployConfig is exclusive to OP-Mainnet and the testing of OP-Mainnet-like chains.
type GovernanceDeployConfig struct {
// EnableGovernance configures whether or not include governance token predeploy.
EnableGovernance bool `json:"enableGovernance"`
// GovernanceTokenSymbol represents the ERC20 symbol of the GovernanceToken.
GovernanceTokenSymbol string `json:"governanceTokenSymbol"`
// GovernanceTokenName represents the ERC20 name of the GovernanceToken
GovernanceTokenName string `json:"governanceTokenName"`
// GovernanceTokenOwner represents the owner of the GovernanceToken. Has the ability
// to mint and burn tokens.
GovernanceTokenOwner common.Address `json:"governanceTokenOwner"`
}
var _ ConfigChecker = (*GovernanceDeployConfig)(nil)
func (d *GovernanceDeployConfig) Check(log log.Logger) error {
if d.EnableGovernance {
if d.GovernanceTokenName == "" {
return fmt.Errorf("%w: GovernanceToken.name cannot be empty", ErrInvalidDeployConfig)
}
if d.GovernanceTokenSymbol == "" {
return fmt.Errorf("%w: GovernanceToken.symbol cannot be empty", ErrInvalidDeployConfig)
}
if d.GovernanceTokenOwner == (common.Address{}) {
return fmt.Errorf("%w: GovernanceToken owner cannot be address(0)", ErrInvalidDeployConfig)
}
}
return nil
}
func (d *GovernanceDeployConfig) GovernanceEnabled() bool {
return d.EnableGovernance
}
// GasPriceOracleDeployConfig configures the GasPriceOracle L2 predeploy.
type GasPriceOracleDeployConfig struct {
// GasPriceOracleOverhead represents the initial value of the gas overhead in the GasPriceOracle predeploy. // GasPriceOracleOverhead represents the initial value of the gas overhead in the GasPriceOracle predeploy.
// Deprecated: Since Ecotone, this field is superseded by GasPriceOracleBaseFeeScalar and GasPriceOracleBlobBaseFeeScalar. // Deprecated: Since Ecotone, this field is superseded by GasPriceOracleBaseFeeScalar and GasPriceOracleBlobBaseFeeScalar.
GasPriceOracleOverhead uint64 `json:"gasPriceOracleOverhead"` GasPriceOracleOverhead uint64 `json:"gasPriceOracleOverhead"`
...@@ -193,96 +232,115 @@ type DeployConfig struct { ...@@ -193,96 +232,115 @@ type DeployConfig struct {
GasPriceOracleBaseFeeScalar uint32 `json:"gasPriceOracleBaseFeeScalar"` GasPriceOracleBaseFeeScalar uint32 `json:"gasPriceOracleBaseFeeScalar"`
// GasPriceOracleBlobBaseFeeScalar represents the value of the blob base fee scalar used for fee calculations. // GasPriceOracleBlobBaseFeeScalar represents the value of the blob base fee scalar used for fee calculations.
GasPriceOracleBlobBaseFeeScalar uint32 `json:"gasPriceOracleBlobBaseFeeScalar"` GasPriceOracleBlobBaseFeeScalar uint32 `json:"gasPriceOracleBlobBaseFeeScalar"`
// EnableGovernance configures whether or not include governance token predeploy. }
EnableGovernance bool `json:"enableGovernance"`
// GovernanceTokenSymbol represents the ERC20 symbol of the GovernanceToken. var _ ConfigChecker = (*GasPriceOracleDeployConfig)(nil)
GovernanceTokenSymbol string `json:"governanceTokenSymbol"`
// GovernanceTokenName represents the ERC20 name of the GovernanceToken func (d *GasPriceOracleDeployConfig) Check(log log.Logger) error {
GovernanceTokenName string `json:"governanceTokenName"` if d.GasPriceOracleBaseFeeScalar == 0 {
// GovernanceTokenOwner represents the owner of the GovernanceToken. Has the ability log.Warn("GasPriceOracleBaseFeeScalar is 0")
// to mint and burn tokens. }
GovernanceTokenOwner common.Address `json:"governanceTokenOwner"` if d.GasPriceOracleBlobBaseFeeScalar == 0 {
// DeploymentWaitConfirmations is the number of confirmations to wait during log.Warn("GasPriceOracleBlobBaseFeeScalar is 0")
// deployment. This is DEPRECATED and should be removed in a future PR. }
DeploymentWaitConfirmations int `json:"deploymentWaitConfirmations"` return nil
}
// FeeScalar returns the raw serialized fee scalar. Uses pre-Ecotone if legacy config is present,
// otherwise uses the post-Ecotone scalar serialization.
func (d *GasPriceOracleDeployConfig) FeeScalar() [32]byte {
if d.GasPriceOracleScalar != 0 {
return common.BigToHash(big.NewInt(int64(d.GasPriceOracleScalar)))
}
return eth.EncodeScalar(eth.EcotoneScalars{
BlobBaseFeeScalar: d.GasPriceOracleBlobBaseFeeScalar,
BaseFeeScalar: d.GasPriceOracleBaseFeeScalar,
})
}
// GasTokenDeployConfig configures the optional custom gas token functionality.
type GasTokenDeployConfig struct {
// UseCustomGasToken is a flag to indicate that a custom gas token should be used
UseCustomGasToken bool `json:"useCustomGasToken"`
// CustomGasTokenAddress is the address of the ERC20 token to be used to pay for gas on L2.
CustomGasTokenAddress common.Address `json:"customGasTokenAddress"`
}
var _ ConfigChecker = (*GasTokenDeployConfig)(nil)
func (d *GasTokenDeployConfig) Check(log log.Logger) error {
if d.UseCustomGasToken {
if d.CustomGasTokenAddress == (common.Address{}) {
return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig)
}
log.Info("Using custom gas token", "address", d.CustomGasTokenAddress)
}
return nil
}
// OperatorDeployConfig configures the hot-key addresses for operations such as sequencing and batch-submission.
type OperatorDeployConfig struct {
// P2PSequencerAddress is the address of the key the sequencer uses to sign blocks on the P2P layer.
P2PSequencerAddress common.Address `json:"p2pSequencerAddress"`
// BatchSenderAddress represents the initial sequencer account that authorizes batches.
// Transactions sent from this account to the batch inbox address are considered valid.
BatchSenderAddress common.Address `json:"batchSenderAddress"`
}
var _ ConfigChecker = (*OperatorDeployConfig)(nil)
func (d *OperatorDeployConfig) Check(log log.Logger) error {
if d.P2PSequencerAddress == (common.Address{}) {
return fmt.Errorf("%w: P2PSequencerAddress cannot be address(0)", ErrInvalidDeployConfig)
}
if d.BatchSenderAddress == (common.Address{}) {
return fmt.Errorf("%w: BatchSenderAddress cannot be address(0)", ErrInvalidDeployConfig)
}
return nil
}
// EIP1559DeployConfig configures the EIP-1559 parameters of the chain.
type EIP1559DeployConfig struct {
// EIP1559Elasticity is the elasticity of the EIP1559 fee market. // EIP1559Elasticity is the elasticity of the EIP1559 fee market.
EIP1559Elasticity uint64 `json:"eip1559Elasticity"` EIP1559Elasticity uint64 `json:"eip1559Elasticity"`
// EIP1559Denominator is the denominator of EIP1559 base fee market. // EIP1559Denominator is the denominator of EIP1559 base fee market.
EIP1559Denominator uint64 `json:"eip1559Denominator"` EIP1559Denominator uint64 `json:"eip1559Denominator"`
// EIP1559DenominatorCanyon is the denominator of EIP1559 base fee market when Canyon is active. // EIP1559DenominatorCanyon is the denominator of EIP1559 base fee market when Canyon is active.
EIP1559DenominatorCanyon uint64 `json:"eip1559DenominatorCanyon"` EIP1559DenominatorCanyon uint64 `json:"eip1559DenominatorCanyon"`
// SystemConfigStartBlock represents the block at which the op-node should start syncing }
// from. It is an override to set this value on legacy networks where it is not set by
// default. It can be removed once all networks have this value set in their storage.
SystemConfigStartBlock uint64 `json:"systemConfigStartBlock"`
// FaultGameAbsolutePrestate is the absolute prestate of Cannon. This is computed
// by generating a proof from the 0th -> 1st instruction and grabbing the prestate from
// the output JSON. All honest challengers should agree on the setup state of the program.
FaultGameAbsolutePrestate common.Hash `json:"faultGameAbsolutePrestate"`
// FaultGameMaxDepth is the maximum depth of the position tree within the fault dispute game.
// `2^{FaultGameMaxDepth}` is how many instructions the execution trace bisection game
// supports. Ideally, this should be conservatively set so that there is always enough
// room for a full Cannon trace.
FaultGameMaxDepth uint64 `json:"faultGameMaxDepth"`
// FaultGameClockExtension is the amount of time that the dispute game will set the potential grandchild claim's,
// clock to, if the remaining time is less than this value at the time of a claim's creation.
FaultGameClockExtension uint64 `json:"faultGameClockExtension"`
// FaultGameMaxClockDuration is the maximum amount of time that may accumulate on a team's chess clock before they
// may no longer respond.
FaultGameMaxClockDuration uint64 `json:"faultGameMaxClockDuration"`
// FaultGameGenesisBlock is the block number for genesis.
FaultGameGenesisBlock uint64 `json:"faultGameGenesisBlock"`
// FaultGameGenesisOutputRoot is the output root for the genesis block.
FaultGameGenesisOutputRoot common.Hash `json:"faultGameGenesisOutputRoot"`
// FaultGameSplitDepth is the depth at which the fault dispute game splits from output roots to execution trace claims.
FaultGameSplitDepth uint64 `json:"faultGameSplitDepth"`
// FaultGameWithdrawalDelay is the number of seconds that users must wait before withdrawing ETH from a fault game.
FaultGameWithdrawalDelay uint64 `json:"faultGameWithdrawalDelay"`
// PreimageOracleMinProposalSize is the minimum number of bytes that a large preimage oracle proposal can be.
PreimageOracleMinProposalSize uint64 `json:"preimageOracleMinProposalSize"`
// PreimageOracleChallengePeriod is the number of seconds that challengers have to challenge a large preimage proposal.
PreimageOracleChallengePeriod uint64 `json:"preimageOracleChallengePeriod"`
// FundDevAccounts configures whether or not to fund the dev accounts. Should only be used
// during devnet deployments.
FundDevAccounts bool `json:"fundDevAccounts"`
// RequiredProtocolVersion indicates the protocol version that
// nodes are required to adopt, to stay in sync with the network.
RequiredProtocolVersion params.ProtocolVersion `json:"requiredProtocolVersion"`
// RequiredProtocolVersion indicates the protocol version that
// nodes are recommended to adopt, to stay in sync with the network.
RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"`
// ProofMaturityDelaySeconds is the number of seconds that a proof must be
// mature before it can be used to finalize a withdrawal.
ProofMaturityDelaySeconds uint64 `json:"proofMaturityDelaySeconds"`
// DisputeGameFinalityDelaySeconds is an additional number of seconds a
// dispute game must wait before it can be used to finalize a withdrawal.
DisputeGameFinalityDelaySeconds uint64 `json:"disputeGameFinalityDelaySeconds"`
// RespectedGameType is the dispute game type that the OptimismPortal
// contract will respect for finalizing withdrawals.
RespectedGameType uint32 `json:"respectedGameType"`
// UseFaultProofs is a flag that indicates if the system is using fault
// proofs instead of the older output oracle mechanism.
UseFaultProofs bool `json:"useFaultProofs"`
// UseCustomGasToken is a flag to indicate that a custom gas token should be used
UseCustomGasToken bool `json:"useCustomGasToken"`
// CustomGasTokenAddress is the address of the ERC20 token to be used to pay for gas on L2.
CustomGasTokenAddress common.Address `json:"customGasTokenAddress"`
// UsePlasma is a flag that indicates if the system is using op-plasma
UsePlasma bool `json:"usePlasma"`
// DACommitmentType specifies the allowed commitment
DACommitmentType string `json:"daCommitmentType"`
// DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged.
DAChallengeWindow uint64 `json:"daChallengeWindow"`
// DAResolveWindow represents the block interval during which a data availability challenge can be resolved.
DAResolveWindow uint64 `json:"daResolveWindow"`
// DABondSize represents the required bond size to initiate a data availability challenge.
DABondSize uint64 `json:"daBondSize"`
// DAResolverRefundPercentage represents the percentage of the resolving cost to be refunded to the resolver
// such as 100 means 100% refund.
DAResolverRefundPercentage uint64 `json:"daResolverRefundPercentage"`
// DAChallengeProxy represents the L1 address of the DataAvailabilityChallenge contract. var _ ConfigChecker = (*EIP1559DeployConfig)(nil)
DAChallengeProxy common.Address `json:"daChallengeProxy"`
func (d *EIP1559DeployConfig) Check(log log.Logger) error {
if d.EIP1559Denominator == 0 {
return fmt.Errorf("%w: EIP1559Denominator cannot be 0", ErrInvalidDeployConfig)
}
if d.EIP1559Elasticity == 0 {
return fmt.Errorf("%w: EIP1559Elasticity cannot be 0", ErrInvalidDeployConfig)
}
return nil
}
// UpgradeScheduleDeployConfig configures when network upgrades activate.
type UpgradeScheduleDeployConfig struct {
// L2GenesisRegolithTimeOffset is the number of seconds after genesis block that Regolith hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Regolith.
L2GenesisRegolithTimeOffset *hexutil.Uint64 `json:"l2GenesisRegolithTimeOffset,omitempty"`
// L2GenesisCanyonTimeOffset is the number of seconds after genesis block that Canyon hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Canyon.
L2GenesisCanyonTimeOffset *hexutil.Uint64 `json:"l2GenesisCanyonTimeOffset,omitempty"`
// L2GenesisDeltaTimeOffset is the number of seconds after genesis block that Delta hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Delta.
L2GenesisDeltaTimeOffset *hexutil.Uint64 `json:"l2GenesisDeltaTimeOffset,omitempty"`
// L2GenesisEcotoneTimeOffset is the number of seconds after genesis block that Ecotone hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Ecotone.
L2GenesisEcotoneTimeOffset *hexutil.Uint64 `json:"l2GenesisEcotoneTimeOffset,omitempty"`
// L2GenesisFjordTimeOffset is the number of seconds after genesis block that Fjord hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Fjord.
L2GenesisFjordTimeOffset *hexutil.Uint64 `json:"l2GenesisFjordTimeOffset,omitempty"`
// L2GenesisInteropTimeOffset is the number of seconds after genesis block that the Interop hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Interop.
L2GenesisInteropTimeOffset *hexutil.Uint64 `json:"l2GenesisInteropTimeOffset,omitempty"`
// When Cancun activates. Relative to L1 genesis. // When Cancun activates. Relative to L1 genesis.
L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"` L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"`
...@@ -291,43 +349,114 @@ type DeployConfig struct { ...@@ -291,43 +349,114 @@ type DeployConfig struct {
UseInterop bool `json:"useInterop,omitempty"` UseInterop bool `json:"useInterop,omitempty"`
} }
// Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy var _ ConfigChecker = (*UpgradeScheduleDeployConfig)(nil)
// which makes it easier to maintain, we do not need efficiency in this case.
func (d *DeployConfig) Copy() *DeployConfig {
raw, err := json.Marshal(d)
if err != nil {
panic(err)
}
cpy := DeployConfig{} func offsetToUpgradeTime(offset *hexutil.Uint64, genesisTime uint64) *uint64 {
if err = json.Unmarshal(raw, &cpy); err != nil { if offset == nil {
panic(err) return nil
} }
return &cpy v := uint64(0)
if offset := *offset; offset > 0 {
v = genesisTime + uint64(offset)
}
return &v
} }
// Check will ensure that the config is sane and return an error when it is not func (d *UpgradeScheduleDeployConfig) RegolithTime(genesisTime uint64) *uint64 {
func (d *DeployConfig) Check() error { return offsetToUpgradeTime(d.L2GenesisRegolithTimeOffset, genesisTime)
if d.L1StartingBlockTag == nil { }
return fmt.Errorf("%w: L1StartingBlockTag cannot be nil", ErrInvalidDeployConfig)
func (d *UpgradeScheduleDeployConfig) CanyonTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisCanyonTimeOffset, genesisTime)
}
func (d *UpgradeScheduleDeployConfig) DeltaTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisDeltaTimeOffset, genesisTime)
}
func (d *UpgradeScheduleDeployConfig) EcotoneTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisEcotoneTimeOffset, genesisTime)
}
func (d *UpgradeScheduleDeployConfig) FjordTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisFjordTimeOffset, genesisTime)
}
func (d *UpgradeScheduleDeployConfig) InteropTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisInteropTimeOffset, genesisTime)
}
func (d *UpgradeScheduleDeployConfig) Check(log log.Logger) error {
// checkFork checks that fork A is before or at the same time as fork B
checkFork := func(a, b *hexutil.Uint64, aName, bName string) error {
if a == nil && b == nil {
return nil
}
if a == nil && b != nil {
return fmt.Errorf("fork %s set (to %d), but prior fork %s missing", bName, *b, aName)
}
if a != nil && b == nil {
return nil
}
if *a > *b {
return fmt.Errorf("fork %s set to %d, but prior fork %s has higher offset %d", bName, *b, aName, *a)
}
return nil
} }
if d.L1ChainID == 0 { if err := checkFork(d.L2GenesisRegolithTimeOffset, d.L2GenesisCanyonTimeOffset, "regolith", "canyon"); err != nil {
return fmt.Errorf("%w: L1ChainID cannot be 0", ErrInvalidDeployConfig) return err
} }
if d.L2ChainID == 0 { if err := checkFork(d.L2GenesisCanyonTimeOffset, d.L2GenesisDeltaTimeOffset, "canyon", "delta"); err != nil {
return fmt.Errorf("%w: L2ChainID cannot be 0", ErrInvalidDeployConfig) return err
} }
if d.L2BlockTime == 0 { if err := checkFork(d.L2GenesisDeltaTimeOffset, d.L2GenesisEcotoneTimeOffset, "delta", "ecotone"); err != nil {
return fmt.Errorf("%w: L2BlockTime cannot be 0", ErrInvalidDeployConfig) return err
} }
if d.FinalizationPeriodSeconds == 0 { if err := checkFork(d.L2GenesisEcotoneTimeOffset, d.L2GenesisFjordTimeOffset, "ecotone", "fjord"); err != nil {
return fmt.Errorf("%w: FinalizationPeriodSeconds cannot be 0", ErrInvalidDeployConfig) return err
} }
if d.L2OutputOracleStartingBlockNumber == 0 { return nil
log.Warn("L2OutputOracleStartingBlockNumber is 0, should only be 0 for fresh chains") }
// L2CoreDeployConfig configures the core protocol parameters of the chain.
type L2CoreDeployConfig struct {
// L1ChainID is the chain ID of the L1 chain.
L1ChainID uint64 `json:"l1ChainID"`
// L2ChainID is the chain ID of the L2 chain.
L2ChainID uint64 `json:"l2ChainID"`
// L2BlockTime is the number of seconds between each L2 block.
L2BlockTime uint64 `json:"l2BlockTime"`
// FinalizationPeriodSeconds represents the number of seconds before an output is considered
// finalized. This impacts the amount of time that withdrawals take to finalize and is
// generally set to 1 week.
FinalizationPeriodSeconds uint64 `json:"finalizationPeriodSeconds"`
// MaxSequencerDrift is the number of seconds after the L1 timestamp of the end of the
// sequencing window that batches must be included, otherwise L2 blocks including
// deposits are force included.
MaxSequencerDrift uint64 `json:"maxSequencerDrift"`
// SequencerWindowSize is the number of L1 blocks per sequencing window.
SequencerWindowSize uint64 `json:"sequencerWindowSize"`
// ChannelTimeout is the number of L1 blocks that a frame stays valid when included in L1.
ChannelTimeout uint64 `json:"channelTimeout"`
// BatchInboxAddress is the L1 account that batches are sent to.
BatchInboxAddress common.Address `json:"batchInboxAddress"`
// SystemConfigStartBlock represents the block at which the op-node should start syncing
// from. It is an override to set this value on legacy networks where it is not set by
// default. It can be removed once all networks have this value set in their storage.
SystemConfigStartBlock uint64 `json:"systemConfigStartBlock"`
}
var _ ConfigChecker = (*L2CoreDeployConfig)(nil)
func (d *L2CoreDeployConfig) Check(log log.Logger) error {
if d.L1ChainID == 0 {
return fmt.Errorf("%w: L1ChainID cannot be 0", ErrInvalidDeployConfig)
} }
if d.SuperchainConfigGuardian == (common.Address{}) { if d.L2ChainID == 0 {
return fmt.Errorf("%w: SuperchainConfigGuardian cannot be address(0)", ErrInvalidDeployConfig) return fmt.Errorf("%w: L2ChainID cannot be 0", ErrInvalidDeployConfig)
} }
if d.MaxSequencerDrift == 0 { if d.MaxSequencerDrift == 0 {
return fmt.Errorf("%w: MaxSequencerDrift cannot be 0", ErrInvalidDeployConfig) return fmt.Errorf("%w: MaxSequencerDrift cannot be 0", ErrInvalidDeployConfig)
...@@ -338,172 +467,243 @@ func (d *DeployConfig) Check() error { ...@@ -338,172 +467,243 @@ func (d *DeployConfig) Check() error {
if d.ChannelTimeout == 0 { if d.ChannelTimeout == 0 {
return fmt.Errorf("%w: ChannelTimeout cannot be 0", ErrInvalidDeployConfig) return fmt.Errorf("%w: ChannelTimeout cannot be 0", ErrInvalidDeployConfig)
} }
if d.P2PSequencerAddress == (common.Address{}) {
return fmt.Errorf("%w: P2PSequencerAddress cannot be address(0)", ErrInvalidDeployConfig)
}
if d.BatchInboxAddress == (common.Address{}) { if d.BatchInboxAddress == (common.Address{}) {
return fmt.Errorf("%w: BatchInboxAddress cannot be address(0)", ErrInvalidDeployConfig) return fmt.Errorf("%w: BatchInboxAddress cannot be address(0)", ErrInvalidDeployConfig)
} }
if d.BatchSenderAddress == (common.Address{}) { if d.L2BlockTime == 0 {
return fmt.Errorf("%w: BatchSenderAddress cannot be address(0)", ErrInvalidDeployConfig) return fmt.Errorf("%w: L2BlockTime cannot be 0", ErrInvalidDeployConfig)
}
if d.L2OutputOracleSubmissionInterval == 0 {
return fmt.Errorf("%w: L2OutputOracleSubmissionInterval cannot be 0", ErrInvalidDeployConfig)
}
if d.L2OutputOracleStartingTimestamp == 0 {
log.Warn("L2OutputOracleStartingTimestamp is 0")
}
if d.L2OutputOracleProposer == (common.Address{}) {
return fmt.Errorf("%w: L2OutputOracleProposer cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L2OutputOracleChallenger == (common.Address{}) {
return fmt.Errorf("%w: L2OutputOracleChallenger cannot be address(0)", ErrInvalidDeployConfig)
}
if d.FinalSystemOwner == (common.Address{}) {
return fmt.Errorf("%w: FinalSystemOwner cannot be address(0)", ErrInvalidDeployConfig)
}
if d.ProxyAdminOwner == (common.Address{}) {
return fmt.Errorf("%w: ProxyAdminOwner cannot be address(0)", ErrInvalidDeployConfig)
}
if d.BaseFeeVaultRecipient == (common.Address{}) {
return fmt.Errorf("%w: BaseFeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
}
if d.L1FeeVaultRecipient == (common.Address{}) {
return fmt.Errorf("%w: L1FeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
}
if d.SequencerFeeVaultRecipient == (common.Address{}) {
return fmt.Errorf("%w: SequencerFeeVaultRecipient cannot be address(0)", ErrInvalidDeployConfig)
}
if !d.BaseFeeVaultWithdrawalNetwork.Valid() {
return fmt.Errorf("%w: BaseFeeVaultWithdrawalNetwork can only be 0 (L1) or 1 (L2)", ErrInvalidDeployConfig)
}
if !d.L1FeeVaultWithdrawalNetwork.Valid() {
return fmt.Errorf("%w: L1FeeVaultWithdrawalNetwork can only be 0 (L1) or 1 (L2)", ErrInvalidDeployConfig)
}
if !d.SequencerFeeVaultWithdrawalNetwork.Valid() {
return fmt.Errorf("%w: SequencerFeeVaultWithdrawalNetwork can only be 0 (L1) or 1 (L2)", ErrInvalidDeployConfig)
}
if d.GasPriceOracleBaseFeeScalar == 0 {
log.Warn("GasPriceOracleBaseFeeScalar is 0")
}
if d.GasPriceOracleBlobBaseFeeScalar == 0 {
log.Warn("GasPriceOracleBlobBaseFeeScalar is 0")
}
if d.EIP1559Denominator == 0 {
return fmt.Errorf("%w: EIP1559Denominator cannot be 0", ErrInvalidDeployConfig)
}
if d.L2GenesisCanyonTimeOffset != nil && d.EIP1559DenominatorCanyon == 0 {
return fmt.Errorf("%w: EIP1559DenominatorCanyon cannot be 0 if Canyon is activated", ErrInvalidDeployConfig)
}
if d.EIP1559Elasticity == 0 {
return fmt.Errorf("%w: EIP1559Elasticity cannot be 0", ErrInvalidDeployConfig)
}
if d.L2GenesisBlockGasLimit == 0 {
return fmt.Errorf("%w: L2 genesis block gas limit cannot be 0", ErrInvalidDeployConfig)
}
// When the initial resource config is made to be configurable by the DeployConfig, ensure
// that this check is updated to use the values from the DeployConfig instead of the defaults.
if uint64(d.L2GenesisBlockGasLimit) < uint64(MaxResourceLimit+SystemTxMaxGas) {
return fmt.Errorf("%w: L2 genesis block gas limit is too small", ErrInvalidDeployConfig)
} }
if d.L2GenesisBlockBaseFeePerGas == nil { if d.FinalizationPeriodSeconds == 0 {
return fmt.Errorf("%w: L2 genesis block base fee per gas cannot be nil", ErrInvalidDeployConfig) return fmt.Errorf("%w: FinalizationPeriodSeconds cannot be 0", ErrInvalidDeployConfig)
} }
if d.EnableGovernance { return nil
if d.GovernanceTokenName == "" { }
return fmt.Errorf("%w: GovernanceToken.name cannot be empty", ErrInvalidDeployConfig)
} // PlasmaDeployConfig configures optional Alt-DA and Plasma functionality.
if d.GovernanceTokenSymbol == "" { type PlasmaDeployConfig struct {
return fmt.Errorf("%w: GovernanceToken.symbol cannot be empty", ErrInvalidDeployConfig) // UsePlasma is a flag that indicates if the system is using op-plasma
UsePlasma bool `json:"usePlasma"`
// DACommitmentType specifies the allowed commitment
DACommitmentType string `json:"daCommitmentType"`
// DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged.
DAChallengeWindow uint64 `json:"daChallengeWindow"`
// DAResolveWindow represents the block interval during which a data availability challenge can be resolved.
DAResolveWindow uint64 `json:"daResolveWindow"`
// DABondSize represents the required bond size to initiate a data availability challenge.
DABondSize uint64 `json:"daBondSize"`
// DAResolverRefundPercentage represents the percentage of the resolving cost to be refunded to the resolver
// such as 100 means 100% refund.
DAResolverRefundPercentage uint64 `json:"daResolverRefundPercentage"`
}
var _ ConfigChecker = (*PlasmaDeployConfig)(nil)
func (d *PlasmaDeployConfig) Check(log log.Logger) error {
if d.UsePlasma {
if !(d.DACommitmentType == plasma.KeccakCommitmentString || d.DACommitmentType == plasma.GenericCommitmentString) {
return fmt.Errorf("%w: DACommitmentType must be either KeccakCommitment or GenericCommitment", ErrInvalidDeployConfig)
} }
if d.GovernanceTokenOwner == (common.Address{}) { // only enforce challenge and resolve window if using alt-da mode with Keccak Commitments
return fmt.Errorf("%w: GovernanceToken owner cannot be address(0)", ErrInvalidDeployConfig) if d.DACommitmentType != plasma.GenericCommitmentString {
if d.DAChallengeWindow == 0 {
return fmt.Errorf("%w: DAChallengeWindow cannot be 0 when using alt-da mode with Keccak Commitments", ErrInvalidDeployConfig)
}
if d.DAResolveWindow == 0 {
return fmt.Errorf("%w: DAResolveWindow cannot be 0 when using alt-da mode with Keccak Commitments", ErrInvalidDeployConfig)
}
} }
} }
// L2 block time must always be smaller than L1 block time return nil
if d.L1BlockTime < d.L2BlockTime { }
return fmt.Errorf("L2 block time (%d) is larger than L1 block time (%d)", d.L2BlockTime, d.L1BlockTime)
} // L2InitializationConfig represents all L2 configuration
// data that can be configured before the deployment of any L1 contracts.
type L2InitializationConfig struct {
DevDeployConfig
L2GenesisBlockDeployConfig
OwnershipDeployConfig
L2VaultsDeployConfig
GovernanceDeployConfig
GasPriceOracleDeployConfig
GasTokenDeployConfig
OperatorDeployConfig
EIP1559DeployConfig
UpgradeScheduleDeployConfig
L2CoreDeployConfig
PlasmaDeployConfig
}
func (d *L2InitializationConfig) Check(log log.Logger) error {
return checkConfigBundle(d, log)
}
// DevL1DeployConfig is used to configure a L1 chain for development/testing purposes.
// A production L2 deployment does not utilize this configuration,
// except of a L1BlockTime sanity-check (set this to 12 for L1 Ethereum).
type DevL1DeployConfig struct {
L1BlockTime uint64 `json:"l1BlockTime"`
L1GenesisBlockTimestamp hexutil.Uint64 `json:"l1GenesisBlockTimestamp"`
L1GenesisBlockNonce hexutil.Uint64 `json:"l1GenesisBlockNonce"`
L1GenesisBlockGasLimit hexutil.Uint64 `json:"l1GenesisBlockGasLimit"`
L1GenesisBlockDifficulty *hexutil.Big `json:"l1GenesisBlockDifficulty"`
L1GenesisBlockMixHash common.Hash `json:"l1GenesisBlockMixHash"`
L1GenesisBlockCoinbase common.Address `json:"l1GenesisBlockCoinbase"`
L1GenesisBlockNumber hexutil.Uint64 `json:"l1GenesisBlockNumber"`
L1GenesisBlockGasUsed hexutil.Uint64 `json:"l1GenesisBlockGasUsed"`
L1GenesisBlockParentHash common.Hash `json:"l1GenesisBlockParentHash"`
L1GenesisBlockBaseFeePerGas *hexutil.Big `json:"l1GenesisBlockBaseFeePerGas"`
}
// SuperchainL1DeployConfig configures parameters of the superchain-wide deployed contracts to L1.
// This deployment is global, and can be reused between L2s that target the same superchain.
type SuperchainL1DeployConfig struct {
// RequiredProtocolVersion indicates the protocol version that
// nodes are required to adopt, to stay in sync with the network.
RequiredProtocolVersion params.ProtocolVersion `json:"requiredProtocolVersion"`
// RequiredProtocolVersion indicates the protocol version that
// nodes are recommended to adopt, to stay in sync with the network.
RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"`
// SuperchainConfigGuardian represents the GUARDIAN account in the SuperchainConfig. Has the ability to pause withdrawals.
SuperchainConfigGuardian common.Address `json:"superchainConfigGuardian"`
}
func (d *SuperchainL1DeployConfig) Check(log log.Logger) error {
if d.RequiredProtocolVersion == (params.ProtocolVersion{}) { if d.RequiredProtocolVersion == (params.ProtocolVersion{}) {
log.Warn("RequiredProtocolVersion is empty") log.Warn("RequiredProtocolVersion is empty")
} }
if d.RecommendedProtocolVersion == (params.ProtocolVersion{}) { if d.RecommendedProtocolVersion == (params.ProtocolVersion{}) {
log.Warn("RecommendedProtocolVersion is empty") log.Warn("RecommendedProtocolVersion is empty")
} }
if d.ProofMaturityDelaySeconds == 0 { if d.SuperchainConfigGuardian == (common.Address{}) {
log.Warn("ProofMaturityDelaySeconds is 0") return fmt.Errorf("%w: SuperchainConfigGuardian cannot be address(0)", ErrInvalidDeployConfig)
}
if d.DisputeGameFinalityDelaySeconds == 0 {
log.Warn("DisputeGameFinalityDelaySeconds is 0")
} }
if d.UsePlasma { return nil
if !(d.DACommitmentType == plasma.KeccakCommitmentString || d.DACommitmentType == plasma.GenericCommitmentString) { }
return fmt.Errorf("%w: DACommitmentType must be either KeccakCommitment or GenericCommitment", ErrInvalidDeployConfig)
} // OutputOracleDeployConfig configures the legacy OutputOracle deployment to L1.
// only enforce challenge and resolve window if using alt-da mode with Keccak Commitments // This is obsoleted with Fault Proofs. See FaultProofDeployConfig.
if d.DACommitmentType != plasma.GenericCommitmentString { type OutputOracleDeployConfig struct {
if d.DAChallengeWindow == 0 { // L2OutputOracleSubmissionInterval is the number of L2 blocks between outputs that are submitted
return fmt.Errorf("%w: DAChallengeWindow cannot be 0 when using alt-da mode with Keccak Commitments", ErrInvalidDeployConfig) // to the L2OutputOracle contract located on L1.
} L2OutputOracleSubmissionInterval uint64 `json:"l2OutputOracleSubmissionInterval"`
if d.DAResolveWindow == 0 { // L2OutputOracleStartingTimestamp is the starting timestamp for the L2OutputOracle.
return fmt.Errorf("%w: DAResolveWindow cannot be 0 when using alt-da mode with Keccak Commitments", ErrInvalidDeployConfig) // MUST be the same as the timestamp of the L2OO start block.
} L2OutputOracleStartingTimestamp int `json:"l2OutputOracleStartingTimestamp"`
} // L2OutputOracleStartingBlockNumber is the starting block number for the L2OutputOracle.
// Must be greater than or equal to the first Bedrock block. The first L2 output will correspond
// to this value plus the submission interval.
L2OutputOracleStartingBlockNumber uint64 `json:"l2OutputOracleStartingBlockNumber"`
// L2OutputOracleProposer is the address of the account that proposes L2 outputs.
L2OutputOracleProposer common.Address `json:"l2OutputOracleProposer"`
// L2OutputOracleChallenger is the address of the account that challenges L2 outputs.
L2OutputOracleChallenger common.Address `json:"l2OutputOracleChallenger"`
}
func (d *OutputOracleDeployConfig) Check(log log.Logger) error {
if d.L2OutputOracleSubmissionInterval == 0 {
return fmt.Errorf("%w: L2OutputOracleSubmissionInterval cannot be 0", ErrInvalidDeployConfig)
} }
if d.UseCustomGasToken { if d.L2OutputOracleStartingTimestamp == 0 {
if d.CustomGasTokenAddress == (common.Address{}) { log.Warn("L2OutputOracleStartingTimestamp is 0")
return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig)
}
log.Info("Using custom gas token", "address", d.CustomGasTokenAddress)
} }
// checkFork checks that fork A is before or at the same time as fork B if d.L2OutputOracleProposer == (common.Address{}) {
checkFork := func(a, b *hexutil.Uint64, aName, bName string) error { return fmt.Errorf("%w: L2OutputOracleProposer cannot be address(0)", ErrInvalidDeployConfig)
if a == nil && b == nil {
return nil
}
if a == nil && b != nil {
return fmt.Errorf("fork %s set (to %d), but prior fork %s missing", bName, *b, aName)
}
if a != nil && b == nil {
return nil
}
if *a > *b {
return fmt.Errorf("fork %s set to %d, but prior fork %s has higher offset %d", bName, *b, aName, *a)
}
return nil
} }
if err := checkFork(d.L2GenesisRegolithTimeOffset, d.L2GenesisCanyonTimeOffset, "regolith", "canyon"); err != nil { if d.L2OutputOracleChallenger == (common.Address{}) {
return err return fmt.Errorf("%w: L2OutputOracleChallenger cannot be address(0)", ErrInvalidDeployConfig)
} }
if err := checkFork(d.L2GenesisCanyonTimeOffset, d.L2GenesisDeltaTimeOffset, "canyon", "delta"); err != nil { if d.L2OutputOracleStartingBlockNumber == 0 {
return err log.Warn("L2OutputOracleStartingBlockNumber is 0, should only be 0 for fresh chains")
} }
if err := checkFork(d.L2GenesisDeltaTimeOffset, d.L2GenesisEcotoneTimeOffset, "delta", "ecotone"); err != nil { return nil
return err }
// FaultProofDeployConfig configures the fault-proof deployment to L1.
type FaultProofDeployConfig struct {
// UseFaultProofs is a flag that indicates if the system is using fault
// proofs instead of the older output oracle mechanism.
UseFaultProofs bool `json:"useFaultProofs"`
// FaultGameAbsolutePrestate is the absolute prestate of Cannon. This is computed
// by generating a proof from the 0th -> 1st instruction and grabbing the prestate from
// the output JSON. All honest challengers should agree on the setup state of the program.
FaultGameAbsolutePrestate common.Hash `json:"faultGameAbsolutePrestate"`
// FaultGameMaxDepth is the maximum depth of the position tree within the fault dispute game.
// `2^{FaultGameMaxDepth}` is how many instructions the execution trace bisection game
// supports. Ideally, this should be conservatively set so that there is always enough
// room for a full Cannon trace.
FaultGameMaxDepth uint64 `json:"faultGameMaxDepth"`
// FaultGameClockExtension is the amount of time that the dispute game will set the potential grandchild claim's,
// clock to, if the remaining time is less than this value at the time of a claim's creation.
FaultGameClockExtension uint64 `json:"faultGameClockExtension"`
// FaultGameMaxClockDuration is the maximum amount of time that may accumulate on a team's chess clock before they
// may no longer respond.
FaultGameMaxClockDuration uint64 `json:"faultGameMaxClockDuration"`
// FaultGameGenesisBlock is the block number for genesis.
FaultGameGenesisBlock uint64 `json:"faultGameGenesisBlock"`
// FaultGameGenesisOutputRoot is the output root for the genesis block.
FaultGameGenesisOutputRoot common.Hash `json:"faultGameGenesisOutputRoot"`
// FaultGameSplitDepth is the depth at which the fault dispute game splits from output roots to execution trace claims.
FaultGameSplitDepth uint64 `json:"faultGameSplitDepth"`
// FaultGameWithdrawalDelay is the number of seconds that users must wait before withdrawing ETH from a fault game.
FaultGameWithdrawalDelay uint64 `json:"faultGameWithdrawalDelay"`
// PreimageOracleMinProposalSize is the minimum number of bytes that a large preimage oracle proposal can be.
PreimageOracleMinProposalSize uint64 `json:"preimageOracleMinProposalSize"`
// PreimageOracleChallengePeriod is the number of seconds that challengers have to challenge a large preimage proposal.
PreimageOracleChallengePeriod uint64 `json:"preimageOracleChallengePeriod"`
// ProofMaturityDelaySeconds is the number of seconds that a proof must be
// mature before it can be used to finalize a withdrawal.
ProofMaturityDelaySeconds uint64 `json:"proofMaturityDelaySeconds"`
// DisputeGameFinalityDelaySeconds is an additional number of seconds a
// dispute game must wait before it can be used to finalize a withdrawal.
DisputeGameFinalityDelaySeconds uint64 `json:"disputeGameFinalityDelaySeconds"`
// RespectedGameType is the dispute game type that the OptimismPortal
// contract will respect for finalizing withdrawals.
RespectedGameType uint32 `json:"respectedGameType"`
}
func (d *FaultProofDeployConfig) Check(log log.Logger) error {
if d.ProofMaturityDelaySeconds == 0 {
log.Warn("ProofMaturityDelaySeconds is 0")
} }
if err := checkFork(d.L2GenesisEcotoneTimeOffset, d.L2GenesisFjordTimeOffset, "ecotone", "fjord"); err != nil { if d.DisputeGameFinalityDelaySeconds == 0 {
return err log.Warn("DisputeGameFinalityDelaySeconds is 0")
} }
return nil return nil
} }
// FeeScalar returns the raw serialized fee scalar. Uses pre-Ecotone if legacy config is present, // L1DependenciesConfig is the set of addresses that affect the L2 genesis construction,
// otherwise uses the post-Ecotone scalar serialization. // and is dependent on prior deployment of contracts to L1. This is generally not configured in deploy-config JSON,
func (d *DeployConfig) FeeScalar() [32]byte { // but rather merged in through a L1 deployments JSON file.
if d.GasPriceOracleScalar != 0 { type L1DependenciesConfig struct {
return common.BigToHash(big.NewInt(int64(d.GasPriceOracleScalar))) // L1StandardBridgeProxy represents the address of the L1StandardBridgeProxy on L1 and is used
} // as part of building the L2 genesis state.
return eth.EncodeScalar(eth.EcotoneScalars{ L1StandardBridgeProxy common.Address `json:"l1StandardBridgeProxy"`
BlobBaseFeeScalar: d.GasPriceOracleBlobBaseFeeScalar, // L1CrossDomainMessengerProxy represents the address of the L1CrossDomainMessengerProxy on L1 and is used
BaseFeeScalar: d.GasPriceOracleBaseFeeScalar, // as part of building the L2 genesis state.
}) L1CrossDomainMessengerProxy common.Address `json:"l1CrossDomainMessengerProxy"`
// L1ERC721BridgeProxy represents the address of the L1ERC721Bridge on L1 and is used
// as part of building the L2 genesis state.
L1ERC721BridgeProxy common.Address `json:"l1ERC721BridgeProxy"`
// SystemConfigProxy represents the address of the SystemConfigProxy on L1 and is used
// as part of the derivation pipeline.
SystemConfigProxy common.Address `json:"systemConfigProxy"`
// OptimismPortalProxy represents the address of the OptimismPortalProxy on L1 and is used
// as part of the derivation pipeline.
OptimismPortalProxy common.Address `json:"optimismPortalProxy"`
// DAChallengeProxy represents the L1 address of the DataAvailabilityChallenge contract.
DAChallengeProxy common.Address `json:"daChallengeProxy"`
} }
// CheckAddresses will return an error if the addresses are not set. // DependencyContext is the contextual configuration needed to verify the L1 dependencies,
// These values are required to create the L2 genesis state and are present in the deploy config // used by DeployConfig.CheckAddresses.
// even though the deploy config is required to deploy the contracts on L1. This creates a type DependencyContext struct {
// circular dependency that should be resolved in the future. UsePlasma bool
func (d *DeployConfig) CheckAddresses() error { DACommitmentType string
}
func (d *L1DependenciesConfig) CheckAddresses(dependencyContext DependencyContext) error {
if d.L1StandardBridgeProxy == (common.Address{}) { if d.L1StandardBridgeProxy == (common.Address{}) {
return fmt.Errorf("%w: L1StandardBridgeProxy cannot be address(0)", ErrInvalidDeployConfig) return fmt.Errorf("%w: L1StandardBridgeProxy cannot be address(0)", ErrInvalidDeployConfig)
} }
...@@ -519,92 +719,112 @@ func (d *DeployConfig) CheckAddresses() error { ...@@ -519,92 +719,112 @@ func (d *DeployConfig) CheckAddresses() error {
if d.OptimismPortalProxy == (common.Address{}) { if d.OptimismPortalProxy == (common.Address{}) {
return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig) return fmt.Errorf("%w: OptimismPortalProxy cannot be address(0)", ErrInvalidDeployConfig)
} }
if d.UsePlasma && d.DACommitmentType == plasma.KeccakCommitmentString && d.DAChallengeProxy == (common.Address{}) {
if dependencyContext.UsePlasma && dependencyContext.DACommitmentType == plasma.KeccakCommitmentString && d.DAChallengeProxy == (common.Address{}) {
return fmt.Errorf("%w: DAChallengeContract cannot be address(0) when using alt-da mode", ErrInvalidDeployConfig) return fmt.Errorf("%w: DAChallengeContract cannot be address(0) when using alt-da mode", ErrInvalidDeployConfig)
} else if d.UsePlasma && d.DACommitmentType == plasma.GenericCommitmentString && d.DAChallengeProxy != (common.Address{}) { } else if dependencyContext.UsePlasma && dependencyContext.DACommitmentType == plasma.GenericCommitmentString && d.DAChallengeProxy != (common.Address{}) {
return fmt.Errorf("%w: DAChallengeContract must be address(0) when using generic commitments in alt-da mode", ErrInvalidDeployConfig) return fmt.Errorf("%w: DAChallengeContract must be address(0) when using generic commitments in alt-da mode", ErrInvalidDeployConfig)
} }
return nil return nil
} }
// SetDeployments will merge a Deployments into a DeployConfig. // LegacyDeployConfig retains legacy DeployConfig attributes.
func (d *DeployConfig) SetDeployments(deployments *L1Deployments) { // The genesis generation may log warnings, do a best-effort support attempt,
d.L1StandardBridgeProxy = deployments.L1StandardBridgeProxy // or ignore these attributes completely.
d.L1CrossDomainMessengerProxy = deployments.L1CrossDomainMessengerProxy type LegacyDeployConfig struct {
d.L1ERC721BridgeProxy = deployments.L1ERC721BridgeProxy // CliqueSignerAddress represents the signer address for the clique consensus engine.
d.SystemConfigProxy = deployments.SystemConfigProxy // It is used in the multi-process devnet to sign blocks.
d.OptimismPortalProxy = deployments.OptimismPortalProxy CliqueSignerAddress common.Address `json:"cliqueSignerAddress"`
d.DAChallengeProxy = deployments.DataAvailabilityChallengeProxy // L1UseClique represents whether or not to use the clique consensus engine.
} L1UseClique bool `json:"l1UseClique"`
func (d *DeployConfig) GovernanceEnabled() bool { // DeploymentWaitConfirmations is the number of confirmations to wait during
return d.EnableGovernance // deployment. This is DEPRECATED and should be removed in a future PR.
DeploymentWaitConfirmations int `json:"deploymentWaitConfirmations"`
} }
func (d *DeployConfig) RegolithTime(genesisTime uint64) *uint64 { // DeployConfig represents the deployment configuration for an OP Stack chain.
if d.L2GenesisRegolithTimeOffset == nil { // It is used to deploy the L1 contracts as well as create the L2 genesis state.
return nil type DeployConfig struct {
} // Pre-L1-deployment L2 configs
v := uint64(0) L2InitializationConfig
if offset := *d.L2GenesisRegolithTimeOffset; offset > 0 {
v = genesisTime + uint64(offset) // Development purposes only
} DevL1DeployConfig
return &v
// L1StartingBlockTag anchors the L2 at an L1 block.
// The timestamp of the block referenced by l1StartingBlockTag is used
// in the L2 genesis block, rollup-config, and L1 output-oracle contract.
// The Output oracle deploy script may use it if the L2 starting timestamp is nil, assuming the L2 genesis is set up with this.
// The L2 genesis timestamp does not affect the initial L2 account state:
// the storage of the L1Block contract at genesis is zeroed, since the adoption of
// the L2-genesis allocs-generation through solidity script.
L1StartingBlockTag *MarshalableRPCBlockNumberOrHash `json:"l1StartingBlockTag"`
// L1 contracts configuration.
// The deployer of the contracts chooses which sub-systems to deploy.
SuperchainL1DeployConfig
OutputOracleDeployConfig
FaultProofDeployConfig
// Post-L1-deployment L2 configs
L1DependenciesConfig
// Legacy, ignored, here for strict-JSON decoding to be accepted.
LegacyDeployConfig
} }
func (d *DeployConfig) CanyonTime(genesisTime uint64) *uint64 { // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
if d.L2GenesisCanyonTimeOffset == nil { // which makes it easier to maintain, we do not need efficiency in this case.
return nil func (d *DeployConfig) Copy() *DeployConfig {
raw, err := json.Marshal(d)
if err != nil {
panic(err)
} }
v := uint64(0)
if offset := *d.L2GenesisCanyonTimeOffset; offset > 0 { cpy := DeployConfig{}
v = genesisTime + uint64(offset) if err = json.Unmarshal(raw, &cpy); err != nil {
panic(err)
} }
return &v return &cpy
} }
func (d *DeployConfig) DeltaTime(genesisTime uint64) *uint64 { // Check will ensure that the config is sane and return an error when it is not
if d.L2GenesisDeltaTimeOffset == nil { func (d *DeployConfig) Check(log log.Logger) error {
return nil if d.L1StartingBlockTag == nil {
} return fmt.Errorf("%w: L1StartingBlockTag cannot be nil", ErrInvalidDeployConfig)
v := uint64(0)
if offset := *d.L2GenesisDeltaTimeOffset; offset > 0 {
v = genesisTime + uint64(offset)
} }
return &v
}
func (d *DeployConfig) EcotoneTime(genesisTime uint64) *uint64 { if d.L2GenesisCanyonTimeOffset != nil && d.EIP1559DenominatorCanyon == 0 {
if d.L2GenesisEcotoneTimeOffset == nil { return fmt.Errorf("%w: EIP1559DenominatorCanyon cannot be 0 if Canyon is activated", ErrInvalidDeployConfig)
return nil
} }
v := uint64(0) // L2 block time must always be smaller than L1 block time
if offset := *d.L2GenesisEcotoneTimeOffset; offset > 0 { if d.L1BlockTime < d.L2BlockTime {
v = genesisTime + uint64(offset) return fmt.Errorf("L2 block time (%d) is larger than L1 block time (%d)", d.L2BlockTime, d.L1BlockTime)
} }
return &v
return checkConfigBundle(d, log)
} }
func (d *DeployConfig) FjordTime(genesisTime uint64) *uint64 { // CheckAddresses will return an error if the addresses are not set.
if d.L2GenesisFjordTimeOffset == nil { // These values are required to create the L2 genesis state and are present in the deploy config
return nil // even though the deploy config is required to deploy the contracts on L1. This creates a
} // circular dependency that should be resolved in the future.
v := uint64(0) func (d *DeployConfig) CheckAddresses() error {
if offset := *d.L2GenesisFjordTimeOffset; offset > 0 { return d.L1DependenciesConfig.CheckAddresses(DependencyContext{
v = genesisTime + uint64(offset) UsePlasma: d.UsePlasma,
} DACommitmentType: d.DACommitmentType,
return &v })
} }
func (d *DeployConfig) InteropTime(genesisTime uint64) *uint64 { // SetDeployments will merge a Deployments into a DeployConfig.
if d.L2GenesisInteropTimeOffset == nil { func (d *DeployConfig) SetDeployments(deployments *L1Deployments) {
return nil d.L1StandardBridgeProxy = deployments.L1StandardBridgeProxy
} d.L1CrossDomainMessengerProxy = deployments.L1CrossDomainMessengerProxy
v := uint64(0) d.L1ERC721BridgeProxy = deployments.L1ERC721BridgeProxy
if offset := *d.L2GenesisInteropTimeOffset; offset > 0 { d.SystemConfigProxy = deployments.SystemConfigProxy
v = genesisTime + uint64(offset) d.OptimismPortalProxy = deployments.OptimismPortalProxy
} d.DAChallengeProxy = deployments.DataAvailabilityChallengeProxy
return &v
} }
// RollupConfig converts a DeployConfig to a rollup.Config. If Ecotone is active at genesis, the // RollupConfig converts a DeployConfig to a rollup.Config. If Ecotone is active at genesis, the
......
...@@ -9,9 +9,12 @@ import ( ...@@ -9,9 +9,12 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-service/testlog"
) )
func TestConfigDataMarshalUnmarshal(t *testing.T) { func TestConfigDataMarshalUnmarshal(t *testing.T) {
...@@ -23,6 +26,8 @@ func TestConfigDataMarshalUnmarshal(t *testing.T) { ...@@ -23,6 +26,8 @@ func TestConfigDataMarshalUnmarshal(t *testing.T) {
require.NoError(t, dec.Decode(decoded)) require.NoError(t, dec.Decode(decoded))
require.EqualValues(t, "non-default value", string(decoded.L2GenesisBlockExtraData)) require.EqualValues(t, "non-default value", string(decoded.L2GenesisBlockExtraData))
require.NoError(t, decoded.Check(testlog.Logger(t, log.LevelDebug)))
encoded, err := json.MarshalIndent(decoded, "", " ") encoded, err := json.MarshalIndent(decoded, "", " ")
require.NoError(t, err) require.NoError(t, err)
require.JSONEq(t, string(b), string(encoded)) require.JSONEq(t, string(b), string(encoded))
...@@ -39,25 +44,37 @@ func TestUnmarshalL1StartingBlockTag(t *testing.T) { ...@@ -39,25 +44,37 @@ func TestUnmarshalL1StartingBlockTag(t *testing.T) {
func TestRegolithTimeZero(t *testing.T) { func TestRegolithTimeZero(t *testing.T) {
regolithOffset := hexutil.Uint64(0) regolithOffset := hexutil.Uint64(0)
config := &DeployConfig{L2GenesisRegolithTimeOffset: &regolithOffset} config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisRegolithTimeOffset: &regolithOffset}}}
require.Equal(t, uint64(0), *config.RegolithTime(1234)) require.Equal(t, uint64(0), *config.RegolithTime(1234))
} }
func TestRegolithTimeAsOffset(t *testing.T) { func TestRegolithTimeAsOffset(t *testing.T) {
regolithOffset := hexutil.Uint64(1500) regolithOffset := hexutil.Uint64(1500)
config := &DeployConfig{L2GenesisRegolithTimeOffset: &regolithOffset} config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisRegolithTimeOffset: &regolithOffset}}}
require.Equal(t, uint64(1500+5000), *config.RegolithTime(5000)) require.Equal(t, uint64(1500+5000), *config.RegolithTime(5000))
} }
func TestCanyonTimeZero(t *testing.T) { func TestCanyonTimeZero(t *testing.T) {
canyonOffset := hexutil.Uint64(0) canyonOffset := hexutil.Uint64(0)
config := &DeployConfig{L2GenesisCanyonTimeOffset: &canyonOffset} config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisCanyonTimeOffset: &canyonOffset}}}
require.Equal(t, uint64(0), *config.CanyonTime(1234)) require.Equal(t, uint64(0), *config.CanyonTime(1234))
} }
func TestCanyonTimeOffset(t *testing.T) { func TestCanyonTimeOffset(t *testing.T) {
canyonOffset := hexutil.Uint64(1500) canyonOffset := hexutil.Uint64(1500)
config := &DeployConfig{L2GenesisCanyonTimeOffset: &canyonOffset} config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisCanyonTimeOffset: &canyonOffset}}}
require.Equal(t, uint64(1234+1500), *config.CanyonTime(1234)) require.Equal(t, uint64(1234+1500), *config.CanyonTime(1234))
} }
......
...@@ -45,6 +45,8 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) { ...@@ -45,6 +45,8 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
genesisBlock := hexutil.Uint64(0) genesisBlock := hexutil.Uint64(0)
ecotoneOffset := hexutil.Uint64(4) ecotoneOffset := hexutil.Uint64(4)
log := testlog.Logger(t, log.LevelDebug)
dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default
// Activate all forks at genesis, and schedule Ecotone the block after // Activate all forks at genesis, and schedule Ecotone the block after
...@@ -52,10 +54,9 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) { ...@@ -52,10 +54,9 @@ func TestEcotoneNetworkUpgradeTransactions(gt *testing.T) {
dp.DeployConfig.L2GenesisCanyonTimeOffset = &genesisBlock dp.DeployConfig.L2GenesisCanyonTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisEcotoneTimeOffset = &ecotoneOffset dp.DeployConfig.L2GenesisEcotoneTimeOffset = &ecotoneOffset
require.NoError(t, dp.DeployConfig.Check(), "must have valid config") require.NoError(t, dp.DeployConfig.Check(log), "must have valid config")
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LevelDebug)
_, _, miner, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log) _, _, miner, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log)
ethCl := engine.EthClient() ethCl := engine.EthClient()
......
...@@ -34,6 +34,8 @@ func TestFjordNetworkUpgradeTransactions(gt *testing.T) { ...@@ -34,6 +34,8 @@ func TestFjordNetworkUpgradeTransactions(gt *testing.T) {
genesisBlock := hexutil.Uint64(0) genesisBlock := hexutil.Uint64(0)
fjordOffset := hexutil.Uint64(2) fjordOffset := hexutil.Uint64(2)
log := testlog.Logger(t, log.LvlDebug)
dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default dp.DeployConfig.L1CancunTimeOffset = &genesisBlock // can be removed once Cancun on L1 is the default
// Activate all forks at genesis, and schedule Fjord the block after // Activate all forks at genesis, and schedule Fjord the block after
...@@ -42,10 +44,9 @@ func TestFjordNetworkUpgradeTransactions(gt *testing.T) { ...@@ -42,10 +44,9 @@ func TestFjordNetworkUpgradeTransactions(gt *testing.T) {
dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisEcotoneTimeOffset = &genesisBlock dp.DeployConfig.L2GenesisEcotoneTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisFjordTimeOffset = &fjordOffset dp.DeployConfig.L2GenesisFjordTimeOffset = &fjordOffset
require.NoError(t, dp.DeployConfig.Check(), "must have valid config") require.NoError(t, dp.DeployConfig.Check(log), "must have valid config")
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)
_, _, _, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log) _, _, _, sequencer, engine, verifier, _, _ := setupReorgTestActors(t, dp, sd, log)
ethCl := engine.EthClient() ethCl := engine.EthClient()
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -63,7 +64,8 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams { ...@@ -63,7 +64,8 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams {
deployConfig.UsePlasma = tp.UsePlasma deployConfig.UsePlasma = tp.UsePlasma
ApplyDeployConfigForks(deployConfig) ApplyDeployConfigForks(deployConfig)
require.NoError(t, deployConfig.Check()) logger := log.NewLogger(log.DiscardHandler())
require.NoError(t, deployConfig.Check(logger))
require.Equal(t, addresses.Batcher, deployConfig.BatchSenderAddress) require.Equal(t, addresses.Batcher, deployConfig.BatchSenderAddress)
require.Equal(t, addresses.Proposer, deployConfig.L2OutputOracleProposer) require.Equal(t, addresses.Proposer, deployConfig.L2OutputOracleProposer)
require.Equal(t, addresses.SequencerP2P, deployConfig.P2PSequencerAddress) require.Equal(t, addresses.SequencerP2P, deployConfig.P2PSequencerAddress)
...@@ -105,7 +107,8 @@ func Ether(v uint64) *big.Int { ...@@ -105,7 +107,8 @@ func Ether(v uint64) *big.Int {
func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *SetupData { func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *SetupData {
deployConf := deployParams.DeployConfig.Copy() deployConf := deployParams.DeployConfig.Copy()
deployConf.L1GenesisBlockTimestamp = hexutil.Uint64(time.Now().Unix()) deployConf.L1GenesisBlockTimestamp = hexutil.Uint64(time.Now().Unix())
require.NoError(t, deployConf.Check()) logger := log.NewLogger(log.DiscardHandler())
require.NoError(t, deployConf.Check(logger))
l1Deployments := config.L1Deployments.Copy() l1Deployments := config.L1Deployments.Copy()
require.NoError(t, l1Deployments.Check(deployConf)) require.NoError(t, l1Deployments.Check(deployConf))
......
...@@ -101,7 +101,8 @@ func DefaultSystemConfig(t testing.TB) SystemConfig { ...@@ -101,7 +101,8 @@ func DefaultSystemConfig(t testing.TB) SystemConfig {
deployConfig := config.DeployConfig.Copy() deployConfig := config.DeployConfig.Copy()
deployConfig.L1GenesisBlockTimestamp = hexutil.Uint64(time.Now().Unix()) deployConfig.L1GenesisBlockTimestamp = hexutil.Uint64(time.Now().Unix())
e2eutils.ApplyDeployConfigForks(deployConfig) e2eutils.ApplyDeployConfigForks(deployConfig)
require.NoError(t, deployConfig.Check(), "Deploy config is invalid, do you need to run make devnet-allocs?") require.NoError(t, deployConfig.Check(testlog.Logger(t, log.LevelInfo)),
"Deploy config is invalid, do you need to run make devnet-allocs?")
l1Deployments := config.L1Deployments.Copy() l1Deployments := config.L1Deployments.Copy()
require.NoError(t, l1Deployments.Check(deployConfig)) require.NoError(t, l1Deployments.Check(deployConfig))
...@@ -507,7 +508,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -507,7 +508,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
c = sys.TimeTravelClock c = sys.TimeTravelClock
} }
if err := cfg.DeployConfig.Check(); err != nil { if err := cfg.DeployConfig.Check(testlog.Logger(t, log.LevelInfo)); err != nil {
return nil, err return nil, err
} }
......
...@@ -9,13 +9,12 @@ import ( ...@@ -9,13 +9,12 @@ import (
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-service/jsonutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
) )
var ( var (
...@@ -97,7 +96,9 @@ var Subcommands = cli.Commands{ ...@@ -97,7 +96,9 @@ var Subcommands = cli.Commands{
config.SetDeployments(deployments) config.SetDeployments(deployments)
} }
if err := config.Check(); err != nil { cfg := oplog.DefaultCLIConfig()
logger := oplog.NewLogger(ctx.App.Writer, cfg)
if err := config.Check(logger); err != nil {
return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err) return fmt.Errorf("deploy config at %s invalid: %w", deployConfig, err)
} }
...@@ -132,8 +133,11 @@ var Subcommands = cli.Commands{ ...@@ -132,8 +133,11 @@ var Subcommands = cli.Commands{
"or it can be provided as a JSON file.", "or it can be provided as a JSON file.",
Flags: l2Flags, Flags: l2Flags,
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
cfg := oplog.DefaultCLIConfig()
logger := oplog.NewLogger(ctx.App.Writer, cfg)
deployConfig := ctx.Path(deployConfigFlag.Name) deployConfig := ctx.Path(deployConfigFlag.Name)
log.Info("Deploy config", "path", deployConfig) logger.Info("Deploy config", "path", deployConfig)
config, err := genesis.NewDeployConfig(deployConfig) config, err := genesis.NewDeployConfig(deployConfig)
if err != nil { if err != nil {
return err return err
...@@ -171,16 +175,17 @@ var Subcommands = cli.Commands{ ...@@ -171,16 +175,17 @@ var Subcommands = cli.Commands{
return fmt.Errorf("failed to fetch startBlock from SystemConfig: %w", err) return fmt.Errorf("failed to fetch startBlock from SystemConfig: %w", err)
} }
log.Info("Using L1 Start Block", "number", startBlock) logger.Info("Using L1 Start Block", "number", startBlock)
// retry because local devnet can experience a race condition where L1 geth isn't ready yet // retry because local devnet can experience a race condition where L1 geth isn't ready yet
l1StartBlock, err := retry.Do(ctx.Context, 24, retry.Fixed(1*time.Second), func() (*types.Block, error) { return client.BlockByNumber(ctx.Context, startBlock) }) l1StartBlock, err := retry.Do(ctx.Context, 24, retry.Fixed(1*time.Second), func() (*types.Block, error) { return client.BlockByNumber(ctx.Context, startBlock) })
if err != nil { if err != nil {
return fmt.Errorf("fetching start block by number: %w", err) return fmt.Errorf("fetching start block by number: %w", err)
} }
log.Info("Fetched L1 Start Block", "hash", l1StartBlock.Hash().Hex()) logger.Info("Fetched L1 Start Block", "hash", l1StartBlock.Hash().Hex())
// Sanity check the config // Sanity check the config. Do this after filling in the L1StartingBlockTag
if err := config.Check(); err != nil { // if it is not defined.
if err := config.Check(logger); err != nil {
return err return err
} }
......
...@@ -273,14 +273,22 @@ func setupOracleBackedChainWithLowerHead(t *testing.T, blockCount int, headBlock ...@@ -273,14 +273,22 @@ func setupOracleBackedChainWithLowerHead(t *testing.T, blockCount int, headBlock
func setupOracle(t *testing.T, blockCount int, headBlockNumber int, enableEcotone bool) (*params.ChainConfig, []*types.Block, *l2test.StubBlockOracle) { func setupOracle(t *testing.T, blockCount int, headBlockNumber int, enableEcotone bool) (*params.ChainConfig, []*types.Block, *l2test.StubBlockOracle) {
deployConfig := &genesis.DeployConfig{ deployConfig := &genesis.DeployConfig{
L1ChainID: 900, L2InitializationConfig: genesis.L2InitializationConfig{
L2ChainID: 901, DevDeployConfig: genesis.DevDeployConfig{
L2BlockTime: 2, FundDevAccounts: true,
FundDevAccounts: true, },
L2GenesisBlockGasLimit: 30_000_000, L2GenesisBlockDeployConfig: genesis.L2GenesisBlockDeployConfig{
// Arbitrary non-zero difficulty in genesis. L2GenesisBlockGasLimit: 30_000_000,
// This is slightly weird for a chain starting post-merge but it happens so need to make sure it works // Arbitrary non-zero difficulty in genesis.
L2GenesisBlockDifficulty: (*hexutil.Big)(big.NewInt(100)), // This is slightly weird for a chain starting post-merge but it happens so need to make sure it works
L2GenesisBlockDifficulty: (*hexutil.Big)(big.NewInt(100)),
},
L2CoreDeployConfig: genesis.L2CoreDeployConfig{
L1ChainID: 900,
L2ChainID: 901,
L2BlockTime: 2,
},
},
} }
if enableEcotone { if enableEcotone {
ts := hexutil.Uint64(0) ts := hexutil.Uint64(0)
......
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