Commit a9599b05 authored by protolambda's avatar protolambda

op-node: protocol versions signal handling

parent 1febf8a1
...@@ -208,6 +208,6 @@ require ( ...@@ -208,6 +208,6 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect 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 //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 ...@@ -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/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 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= 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.2-rc.1.0.20230913104329-60b827bfc2de h1:nxgmsH13qzkWlX5dXrVzsTHqtu/XHfAWkyfpH+9ljqw=
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/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 h1:2CzkJkkTLuVyoVFkoW5w6vDB2Q7eJzxXw/ybA17xjqM=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20230817174831-5d3ca1966435/go.mod h1:v2YpePbdGBF0Gr6VWq49MFFmcTW0kRYZ2ingBJYWEwg= 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= github.com/ethereum/c-kzg-4844 v0.2.0 h1:+cUvymlnoDDQgMInp25Bo3OmLajmmY8mLJ/tLjqd77Q=
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
gstate "github.com/ethereum/go-ethereum/core/state" gstate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-bindings/hardhat" "github.com/ethereum-optimism/optimism/op-bindings/hardhat"
...@@ -199,10 +200,10 @@ type DeployConfig struct { ...@@ -199,10 +200,10 @@ type DeployConfig struct {
FundDevAccounts bool `json:"fundDevAccounts"` FundDevAccounts bool `json:"fundDevAccounts"`
// RequiredProtocolVersion indicates the protocol version that // RequiredProtocolVersion indicates the protocol version that
// nodes are required to adopt, to stay in sync with the network. // 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 // RequiredProtocolVersion indicates the protocol version that
// nodes are recommended to adopt, to stay in sync with the network. // 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 // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
......
...@@ -418,6 +418,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -418,6 +418,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
DepositContractAddress: cfg.DeployConfig.OptimismPortalProxy, DepositContractAddress: cfg.DeployConfig.OptimismPortalProxy,
L1SystemConfigAddress: cfg.DeployConfig.SystemConfigProxy, L1SystemConfigAddress: cfg.DeployConfig.SystemConfigProxy,
RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy,
} }
} }
defaultConfig := makeRollupConfig() defaultConfig := makeRollupConfig()
......
...@@ -2,6 +2,7 @@ package op_e2e ...@@ -2,6 +2,7 @@ package op_e2e
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
...@@ -1434,3 +1435,120 @@ func TestRuntimeConfigReload(t *testing.T) { ...@@ -1434,3 +1435,120 @@ func TestRuntimeConfigReload(t *testing.T) {
}) })
require.NoError(t, err) 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 ( ...@@ -244,6 +244,18 @@ var (
EnvVars: prefixEnvVars("BETA_EXTRA_NETWORKS"), EnvVars: prefixEnvVars("BETA_EXTRA_NETWORKS"),
Hidden: true, 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{ var requiredFlags = []cli.Flag{
...@@ -286,6 +298,8 @@ var optionalFlags = []cli.Flag{ ...@@ -286,6 +298,8 @@ var optionalFlags = []cli.Flag{
L2EngineSyncEnabled, L2EngineSyncEnabled,
SkipSyncStartCheck, SkipSyncStartCheck,
BetaExtraNetworks, BetaExtraNetworks,
BetaRollupHalt,
BetaRollupLoadProtocolVersions,
} }
// Flags contains the list of configuration options available to the binary. // Flags contains the list of configuration options available to the binary.
......
...@@ -10,6 +10,8 @@ import ( ...@@ -10,6 +10,8 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-node/p2p/store" "github.com/ethereum-optimism/optimism/op-node/p2p/store"
ophttp "github.com/ethereum-optimism/optimism/op-service/httputil" ophttp "github.com/ethereum-optimism/optimism/op-service/httputil"
"github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/metrics"
...@@ -78,6 +80,7 @@ type Metricer interface { ...@@ -78,6 +80,7 @@ type Metricer interface {
RecordIPUnban() RecordIPUnban()
RecordDial(allow bool) RecordDial(allow bool)
RecordAccept(allow bool) RecordAccept(allow bool)
ReportProtocolVersions(local, engine, recommended, required params.ProtocolVersion)
} }
// Metrics tracks all the metrics for the op-node. // Metrics tracks all the metrics for the op-node.
...@@ -153,6 +156,12 @@ type Metrics struct { ...@@ -153,6 +156,12 @@ type Metrics struct {
ChannelInputBytes prometheus.Counter 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 registry *prometheus.Registry
factory metrics.Factory factory metrics.Factory
} }
...@@ -452,6 +461,24 @@ func NewMetrics(procName string) *Metrics { ...@@ -452,6 +461,24 @@ func NewMetrics(procName string) *Metrics {
Help: "Number of sequencer block sealing jobs", 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, registry: registry,
factory: factory, factory: factory,
} }
...@@ -747,6 +774,13 @@ func (m *Metrics) RecordAccept(allow bool) { ...@@ -747,6 +774,13 @@ func (m *Metrics) RecordAccept(allow bool) {
m.Accepts.WithLabelValues("false").Inc() 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{} type noopMetricer struct{}
...@@ -874,3 +908,5 @@ func (n *noopMetricer) RecordDial(allow bool) { ...@@ -874,3 +908,5 @@ func (n *noopMetricer) RecordDial(allow bool) {
func (n *noopMetricer) RecordAccept(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 { ...@@ -42,7 +42,7 @@ type Config struct {
ConfigPersistence ConfigPersistence ConfigPersistence ConfigPersistence
// RuntimeConfigReloadInterval defines the interval between runtime config reloads. // RuntimeConfigReloadInterval defines the interval between runtime config reloads.
// Disabled if 0. // Disabled if <= 0.
// Runtime config changes should be picked up from log-events, // 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. // but if log-events are not coming in (e.g. not syncing blocks) then the reload ensures the config stays accurate.
RuntimeConfigReloadInterval time.Duration RuntimeConfigReloadInterval time.Duration
...@@ -52,6 +52,10 @@ type Config struct { ...@@ -52,6 +52,10 @@ type Config struct {
Heartbeat HeartbeatConfig Heartbeat HeartbeatConfig
Sync sync.Config 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 { type RPCConfig struct {
...@@ -128,5 +132,8 @@ func (cfg *Config) Check() error { ...@@ -128,5 +132,8 @@ func (cfg *Config) Check() error {
return fmt.Errorf("p2p config error: %w", err) 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 return nil
} }
...@@ -2,7 +2,9 @@ package node ...@@ -2,7 +2,9 @@ package node
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"sync/atomic"
"time" "time"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
...@@ -40,10 +42,14 @@ type OpNode struct { ...@@ -40,10 +42,14 @@ type OpNode struct {
tracer Tracer // tracer to get events for testing/debugging tracer Tracer // tracer to get events for testing/debugging
runCfg *RuntimeConfig // runtime configurables 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), // some resources cannot be stopped directly, like the p2p gossipsub router (not our design),
// and depend on this ctx to be closed. // and depend on this ctx to be closed.
resourcesCtx context.Context resourcesCtx context.Context
resourcesClose context.CancelFunc resourcesClose context.CancelFunc
closed atomic.Bool
} }
// The OpNode handles incoming gossip // The OpNode handles incoming gossip
...@@ -58,6 +64,7 @@ func New(ctx context.Context, cfg *Config, log log.Logger, snapshotLog log.Logge ...@@ -58,6 +64,7 @@ func New(ctx context.Context, cfg *Config, log log.Logger, snapshotLog log.Logge
log: log, log: log,
appVersion: appVersion, appVersion: appVersion,
metrics: m, metrics: m,
rollupHalt: cfg.RollupHalt,
} }
// not a context leak, gossipsub is closed with a context. // not a context leak, gossipsub is closed with a context.
n.resourcesCtx, n.resourcesClose = context.WithCancel(context.Background()) n.resourcesCtx, n.resourcesClose = context.WithCancel(context.Background())
...@@ -189,6 +196,9 @@ func (n *OpNode) initRuntimeConfig(ctx context.Context, cfg *Config) error { ...@@ -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) n.log.Error("failed to fetch runtime config data", "err", err)
return l1Head, err return l1Head, err
} }
n.handleProtocolVersionsUpdate(ctx)
return l1Head, nil return l1Head, nil
} }
...@@ -446,6 +456,10 @@ func (n *OpNode) RuntimeConfig() ReadonlyRuntimeConfig { ...@@ -446,6 +456,10 @@ func (n *OpNode) RuntimeConfig() ReadonlyRuntimeConfig {
// Close closes all resources. // Close closes all resources.
func (n *OpNode) Close() error { func (n *OpNode) Close() error {
if n.closed.Load() {
return errors.New("node is already closed")
}
var result *multierror.Error var result *multierror.Error
if n.server != nil { if n.server != nil {
...@@ -494,9 +508,18 @@ func (n *OpNode) Close() error { ...@@ -494,9 +508,18 @@ func (n *OpNode) Close() error {
if n.l1Source != nil { if n.l1Source != nil {
n.l1Source.Close() n.l1Source.Close()
} }
if result == nil { // mark as closed if we successfully fully closed
n.closed.Store(true)
}
return result.ErrorOrNil() return result.ErrorOrNil()
} }
func (n *OpNode) Closed() bool {
return n.closed.Load()
}
func (n *OpNode) ListenAddr() string { func (n *OpNode) ListenAddr() string {
return n.server.listenAddr.String() return n.server.listenAddr.String()
} }
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/p2p"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
...@@ -17,6 +18,14 @@ var ( ...@@ -17,6 +18,14 @@ var (
// UnsafeBlockSignerAddressSystemConfigStorageSlot is the storage slot identifier of the unsafeBlockSigner // UnsafeBlockSignerAddressSystemConfigStorageSlot is the storage slot identifier of the unsafeBlockSigner
// `address` storage value in the SystemConfig L1 contract. Computed as `keccak256("systemconfig.unsafeblocksigner")` // `address` storage value in the SystemConfig L1 contract. Computed as `keccak256("systemconfig.unsafeblocksigner")`
UnsafeBlockSignerAddressSystemConfigStorageSlot = common.HexToHash("0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08") 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 { type RuntimeCfgL1Source interface {
...@@ -25,6 +34,8 @@ type RuntimeCfgL1Source interface { ...@@ -25,6 +34,8 @@ type RuntimeCfgL1Source interface {
type ReadonlyRuntimeConfig interface { type ReadonlyRuntimeConfig interface {
P2PSequencerAddress() common.Address P2PSequencerAddress() common.Address
RequiredProtocolVersion() params.ProtocolVersion
RecommendedProtocolVersion() params.ProtocolVersion
} }
// RuntimeConfig maintains runtime-configurable options. // RuntimeConfig maintains runtime-configurable options.
...@@ -49,6 +60,10 @@ type RuntimeConfig struct { ...@@ -49,6 +60,10 @@ type RuntimeConfig struct {
// runtimeConfigData is a flat bundle of configurable data, easy and light to copy around. // runtimeConfigData is a flat bundle of configurable data, easy and light to copy around.
type runtimeConfigData struct { type runtimeConfigData struct {
p2pBlockSignerAddr common.Address p2pBlockSignerAddr common.Address
// superchain protocol version signals
recommended params.ProtocolVersion
required params.ProtocolVersion
} }
var _ p2p.GossipRuntimeConfig = (*RuntimeConfig)(nil) var _ p2p.GossipRuntimeConfig = (*RuntimeConfig)(nil)
...@@ -67,18 +82,46 @@ func (r *RuntimeConfig) P2PSequencerAddress() common.Address { ...@@ -67,18 +82,46 @@ func (r *RuntimeConfig) P2PSequencerAddress() common.Address {
return r.p2pBlockSignerAddr 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 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, // 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. // 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 { 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 { if err != nil {
return fmt.Errorf("failed to fetch unsafe block signing address from system config: %w", err) 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() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
r.l1Ref = l1Ref 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) r.log.Info("loaded new runtime config values!", "p2p_seq_address", r.p2pBlockSignerAddr)
return nil 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 ( ...@@ -4,12 +4,16 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/superchain-registry/superchain" "github.com/ethereum-optimism/superchain-registry/superchain"
) )
var OPStackSupport = params.ToProtocolVersion([8]byte{}, 3, 1, 0, 1)
const ( const (
opMainnet = 10 opMainnet = 10
opGoerli = 420 opGoerli = 420
......
...@@ -84,6 +84,9 @@ type Config struct { ...@@ -84,6 +84,9 @@ type Config struct {
DepositContractAddress common.Address `json:"deposit_contract_address"` DepositContractAddress common.Address `json:"deposit_contract_address"`
// L1 System Config Address // L1 System Config Address
L1SystemConfigAddress common.Address `json:"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. // ValidateL1Config checks L1 config variables for errors.
...@@ -283,6 +286,8 @@ func (c *Config) Description(l2Chains map[string]string) string { ...@@ -283,6 +286,8 @@ func (c *Config) Description(l2Chains map[string]string) string {
// Report the upgrade configuration // Report the upgrade configuration
banner += "Post-Bedrock Network Upgrades (timestamp based):\n" banner += "Post-Bedrock Network Upgrades (timestamp based):\n"
banner += fmt.Sprintf(" - Regolith: %s\n", fmtForkTimeOrUnset(c.RegolithTime)) 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 return banner
} }
......
...@@ -36,6 +36,10 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -36,6 +36,10 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
return nil, err return nil, err
} }
if !ctx.Bool(flags.BetaRollupLoadProtocolVersions.Name) {
rollupConfig.ProtocolVersionsAddress = common.Address{}
}
configPersistence := NewConfigPersistence(ctx) configPersistence := NewConfigPersistence(ctx)
driverConfig := NewDriverConfig(ctx) driverConfig := NewDriverConfig(ctx)
...@@ -61,6 +65,11 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -61,6 +65,11 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
syncConfig := NewSyncConfig(ctx) syncConfig := NewSyncConfig(ctx)
haltOption := ctx.String(flags.BetaRollupHalt.Name)
if haltOption == "none" {
haltOption = ""
}
cfg := &node.Config{ cfg := &node.Config{
L1: l1Endpoint, L1: l1Endpoint,
L2: l2Endpoint, L2: l2Endpoint,
...@@ -93,6 +102,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -93,6 +102,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
}, },
ConfigPersistence: configPersistence, ConfigPersistence: configPersistence,
Sync: *syncConfig, Sync: *syncConfig,
RollupHalt: haltOption,
} }
if err := cfg.LoadPersisted(log); err != nil { if err := cfg.LoadPersisted(log); err != nil {
......
...@@ -5,6 +5,9 @@ import ( ...@@ -5,6 +5,9 @@ import (
"fmt" "fmt"
"time" "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/client"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/sources/caching" "github.com/ethereum-optimism/optimism/op-node/sources/caching"
...@@ -125,3 +128,12 @@ func (s *EngineClient) GetPayload(ctx context.Context, payloadId eth.PayloadID) ...@@ -125,3 +128,12 @@ func (s *EngineClient) GetPayload(ctx context.Context, payloadId eth.PayloadID)
e.Trace("Received payload") e.Trace("Received payload")
return &result, nil 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 { ...@@ -51,6 +51,9 @@ func (res *AccountResult) Verify(stateRoot common.Hash) error {
if err != nil { 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) 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()) comparison, err := rlp.EncodeToBytes(entry.Value.ToInt().Bytes())
if err != nil { 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) 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