diff --git a/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs index cefc5943b0c..379dff65d72 100644 --- a/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs @@ -32,7 +32,7 @@ public void ProcessRequests(Block block, IWorldState state, TxReceipt[] receipts requestsList.AddRange(_consolidationRequestsProcessor.ReadConsolidationRequests(block, state, spec)); ConsensusRequest[] requests = requestsList.ToArray(); - Hash256 root = new RequestsTrie(requests).RootHash; + Hash256 root = requests.CalculateRootHash(); block.Body.Requests = requests; block.Header.RequestsRoot = root; } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index 1bfbdf072b7..3554e88eb95 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -466,7 +466,7 @@ private static bool ValidateRequestsHashMatches(BlockHeader header, BlockBody bo return header.RequestsRoot is null; } - return (requestsRoot = new RequestsTrie(body.Requests).RootHash) == header.RequestsRoot; + return (requestsRoot = body.Requests.CalculateRootHash()) == header.RequestsRoot; } private static string Invalid(Block block) => diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs index 4b5e591949b..7c0a3219482 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs @@ -293,7 +293,7 @@ public BlockBuilder WithConsensusRequests(params ConsensusRequest[]? requests) TestObjectInternal.Header.RequestsRoot = requests is null ? null - : new RequestsTrie(requests).RootHash; + : requests.CalculateRootHash(); return this; } diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs index e9e5f6d38e1..88103c8aa58 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs @@ -5,6 +5,7 @@ using System; using System.Text.Json.Serialization; + namespace Nethermind.Core.ConsensusRequests; public enum ConsensusRequestsType : byte @@ -18,6 +19,21 @@ public abstract class ConsensusRequest { [JsonIgnore] public ConsensusRequestsType Type { get; protected set; } + + /// + /// Encodes the request into a byte array + /// reference: https://eips.ethereum.org/EIPS/eip-7685 + /// + /// request = request_type ++ request_data + public abstract byte[] Encode(); + + /// + /// Decodes the request from a byte array + /// reference: https://eips.ethereum.org/EIPS/eip-7685 + /// + /// request = request_type ++ request_data + /// request + public abstract ConsensusRequest Decode(byte[] data); } public static class ConsensusRequestExtensions @@ -75,4 +91,33 @@ public static (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests, Con return (deposits, withdrawalRequests, consolidationRequests); } + + public static byte[][] Encode(this ConsensusRequest[]? requests) + { + if (requests is null) return Array.Empty(); + byte[][] requestsEncoded = new byte[requests.Length][]; + for (int i = 0; i < requests.Length; i++) + { + requestsEncoded[i] = requests[i].Encode(); + } + return requestsEncoded; + } + + public static ConsensusRequest Decode(byte[] data) + { + if (data.Length < 2) + { + throw new ArgumentException("Invalid data length"); + } + + ConsensusRequestsType type = (ConsensusRequestsType)data[0]; + return type switch + { + ConsensusRequestsType.Deposit => new Deposit().Decode(data), + ConsensusRequestsType.WithdrawalRequest => new WithdrawalRequest().Decode(data), + ConsensusRequestsType.ConsolidationRequest => new ConsolidationRequest().Decode(data), + _ => throw new ArgumentException("Invalid request type") + }; + } + } diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs index abf79b924a1..75def5d8307 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Linq; using Nethermind.Core.Extensions; namespace Nethermind.Core.ConsensusRequests; @@ -27,5 +28,26 @@ public string ToString(string indentation) => @$"{indentation}{nameof(Consolidat {nameof(TargetPubkey)}: {TargetPubkey?.Span.ToHexString()}, }}"; + public override byte[] Encode() + { + byte[] type = new byte[] { (byte)Type }; + return type + .Concat(SourceAddress?.Bytes ?? Array.Empty()) + .Concat(SourcePubkey?.ToArray() ?? Array.Empty()) + .Concat(TargetPubkey?.ToArray() ?? Array.Empty()).ToArray(); + } + public override ConsensusRequest Decode(byte[] data) + { + if (data.Length < 2) + { + throw new ArgumentException("Invalid data length"); + } + + Type = (ConsensusRequestsType)data[0]; + SourceAddress = new Address(data.Slice(1, Address.Size)); + SourcePubkey = data.AsMemory().Slice(1 + Address.Size); + TargetPubkey = data.AsMemory().Slice(1 + Address.Size + SourcePubkey.Value.Length); + return this; + } } diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs index 50b46fc27c2..d501e90e3b7 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Linq; using Nethermind.Core.Extensions; namespace Nethermind.Core.ConsensusRequests; @@ -32,5 +33,31 @@ public string ToString(string indentation) => @$"{indentation}{nameof(Deposit)} {nameof(Signature)}: {Signature?.ToHexString()}, {nameof(Pubkey)}: {Pubkey?.Span.ToHexString()}}}"; + public override byte[] Encode() + { + byte[] type = new byte[] { (byte)Type }; + return type + .Concat(Pubkey?.ToArray() ?? Array.Empty()) + .Concat(WithdrawalCredentials ?? Array.Empty()) + .Concat(BitConverter.GetBytes(Amount)) + .Concat(Signature ?? Array.Empty()) + .Concat(Index.HasValue ? BitConverter.GetBytes(Index.Value) : Array.Empty()).ToArray(); + } + + public override ConsensusRequest Decode(byte[] data) + { + if (data.Length < 2) + { + throw new ArgumentException("Invalid data length"); + } + + Type = (ConsensusRequestsType)data[0]; + Pubkey = data.AsMemory()[1..33]; + WithdrawalCredentials = data.Slice(1 + Pubkey.Value.Length, 32); + Amount = BitConverter.ToUInt64(data, 1 + Pubkey.Value.Length + 32); + Signature = data.Slice(1 + Pubkey.Value.Length + 32 + sizeof(ulong)); + Index = data.Length > 1 + Pubkey.Value.Length + 32 + sizeof(ulong) + Signature!.Length ? BitConverter.ToUInt64(data, 1 + Pubkey.Value.Length + 32 + sizeof(ulong) + Signature!.Length) : null; + return this; + } } diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs index f9d79f127c7..3560db94faf 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs @@ -3,7 +3,7 @@ using System; using Nethermind.Core.Extensions; -using System.Text; +using System.Linq; namespace Nethermind.Core.ConsensusRequests; @@ -30,4 +30,26 @@ public string ToString(string indentation) => @$"{indentation}{nameof(Withdrawal {nameof(Amount)}: {Amount}}}"; + public override byte[] Encode() + { + byte[] type = new byte[] { (byte)Type }; + return type + .Concat(SourceAddress?.Bytes ?? Array.Empty()) + .Concat(ValidatorPubkey?.ToArray() ?? Array.Empty()) + .Concat(BitConverter.GetBytes(Amount)).ToArray(); + } + + public override ConsensusRequest Decode(byte[] data) + { + if (data.Length < 2) + { + throw new ArgumentException("Invalid data length"); + } + + Type = (ConsensusRequestsType)data[0]; + SourceAddress = new Address(data.Slice(1, Address.Size)); + ValidatorPubkey = data.AsMemory().Slice(1 + Address.Size); + Amount = BitConverter.ToUInt64(data, 1 + Address.Size + ValidatorPubkey.Value.Length); + return this; + } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs index e0d65da1215..373bc6946a9 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs @@ -62,7 +62,7 @@ public override bool TryGetBlock([NotNullWhen(true)] out Block? block, UInt256? } block.Body.Requests = requests; - block.Header.RequestsRoot = new RequestsTrie(requests).RootHash; + block.Header.RequestsRoot = requests.CalculateRootHash(); } else { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 0530f141fd8..aa5a9542e6e 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -122,10 +122,11 @@ public class BlockDecoder : IRlpValueDecoder, IRlpStreamDecoder while (rlpStream.Position < requestsCheck) { - requests.Add(Rlp.Decode(rlpStream)); + requests.Add(ConsensusRequestExtensions.Decode(rlpStream.DecodeByteArray())); } rlpStream.Check(requestsCheck); + } } @@ -303,7 +304,7 @@ public int GetLength(Block? item, RlpBehaviors rlpBehaviors) while (decoderContext.Position < requestsCheck) { - requests.Add(Rlp.Decode(ref decoderContext)); + requests.Add(ConsensusRequestExtensions.Decode(decoderContext.DecodeByteArray())); } decoderContext.Check(requestsCheck); @@ -359,12 +360,7 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl if (requestsLength.HasValue) { - stream.StartSequence(requestsLength.Value); - - for (int i = 0; i < item.Requests.Length; i++) - { - stream.Encode(item.Requests[i]); - } + stream.Encode(item.Requests.Encode()); } } diff --git a/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs b/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs index d69e1d0c8d7..91156e0b3b2 100644 --- a/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs @@ -33,3 +33,18 @@ public static Hash256 CalculateRoot(ConsensusRequest[] requests) return rootHash; } } + + +public static class ConsensusRequestExtensions +{ + public static Hash256 CalculateRootHash(this ConsensusRequest[]? requests) + { + Rlp[] encodedRequests = new Rlp[requests!.Length]; + for (int i = 0; i < encodedRequests.Length; i++) + { + encodedRequests[i] = Rlp.Encode(requests![i].Encode()); + } + + return Keccak.Compute(Rlp.Encode(encodedRequests).Bytes); + } +}