diff --git a/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/Extensions.cs b/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/Extensions.cs new file mode 100644 index 000000000000..0802d8023762 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/Extensions.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using FluentAssertions.Collections; + +namespace Nethermind.Serialization.FluentRlp.Test; + +// NOTE: `FluentAssertions` currently does not support `(ReadOnly)Span` assertions. +public static class Extensions +{ + public static GenericCollectionAssertions Should(this ReadOnlySpan span) => span.ToArray().Should(); + + public static AndConstraint> BeEquivalentTo( + this GenericCollectionAssertions @this, + ReadOnlySpan expectation, + string because = "", + params object[] becauseArgs) + { + return @this.BeEquivalentTo(expectation.ToArray(), config => config, because, becauseArgs); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpDerivedTest.cs b/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpDerivedTest.cs index ca20b987441c..98558ca22de4 100644 --- a/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpDerivedTest.cs +++ b/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpDerivedTest.cs @@ -43,7 +43,7 @@ public class RlpDerivedTest public void FlatRecord() { var player = new Player(Id: 42, Username: "SuperUser"); - ReadOnlySpan rlp = Rlp.Write(player, static (ref RlpWriter w, Player player) => w.Write(player)); + var rlp = Rlp.Write(player, static (ref RlpWriter w, Player player) => w.Write(player)); var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadPlayer()); decoded.Should().BeEquivalentTo(player); @@ -53,7 +53,7 @@ public void FlatRecord() public void RecordWithList() { var player = new PlayerWithFriends(Id: 42, Username: "SuperUser", Friends: ["ana", "bob"]); - ReadOnlySpan rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithFriends player) => w.Write(player)); + var rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithFriends player) => w.Write(player)); var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadPlayerWithFriends()); decoded.Should().BeEquivalentTo(player); @@ -63,7 +63,7 @@ public void RecordWithList() public void RecordWithArray() { var player = new PlayerWithCodes(Id: 42, Username: "SuperUser", Codes: [2, 4, 8, 16, 32, 64]); - ReadOnlySpan rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithCodes player) => w.Write(player)); + var rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithCodes player) => w.Write(player)); var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadPlayerWithCodes()); decoded.Should().BeEquivalentTo(player); @@ -77,7 +77,7 @@ public void RecordWithDictionary() { "foo", 42 }, { "bar", 1337 } }); - ReadOnlySpan rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithScores player) => w.Write(player)); + var rlp = Rlp.Write(player, static (ref RlpWriter w, PlayerWithScores player) => w.Write(player)); var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadPlayerWithScores()); decoded.Should().BeEquivalentTo(player); @@ -87,7 +87,7 @@ public void RecordWithDictionary() public void RecordWithTuple() { var integerTuple = new IntegerTuple((42, 1337)); - ReadOnlySpan rlp = Rlp.Write(integerTuple, static (ref RlpWriter w, IntegerTuple tuple) => w.Write(tuple)); + var rlp = Rlp.Write(integerTuple, static (ref RlpWriter w, IntegerTuple tuple) => w.Write(tuple)); var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadIntegerTuple()); decoded.Should().BeEquivalentTo(integerTuple); @@ -103,7 +103,7 @@ [new Tree("dog", [])]), new Tree("qux", [new Tree("cat", [])]) ]); - ReadOnlySpan rlp = Rlp.Write(tree, static (ref RlpWriter w, Tree tree) => w.Write(tree)); + var rlp = Rlp.Write(tree, static (ref RlpWriter w, Tree tree) => w.Write(tree)); var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadTree()); decoded.Should().BeEquivalentTo(tree); @@ -135,7 +135,7 @@ public void RecordWithNestedGenerics() (new Address("0xFEDCBA0987654321"), [2, 4, 6, 8, 10]) ]); - ReadOnlySpan rlp = Rlp.Write(accessList, (ref RlpWriter writer, AccessList value) => writer.Write(value)); + var rlp = Rlp.Write(accessList, (ref RlpWriter writer, AccessList value) => writer.Write(value)); var decoded = Rlp.Read(rlp, static (scoped ref RlpReader r) => r.ReadAccessList()); decoded.Should().BeEquivalentTo(accessList); diff --git a/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpReadWriteTest.cs b/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpReadWriteTest.cs index 1bb57f1d2ec1..46eacf22f4f9 100644 --- a/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpReadWriteTest.cs +++ b/src/Nethermind/Nethermind.Serialization.FluentRlp.Test/RlpReadWriteTest.cs @@ -201,11 +201,10 @@ public void Choice() { RefRlpReaderFunc intReader = static (scoped ref RlpReader r) => r.ReadInt32(); RefRlpReaderFunc wrappedReader = (scoped ref RlpReader r) => r.ReadSequence(intReader); - var intRlp = Rlp.Write(static (ref RlpWriter w) => { w.Write(42); }); var wrappedIntRlp = Rlp.Write(static (ref RlpWriter w) => w.WriteSequence(static (ref RlpWriter w) => { w.Write(42); })); - foreach (var rlp in (byte[][])[intRlp, wrappedIntRlp]) + foreach (var rlp in (ReadOnlyMemory[])[intRlp, wrappedIntRlp]) { int decoded = Rlp.Read(rlp, (scoped ref RlpReader r) => r.Choice(wrappedReader, intReader)); diff --git a/src/Nethermind/Nethermind.Serialization.FluentRlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.FluentRlp/Rlp.cs index 3cfe43eb5ff5..0768079d1e94 100644 --- a/src/Nethermind/Nethermind.Serialization.FluentRlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.FluentRlp/Rlp.cs @@ -2,26 +2,32 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Runtime.CompilerServices; namespace Nethermind.Serialization.FluentRlp; public static class Rlp { - public static byte[] Write(RefRlpWriterAction action) + public static ReadOnlyMemory Write(RefRlpWriterAction action) => Write(action, static (ref RlpWriter w, RefRlpWriterAction action) => action(ref w)); - public static byte[] Write(TContext ctx, RefRlpWriterAction action) + public static ReadOnlyMemory Write(TContext ctx, RefRlpWriterAction action) where TContext : allows ref struct { var lengthWriter = RlpWriter.LengthWriter(); action(ref lengthWriter, ctx); - var serialized = new byte[lengthWriter.Length]; - var contentWriter = RlpWriter.ContentWriter(serialized); + var buffer = new ArrayBufferWriter(lengthWriter.Length); + var contentWriter = RlpWriter.ContentWriter(buffer); action(ref contentWriter, ctx); - return serialized; + return buffer.WrittenMemory; } + public static T Read(ReadOnlyMemory source, RefRlpReaderFunc func) where T : allows ref struct + => Read(source.Span, func); + + [OverloadResolutionPriority(1)] public static T Read(ReadOnlySpan source, RefRlpReaderFunc func) where T : allows ref struct { diff --git a/src/Nethermind/Nethermind.Serialization.FluentRlp/RlpWriter.cs b/src/Nethermind/Nethermind.Serialization.FluentRlp/RlpWriter.cs index 5e982f14de6a..bcc144b28b56 100644 --- a/src/Nethermind/Nethermind.Serialization.FluentRlp/RlpWriter.cs +++ b/src/Nethermind/Nethermind.Serialization.FluentRlp/RlpWriter.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Buffers.Binary; using System.Numerics; @@ -21,8 +22,7 @@ public ref struct RlpWriter public int Length { get; private set; } - private byte[] _buffer; - private int _position; + private IBufferWriter _buffer; public static RlpWriter LengthWriter() { @@ -32,7 +32,7 @@ public static RlpWriter LengthWriter() }; } - public static RlpWriter ContentWriter(byte[] buffer) + public static RlpWriter ContentWriter(IBufferWriter buffer) { return new RlpWriter { @@ -84,11 +84,11 @@ private unsafe void ContentWrite(T value) where T : unmanaged, IBinaryInteger if (bigEndian.Length == 0) { - _buffer[_position++] = 0x80; + _buffer.Write([(byte)0x80]); } else if (bigEndian.Length == 1 && bigEndian[0] < 0x80) { - _buffer[_position++] = bigEndian[0]; + _buffer.Write(bigEndian[..1]); } else { @@ -130,20 +130,19 @@ private void ContentWrite(scoped ReadOnlySpan value) { if (value.Length < 55) { - _buffer[_position++] = (byte)(0x80 + value.Length); + _buffer.Write([(byte)(0x80 + value.Length)]); } else { Span binaryLength = stackalloc byte[sizeof(int)]; BinaryPrimitives.WriteInt32BigEndian(binaryLength, value.Length); binaryLength = binaryLength.TrimStart((byte)0); - _buffer[_position++] = (byte)(0xB7 + binaryLength.Length); - binaryLength.CopyTo(_buffer.AsSpan()[_position..]); - _position += binaryLength.Length; + + _buffer.Write([(byte)(0xB7 + binaryLength.Length)]); + _buffer.Write(binaryLength); } - value.CopyTo(_buffer.AsSpan()[_position..]); - _position += value.Length; + _buffer.Write(value); } public void WriteSequence(RefRlpWriterAction action) @@ -185,16 +184,16 @@ private void ContentWriteSequence(TContext ctx, RefRlpWriterAction binaryLength = stackalloc byte[sizeof(Int32)]; BinaryPrimitives.WriteInt32BigEndian(binaryLength, lengthWriter.Length); binaryLength = binaryLength.TrimStart((byte)0); - _buffer[_position++] = (byte)(0xF7 + binaryLength.Length); - binaryLength.CopyTo(_buffer.AsSpan()[_position..]); - _position += binaryLength.Length; + + _buffer.Write([(byte)(0xF7 + binaryLength.Length)]); + _buffer.Write(binaryLength); } action(ref this, ctx);