Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add engine_signalSuperchainV1 #7693

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e0c7cd4
Add `TakeAndMove` on Span
emlautarom1 Oct 29, 2024
e78112a
Initial `OptimismProtocolVersion`
emlautarom1 Oct 29, 2024
2773bf2
Change to discriminated union
emlautarom1 Oct 29, 2024
bc16b27
Mark tests as parallelizable
emlautarom1 Oct 29, 2024
fa97b30
Test for Read/Write
emlautarom1 Oct 29, 2024
e279da6
Test renaming
emlautarom1 Oct 30, 2024
3fefaf0
Add V0 reserved test
emlautarom1 Oct 30, 2024
8252443
Overload operators
emlautarom1 Oct 30, 2024
6c49dab
Initial `engine_signalSuperchainV1`
emlautarom1 Oct 30, 2024
8f1c192
Test for return value
emlautarom1 Oct 30, 2024
47b6bd4
Pass signal values to handlers
emlautarom1 Oct 30, 2024
6507d97
Add Logging impl for `IOptimismSuperchainSignalHandler`
emlautarom1 Oct 30, 2024
337f11a
Improve logging
emlautarom1 Oct 30, 2024
f04b956
Discard var
emlautarom1 Oct 30, 2024
d60d46e
Initial `ToString`
emlautarom1 Oct 30, 2024
0c3a219
Parse and use system version
emlautarom1 Oct 31, 2024
ad54339
Wrap engine_signalsuperchainv1 response
emlautarom1 Oct 31, 2024
431492f
Add JSON serialization
emlautarom1 Oct 31, 2024
9369bdd
Renaming and file reordering
emlautarom1 Oct 31, 2024
0488965
Use records
emlautarom1 Oct 31, 2024
1d618e0
Mark tests parallelizable
emlautarom1 Oct 31, 2024
9bb9486
Include Nethermind build
emlautarom1 Oct 31, 2024
728446c
Support optional PreRelease
emlautarom1 Oct 31, 2024
f0f6f61
Merge branch 'master' into feat/engine_signalsuperchainv1
emlautarom1 Oct 31, 2024
33b5861
Optimize allocations
emlautarom1 Nov 1, 2024
8157d0b
Avoid allocations when parsing versions
emlautarom1 Nov 1, 2024
2da1181
Make methods generic
emlautarom1 Nov 1, 2024
af797cf
Don't use async
emlautarom1 Nov 1, 2024
b903870
Avoid allocation on JSON deserializing
emlautarom1 Nov 1, 2024
873285c
Merge branch 'master' into feat/engine_signalsuperchainv1
emlautarom1 Nov 1, 2024
0ee5115
Merge branch 'master' into feat/engine_signalsuperchainv1
emlautarom1 Nov 4, 2024
836c6a9
Use the right protocol version
emlautarom1 Nov 4, 2024
ee99373
Remove unused import
emlautarom1 Nov 4, 2024
4c1361f
Ensure `Build` is always length 8
emlautarom1 Nov 4, 2024
9ec868c
Remove string parsing
emlautarom1 Nov 4, 2024
256d69e
Rename tests
emlautarom1 Nov 4, 2024
5da12ff
Merge branch 'master' into feat/engine_signalsuperchainv1
emlautarom1 Nov 4, 2024
c83a075
Merge branch 'master' into feat/engine_signalsuperchainv1
emlautarom1 Nov 5, 2024
36bd14b
Merge branch 'master' into feat/engine_signalsuperchainv1
emlautarom1 Nov 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/Nethermind/Nethermind.Core/ByteArrayConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,28 @@ public class ByteArrayConverter : JsonConverter<byte[]>
return returnVal;
}

public static void Convert(ref Utf8JsonReader reader, scoped Span<byte> span)
{
JsonTokenType tokenType = reader.TokenType;
if (tokenType == JsonTokenType.None || tokenType == JsonTokenType.Null)
{
return;
}

if (tokenType != JsonTokenType.String)
{
ThrowInvalidOperationException();
}

ReadOnlySpan<byte> hex = reader.ValueSpan;
if (hex.Length >= 2 && Unsafe.As<byte, ushort>(ref MemoryMarshal.GetReference(hex)) == _hexPrefix)
{
hex = hex[2..];
}

Bytes.FromUtf8HexString(hex, span);
}

