From e4523b08775626b6863899cf971a9cf9e6c07c70 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 7 Sep 2023 15:52:58 +0200 Subject: [PATCH 1/8] introduce engine_signalSuperchainV1 --- eth/catalyst/superchain.go | 70 +++++++++++++++++++++++++ params/superchain.go | 104 +++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 eth/catalyst/superchain.go diff --git a/eth/catalyst/superchain.go b/eth/catalyst/superchain.go new file mode 100644 index 0000000000..8c691b0a53 --- /dev/null +++ b/eth/catalyst/superchain.go @@ -0,0 +1,70 @@ +package catalyst + +import ( + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" +) + +var ( + requiredProtocolDeltaGauge = metrics.NewRegisteredGauge("superchain/required/delta", nil) + recommendedProtocolDeltaGauge = metrics.NewRegisteredGauge("superchain/recommended/delta", nil) +) + +type SuperchainSignal struct { + Recommended params.ProtocolVersion `json:"recommended"` + Required params.ProtocolVersion `json:"required"` +} + +func (api *ConsensusAPI) SignalSuperchainV1(signal *SuperchainSignal) params.ProtocolVersion { + if signal == nil { + log.Info("received empty superchain version signal", "local", params.OPStackSupport) + return params.OPStackSupport + } + logger := log.New("local", params.OPStackSupport, "required", signal.Required, "recommended", signal.Recommended) + requiredCmp := params.OPStackSupport.Compare(signal.Required) + requiredProtocolDeltaGauge.Update(int64(requiredCmp)) + switch requiredCmp { + case params.AheadMajor: + logger.Info("node is ahead of major required protocol change") + case params.AheadMinor, params.AheadPatch, params.AheadPrerelease: + logger.Debug("node is ahead of compatible required protocol change") + case params.Matching: + logger.Debug("node supports latest required protocol change") + case params.OutdatedMajor: + logger.Error("node does not support major required protocol change") + case params.OutdatedMinor: + logger.Warn("node does not support minor required protocol change") + case params.OutdatedPatch: + logger.Warn("node does not support backwards-compatible required protocol change") + case params.OutdatedPrerelease: + logger.Debug("new required protocol pre-release is available") + case params.DiffBuild: + logger.Debug("ignoring required-protocol-version signal, build is different") + case params.DiffVersionType: + logger.Warn("unrecognized required-protocol-version signal version-type") + } + recommendedCmp := params.OPStackSupport.Compare(signal.Recommended) + recommendedProtocolDeltaGauge.Update(int64(recommendedCmp)) + switch recommendedCmp { + case params.AheadMajor: + logger.Info("node is ahead of major recommended protocol change") + case params.AheadMinor, params.AheadPatch, params.AheadPrerelease: + logger.Debug("node is ahead of compatible recommended protocol change") + case params.Matching: + logger.Debug("node supports latest recommended protocol change") + case params.OutdatedMajor: + logger.Warn("node does not support major recommended protocol change") + case params.OutdatedMinor: + logger.Info("node does not support minor recommended protocol change") + case params.OutdatedPatch: + logger.Debug("node does not support backwards-compatible recommended protocol change") + case params.OutdatedPrerelease: + logger.Debug("new recommended protocol pre-release is available") + case params.DiffBuild: + logger.Debug("ignoring recommended-protocol-version signal, build is different") + case params.DiffVersionType: + logger.Warn("unrecognized recommended-protocol-version signal version-type") + } + return params.OPStackSupport +} diff --git a/params/superchain.go b/params/superchain.go index f46c4eb045..07bf78b5b9 100644 --- a/params/superchain.go +++ b/params/superchain.go @@ -1,6 +1,7 @@ package params import ( + "encoding/binary" "fmt" "math/big" @@ -8,6 +9,9 @@ import ( "github.com/ethereum/go-ethereum/common" ) +// TODO initial version +var OPStackSupport = ToProtocolVersion(0, 0, 0, 1, 0) + func init() { for id, ch := range superchain.OPChains { NetworkNames[fmt.Sprintf("%d", id)] = ch.Name @@ -94,3 +98,103 @@ func LoadOPStackChainConfig(chainID uint64) (*ChainConfig, error) { return out, nil } + +// ProtocolVersion encodes the OP-Stack protocol version. See OP-Stack superchain-upgrade specification. +type ProtocolVersion [32]byte + +func (p ProtocolVersion) MarshalText() ([]byte, error) { + return common.Hash(p).MarshalText() +} + +func (p *ProtocolVersion) UnmarshalText(input []byte) error { + return (*common.Hash)(p).UnmarshalText(input) +} + +func (p ProtocolVersion) Parse() (versionType uint8, build uint64, major, minor, patch, preRelease uint32) { + versionType = p[0] + if versionType != 0 { + return + } + // bytes 1:8 reserved for future use + build = binary.BigEndian.Uint64(p[8:16]) // differentiates forks and custom-builds of standard protocol + major = binary.BigEndian.Uint32(p[16:20]) // incompatible API changes + minor = binary.BigEndian.Uint32(p[20:24]) // identifies additional functionality in backwards compatible manner + patch = binary.BigEndian.Uint32(p[24:28]) // identifies backward-compatible bug-fixes + preRelease = binary.BigEndian.Uint32(p[28:32]) // identifies unstable versions that may not satisfy the above + return +} + +func (p ProtocolVersion) String() string { + versionType, build, major, minor, patch, preRelease := p.Parse() + if versionType != 0 { + return "v0.0.0-unknown." + common.Hash(p).String() + } + ver := fmt.Sprintf("v%d.%d.%d", major, minor, patch) + if preRelease != 0 { + ver += fmt.Sprintf("-%d", preRelease) + } + if build != 0 { + ver += fmt.Sprintf("+%d", build) + } + return ver +} + +// ProtocolVersionComparison is used to identify how far ahead/outdated a protocol version is relative to another. +// This value is used in metrics and switch comparisons, to easily identify each type of version difference. +// Negative values mean the version is outdated. +// Positive values mean the version is up-to-date. +// Matching versions have a 0. +type ProtocolVersionComparison int + +const ( + AheadMajor ProtocolVersionComparison = 4 + OutdatedMajor = -4 + AheadMinor = 3 + OutdatedMinor = -3 + AheadPatch = 2 + OutdatedPatch = -2 + AheadPrerelease = 1 + OutdatedPrerelease = -1 + Matching = 0 + DiffVersionType = 100 + DiffBuild = 101 +) + +func (p ProtocolVersion) Compare(other ProtocolVersion) (cmp ProtocolVersionComparison) { + aVersionType, aBuild, aMajor, aMinor, aPatch, aPreRelease := p.Parse() + bVersionType, bBuild, bMajor, bMinor, bPatch, bPreRelease := other.Parse() + if aVersionType != bVersionType { + return DiffVersionType + } + if aBuild != bBuild { + return DiffBuild + } + fn := func(a, b uint32, ahead, outdated ProtocolVersionComparison) ProtocolVersionComparison { + if a == b { + return Matching + } + if a > b { + return ahead + } + return outdated + } + if c := fn(aMajor, bMajor, AheadMajor, OutdatedMajor); c != Matching { + return c + } + if c := fn(aMinor, bMinor, AheadMinor, OutdatedMinor); c != Matching { + return c + } + if c := fn(aPatch, bPatch, AheadPatch, OutdatedPatch); c != Matching { + return c + } + return fn(aPreRelease, bPreRelease, AheadPrerelease, OutdatedPrerelease) +} + +func ToProtocolVersion(build uint64, major, minor, patch, preRelease uint32) (out ProtocolVersion) { + binary.BigEndian.PutUint64(out[8:16], build) + binary.BigEndian.PutUint32(out[16:20], major) + binary.BigEndian.PutUint32(out[20:24], minor) + binary.BigEndian.PutUint32(out[24:28], patch) + binary.BigEndian.PutUint32(out[28:32], preRelease) + return +} From e7a7ebbd3e281918ba02c76dd4e1e199033ffb63 Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 11 Sep 2023 17:18:48 +0200 Subject: [PATCH 2/8] params,eth/catalyst: clean up protocol version logging --- eth/catalyst/superchain.go | 58 +++++++++++++++----------------------- params/superchain.go | 4 +++ 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/eth/catalyst/superchain.go b/eth/catalyst/superchain.go index 8c691b0a53..9f492870cb 100644 --- a/eth/catalyst/superchain.go +++ b/eth/catalyst/superchain.go @@ -1,6 +1,7 @@ package catalyst import ( + "fmt" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" @@ -21,50 +22,37 @@ func (api *ConsensusAPI) SignalSuperchainV1(signal *SuperchainSignal) params.Pro log.Info("received empty superchain version signal", "local", params.OPStackSupport) return params.OPStackSupport } + // update metrics and log any warnings/info + requiredProtocolDeltaGauge.Update(int64(params.OPStackSupport.Compare(signal.Required))) + recommendedProtocolDeltaGauge.Update(int64(params.OPStackSupport.Compare(signal.Recommended))) logger := log.New("local", params.OPStackSupport, "required", signal.Required, "recommended", signal.Recommended) - requiredCmp := params.OPStackSupport.Compare(signal.Required) - requiredProtocolDeltaGauge.Update(int64(requiredCmp)) - switch requiredCmp { - case params.AheadMajor: - logger.Info("node is ahead of major required protocol change") - case params.AheadMinor, params.AheadPatch, params.AheadPrerelease: - logger.Debug("node is ahead of compatible required protocol change") - case params.Matching: - logger.Debug("node supports latest required protocol change") - case params.OutdatedMajor: - logger.Error("node does not support major required protocol change") - case params.OutdatedMinor: - logger.Warn("node does not support minor required protocol change") - case params.OutdatedPatch: - logger.Warn("node does not support backwards-compatible required protocol change") - case params.OutdatedPrerelease: - logger.Debug("new required protocol pre-release is available") - case params.DiffBuild: - logger.Debug("ignoring required-protocol-version signal, build is different") - case params.DiffVersionType: - logger.Warn("unrecognized required-protocol-version signal version-type") - } - recommendedCmp := params.OPStackSupport.Compare(signal.Recommended) - recommendedProtocolDeltaGauge.Update(int64(recommendedCmp)) - switch recommendedCmp { + LogProtocolVersionSupport(logger, params.OPStackSupport, signal.Recommended, "recommended") + LogProtocolVersionSupport(logger, params.OPStackSupport, signal.Required, "required") + + return params.OPStackSupport +} + +func LogProtocolVersionSupport(logger log.Logger, local, other params.ProtocolVersion, name string) { + switch local.Compare(other) { case params.AheadMajor: - logger.Info("node is ahead of major recommended protocol change") + logger.Info(fmt.Sprintf("ahead with major %s protocol version change", name)) case params.AheadMinor, params.AheadPatch, params.AheadPrerelease: - logger.Debug("node is ahead of compatible recommended protocol change") + logger.Debug(fmt.Sprintf("ahead with compatible %s protocol version change", name)) case params.Matching: - logger.Debug("node supports latest recommended protocol change") + logger.Debug(fmt.Sprintf("latest %s protocol version is supported", name)) case params.OutdatedMajor: - logger.Warn("node does not support major recommended protocol change") + logger.Error(fmt.Sprintf("outdated with major %s protocol change", name)) case params.OutdatedMinor: - logger.Info("node does not support minor recommended protocol change") + logger.Warn(fmt.Sprintf("outdated with minor backward-compatible %s protocol change", name)) case params.OutdatedPatch: - logger.Debug("node does not support backwards-compatible recommended protocol change") + logger.Info(fmt.Sprintf("outdated with support backward-compatible %s protocol change", name)) case params.OutdatedPrerelease: - logger.Debug("new recommended protocol pre-release is available") + logger.Debug(fmt.Sprintf("new %s protocol pre-release is available", name)) case params.DiffBuild: - logger.Debug("ignoring recommended-protocol-version signal, build is different") + logger.Debug(fmt.Sprintf("ignoring %s protocolversion signal, local build is different", name)) case params.DiffVersionType: - logger.Warn("unrecognized recommended-protocol-version signal version-type") + logger.Warn(fmt.Sprintf("failed to recognize %s protocol version signal version-type", name)) + case params.EmptyVersion: + logger.Debug(fmt.Sprintf("no %s protocol version available to check", name)) } - return params.OPStackSupport } diff --git a/params/superchain.go b/params/superchain.go index 07bf78b5b9..571f275ca2 100644 --- a/params/superchain.go +++ b/params/superchain.go @@ -158,9 +158,13 @@ const ( Matching = 0 DiffVersionType = 100 DiffBuild = 101 + EmptyVersion = 102 ) func (p ProtocolVersion) Compare(other ProtocolVersion) (cmp ProtocolVersionComparison) { + if p == (ProtocolVersion{}) || (other == (ProtocolVersion{})) { + return EmptyVersion + } aVersionType, aBuild, aMajor, aMinor, aPatch, aPreRelease := p.Parse() bVersionType, bBuild, bMajor, bMinor, bPatch, bPreRelease := other.Parse() if aVersionType != bVersionType { From 5d5b1c12681a6f286376a31a00f19a120e98a8a0 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 12 Sep 2023 21:09:03 +0200 Subject: [PATCH 3/8] params: superchain protocol version unit test --- params/superchain_test.go | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 params/superchain_test.go diff --git a/params/superchain_test.go b/params/superchain_test.go new file mode 100644 index 0000000000..9e1eceb24a --- /dev/null +++ b/params/superchain_test.go @@ -0,0 +1,87 @@ +package params + +import ( + "fmt" + "testing" +) + +type HumanProtocolVersion struct { + VersionType uint8 + Major, Minor, Patch uint32 + Prerelease uint32 + Build uint64 +} + +type ComparisonCase struct { + A, B HumanProtocolVersion + Cmp ProtocolVersionComparison +} + +func TestProtocolVersion_Compare(t *testing.T) { + testCases := []ComparisonCase{ + { + A: HumanProtocolVersion{0, 2, 1, 1, 1, 0}, + B: HumanProtocolVersion{0, 1, 2, 2, 2, 0}, + Cmp: AheadMajor, + }, + { + A: HumanProtocolVersion{0, 1, 2, 1, 1, 0}, + B: HumanProtocolVersion{0, 1, 1, 2, 2, 0}, + Cmp: AheadMinor, + }, + { + A: HumanProtocolVersion{0, 1, 1, 2, 1, 0}, + B: HumanProtocolVersion{0, 1, 1, 1, 2, 0}, + Cmp: AheadPatch, + }, + { + A: HumanProtocolVersion{0, 1, 1, 1, 2, 0}, + B: HumanProtocolVersion{0, 1, 1, 1, 1, 0}, + Cmp: AheadPrerelease, + }, + { + A: HumanProtocolVersion{0, 1, 2, 3, 4, 0}, + B: HumanProtocolVersion{0, 1, 2, 3, 4, 0}, + Cmp: Matching, + }, + { + A: HumanProtocolVersion{0, 3, 2, 1, 5, 3}, + B: HumanProtocolVersion{1, 1, 2, 3, 3, 6}, + Cmp: DiffVersionType, + }, + { + A: HumanProtocolVersion{0, 3, 2, 1, 5, 3}, + B: HumanProtocolVersion{0, 1, 2, 3, 3, 6}, + Cmp: DiffBuild, + }, + { + A: HumanProtocolVersion{0, 0, 0, 0, 0, 0}, + B: HumanProtocolVersion{0, 1, 3, 3, 3, 3}, + Cmp: EmptyVersion, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { + a := ToProtocolVersion(tc.A.Build, tc.A.Major, tc.A.Minor, tc.A.Patch, tc.A.Prerelease) + a[0] = tc.A.VersionType + b := ToProtocolVersion(tc.B.Build, tc.B.Major, tc.B.Minor, tc.B.Patch, tc.B.Prerelease) + b[0] = tc.B.VersionType + cmp := a.Compare(b) + if cmp != tc.Cmp { + t.Fatalf("expected %d but got %d", tc.Cmp, cmp) + } + switch tc.Cmp { + case AheadMajor, AheadMinor, AheadPatch, AheadPrerelease: + inv := b.Compare(a) + if inv != -tc.Cmp { + t.Fatalf("expected inverse when reversing the comparison, %d but got %d", -tc.Cmp, inv) + } + case DiffVersionType, DiffBuild, EmptyVersion, Matching: + inv := b.Compare(a) + if inv != tc.Cmp { + t.Fatalf("expected comparison reversed to hold the same, expected %d but got %d", tc.Cmp, inv) + } + } + }) + } +} From 1d076fa649cb940ea5b10f0fff31b2ea33d0eb9b Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 12 Sep 2023 22:24:49 +0200 Subject: [PATCH 4/8] superchain: opt-in halting option if node is outdated --- cmd/utils/flags.go | 6 ++ eth/backend.go | 31 ++++++++++ eth/catalyst/api_test.go | 7 ++- eth/catalyst/superchain.go | 12 +++- eth/catalyst/superchain_test.go | 100 ++++++++++++++++++++++++++++++++ eth/ethconfig/config.go | 11 ++-- params/superchain.go | 22 +++---- 7 files changed, 169 insertions(+), 20 deletions(-) create mode 100644 eth/catalyst/superchain_test.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f374c99556..791ed19368 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -893,6 +893,11 @@ var ( Usage: "By default the pending block equals the latest block to save resources and not leak txs from the tx-pool, this flag enables computing of the pending block from the tx-pool instead.", Category: flags.RollupCategory, } + RollupHaltOnIncompatibleProtocolVersionFlag = &cli.StringFlag{ + Name: "beta.rollup.halt", + Usage: "Opt-in option to halt on incompatible protocol version requirements of the given level (major/minor/patch/none), as signaled through the Engine API by the rollup node", + Category: flags.RollupCategory, + } // Metrics flags MetricsEnabledFlag = &cli.BoolFlag{ @@ -1839,6 +1844,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } cfg.RollupDisableTxPoolGossip = ctx.Bool(RollupDisableTxPoolGossipFlag.Name) cfg.RollupDisableTxPoolAdmission = cfg.RollupSequencerHTTP != "" && !ctx.Bool(RollupEnableTxPoolAdmissionFlag.Name) + cfg.RollupHaltOnIncompatibleProtocolVersion = ctx.String(RollupHaltOnIncompatibleProtocolVersionFlag.Name) // Override any default configs for hard coded networks. switch { case ctx.Bool(MainnetFlag.Name): diff --git a/eth/backend.go b/eth/backend.go index 294fb4e12e..79d9d6656c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -103,6 +103,8 @@ type Ethereum struct { lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully + + nodeCloser func() error } // New creates a new Ethereum object (including the @@ -162,6 +164,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), p2pServer: stack.Server(), shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb), + nodeCloser: stack.Close, } bcVersion := rawdb.ReadDatabaseVersion(chainDb) @@ -575,3 +578,31 @@ func (s *Ethereum) Stop() error { return nil } + +func (s *Ethereum) HandleRequiredProtocolVersion(required params.ProtocolVersion) error { + var needLevel int + switch s.config.RollupHaltOnIncompatibleProtocolVersion { + case "major": + needLevel = 3 + case "minor": + needLevel = 2 + case "patch": + needLevel = 1 + default: + return nil // do not consider halting if not configured to + } + haveLevel := 0 + switch params.OPStackSupport.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 + log.Error("opted to halt, unprepared for protocol change", "required", required, "local", params.OPStackSupport) + return s.nodeCloser() + } + return nil +} diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 5bab7ba186..4df09636fe 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -428,6 +428,11 @@ func TestEth2DeepReorg(t *testing.T) { // startEthService creates a full node instance for testing. func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) { + ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256} + return startEthServiceWithConfigFn(t, blocks, ethcfg) +} + +func startEthServiceWithConfigFn(t *testing.T, blocks []*types.Block, ethcfg *ethconfig.Config) (*node.Node, *eth.Ethereum) { t.Helper() n, err := node.New(&node.Config{ @@ -440,7 +445,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) t.Fatal("can't create node:", err) } - ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256} + // default eth config is moved to startEthService ethservice, err := eth.New(n, ethcfg) if err != nil { t.Fatal("can't create eth service:", err) diff --git a/eth/catalyst/superchain.go b/eth/catalyst/superchain.go index 9f492870cb..e277d2c99d 100644 --- a/eth/catalyst/superchain.go +++ b/eth/catalyst/superchain.go @@ -2,6 +2,7 @@ package catalyst import ( "fmt" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" @@ -17,10 +18,10 @@ type SuperchainSignal struct { Required params.ProtocolVersion `json:"required"` } -func (api *ConsensusAPI) SignalSuperchainV1(signal *SuperchainSignal) params.ProtocolVersion { +func (api *ConsensusAPI) SignalSuperchainV1(signal *SuperchainSignal) (params.ProtocolVersion, error) { if signal == nil { log.Info("received empty superchain version signal", "local", params.OPStackSupport) - return params.OPStackSupport + return params.OPStackSupport, nil } // update metrics and log any warnings/info requiredProtocolDeltaGauge.Update(int64(params.OPStackSupport.Compare(signal.Required))) @@ -29,7 +30,12 @@ func (api *ConsensusAPI) SignalSuperchainV1(signal *SuperchainSignal) params.Pro LogProtocolVersionSupport(logger, params.OPStackSupport, signal.Recommended, "recommended") LogProtocolVersionSupport(logger, params.OPStackSupport, signal.Required, "required") - return params.OPStackSupport + if err := api.eth.HandleRequiredProtocolVersion(signal.Required); err != nil { + log.Error("failed to handle required protocol version", "err", err, "required", signal.Required) + return params.OPStackSupport, err + } + + return params.OPStackSupport, nil } func LogProtocolVersionSupport(logger log.Logger, local, other params.ProtocolVersion, name string) { diff --git a/eth/catalyst/superchain_test.go b/eth/catalyst/superchain_test.go new file mode 100644 index 0000000000..f067b57c66 --- /dev/null +++ b/eth/catalyst/superchain_test.go @@ -0,0 +1,100 @@ +package catalyst + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" +) + +func TestSignalSuperchainV1(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(2, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + api := NewConsensusAPI(ethservice) + t.Run("matching", func(t *testing.T) { + out, err := api.SignalSuperchainV1(&SuperchainSignal{ + Recommended: params.OPStackSupport, + Required: params.OPStackSupport, + }) + if err != nil { + t.Fatalf("failed to process signal: %v", err) + } + if out != params.OPStackSupport { + t.Fatalf("expected %s but got %s", params.OPStackSupport, out) + } + }) + t.Run("null_arg", func(t *testing.T) { + out, err := api.SignalSuperchainV1(nil) + if err != nil { + t.Fatalf("failed to process signal: %v", err) + } + if out != params.OPStackSupport { + t.Fatalf("expected %s but got %s", params.OPStackSupport, out) + } + }) +} + +func TestSignalSuperchainV1Halt(t *testing.T) { + testCases := []struct { + cfg string + bump string + halt bool + }{ + {"none", "major", false}, + {"major", "major", true}, + {"minor", "major", true}, + {"patch", "major", true}, + {"major", "minor", false}, + {"minor", "minor", true}, + {"patch", "minor", true}, + {"major", "patch", false}, + {"minor", "patch", false}, + {"patch", "patch", true}, + } + for _, tc := range testCases { + t.Run(tc.cfg+"_"+tc.bump, func(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(2, false) + ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256} + ethcfg.RollupHaltOnIncompatibleProtocolVersion = tc.cfg // opt-in to halting (or not) + n, ethservice := startEthServiceWithConfigFn(t, preMergeBlocks, ethcfg) + defer n.Close() // close at the end, regardless of any prior (failed) closing + api := NewConsensusAPI(ethservice) + _, build, major, minor, patch, preRelease := params.OPStackSupport.Parse() + majorSignal, minorSignal, patchSignal := major, minor, patch + switch tc.bump { + case "major": + majorSignal += 1 + case "minor": + minorSignal += 1 + case "patch": + patchSignal += 1 + } + out, err := api.SignalSuperchainV1(&SuperchainSignal{ + Recommended: params.OPStackSupport, // required version change should be enough + Required: params.ToProtocolVersion(build, majorSignal, minorSignal, patchSignal, preRelease), + }) + if err != nil { + t.Fatalf("failed to process signal: %v", err) + } + if out != params.OPStackSupport { + t.Fatalf("expected %s but got %s", params.OPStackSupport, out) + } + closeErr := n.Close() + if tc.halt { + // assert no halt by closing, and not getting any error + if closeErr == nil { + t.Fatalf("expected not to have closed already, but just closed without error") + } + } else { + // assert halt by closing again, and seeing if things error + if closeErr == node.ErrNodeStopped { + t.Fatalf("expected to have already closed and get a ErrNodeStopped error, but got %v", closeErr) + } + } + }) + } +} diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 2a7746a193..09685792e3 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -168,11 +168,12 @@ type Config struct { OverrideOptimismRegolith *uint64 `toml:",omitempty"` OverrideOptimism *bool - RollupSequencerHTTP string - RollupHistoricalRPC string - RollupHistoricalRPCTimeout time.Duration - RollupDisableTxPoolGossip bool - RollupDisableTxPoolAdmission bool + RollupSequencerHTTP string + RollupHistoricalRPC string + RollupHistoricalRPCTimeout time.Duration + RollupDisableTxPoolGossip bool + RollupDisableTxPoolAdmission bool + RollupHaltOnIncompatibleProtocolVersion string } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/params/superchain.go b/params/superchain.go index 571f275ca2..944dc4d9ee 100644 --- a/params/superchain.go +++ b/params/superchain.go @@ -148,17 +148,17 @@ type ProtocolVersionComparison int const ( AheadMajor ProtocolVersionComparison = 4 - OutdatedMajor = -4 - AheadMinor = 3 - OutdatedMinor = -3 - AheadPatch = 2 - OutdatedPatch = -2 - AheadPrerelease = 1 - OutdatedPrerelease = -1 - Matching = 0 - DiffVersionType = 100 - DiffBuild = 101 - EmptyVersion = 102 + OutdatedMajor ProtocolVersionComparison = -4 + AheadMinor ProtocolVersionComparison = 3 + OutdatedMinor ProtocolVersionComparison = -3 + AheadPatch ProtocolVersionComparison = 2 + OutdatedPatch ProtocolVersionComparison = -2 + AheadPrerelease ProtocolVersionComparison = 1 + OutdatedPrerelease ProtocolVersionComparison = -1 + Matching ProtocolVersionComparison = 0 + DiffVersionType ProtocolVersionComparison = 100 + DiffBuild ProtocolVersionComparison = 101 + EmptyVersion ProtocolVersionComparison = 102 ) func (p ProtocolVersion) Compare(other ProtocolVersion) (cmp ProtocolVersionComparison) { From 89c1ffaf1498701b4bee4698e511297293142e34 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 13 Sep 2023 01:35:04 +0200 Subject: [PATCH 5/8] params: set OP Stack protocol version to v3.1.0-1 --- params/superchain.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/params/superchain.go b/params/superchain.go index 944dc4d9ee..9d5d7b7cb2 100644 --- a/params/superchain.go +++ b/params/superchain.go @@ -9,8 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// TODO initial version -var OPStackSupport = ToProtocolVersion(0, 0, 0, 1, 0) +var OPStackSupport = ToProtocolVersion(0, 3, 1, 0, 1) func init() { for id, ch := range superchain.OPChains { From 60b827bfc2deb74d72b46cda1777cd824f445ae1 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 13 Sep 2023 12:43:29 +0200 Subject: [PATCH 6/8] params: superchain version formatting, bytes8 instead of uint64 build attribute --- params/superchain.go | 37 ++++++++++++++++++++----- params/superchain_test.go | 58 +++++++++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/params/superchain.go b/params/superchain.go index 9d5d7b7cb2..023208c995 100644 --- a/params/superchain.go +++ b/params/superchain.go @@ -4,12 +4,13 @@ import ( "encoding/binary" "fmt" "math/big" + "strings" "github.com/ethereum-optimism/superchain-registry/superchain" "github.com/ethereum/go-ethereum/common" ) -var OPStackSupport = ToProtocolVersion(0, 3, 1, 0, 1) +var OPStackSupport = ToProtocolVersion([8]byte{}, 3, 1, 0, 1) func init() { for id, ch := range superchain.OPChains { @@ -109,13 +110,13 @@ func (p *ProtocolVersion) UnmarshalText(input []byte) error { return (*common.Hash)(p).UnmarshalText(input) } -func (p ProtocolVersion) Parse() (versionType uint8, build uint64, major, minor, patch, preRelease uint32) { +func (p ProtocolVersion) Parse() (versionType uint8, build [8]byte, major, minor, patch, preRelease uint32) { versionType = p[0] if versionType != 0 { return } // bytes 1:8 reserved for future use - build = binary.BigEndian.Uint64(p[8:16]) // differentiates forks and custom-builds of standard protocol + copy(build[:], p[8:16]) // differentiates forks and custom-builds of standard protocol major = binary.BigEndian.Uint32(p[16:20]) // incompatible API changes minor = binary.BigEndian.Uint32(p[20:24]) // identifies additional functionality in backwards compatible manner patch = binary.BigEndian.Uint32(p[24:28]) // identifies backward-compatible bug-fixes @@ -132,12 +133,34 @@ func (p ProtocolVersion) String() string { if preRelease != 0 { ver += fmt.Sprintf("-%d", preRelease) } - if build != 0 { - ver += fmt.Sprintf("+%d", build) + if build != ([8]byte{}) { + if humanBuildTag(build) { + ver += fmt.Sprintf("+%s", strings.TrimRight(string(build[:]), "\x00")) + } else { + ver += fmt.Sprintf("+0x%x", build) + } } return ver } +// humanBuildTag identifies which build tag we can stringify for human-readable versions +func humanBuildTag(v [8]byte) bool { + for i, c := range v { // following semver.org advertised regex, alphanumeric with '-' and '.', except leading '.'. + if c == 0 { // trailing zeroed are allowed + for _, d := range v[i:] { + if d != 0 { + return false + } + } + return true + } + if !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || (c == '.' && i > 0)) { + return false + } + } + return true +} + // ProtocolVersionComparison is used to identify how far ahead/outdated a protocol version is relative to another. // This value is used in metrics and switch comparisons, to easily identify each type of version difference. // Negative values mean the version is outdated. @@ -193,8 +216,8 @@ func (p ProtocolVersion) Compare(other ProtocolVersion) (cmp ProtocolVersionComp return fn(aPreRelease, bPreRelease, AheadPrerelease, OutdatedPrerelease) } -func ToProtocolVersion(build uint64, major, minor, patch, preRelease uint32) (out ProtocolVersion) { - binary.BigEndian.PutUint64(out[8:16], build) +func ToProtocolVersion(build [8]byte, major, minor, patch, preRelease uint32) (out ProtocolVersion) { + copy(out[8:16], build[:]) binary.BigEndian.PutUint32(out[16:20], major) binary.BigEndian.PutUint32(out[20:24], minor) binary.BigEndian.PutUint32(out[24:28], patch) diff --git a/params/superchain_test.go b/params/superchain_test.go index 9e1eceb24a..8194e71602 100644 --- a/params/superchain_test.go +++ b/params/superchain_test.go @@ -9,7 +9,7 @@ type HumanProtocolVersion struct { VersionType uint8 Major, Minor, Patch uint32 Prerelease uint32 - Build uint64 + Build [8]byte } type ComparisonCase struct { @@ -20,43 +20,43 @@ type ComparisonCase struct { func TestProtocolVersion_Compare(t *testing.T) { testCases := []ComparisonCase{ { - A: HumanProtocolVersion{0, 2, 1, 1, 1, 0}, - B: HumanProtocolVersion{0, 1, 2, 2, 2, 0}, + A: HumanProtocolVersion{0, 2, 1, 1, 1, [8]byte{}}, + B: HumanProtocolVersion{0, 1, 2, 2, 2, [8]byte{}}, Cmp: AheadMajor, }, { - A: HumanProtocolVersion{0, 1, 2, 1, 1, 0}, - B: HumanProtocolVersion{0, 1, 1, 2, 2, 0}, + A: HumanProtocolVersion{0, 1, 2, 1, 1, [8]byte{}}, + B: HumanProtocolVersion{0, 1, 1, 2, 2, [8]byte{}}, Cmp: AheadMinor, }, { - A: HumanProtocolVersion{0, 1, 1, 2, 1, 0}, - B: HumanProtocolVersion{0, 1, 1, 1, 2, 0}, + A: HumanProtocolVersion{0, 1, 1, 2, 1, [8]byte{}}, + B: HumanProtocolVersion{0, 1, 1, 1, 2, [8]byte{}}, Cmp: AheadPatch, }, { - A: HumanProtocolVersion{0, 1, 1, 1, 2, 0}, - B: HumanProtocolVersion{0, 1, 1, 1, 1, 0}, + A: HumanProtocolVersion{0, 1, 1, 1, 2, [8]byte{}}, + B: HumanProtocolVersion{0, 1, 1, 1, 1, [8]byte{}}, Cmp: AheadPrerelease, }, { - A: HumanProtocolVersion{0, 1, 2, 3, 4, 0}, - B: HumanProtocolVersion{0, 1, 2, 3, 4, 0}, + A: HumanProtocolVersion{0, 1, 2, 3, 4, [8]byte{}}, + B: HumanProtocolVersion{0, 1, 2, 3, 4, [8]byte{}}, Cmp: Matching, }, { - A: HumanProtocolVersion{0, 3, 2, 1, 5, 3}, - B: HumanProtocolVersion{1, 1, 2, 3, 3, 6}, + A: HumanProtocolVersion{0, 3, 2, 1, 5, [8]byte{3}}, + B: HumanProtocolVersion{1, 1, 2, 3, 3, [8]byte{6}}, Cmp: DiffVersionType, }, { - A: HumanProtocolVersion{0, 3, 2, 1, 5, 3}, - B: HumanProtocolVersion{0, 1, 2, 3, 3, 6}, + A: HumanProtocolVersion{0, 3, 2, 1, 5, [8]byte{3}}, + B: HumanProtocolVersion{0, 1, 2, 3, 3, [8]byte{6}}, Cmp: DiffBuild, }, { - A: HumanProtocolVersion{0, 0, 0, 0, 0, 0}, - B: HumanProtocolVersion{0, 1, 3, 3, 3, 3}, + A: HumanProtocolVersion{0, 0, 0, 0, 0, [8]byte{}}, + B: HumanProtocolVersion{0, 1, 3, 3, 3, [8]byte{3}}, Cmp: EmptyVersion, }, } @@ -85,3 +85,27 @@ func TestProtocolVersion_Compare(t *testing.T) { }) } } +func TestProtocolVersion_String(t *testing.T) { + testCases := []struct { + version ProtocolVersion + expected string + }{ + {ToProtocolVersion([8]byte{}, 0, 0, 0, 0), "v0.0.0"}, + {ToProtocolVersion([8]byte{}, 0, 0, 0, 1), "v0.0.0-1"}, + {ToProtocolVersion([8]byte{}, 0, 0, 1, 0), "v0.0.1"}, + {ToProtocolVersion([8]byte{}, 4, 3, 2, 1), "v4.3.2-1"}, + {ToProtocolVersion([8]byte{}, 0, 100, 2, 0), "v0.100.2"}, + {ToProtocolVersion([8]byte{'O', 'P', '-', 'm', 'o', 'd'}, 42, 0, 2, 1), "v42.0.2-1+OP-mod"}, + {ToProtocolVersion([8]byte{'b', 'e', 't', 'a', '.', '1', '2', '3'}, 1, 0, 0, 0), "v1.0.0+beta.123"}, + {ToProtocolVersion([8]byte{'a', 'b', 1}, 42, 0, 2, 0), "v42.0.2+0x6162010000000000"}, // do not render invalid alpha numeric + {ToProtocolVersion([8]byte{1, 2, 3, 4, 5, 6, 7, 8}, 42, 0, 2, 0), "v42.0.2+0x0102030405060708"}, + } + for _, tc := range testCases { + t.Run(tc.expected, func(t *testing.T) { + got := tc.version.String() + if got != tc.expected { + t.Fatalf("got %q but expected %q", got, tc.expected) + } + }) + } +} From 5e58eb589640846212c698646683100a286ce0c7 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 14 Sep 2023 23:33:15 +0200 Subject: [PATCH 7/8] superchain: implement protocol-version handling review suggestions --- cmd/geth/main.go | 1 + eth/backend.go | 4 +++- eth/catalyst/superchain.go | 24 ++++++++++++------------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 6cf2fd57ff..5884b5311f 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -146,6 +146,7 @@ var ( utils.RollupHistoricalRPCTimeoutFlag, utils.RollupDisableTxPoolGossipFlag, utils.RollupComputePendingBlock, + utils.RollupHaltOnIncompatibleProtocolVersionFlag, configFileFlag, }, utils.NetworkFlags, utils.DatabasePathFlags) diff --git a/eth/backend.go b/eth/backend.go index 79d9d6656c..7c8df0d786 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -579,6 +579,8 @@ func (s *Ethereum) Stop() error { return nil } +// HandleRequiredProtocolVersion handles the protocol version signal. This implements opt-in halting, +// the protocol version data is already logged and metered when signaled through the Engine API. func (s *Ethereum) HandleRequiredProtocolVersion(required params.ProtocolVersion) error { var needLevel int switch s.config.RollupHaltOnIncompatibleProtocolVersion { @@ -601,7 +603,7 @@ func (s *Ethereum) HandleRequiredProtocolVersion(required params.ProtocolVersion haveLevel = 1 } if haveLevel >= needLevel { // halt if we opted in to do so at this granularity - log.Error("opted to halt, unprepared for protocol change", "required", required, "local", params.OPStackSupport) + log.Error("Opted to halt, unprepared for protocol change", "required", required, "local", params.OPStackSupport) return s.nodeCloser() } return nil diff --git a/eth/catalyst/superchain.go b/eth/catalyst/superchain.go index e277d2c99d..2d44647046 100644 --- a/eth/catalyst/superchain.go +++ b/eth/catalyst/superchain.go @@ -20,7 +20,7 @@ type SuperchainSignal struct { func (api *ConsensusAPI) SignalSuperchainV1(signal *SuperchainSignal) (params.ProtocolVersion, error) { if signal == nil { - log.Info("received empty superchain version signal", "local", params.OPStackSupport) + log.Info("Received empty superchain version signal", "local", params.OPStackSupport) return params.OPStackSupport, nil } // update metrics and log any warnings/info @@ -31,7 +31,7 @@ func (api *ConsensusAPI) SignalSuperchainV1(signal *SuperchainSignal) (params.Pr LogProtocolVersionSupport(logger, params.OPStackSupport, signal.Required, "required") if err := api.eth.HandleRequiredProtocolVersion(signal.Required); err != nil { - log.Error("failed to handle required protocol version", "err", err, "required", signal.Required) + log.Error("Failed to handle required protocol version", "err", err, "required", signal.Required) return params.OPStackSupport, err } @@ -41,24 +41,24 @@ func (api *ConsensusAPI) SignalSuperchainV1(signal *SuperchainSignal) (params.Pr func LogProtocolVersionSupport(logger log.Logger, local, other params.ProtocolVersion, name string) { switch local.Compare(other) { case params.AheadMajor: - logger.Info(fmt.Sprintf("ahead with major %s protocol version change", name)) + logger.Info(fmt.Sprintf("Ahead with major %s protocol version change", name)) case params.AheadMinor, params.AheadPatch, params.AheadPrerelease: - logger.Debug(fmt.Sprintf("ahead with compatible %s protocol version change", name)) + logger.Debug(fmt.Sprintf("Ahead with compatible %s protocol version change", name)) case params.Matching: - logger.Debug(fmt.Sprintf("latest %s protocol version is supported", name)) + logger.Debug(fmt.Sprintf("Latest %s protocol version is supported", name)) case params.OutdatedMajor: - logger.Error(fmt.Sprintf("outdated with major %s protocol change", name)) + logger.Error(fmt.Sprintf("Outdated with major %s protocol change", name)) case params.OutdatedMinor: - logger.Warn(fmt.Sprintf("outdated with minor backward-compatible %s protocol change", name)) + logger.Warn(fmt.Sprintf("Outdated with minor backward-compatible %s protocol change", name)) case params.OutdatedPatch: - logger.Info(fmt.Sprintf("outdated with support backward-compatible %s protocol change", name)) + logger.Info(fmt.Sprintf("Outdated with support backward-compatible %s protocol change", name)) case params.OutdatedPrerelease: - logger.Debug(fmt.Sprintf("new %s protocol pre-release is available", name)) + logger.Debug(fmt.Sprintf("New %s protocol pre-release is available", name)) case params.DiffBuild: - logger.Debug(fmt.Sprintf("ignoring %s protocolversion signal, local build is different", name)) + logger.Debug(fmt.Sprintf("Ignoring %s protocolversion signal, local build is different", name)) case params.DiffVersionType: - logger.Warn(fmt.Sprintf("failed to recognize %s protocol version signal version-type", name)) + logger.Warn(fmt.Sprintf("Failed to recognize %s protocol version signal version-type", name)) case params.EmptyVersion: - logger.Debug(fmt.Sprintf("no %s protocol version available to check", name)) + logger.Debug(fmt.Sprintf("No %s protocol version available to check", name)) } } From 92eb9d4805ddb9a479f117c99d0049eeff696475 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 15 Sep 2023 00:09:22 +0200 Subject: [PATCH 8/8] params: ProtocolVersionV0 struct helper for human-readable encoding call --- eth/catalyst/superchain_test.go | 2 +- params/superchain.go | 19 ++++++++++++------- params/superchain_test.go | 22 +++++++++++----------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/eth/catalyst/superchain_test.go b/eth/catalyst/superchain_test.go index f067b57c66..e06770fa32 100644 --- a/eth/catalyst/superchain_test.go +++ b/eth/catalyst/superchain_test.go @@ -75,7 +75,7 @@ func TestSignalSuperchainV1Halt(t *testing.T) { } out, err := api.SignalSuperchainV1(&SuperchainSignal{ Recommended: params.OPStackSupport, // required version change should be enough - Required: params.ToProtocolVersion(build, majorSignal, minorSignal, patchSignal, preRelease), + Required: params.ProtocolVersionV0{Build: build, Major: majorSignal, Minor: minorSignal, Patch: patchSignal, PreRelease: preRelease}.Encode(), }) if err != nil { t.Fatalf("failed to process signal: %v", err) diff --git a/params/superchain.go b/params/superchain.go index 023208c995..a55c2eb1bf 100644 --- a/params/superchain.go +++ b/params/superchain.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) -var OPStackSupport = ToProtocolVersion([8]byte{}, 3, 1, 0, 1) +var OPStackSupport = ProtocolVersionV0{Build: [8]byte{}, Major: 3, Minor: 1, Patch: 0, PreRelease: 1}.Encode() func init() { for id, ch := range superchain.OPChains { @@ -216,11 +216,16 @@ func (p ProtocolVersion) Compare(other ProtocolVersion) (cmp ProtocolVersionComp return fn(aPreRelease, bPreRelease, AheadPrerelease, OutdatedPrerelease) } -func ToProtocolVersion(build [8]byte, major, minor, patch, preRelease uint32) (out ProtocolVersion) { - copy(out[8:16], build[:]) - binary.BigEndian.PutUint32(out[16:20], major) - binary.BigEndian.PutUint32(out[20:24], minor) - binary.BigEndian.PutUint32(out[24:28], patch) - binary.BigEndian.PutUint32(out[28:32], preRelease) +type ProtocolVersionV0 struct { + Build [8]byte + Major, Minor, Patch, PreRelease uint32 +} + +func (v ProtocolVersionV0) Encode() (out ProtocolVersion) { + copy(out[8:16], v.Build[:]) + binary.BigEndian.PutUint32(out[16:20], v.Major) + binary.BigEndian.PutUint32(out[20:24], v.Minor) + binary.BigEndian.PutUint32(out[24:28], v.Patch) + binary.BigEndian.PutUint32(out[28:32], v.PreRelease) return } diff --git a/params/superchain_test.go b/params/superchain_test.go index 8194e71602..a1fe1d5332 100644 --- a/params/superchain_test.go +++ b/params/superchain_test.go @@ -62,9 +62,9 @@ func TestProtocolVersion_Compare(t *testing.T) { } for i, tc := range testCases { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { - a := ToProtocolVersion(tc.A.Build, tc.A.Major, tc.A.Minor, tc.A.Patch, tc.A.Prerelease) + a := ProtocolVersionV0{tc.A.Build, tc.A.Major, tc.A.Minor, tc.A.Patch, tc.A.Prerelease}.Encode() a[0] = tc.A.VersionType - b := ToProtocolVersion(tc.B.Build, tc.B.Major, tc.B.Minor, tc.B.Patch, tc.B.Prerelease) + b := ProtocolVersionV0{tc.B.Build, tc.B.Major, tc.B.Minor, tc.B.Patch, tc.B.Prerelease}.Encode() b[0] = tc.B.VersionType cmp := a.Compare(b) if cmp != tc.Cmp { @@ -90,15 +90,15 @@ func TestProtocolVersion_String(t *testing.T) { version ProtocolVersion expected string }{ - {ToProtocolVersion([8]byte{}, 0, 0, 0, 0), "v0.0.0"}, - {ToProtocolVersion([8]byte{}, 0, 0, 0, 1), "v0.0.0-1"}, - {ToProtocolVersion([8]byte{}, 0, 0, 1, 0), "v0.0.1"}, - {ToProtocolVersion([8]byte{}, 4, 3, 2, 1), "v4.3.2-1"}, - {ToProtocolVersion([8]byte{}, 0, 100, 2, 0), "v0.100.2"}, - {ToProtocolVersion([8]byte{'O', 'P', '-', 'm', 'o', 'd'}, 42, 0, 2, 1), "v42.0.2-1+OP-mod"}, - {ToProtocolVersion([8]byte{'b', 'e', 't', 'a', '.', '1', '2', '3'}, 1, 0, 0, 0), "v1.0.0+beta.123"}, - {ToProtocolVersion([8]byte{'a', 'b', 1}, 42, 0, 2, 0), "v42.0.2+0x6162010000000000"}, // do not render invalid alpha numeric - {ToProtocolVersion([8]byte{1, 2, 3, 4, 5, 6, 7, 8}, 42, 0, 2, 0), "v42.0.2+0x0102030405060708"}, + {ProtocolVersionV0{[8]byte{}, 0, 0, 0, 0}.Encode(), "v0.0.0"}, + {ProtocolVersionV0{[8]byte{}, 0, 0, 0, 1}.Encode(), "v0.0.0-1"}, + {ProtocolVersionV0{[8]byte{}, 0, 0, 1, 0}.Encode(), "v0.0.1"}, + {ProtocolVersionV0{[8]byte{}, 4, 3, 2, 1}.Encode(), "v4.3.2-1"}, + {ProtocolVersionV0{[8]byte{}, 0, 100, 2, 0}.Encode(), "v0.100.2"}, + {ProtocolVersionV0{[8]byte{'O', 'P', '-', 'm', 'o', 'd'}, 42, 0, 2, 1}.Encode(), "v42.0.2-1+OP-mod"}, + {ProtocolVersionV0{[8]byte{'b', 'e', 't', 'a', '.', '1', '2', '3'}, 1, 0, 0, 0}.Encode(), "v1.0.0+beta.123"}, + {ProtocolVersionV0{[8]byte{'a', 'b', 1}, 42, 0, 2, 0}.Encode(), "v42.0.2+0x6162010000000000"}, // do not render invalid alpha numeric + {ProtocolVersionV0{[8]byte{1, 2, 3, 4, 5, 6, 7, 8}, 42, 0, 2, 0}.Encode(), "v42.0.2+0x0102030405060708"}, } for _, tc := range testCases { t.Run(tc.expected, func(t *testing.T) {