diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs index cc8e1349d68..178027c5d29 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs @@ -243,7 +243,7 @@ public void Dispose() ArrayPoolListCore.Dispose(_arrayPool, ref _array, ref _count, ref _capacity, ref _disposed); #if DEBUG - GC.SuppressFinalize(this); + GC.SuppressFinalize(this); #endif } diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index 80305a26023..2568c84941f 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -109,7 +109,7 @@ private bool TryGetCanonicalTransaction( return (null, 0, null, 0); } - public (TxReceipt? Receipt, Transaction Transaction, UInt256? baseFee) GetTransaction(Hash256 txHash, bool checkTxnPool = true) => + public (TxReceipt? Receipt, Transaction? Transaction, UInt256? baseFee) GetTransaction(Hash256 txHash, bool checkTxnPool = true) => TryGetCanonicalTransaction(txHash, out Transaction? tx, out TxReceipt? txReceipt, out Block? block, out TxReceipt[]? _) ? (txReceipt, tx, block.BaseFeePerGas) : checkTxnPool && txPool.TryGetPendingTransaction(txHash, out Transaction? transaction) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index bc10ea8e5b6..473d78f0df0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1553,7 +1553,8 @@ public void Should_return_expected_capabilities_for_mainnet() nameof(IEngineRpcModule.engine_newPayloadV4), nameof(IEngineRpcModule.engine_getPayloadV5), - nameof(IEngineRpcModule.engine_getBlobsV2) + nameof(IEngineRpcModule.engine_getBlobsV2), + nameof(IEngineRpcModule.engine_getBlobsV3) }; Assert.That(result, Is.EquivalentTo(expectedMethods)); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index 4f29e8155cf..7940bc6c34b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -385,7 +385,7 @@ public async Task NewPayloadV3_should_verify_blob_versioned_hashes_again Substitute.For>(), Substitute.For, IEnumerable>>(), Substitute.For>>(), - Substitute.For?>>(), + Substitute.For?>>(), Substitute.For(), chain.SpecProvider, new GCKeeper(NoGCStrategy.Instance, chain.LogManager), diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs index 681110c95fc..825da6cc4e1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs @@ -52,7 +52,7 @@ public async Task GetBlobsV2_should_throw_if_more_than_128_requested_blobs([Valu request.Add(Bytes.FromHexString(i.ToString("X64"))); } - ResultWrapper?> result = await rpcModule.engine_getBlobsV2(request.ToArray()); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2(request.ToArray()); if (requestSize > 128) { @@ -75,7 +75,7 @@ public async Task GetBlobsV2_should_handle_empty_request() }); IEngineRpcModule rpcModule = chain.EngineRpcModule; - ResultWrapper?> result = await rpcModule.engine_getBlobsV2([]); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2([]); result.Result.Should().Be(Result.Success); result.Data.Should().BeEquivalentTo(ArraySegment.Empty); @@ -99,14 +99,14 @@ public async Task GetBlobsV2_should_return_requested_blobs([Values(1, 2, 3, 4, 5 chain.TxPool.SubmitTx(blobTx, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); - ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobTx.BlobVersionedHashes!); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobTx.BlobVersionedHashes!); ShardBlobNetworkWrapper wrapper = (ShardBlobNetworkWrapper)blobTx.NetworkWrapper!; result.Data.Should().NotBeNull(); - result.Data!.Select(static b => b.Blob).Should().BeEquivalentTo(wrapper.Blobs); - result.Data!.Select(static b => b.Proofs.Length).Should().HaveCount(numberOfBlobs); - result.Data!.Select(static b => b.Proofs).Should().BeEquivalentTo(wrapper.Proofs.Chunk(128)); + result.Data!.Select(static b => b!.Blob).Should().BeEquivalentTo(wrapper.Blobs); + result.Data!.Select(static b => b!.Proofs.Length).Should().HaveCount(numberOfBlobs); + result.Data!.Select(static b => b!.Proofs).Should().BeEquivalentTo(wrapper.Proofs.Chunk(128)); } [Test] @@ -127,7 +127,7 @@ public async Task GetBlobsV2_should_return_empty_array_when_blobs_not_found([Val .SignedAndResolved(chain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject; // requesting hashes that are not present in TxPool - ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobTx.BlobVersionedHashes!); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobTx.BlobVersionedHashes!); result.Result.Should().Be(Result.Success); result.Data.Should().BeNull(); @@ -162,7 +162,7 @@ public async Task GetBlobsV2_should_return_empty_array_when_only_some_blobs_foun blobVersionedHashesRequest.Add(addActualHash ? blobTx.BlobVersionedHashes![actualIndex++]! : Bytes.FromHexString(i.ToString("X64"))); } - ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobVersionedHashesRequest.ToArray()); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobVersionedHashesRequest.ToArray()); if (multiplier > 1) { result.Result.Should().Be(Result.Success); @@ -173,9 +173,65 @@ public async Task GetBlobsV2_should_return_empty_array_when_only_some_blobs_foun ShardBlobNetworkWrapper wrapper = (ShardBlobNetworkWrapper)blobTx.NetworkWrapper!; result.Data.Should().NotBeNull(); - result.Data!.Select(static b => b.Blob).Should().BeEquivalentTo(wrapper.Blobs); - result.Data!.Select(static b => b.Proofs.Length).Should().HaveCount(numberOfBlobs); - result.Data!.Select(static b => b.Proofs).Should().BeEquivalentTo(wrapper.Proofs.Chunk(128)); + result.Data!.Select(static b => b!.Blob).Should().BeEquivalentTo(wrapper.Blobs); + result.Data!.Select(static b => b!.Proofs.Length).Should().HaveCount(numberOfBlobs); + result.Data!.Select(static b => b!.Proofs).Should().BeEquivalentTo(wrapper.Proofs.Chunk(128)); + } + } + + [Test] + public async Task GetBlobsV3_should_return_partial_results_with_nulls_for_missing_blobs([Values(1, 2, 3, 4, 5, 6)] int numberOfBlobs, [Values(1, 2)] int multiplier) + { + int requestSize = multiplier * numberOfBlobs; + + MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Osaka.Instance, mergeConfig: new MergeConfig() + { + NewPayloadBlockProcessingTimeout = (int)TimeSpan.FromDays(1).TotalMilliseconds + }); + IEngineRpcModule rpcModule = chain.EngineRpcModule; + + Transaction blobTx = Build.A.Transaction + .WithShardBlobTxTypeAndFields(numberOfBlobs, spec: Osaka.Instance) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithMaxFeePerBlobGas(1000.Wei()) + .SignedAndResolved(chain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject; + + chain.TxPool.SubmitTx(blobTx, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + List blobVersionedHashesRequest = new List(requestSize); + + int actualIndex = 0; + for (int i = 0; i < requestSize; i++) + { + bool addActualHash = i % multiplier == 0; + blobVersionedHashesRequest.Add(addActualHash ? blobTx.BlobVersionedHashes![actualIndex++]! : Bytes.FromHexString(i.ToString("X64"))); + } + + ResultWrapper?> result = await rpcModule.engine_getBlobsV3(blobVersionedHashesRequest.ToArray()); + + result.Result.Should().Be(Result.Success); + result.Data.Should().NotBeNull(); + result.Data!.Should().HaveCount(requestSize); + + ShardBlobNetworkWrapper wrapper = (ShardBlobNetworkWrapper)blobTx.NetworkWrapper!; + + // V3 returns partial results with nulls for missing blobs + int foundIndex = 0; + for (int i = 0; i < requestSize; i++) + { + bool shouldBeFound = i % multiplier == 0; + if (shouldBeFound) + { + result.Data!.ElementAt(i).Should().NotBeNull(); + result.Data!.ElementAt(i)!.Blob.Should().BeEquivalentTo(wrapper.Blobs[foundIndex]); + result.Data!.ElementAt(i)!.Proofs.Should().BeEquivalentTo(wrapper.Proofs.Skip(foundIndex * 128).Take(128)); + foundIndex++; + } + else + { + result.Data!.ElementAt(i).Should().BeNull(); + } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs index fe52ec00e62..fcc4877246c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs @@ -12,11 +12,14 @@ namespace Nethermind.Merge.Plugin; public partial class EngineRpcModule : IEngineRpcModule { private readonly IAsyncHandler _getPayloadHandlerV5; - private readonly IAsyncHandler?> _getBlobsHandlerV2; + private readonly IAsyncHandler?> _getBlobsHandlerV2; public Task> engine_getPayloadV5(byte[] payloadId) => _getPayloadHandlerV5.HandleAsync(payloadId); - public Task?>> engine_getBlobsV2(byte[][] blobVersionedHashes) - => _getBlobsHandlerV2.HandleAsync(blobVersionedHashes); + public Task?>> engine_getBlobsV2(byte[][] blobVersionedHashes) + => _getBlobsHandlerV2.HandleAsync(new(blobVersionedHashes)); + + public Task?>> engine_getBlobsV3(byte[][] blobVersionedHashes) + => _getBlobsHandlerV2.HandleAsync(new(blobVersionedHashes, true)); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs index ee951fa0a52..9d0edf55a74 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs @@ -34,7 +34,7 @@ public EngineRpcModule( IHandler transitionConfigurationHandler, IHandler, IEnumerable> capabilitiesHandler, IAsyncHandler> getBlobsHandler, - IAsyncHandler?> getBlobsHandlerV2, + IAsyncHandler?> getBlobsHandlerV2, IEngineRequestsTracker engineRequestsTracker, ISpecProvider specProvider, GCKeeper gcKeeper, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs index 123ddd1a6f3..00e60cf5026 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs @@ -53,6 +53,7 @@ public EngineRpcCapabilitiesProvider(ISpecProvider specProvider) // Osaka _capabilities[nameof(IEngineRpcModule.engine_getPayloadV5)] = (spec.IsEip7594Enabled, spec.IsEip7594Enabled); _capabilities[nameof(IEngineRpcModule.engine_getBlobsV2)] = (spec.IsEip7594Enabled, false); + _capabilities[nameof(IEngineRpcModule.engine_getBlobsV3)] = (spec.IsEip7594Enabled, false); } return _capabilities; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs index c04978430f3..47d22383328 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Nethermind.Core.Collections; using Nethermind.JsonRpc; @@ -11,41 +10,45 @@ namespace Nethermind.Merge.Plugin.Handlers; -public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler?> +public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler?> { private const int MaxRequest = 128; - private static readonly Task?>> NotFound = Task.FromResult(ResultWrapper?>.Success(null)); + private static readonly Task?>> NotFound = Task.FromResult(ResultWrapper?>.Success(null)); - public Task?>> HandleAsync(byte[][] request) + public Task?>> HandleAsync(GetBlobsHandlerV2Request request) { - if (request.Length > MaxRequest) + if (request.BlobVersionedHashes.Length > MaxRequest) { var error = $"The number of requested blobs must not exceed {MaxRequest}"; - return ResultWrapper?>.Fail(error, MergeErrorCodes.TooLargeRequest); + return ResultWrapper?>.Fail(error, MergeErrorCodes.TooLargeRequest); } - Metrics.GetBlobsRequestsTotal += request.Length; + Metrics.GetBlobsRequestsTotal += request.BlobVersionedHashes.Length; - var count = txPool.GetBlobCounts(request); + int count = txPool.GetBlobCounts(request.BlobVersionedHashes); Metrics.GetBlobsRequestsInBlobpoolTotal += count; - // quick fail if we don't have some blob - if (count != request.Length) + // quick fail if we don't have some blob (unless partial return is allowed) + if (!request.AllowPartialReturn && count != request.BlobVersionedHashes.Length) { return ReturnEmptyArray(); } - ArrayPoolList response = new(request.Length); + ArrayPoolList response = new(request.BlobVersionedHashes.Length); try { - foreach (byte[] requestedBlobVersionedHash in request) + foreach (byte[] requestedBlobVersionedHash in request.BlobVersionedHashes) { if (txPool.TryGetBlobAndProofV1(requestedBlobVersionedHash, out byte[]? blob, out byte[][]? cellProofs)) { response.Add(new BlobAndProofV2(blob, cellProofs)); } + else if (request.AllowPartialReturn) + { + response.Add(null); + } else { // fail if we were not able to collect full blob data @@ -55,7 +58,7 @@ public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler?>.Success(response); + return ResultWrapper?>.Success(response); } catch { @@ -64,9 +67,11 @@ public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler?>> ReturnEmptyArray() + private Task?>> ReturnEmptyArray() { Metrics.GetBlobsRequestsFailureTotal++; return NotFound; } } + +public readonly record struct GetBlobsHandlerV2Request(byte[][] BlobVersionedHashes, bool AllowPartialReturn = false); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs index b2cd12fe96f..bd05aa9e0c4 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs @@ -21,5 +21,11 @@ public partial interface IEngineRpcModule : IRpcModule Description = "Returns requested blobs and proofs.", IsSharable = true, IsImplemented = true)] - public Task?>> engine_getBlobsV2(byte[][] blobVersionedHashes); + public Task?>> engine_getBlobsV2(byte[][] blobVersionedHashes); + + [JsonRpcMethod( + Description = "Returns requested blobs and proofs.", + IsSharable = true, + IsImplemented = true)] + public Task?>> engine_getBlobsV3(byte[][] blobVersionedHashes); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index d0f06ff7b25..73a85cae16e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -330,7 +330,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton, IEnumerable>, ExchangeCapabilitiesHandler>() .AddSingleton() .AddSingleton>, GetBlobsHandler>() - .AddSingleton?>, GetBlobsHandlerV2>() + .AddSingleton?>, GetBlobsHandlerV2>() .AddSingleton() .AddSingleton((ctx) => diff --git a/src/Nethermind/Nethermind.Taiko.Test/TxPoolContentListsTests.cs b/src/Nethermind/Nethermind.Taiko.Test/TxPoolContentListsTests.cs index f34533be574..786ab8f6185 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/TxPoolContentListsTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/TxPoolContentListsTests.cs @@ -83,7 +83,7 @@ public int[][] Test_TxLists_AreConstructed( Substitute.For>(), Substitute.For, IEnumerable>>(), Substitute.For>>(), - Substitute.For?>>(), + Substitute.For?>>(), Substitute.For(), Substitute.For(), null!, diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs index dfa8fb64474..6bda82d9b35 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs @@ -46,7 +46,7 @@ public class TaikoEngineRpcModule(IAsyncHandler getPa IHandler transitionConfigurationHandler, IHandler, IEnumerable> capabilitiesHandler, IAsyncHandler> getBlobsHandler, - IAsyncHandler?> getBlobsHandlerV2, + IAsyncHandler?> getBlobsHandlerV2, IEngineRequestsTracker engineRequestsTracker, ISpecProvider specProvider, GCKeeper gcKeeper,