Commit a9599b05 authored by protolambda's avatar protolambda

op-node: protocol versions signal handling

parent 1febf8a1
......@@ -208,6 +208,6 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)
replace github.com/ethereum/go-ethereum v1.12.0 => github.com/ethereum-optimism/op-geth v1.101200.0-rc.1.0.20230818191139-f7376a28049b
replace github.com/ethereum/go-ethereum v1.12.0 => github.com/ethereum-optimism/op-geth v1.101200.2-rc.1.0.20230913104329-60b827bfc2de
//replace github.com/ethereum/go-ethereum v1.12.0 => ../go-ethereum
......@@ -162,8 +162,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101200.0-rc.1.0.20230818191139-f7376a28049b h1:YF2FE/QnbhvrHwDYJHnbTKgJvw2aKwB/dd7PO1zKNqY=
github.com/ethereum-optimism/op-geth v1.101200.0-rc.1.0.20230818191139-f7376a28049b/go.mod h1:gRnPb21PoKcHm3kHqj9BQlQkwmhOGUvQoGEbC7z852Q=
github.com/ethereum-optimism/op-geth v1.101200.2-rc.1.0.20230913104329-60b827bfc2de h1:nxgmsH13qzkWlX5dXrVzsTHqtu/XHfAWkyfpH+9ljqw=
github.com/ethereum-optimism/op-geth v1.101200.2-rc.1.0.20230913104329-60b827bfc2de/go.mod h1:gRnPb21PoKcHm3kHqj9BQlQkwmhOGUvQoGEbC7z852Q=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230817174831-5d3ca1966435 h1:2CzkJkkTLuVyoVFkoW5w6vDB2Q7eJzxXw/ybA17xjqM=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230817174831-5d3ca1966435/go.mod h1:v2YpePbdGBF0Gr6VWq49MFFmcTW0kRYZ2ingBJYWEwg=
github.com/ethereum/c-kzg-4844 v0.2.0 h1:+cUvymlnoDDQgMInp25Bo3OmLajmmY8mLJ/tLjqd77Q=
......
......@@ -15,6 +15,7 @@ import (
gstate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-bindings/hardhat"
......@@ -199,10 +200,10 @@ type DeployConfig struct {
FundDevAccounts bool `json:"fundDevAccounts"`
// RequiredProtocolVersion indicates the protocol version that
// nodes are required to adopt, to stay in sync with the network.
RequiredProtocolVersion common.Hash `json:"requiredProtocolVersion"`
RequiredProtocolVersion params.ProtocolVersion `json:"requiredProtocolVersion"`
// RequiredProtocolVersion indicates the protocol version that
// nodes are recommended to adopt, to stay in sync with the network.
RecommendedProtocolVersion common.Hash `json:"recommendedProtocolVersion"`
RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"`
}
// Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
......
......@@ -408,16 +408,17 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
L2Time: uint64(cfg.DeployConfig.L1GenesisBlockTimestamp),
SystemConfig: e2eutils.SystemConfigFromDeployConfig(cfg.DeployConfig),
},
BlockTime: cfg.DeployConfig.L2BlockTime,
MaxSequencerDrift: cfg.DeployConfig.MaxSequencerDrift,
SeqWindowSize: cfg.DeployConfig.SequencerWindowSize,
ChannelTimeout: cfg.DeployConfig.ChannelTimeout,
L1ChainID: cfg.L1ChainIDBig(),
L2ChainID: cfg.L2ChainIDBig(),
BatchInboxAddress: cfg.DeployConfig.BatchInboxAddress,
DepositContractAddress: cfg.DeployConfig.OptimismPortalProxy,
L1SystemConfigAddress: cfg.DeployConfig.SystemConfigProxy,
RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
BlockTime: cfg.DeployConfig.L2BlockTime,
MaxSequencerDrift: cfg.DeployConfig.MaxSequencerDrift,
SeqWindowSize: cfg.DeployConfig.SequencerWindowSize,
ChannelTimeout: cfg.DeployConfig.ChannelTimeout,
L1ChainID: cfg.L1ChainIDBig(),
L2ChainID: cfg.L2ChainIDBig(),
BatchInboxAddress: cfg.DeployConfig.BatchInboxAddress,
DepositContractAddress: cfg.DeployConfig.OptimismPortalProxy,
L1SystemConfigAddress: cfg.DeployConfig.SystemConfigProxy,
RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy,
}
}
defaultConfig := makeRollupConfig()
......
......@@ -2,6 +2,7 @@ package op_e2e
import (
"context"
"errors"
"fmt"
"math/big"
"os"
......@@ -1434,3 +1435,120 @@ func TestRuntimeConfigReload(t *testing.T) {
})
require.NoError(t, err)
}
func TestRecommendedProtocolVersionChange(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
require.NotEqual(t, common.Address{}, cfg.L1Deployments.ProtocolVersions, "need ProtocolVersions contract deployment")
// to speed up the test, make it reload the config more often, and do not impose a long conf depth
cfg.Nodes["verifier"].RuntimeConfigReloadInterval = time.Second * 5
cfg.Nodes["verifier"].Driver.VerifierConfDepth = 1
sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system")
defer sys.Close()
runtimeConfig := sys.RollupNodes["verifier"].RuntimeConfig()
// Change the superchain-config via L1
l1 := sys.Clients["l1"]
_, build, major, minor, patch, preRelease := params.OPStackSupport.Parse()
newRecommendedProtocolVersion := params.ToProtocolVersion(build, major+1, minor, patch, preRelease)
require.NotEqual(t, runtimeConfig.RecommendedProtocolVersion(), newRecommendedProtocolVersion, "changing to a different protocol version")
protVersions, err := bindings.NewProtocolVersions(cfg.L1Deployments.ProtocolVersionsProxy, l1)
require.NoError(t, err)
// ProtocolVersions contract is owned by same key as SystemConfig in devnet
opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.SysCfgOwner, cfg.L1ChainIDBig())
require.NoError(t, err)
// Change recommended protocol version
tx, err := protVersions.SetRecommended(opts, new(big.Int).SetBytes(newRecommendedProtocolVersion[:]))
require.NoError(t, err)
// wait for the change to confirm
_, err = wait.ForReceiptOK(context.Background(), l1, tx.Hash())
require.NoError(t, err)
// wait for the recommended protocol version to change
_, err = retry.Do(context.Background(), 10, retry.Fixed(time.Second*10), func() (struct{}, error) {
v := sys.RollupNodes["verifier"].RuntimeConfig().RecommendedProtocolVersion()
if v == newRecommendedProtocolVersion {
return struct{}{}, nil
}
return struct{}{}, fmt.Errorf("no change yet, seeing %s but looking for %s", v, newRecommendedProtocolVersion)
})
require.NoError(t, err)
}
func TestRequiredProtocolVersionChangeAndHalt(t *testing.T) {
InitParallel(t)
cfg := DefaultSystemConfig(t)
// activate the functionality by updating the config // TODO may need to just activate it by default in op-e2e?
cfg.Nodes["verifier"].Rollup.ProtocolVersionsAddress = cfg.L1Deployments.ProtocolVersions
// to speed up the test, make it reload the config more often, and do not impose a long conf depth
cfg.Nodes["verifier"].RuntimeConfigReloadInterval = time.Second * 5
cfg.Nodes["verifier"].Driver.VerifierConfDepth = 1
// configure halt in verifier op-node
cfg.Nodes["verifier"].RollupHalt = "major"
// configure halt in verifier op-geth node
cfg.GethOptions["verifier"] = []geth.GethOption{
func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error {
ethCfg.RollupHaltOnIncompatibleProtocolVersion = "major"
return nil
},
}
sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system")
defer sys.Close()
runtimeConfig := sys.RollupNodes["verifier"].RuntimeConfig()
// Change the superchain-config via L1
l1 := sys.Clients["l1"]
_, build, major, minor, patch, preRelease := params.OPStackSupport.Parse()
newRequiredProtocolVersion := params.ToProtocolVersion(build, major+1, minor, patch, preRelease)
require.NotEqual(t, runtimeConfig.RequiredProtocolVersion(), newRequiredProtocolVersion, "changing to a different protocol version")
protVersions, err := bindings.NewProtocolVersions(cfg.L1Deployments.ProtocolVersionsProxy, l1)
require.NoError(t, err)
// ProtocolVersions contract is owned by same key as SystemConfig in devnet
opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.SysCfgOwner, cfg.L1ChainIDBig())
require.NoError(t, err)
// Change required protocol version
tx, err := protVersions.SetRequired(opts, new(big.Int).SetBytes(newRequiredProtocolVersion[:]))
require.NoError(t, err)
// wait for the change to confirm
_, err = wait.ForReceiptOK(context.Background(), l1, tx.Hash())
require.NoError(t, err)
// wait for the required protocol version to take effect by halting the verifier that opted in, and halting the op-geth node that opted in.
_, err = retry.Do(context.Background(), 10, retry.Fixed(time.Second*10), func() (struct{}, error) {
if !sys.RollupNodes["verifier"].Closed() {
return struct{}{}, errors.New("verifier rollup node is not closed yet")
}
return struct{}{}, nil
})
require.NoError(t, err)
t.Log("verified that op-node closed!")
// Checking if the engine is down is not trivial in op-e2e.
// In op-geth we have halting tests covering the Engine API, in op-e2e we instead check if the API stops.
_, err = retry.Do(context.Background(), 10, retry.Fixed(time.Second*10), func() (struct{}, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
_, err := sys.Clients["verifier"].ChainID(ctx)
cancel()
if err != nil && !errors.Is(err, ctx.Err()) { // waiting for client to stop responding to chainID requests
return struct{}{}, nil
}
return struct{}{}, errors.New("verifier rollup node is not closed yet")
})
require.NoError(t, err)
t.Log("verified that op-geth closed!")
}
......@@ -244,6 +244,18 @@ var (
EnvVars: prefixEnvVars("BETA_EXTRA_NETWORKS"),
Hidden: true,
}
BetaRollupHalt = &cli.StringFlag{
Name: "beta.rollup.halt",
Usage: "Beta feature: opt-in option to halt on incompatible protocol version requirements of the given level (major/minor/patch/none), as signaled onchain in L1",
EnvVars: prefixEnvVars("BETA_ROLLUP_HALT"),
Hidden: true,
}
BetaRollupLoadProtocolVersions = &cli.BoolFlag{
Name: "beta.rollup.load-protocol-versions",
Usage: "Beta feature: load protocol versions from the superchain L1 ProtocolVersions contract (if available), and report in logs and metrics",
EnvVars: prefixEnvVars("BETA_ROLLUP_LOAD_PROTOCOL_VERSIONS"),
Hidden: true,
}
)
var requiredFlags = []cli.Flag{
......@@ -286,6 +298,8 @@ var optionalFlags = []cli.Flag{
L2EngineSyncEnabled,
SkipSyncStartCheck,
BetaExtraNetworks,
BetaRollupHalt,
BetaRollupLoadProtocolVersions,
}
// Flags contains the list of configuration options available to the binary.
......
......@@ -10,6 +10,8 @@ import (
"strconv"
"time"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-node/p2p/store"
ophttp "github.com/ethereum-optimism/optimism/op-service/httputil"
"github.com/ethereum-optimism/optimism/op-service/metrics"
......@@ -78,6 +80,7 @@ type Metricer interface {
RecordIPUnban()
RecordDial(allow bool)
RecordAccept(allow bool)
ReportProtocolVersions(local, engine, recommended, required params.ProtocolVersion)
}
// Metrics tracks all the metrics for the op-node.
......@@ -153,6 +156,12 @@ type Metrics struct {
ChannelInputBytes prometheus.Counter
// Protocol version reporting
// Delta = params.ProtocolVersionComparison
ProtocolVersionDelta *prometheus.GaugeVec
// ProtocolVersions is pseudo-metric to report the exact protocol version info
ProtocolVersions *prometheus.GaugeVec
registry *prometheus.Registry
factory metrics.Factory
}
......@@ -452,6 +461,24 @@ func NewMetrics(procName string) *Metrics {
Help: "Number of sequencer block sealing jobs",
}),
ProtocolVersionDelta: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns,
Name: "protocol_version_delta",
Help: "Difference between local and global protocol version, and execution-engine, per type of version",
}, []string{
"type",
}),
ProtocolVersions: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns,
Name: "protocol_versions",
Help: "Pseudo-metric tracking recommended and required protocol version info",
}, []string{
"local",
"engine",
"recommended",
"required",
}),
registry: registry,
factory: factory,
}
......@@ -747,6 +774,13 @@ func (m *Metrics) RecordAccept(allow bool) {
m.Accepts.WithLabelValues("false").Inc()
}
}
func (m *Metrics) ReportProtocolVersions(local, engine, recommended, required params.ProtocolVersion) {
m.ProtocolVersionDelta.WithLabelValues("local_recommended").Set(float64(local.Compare(recommended)))
m.ProtocolVersionDelta.WithLabelValues("local_required").Set(float64(local.Compare(required)))
m.ProtocolVersionDelta.WithLabelValues("engine_recommended").Set(float64(engine.Compare(recommended)))
m.ProtocolVersionDelta.WithLabelValues("engine_required").Set(float64(engine.Compare(required)))
m.ProtocolVersions.WithLabelValues(local.String(), engine.String(), recommended.String(), required.String()).Set(1)
}
type noopMetricer struct{}
......@@ -874,3 +908,5 @@ func (n *noopMetricer) RecordDial(allow bool) {
func (n *noopMetricer) RecordAccept(allow bool) {
}
func (n *noopMetricer) ReportProtocolVersions(local, engine, recommended, required params.ProtocolVersion) {
}
......@@ -42,7 +42,7 @@ type Config struct {
ConfigPersistence ConfigPersistence
// RuntimeConfigReloadInterval defines the interval between runtime config reloads.
// Disabled if 0.
// Disabled if <= 0.
// Runtime config changes should be picked up from log-events,
// but if log-events are not coming in (e.g. not syncing blocks) then the reload ensures the config stays accurate.
RuntimeConfigReloadInterval time.Duration
......@@ -52,6 +52,10 @@ type Config struct {
Heartbeat HeartbeatConfig
Sync sync.Config
// To halt when detecting the node does not support a signaled protocol version
// change of the given severity (major/minor/patch). Disabled if empty.
RollupHalt string
}
type RPCConfig struct {
......@@ -128,5 +132,8 @@ func (cfg *Config) Check() error {
return fmt.Errorf("p2p config error: %w", err)
}
}
if !(cfg.RollupHalt == "" || cfg.RollupHalt == "major" || cfg.RollupHalt == "minor" || cfg.RollupHalt == "patch") {
return fmt.Errorf("invalid rollup halting option: %q", cfg.RollupHalt)
}
return nil
}
......@@ -2,7 +2,9 @@ package node
import (
"context"
"errors"
"fmt"
"sync/atomic"
"time"
"github.com/hashicorp/go-multierror"
......@@ -40,10 +42,14 @@ type OpNode struct {
tracer Tracer // tracer to get events for testing/debugging
runCfg *RuntimeConfig // runtime configurables
rollupHalt string // when to halt the rollup, disabled if empty
// some resources cannot be stopped directly, like the p2p gossipsub router (not our design),
// and depend on this ctx to be closed.
resourcesCtx context.Context
resourcesClose context.CancelFunc
closed atomic.Bool
}
// The OpNode handles incoming gossip
......@@ -58,6 +64,7 @@ func New(ctx context.Context, cfg *Config, log log.Logger, snapshotLog log.Logge
log: log,
appVersion: appVersion,
metrics: m,
rollupHalt: cfg.RollupHalt,
}
// not a context leak, gossipsub is closed with a context.
n.resourcesCtx, n.resourcesClose = context.WithCancel(context.Background())
......@@ -189,6 +196,9 @@ func (n *OpNode) initRuntimeConfig(ctx context.Context, cfg *Config) error {
n.log.Error("failed to fetch runtime config data", "err", err)
return l1Head, err
}
n.handleProtocolVersionsUpdate(ctx)
return l1Head, nil
}
......@@ -446,6 +456,10 @@ func (n *OpNode) RuntimeConfig() ReadonlyRuntimeConfig {
// Close closes all resources.
func (n *OpNode) Close() error {
if n.closed.Load() {
return errors.New("node is already closed")
}
var result *multierror.Error
if n.server != nil {
......@@ -494,9 +508,18 @@ func (n *OpNode) Close() error {
if n.l1Source != nil {
n.l1Source.Close()
}
if result == nil { // mark as closed if we successfully fully closed
n.closed.Store(true)
}
return result.ErrorOrNil()
}
func (n *OpNode) Closed() bool {
return n.closed.Load()
}
func (n *OpNode) ListenAddr() string {
return n.server.listenAddr.String()
}
......
......@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-node/p2p"
"github.com/ethereum-optimism/optimism/op-node/rollup"
......@@ -17,6 +18,14 @@ var (
// UnsafeBlockSignerAddressSystemConfigStorageSlot is the storage slot identifier of the unsafeBlockSigner
// `address` storage value in the SystemConfig L1 contract. Computed as `keccak256("systemconfig.unsafeblocksigner")`
UnsafeBlockSignerAddressSystemConfigStorageSlot = common.HexToHash("0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08")
// RequiredProtocolVersionStorageSlot is the storage slot that the required protocol version is stored at.
// Computed as: `bytes32(uint256(keccak256("protocolversion.required")) - 1)`
RequiredProtocolVersionStorageSlot = common.HexToHash("0x4aaefe95bd84fd3f32700cf3b7566bc944b73138e41958b5785826df2aecace0")
// RecommendedProtocolVersionStorageSlot is the storage slot that the recommended protocol version is stored at.
// Computed as: `bytes32(uint256(keccak256("protocolversion.recommended")) - 1)`
RecommendedProtocolVersionStorageSlot = common.HexToHash("0xe314dfc40f0025322aacc0ba8ef420b62fb3b702cf01e0cdf3d829117ac2ff1a")
)
type RuntimeCfgL1Source interface {
......@@ -25,6 +34,8 @@ type RuntimeCfgL1Source interface {
type ReadonlyRuntimeConfig interface {
P2PSequencerAddress() common.Address
RequiredProtocolVersion() params.ProtocolVersion
RecommendedProtocolVersion() params.ProtocolVersion
}
// RuntimeConfig maintains runtime-configurable options.
......@@ -49,6 +60,10 @@ type RuntimeConfig struct {
// runtimeConfigData is a flat bundle of configurable data, easy and light to copy around.
type runtimeConfigData struct {
p2pBlockSignerAddr common.Address
// superchain protocol version signals
recommended params.ProtocolVersion
required params.ProtocolVersion
}
var _ p2p.GossipRuntimeConfig = (*RuntimeConfig)(nil)
......@@ -67,18 +82,46 @@ func (r *RuntimeConfig) P2PSequencerAddress() common.Address {
return r.p2pBlockSignerAddr
}
func (r *RuntimeConfig) RequiredProtocolVersion() params.ProtocolVersion {
r.mu.RLock()
defer r.mu.RUnlock()
return r.required
}
func (r *RuntimeConfig) RecommendedProtocolVersion() params.ProtocolVersion {
r.mu.RLock()
defer r.mu.RUnlock()
return r.recommended
}
// Load resets the runtime configuration by fetching the latest config data from L1 at the given L1 block.
// Load is safe to call concurrently, but will lock the runtime configuration modifications only,
// and will thus not block other Load calls with possibly alternative L1 block views.
func (r *RuntimeConfig) Load(ctx context.Context, l1Ref eth.L1BlockRef) error {
val, err := r.l1Client.ReadStorageAt(ctx, r.rollupCfg.L1SystemConfigAddress, UnsafeBlockSignerAddressSystemConfigStorageSlot, l1Ref.Hash)
p2pSignerVal, err := r.l1Client.ReadStorageAt(ctx, r.rollupCfg.L1SystemConfigAddress, UnsafeBlockSignerAddressSystemConfigStorageSlot, l1Ref.Hash)
if err != nil {
return fmt.Errorf("failed to fetch unsafe block signing address from system config: %w", err)
}
// The superchain protocol version data is optional; only applicable to rollup configs that specify a ProtocolVersions address.
var requiredProtVersion, recommendedProtoVersion params.ProtocolVersion
if r.rollupCfg.ProtocolVersionsAddress != (common.Address{}) {
requiredVal, err := r.l1Client.ReadStorageAt(ctx, r.rollupCfg.ProtocolVersionsAddress, RequiredProtocolVersionStorageSlot, l1Ref.Hash)
if err != nil {
return fmt.Errorf("required-protocol-version value failed to load from L1 contract: %w", err)
}
requiredProtVersion = params.ProtocolVersion(requiredVal)
recommendedVal, err := r.l1Client.ReadStorageAt(ctx, r.rollupCfg.ProtocolVersionsAddress, RecommendedProtocolVersionStorageSlot, l1Ref.Hash)
if err != nil {
return fmt.Errorf("recommended-protocol-version value failed to load from L1 contract: %w", err)
}
recommendedProtoVersion = params.ProtocolVersion(recommendedVal)
}
r.mu.Lock()
defer r.mu.Unlock()
r.l1Ref = l1Ref
r.p2pBlockSignerAddr = common.BytesToAddress(val[:])
r.p2pBlockSignerAddr = common.BytesToAddress(p2pSignerVal[:])
r.required = requiredProtVersion
r.recommended = recommendedProtoVersion
r.log.Info("loaded new runtime config values!", "p2p_seq_address", r.p2pBlockSignerAddr)
return nil
}
package node
import (
"context"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/params"
)
func (n *OpNode) handleProtocolVersionsUpdate(ctx context.Context) {
recommended := n.runCfg.RecommendedProtocolVersion()
required := n.runCfg.RequiredProtocolVersion()
// if the protocol version sources are disabled we do not process them
if recommended == (params.ProtocolVersion{}) && required == (params.ProtocolVersion{}) {
return
}
local := rollup.OPStackSupport
// forward to execution engine, and get back the protocol version that op-geth supports
engineSupport, err := n.l2Source.SignalSuperchainV1(ctx, recommended, required)
if err != nil {
n.log.Warn("failed to notify engine of protocol version", "err", err)
// engineSupport may still be available, or otherwise zero to signal as unknown
} else {
catalyst.LogProtocolVersionSupport(n.log.New("node", "op-node"), engineSupport, recommended, "recommended")
catalyst.LogProtocolVersionSupport(n.log.New("node", "op-node"), engineSupport, required, "required")
}
n.metrics.ReportProtocolVersions(local, engineSupport, recommended, required)
catalyst.LogProtocolVersionSupport(n.log.New("node", "engine"), local, recommended, "recommended")
catalyst.LogProtocolVersionSupport(n.log.New("node", "engine"), local, required, "required")
// We may need to halt the node, if the user opted in to handling incompatible protocol-version signals
n.HaltMaybe()
}
// HaltMaybe halts the rollup node if the runtime config indicates an incompatible required protocol change
// and the node is configured to opt-in to halting at this protocol-change level.
func (n *OpNode) HaltMaybe() {
var needLevel int
switch n.rollupHalt {
case "major":
needLevel = 3
case "minor":
needLevel = 2
case "patch":
needLevel = 1
default:
return // do not consider halting if not configured to
}
haveLevel := 0
local := rollup.OPStackSupport
required := n.runCfg.RequiredProtocolVersion()
switch local.Compare(required) {
case params.OutdatedMajor:
haveLevel = 3
case params.OutdatedMinor:
haveLevel = 2
case params.OutdatedPatch:
haveLevel = 1
}
if haveLevel >= needLevel { // halt if we opted in to do so at this granularity
n.log.Error("opted to halt, unprepared for protocol change", "required", required, "local", local)
if err := n.Close(); err != nil {
n.log.Error("failed to halt rollup", "err", err)
}
}
}
......@@ -4,12 +4,16 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/superchain-registry/superchain"
)
var OPStackSupport = params.ToProtocolVersion([8]byte{}, 3, 1, 0, 1)
const (
opMainnet = 10
opGoerli = 420
......
......@@ -84,6 +84,9 @@ type Config struct {
DepositContractAddress common.Address `json:"deposit_contract_address"`
// L1 System Config Address
L1SystemConfigAddress common.Address `json:"l1_system_config_address"`
// L1 address that declares the protocol versions, optional (Beta feature)
ProtocolVersionsAddress common.Address `json:"protocol_versions_address,omitempty"`
}
// ValidateL1Config checks L1 config variables for errors.
......@@ -283,6 +286,8 @@ func (c *Config) Description(l2Chains map[string]string) string {
// Report the upgrade configuration
banner += "Post-Bedrock Network Upgrades (timestamp based):\n"
banner += fmt.Sprintf(" - Regolith: %s\n", fmtForkTimeOrUnset(c.RegolithTime))
// Report the protocol version
banner += fmt.Sprintf("Node supports up to OP-Stack Protocol Version: %s\n", OPStackSupport)
return banner
}
......
......@@ -36,6 +36,10 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
return nil, err
}
if !ctx.Bool(flags.BetaRollupLoadProtocolVersions.Name) {
rollupConfig.ProtocolVersionsAddress = common.Address{}
}
configPersistence := NewConfigPersistence(ctx)
driverConfig := NewDriverConfig(ctx)
......@@ -61,6 +65,11 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
syncConfig := NewSyncConfig(ctx)
haltOption := ctx.String(flags.BetaRollupHalt.Name)
if haltOption == "none" {
haltOption = ""
}
cfg := &node.Config{
L1: l1Endpoint,
L2: l2Endpoint,
......@@ -93,6 +102,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
},
ConfigPersistence: configPersistence,
Sync: *syncConfig,
RollupHalt: haltOption,
}
if err := cfg.LoadPersisted(log); err != nil {
......
......@@ -5,6 +5,9 @@ import (
"fmt"
"time"
"github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/sources/caching"
......@@ -125,3 +128,12 @@ func (s *EngineClient) GetPayload(ctx context.Context, payloadId eth.PayloadID)
e.Trace("Received payload")
return &result, nil
}
func (s *EngineClient) SignalSuperchainV1(ctx context.Context, recommended, required params.ProtocolVersion) (params.ProtocolVersion, error) {
var result params.ProtocolVersion
err := s.client.CallContext(ctx, &result, "engine_signalSuperchainV1", &catalyst.SuperchainSignal{
Recommended: recommended,
Required: required,
})
return result, err
}
......@@ -51,6 +51,9 @@ func (res *AccountResult) Verify(stateRoot common.Hash) error {
if err != nil {
return fmt.Errorf("failed to verify storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key, path, res.StorageHash, err)
}
if val == nil && entry.Value.ToInt().Cmp(common.Big0) == 0 { // empty storage is zero by default
continue
}
comparison, err := rlp.EncodeToBytes(entry.Value.ToInt().Bytes())
if err != nil {
return fmt.Errorf("failed to encode storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key, path, res.StorageHash, err)
......
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