[DoesNotReturn]
[StackTraceHidden]
internal static void ThrowInvalidOperationException()
Expand Down
11 changes: 9 additions & 2 deletions src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,16 @@ private static string ToHexStringWithEip55Checksum(ReadOnlySpan<byte> bytes, boo
return result;
}

public static ReadOnlySpan<byte> TakeAndMove(this ref ReadOnlySpan<byte> span, int length)
public static ReadOnlySpan<T> TakeAndMove<T>(this ref ReadOnlySpan<T> span, int length)
{
ReadOnlySpan<byte> s = span[..length];
ReadOnlySpan<T> s = span[..length];
span = span[length..];
return s;
}

public static Span<T> TakeAndMove<T>(this ref Span<T> span, int length)
{
Span<T> s = span[..length];
span = span[length..];
return s;
}
Expand Down
143 changes: 143 additions & 0 deletions src/Nethermind/Nethermind.Optimism.Test/OptimismProtocolVersionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using FluentAssertions;
using Nethermind.Core;
using Nethermind.Core.Extensions;
using Nethermind.Optimism.ProtocolVersion;
using NUnit.Framework;

namespace Nethermind.Optimism.Test;

[Parallelizable(ParallelScope.All)]
public class OptimismProtocolVersionTest
{
private static IEnumerable<(string, OptimismProtocolVersion.V0)> V0ReadWriteCases()
{
yield return ("0x0000000000000000000000000000000000000000000000000000000000000000", new(new byte[8], 0, 0, 0, 0));
yield return ("0x0000000000000000000000000000000000000000000000000000000000000100", new(new byte[8], 0, 0, 0, 1));
yield return ("0x0000000000000000000000000000000000000000000000000000010000000000", new(new byte[8], 0, 0, 1, 0));
yield return ("0x0000000000000000000000000000000000000400000003000000020000000100", new(new byte[8], 4, 3, 2, 1));
yield return ("0x0000000000000000000000000000000000000000000064000000020000000000", new(new byte[8], 0, 100, 2, 0));
yield return ("0x000000000000004f502d6d6f6400000000002a00000000000000020000000100", new([(byte)'O', (byte)'P', (byte)'-', (byte)'m', (byte)'o', (byte)'d', 0, 0], 42, 0, 2, 1));
yield return ("0x00000000000000626574612e3132330000000100000000000000000000000000", new([(byte)'b', (byte)'e', (byte)'t', (byte)'a', (byte)'.', (byte)'1', (byte)'2', (byte)'3'], 1, 0, 0, 0));
yield return ("0x0000000000000061620100000000000000002a00000000000000020000000000", new([(byte)'a', (byte)'b', 1, 0, 0, 0, 0, 0], 42, 0, 2, 0));
yield return ("0x0000000000000001020304050607080000002a00000000000000020000000000", new([1, 2, 3, 4, 5, 6, 7, 8], 42, 0, 2, 0));
}
[TestCaseSource(nameof(V0ReadWriteCases))]
public void OptimismProtocolVersionV0_ReadWrite((string HexString, OptimismProtocolVersion.V0 Expected) testCase)
{
var bytes = Bytes.FromHexString(testCase.HexString);
var actual = OptimismProtocolVersion.Read(bytes);

var bytesWritten = new byte[OptimismProtocolVersion.ByteLength];
actual.Write(bytesWritten);
var bytesWrittenHex = bytesWritten.ToHexString(withZeroX: true);

actual.Should().Be(testCase.Expected);
testCase.HexString.Should().Be(bytesWrittenHex);
}

private static IEnumerable<(OptimismProtocolVersion.V0, OptimismProtocolVersion.V0, int)> V0CompareCases()
{
// Pre-release
yield return (new(new byte[8], 0, 0, 0, 0), new(new byte[8], 0, 0, 0, 1), 1);
yield return (new(new byte[8], 0, 0, 0, 0), new(new byte[8], 0, 0, 0, 2), 1);
yield return (new(new byte[8], 0, 0, 0, 0), new(new byte[8], 0, 0, 0, uint.MaxValue), 1);
yield return (new(new byte[8], 0, 0, 0, 0), new(new byte[8], 0, 0, 0, 0), 0);
yield return (new(new byte[8], 0, 0, 0, 42), new(new byte[8], 0, 0, 0, 42), 0);

// Patch
yield return (new(new byte[8], 0, 0, 1, 0), new(new byte[8], 0, 0, 0, 0), 1);
yield return (new(new byte[8], 0, 0, 2, 0), new(new byte[8], 0, 0, 1, 0), 1);
yield return (new(new byte[8], 0, 0, uint.MaxValue, 0), new(new byte[8], 0, 0, uint.MaxValue - 1, 0), 1);
yield return (new(new byte[8], 0, 0, 0, 0), new(new byte[8], 0, 0, 0, 0), 0);
yield return (new(new byte[8], 0, 0, 101, 0), new(new byte[8], 0, 0, 101, 0), 0);

// Minor
yield return (new(new byte[8], 0, 1, 0, 0), new(new byte[8], 0, 0, 0, 0), 1);
yield return (new(new byte[8], 0, 2, 0, 0), new(new byte[8], 0, 1, 0, 0), 1);
yield return (new(new byte[8], 0, uint.MaxValue, 0, 0), new(new byte[8], 0, uint.MaxValue - 1, 0, 0), 1);
yield return (new(new byte[8], 0, 0, 0, 0), new(new byte[8], 0, 0, 0, 0), 0);
yield return (new(new byte[8], 0, 200, 0, 0), new(new byte[8], 0, 200, 0, 0), 0);

// Major
yield return (new(new byte[8], 1, 0, 0, 0), new(new byte[8], 0, 0, 0, 0), 1);
yield return (new(new byte[8], 2, 0, 0, 0), new(new byte[8], 1, 0, 0, 0), 1);
yield return (new(new byte[8], uint.MaxValue, 0, 0, 0), new(new byte[8], uint.MaxValue - 1, 0, 0, 0), 1);
yield return (new(new byte[8], 0, 0, 0, 0), new(new byte[8], 0, 0, 0, 0), 0);
yield return (new(new byte[8], 300, 0, 0, 0), new(new byte[8], 300, 0, 0, 0), 0);

// Mixed
yield return (new(new byte[8], 1, 2, 3, 4), new(new byte[8], 1, 2, 3, 4), 0);
yield return (new(new byte[8], 1, 2, 4, 3), new(new byte[8], 1, 2, 3, 4), 1);
yield return (new(new byte[8], 1, 3, 3, 4), new(new byte[8], 1, 2, 3, 4), 1);
yield return (new(new byte[8], 2, 2, 3, 4), new(new byte[8], 1, 2, 3, 4), 1);
yield return (new(new byte[8], 2, 0, 0, 0), new(new byte[8], 1, 9, 9, 0), 1);
}
[TestCaseSource(nameof(V0CompareCases))]
public void OptimismProtocolVersionV0_Compare((OptimismProtocolVersion.V0 Left, OptimismProtocolVersion.V0 Right, int Expected) testCase)
{
testCase.Left.CompareTo(testCase.Right).Should().Be(testCase.Expected);
testCase.Right.CompareTo(testCase.Left).Should().Be(testCase.Expected * -1);
}

[TestCase(4)]
[TestCase(6)]
[TestCase(9)]
[TestCase(10)]
public void OptimismProtocolVersionV0_BuildLengthIs8(int buildLength)
{
var build = new byte[buildLength];

Func<OptimismProtocolVersion> read = () => new OptimismProtocolVersion.V0(build, 0, 0, 0, 0);
read.Should().Throw<ArgumentException>();
}

[TestCase("0x0000000000000000000000000000000000000000000000000000000000000000", false)]
[TestCase("0x0010000000000000000000000000000000000000000000000000000000000000", true)]
[TestCase("0x0001000000000000000000000000000000000000000000000000000000000000", true)]
[TestCase("0x0000000000001000000000000000000000000000000000000000000000000000", true)]
[TestCase("0x0000000000000100000000000000000000000000000000000000000000000000", true)]
public void OptimismProtocolVersionV0_ReservedIsZero(string hexString, bool shouldThrow)
{
var bytes = Bytes.FromHexString(hexString);
Action read = () => OptimismProtocolVersion.Read(bytes);

if (shouldThrow)
{
read.Should().Throw<OptimismProtocolVersion.ParseException>();
}
else
{
read.Should().NotThrow();
}
}

private static IEnumerable<byte[]> InvalidByteArrays()
{
yield return new byte[8];
yield return new byte[16];
yield return new byte[24];
}
[TestCaseSource(nameof(InvalidByteArrays))]
public void OptimismProtocolVersion_Throws_Invalid_Length(byte[] bytes)
{
Action read = () => OptimismProtocolVersion.Read(bytes);
read.Should().Throw<OptimismProtocolVersion.ParseException>();
}

[TestCase(1)]
[TestCase(5)]
[TestCase(byte.MaxValue)]
public void OptimismProtocolVersion_Throws_Unknown_Version(byte version)
{
var bytes = new byte[32];
bytes[0] = version;

Action read = () => OptimismProtocolVersion.Read(bytes);
read.Should().Throw<OptimismProtocolVersion.ParseException>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Json;
using Nethermind.JsonRpc;
using Nethermind.JsonRpc.Test;
using Nethermind.Merge.Plugin;
using Nethermind.Optimism.ProtocolVersion;
using Nethermind.Optimism.Rpc;
using Nethermind.Serialization.Json;
using Newtonsoft.Json.Linq;
using NSubstitute;
using NUnit.Framework;

namespace Nethermind.Optimism.Test.Rpc;

[Parallelizable(ParallelScope.All)]
public class OptimismEngineRpcModuleTest
{
private static IEnumerable<(OptimismProtocolVersion, OptimismSuperchainSignal, bool behindRecommended, bool behindRequired)> SignalSuperchainV1Cases()
{
yield return (
new OptimismProtocolVersion.V0(new byte[8], 3, 0, 0, 0),
new OptimismSuperchainSignal(
Recommended: new OptimismProtocolVersion.V0(new byte[8], 2, 0, 0, 0),
Required: new OptimismProtocolVersion.V0(new byte[8], 1, 0, 0, 0)),
behindRecommended: false,
behindRequired: false
);

yield return (
new OptimismProtocolVersion.V0(new byte[8], 2, 0, 0, 0),
new OptimismSuperchainSignal(
Recommended: new OptimismProtocolVersion.V0(new byte[8], 2, 0, 0, 0),
Required: new OptimismProtocolVersion.V0(new byte[8], 1, 0, 0, 0)),
behindRecommended: false,
behindRequired: false
);

yield return (
new OptimismProtocolVersion.V0(new byte[8], 2, 0, 0, 0),
new OptimismSuperchainSignal(
Recommended: new OptimismProtocolVersion.V0(new byte[8], 3, 0, 0, 0),
Required: new OptimismProtocolVersion.V0(new byte[8], 1, 0, 0, 0)),
behindRecommended: true,
behindRequired: false
);

yield return (
new OptimismProtocolVersion.V0(new byte[8], 1, 0, 0, 0),
new OptimismSuperchainSignal(
Recommended: new OptimismProtocolVersion.V0(new byte[8], 2, 0, 0, 0),
Required: new OptimismProtocolVersion.V0(new byte[8], 1, 0, 0, 0)),
behindRecommended: true,
behindRequired: false
);

yield return (
new OptimismProtocolVersion.V0(new byte[8], 1, 0, 0, 0),
new OptimismSuperchainSignal(
Recommended: new OptimismProtocolVersion.V0(new byte[8], 3, 0, 0, 0),
Required: new OptimismProtocolVersion.V0(new byte[8], 2, 0, 0, 0)),
behindRecommended: true,
behindRequired: true
);
}

[TestCaseSource(nameof(SignalSuperchainV1Cases))]
public void SignalSuperchainV1_ComparesRequiredAndRecommendedVersion((OptimismProtocolVersion current, OptimismSuperchainSignal signal, bool behindRecommended, bool behindRequired) testCase)
{
var current = testCase.current;
var signal = testCase.signal;

var handler = Substitute.For<IOptimismSignalSuperchainV1Handler>();
handler.CurrentVersion.Returns(current);
IOptimismEngineRpcModule rpcModule = new OptimismEngineRpcModule(Substitute.For<IEngineRpcModule>(), handler);

_ = rpcModule.engine_signalSuperchainV1(signal);

handler.Received(testCase.behindRecommended ? 1 : 0).OnBehindRecommended(testCase.signal.Recommended);
handler.Received(testCase.behindRequired ? 1 : 0).OnBehindRequired(testCase.signal.Required);
}

[Test]
public void SignalSuperchainV1_ReturnsCurrentVersion()
{
var current = new OptimismProtocolVersion.V0(new byte[8], 3, 2, 1, 0);
var signal = new OptimismSuperchainSignal(
Recommended: new OptimismProtocolVersion.V0(new byte[8], 2, 0, 0, 0),
Required: new OptimismProtocolVersion.V0(new byte[8], 1, 0, 0, 0));

var handler = Substitute.For<IOptimismSignalSuperchainV1Handler>();
handler.CurrentVersion.Returns(current);
IOptimismEngineRpcModule rpcModule = new OptimismEngineRpcModule(Substitute.For<IEngineRpcModule>(), handler);

ResultWrapper<OptimismSignalSuperchainV1Result> result = rpcModule.engine_signalSuperchainV1(signal);

result.Data.Should().Be(new OptimismSignalSuperchainV1Result(current));
}

private static IEnumerable<(string, string, OptimismProtocolVersion)> SignalSuperchainV1JsonCases()
{
yield return (
"""{"recommended":"0x0000000000000000000000000000000000000200000000000000000000000000","required":"0x0000000000000000000000000000000000000100000000000000000000000000"}""",
"""{"protocolVersion":"0x0000000000000000000000000000000000000300000002000000010000000000"}""",
new OptimismProtocolVersion.V0(new byte[8], 3, 2, 1, 0));

yield return (
"""{"recommended":"0x0000000000000000000000000000000000000400000000000000000000000000","required":"0x0000000000000000000000000000000000000300000000000000000000000000"}""",
"""{"protocolVersion":"0x00000000000000000000000000000000000002000000090000000a0000000000"}""",
new OptimismProtocolVersion.V0(new byte[8], 2, 9, 10, 0));
}
[TestCaseSource(nameof(SignalSuperchainV1JsonCases))]
public async Task SignalSuperchainV1_JsonSerialization((string Signal, string Expected, OptimismProtocolVersion Current) testCase)
{
var handler = Substitute.For<IOptimismSignalSuperchainV1Handler>();
handler.CurrentVersion.Returns(testCase.Current);
IOptimismEngineRpcModule rpcModule = new OptimismEngineRpcModule(Substitute.For<IEngineRpcModule>(), handler);

var signal = new EthereumJsonSerializer().Deserialize<OptimismSuperchainSignal>(testCase.Signal);
var response = await RpcTest.TestSerializedRequest(rpcModule, "engine_signalSuperchainV1", signal);

JToken.Parse(response).Should().BeEquivalentTo($$"""{"jsonrpc":"2.0","result":{{testCase.Expected}},"id":67}""");
}
}
9 changes: 8 additions & 1 deletion src/Nethermind/Nethermind.Optimism/OptimismConstants.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Optimism.ProtocolVersion;

namespace Nethermind.Optimism;

public class OptimismConstants
public static class OptimismConstants
{
public const long PreRegolithNonZeroCountOverhead = 68;

/// <remarks>
/// See <see href="https://specs.optimism.io/protocol/superchain-upgrades.html#op-stack-protocol-versions"/>
/// </remarks>
public static OptimismProtocolVersion CurrentProtocolVersion { get; } = new OptimismProtocolVersion.V0([(byte)'N', (byte)'E', (byte)'T', (byte)'H', 0, 0, 0, 0], 8, 0, 0, 0);
}
7 changes: 6 additions & 1 deletion src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using Nethermind.Serialization.Rlp;
using Nethermind.Optimism.Rpc;
using Nethermind.Synchronization;
using Nethermind.Optimism.ProtocolVersion;

namespace Nethermind.Optimism;

Expand Down Expand Up @@ -275,7 +276,11 @@ public async Task InitRpcModules()
: new NoSyncGcRegionStrategy(_api.SyncModeSelector, _mergeConfig), _api.LogManager),
_api.LogManager);

IOptimismEngineRpcModule opEngine = new OptimismEngineRpcModule(engineRpcModule);
IOptimismSignalSuperchainV1Handler signalHandler = new LoggingOptimismSignalSuperchainV1Handler(
OptimismConstants.CurrentProtocolVersion,
_api.LogManager);

IOptimismEngineRpcModule opEngine = new OptimismEngineRpcModule(engineRpcModule, signalHandler);

_api.RpcModuleProvider.RegisterSingle(opEngine);

Expand Down
Loading
Loading