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_getBlobsV1 #7322

Merged
merged 40 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
370693f
first draft
marcindsobczak Aug 9, 2024
4599fe4
improve handler
marcindsobczak Aug 12, 2024
1380390
adjust other classes to engine module changes
marcindsobczak Aug 12, 2024
472513a
cosmetics
marcindsobczak Aug 12, 2024
b2b0ec0
refactor to allow indexing blobs in InMemory mode
marcindsobczak Aug 12, 2024
0ec36a9
add test
marcindsobczak Aug 12, 2024
31424d2
fix file encodings
marcindsobczak Aug 12, 2024
09850c4
cosmetics
marcindsobczak Aug 12, 2024
4447ccb
fix build?
marcindsobczak Aug 12, 2024
33404d9
fix build?
marcindsobczak Aug 12, 2024
fd4fd37
add engine capability
marcindsobczak Aug 12, 2024
fbd031c
add engine_getBlobsV1 as expected cap for mainnet
marcindsobczak Aug 12, 2024
c9ff2fc
cosmetics
marcindsobczak Aug 12, 2024
4570298
add test
marcindsobczak Aug 13, 2024
9caf0d4
refactor index to have string as a key
marcindsobczak Aug 14, 2024
dbbeb3a
add TxPool test for indexing blobs
marcindsobczak Aug 14, 2024
f5ec9a3
add one more engine test
marcindsobczak Aug 14, 2024
9cf95be
add test for max request size
marcindsobczak Aug 14, 2024
c428378
cosmetics
marcindsobczak Aug 14, 2024
361be10
cosmetic
marcindsobczak Aug 14, 2024
bcfce30
move back to byte[] as a key and just use value comparer
marcindsobczak Aug 14, 2024
4835142
refactor
marcindsobczak Aug 20, 2024
ac25e53
fix file encoding
marcindsobczak Aug 20, 2024
05c4d0e
refactor
marcindsobczak Aug 20, 2024
70d1b17
improve validation
marcindsobczak Aug 20, 2024
24ced8a
drop linq
marcindsobczak Aug 20, 2024
853cc50
add test
marcindsobczak Aug 20, 2024
1bcf4ad
cosmetics
marcindsobczak Aug 20, 2024
a1ceb70
Merge remote-tracking branch 'origin/master' into add_engine_get_blob…
marcindsobczak Aug 20, 2024
1f107dd
Update src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.…
marcindsobczak Aug 21, 2024
6dfcb9b
Update src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.…
marcindsobczak Aug 21, 2024
6e2cb34
move BlobAndProofV1 back to merge plugin
marcindsobczak Aug 21, 2024
9d59e4b
refactor blob collecting logic
marcindsobczak Aug 21, 2024
c858d3d
Merge branch 'add_engine_get_blobs_v1' of https:/Nethermi…
marcindsobczak Aug 21, 2024
83789f8
refactor tests
marcindsobczak Aug 21, 2024
9ec8f23
drop GetBlobIndex
marcindsobczak Aug 21, 2024
5ae5fcf
fix: rename in one more place
marcindsobczak Aug 21, 2024
e4fadd7
drop BlobFinder
marcindsobczak Aug 21, 2024
cd1bbee
cosmetics
marcindsobczak Aug 21, 2024
c9797b0
Update src/Nethermind/Nethermind.Merge.Plugin/Data/GetBlobsV1Result.cs
marcindsobczak Aug 21, 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
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConf
new GetPayloadBodiesByRangeV1Handler(chain.BlockTree, chain.LogManager),
new ExchangeTransitionConfigurationV1Handler(chain.PoSSwitcher, chain.LogManager),
new ExchangeCapabilitiesHandler(capabilitiesProvider, chain.LogManager),
new GetBlobsHandler(chain.TxPool),
chain.SpecProvider,
new GCKeeper(NoGCStrategy.Instance, chain.LogManager),
chain.LogManager);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1569,7 +1569,8 @@ public void Should_return_expected_capabilities_for_mainnet()

nameof(IEngineRpcModule.engine_getPayloadV3),
nameof(IEngineRpcModule.engine_forkchoiceUpdatedV3),
nameof(IEngineRpcModule.engine_newPayloadV3)
nameof(IEngineRpcModule.engine_newPayloadV3),
nameof(IEngineRpcModule.engine_getBlobsV1)
};
Assert.That(result, Is.EquivalentTo(expectedMethods));
}
Expand Down
141 changes: 141 additions & 0 deletions src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using Nethermind.Serialization.Json;
using Nethermind.Serialization.Rlp;
using Nethermind.Specs.Forks;
using Nethermind.TxPool;
using NSubstitute;
using NUnit.Framework;

Expand Down Expand Up @@ -361,6 +362,7 @@ public async Task<string> NewPayloadV3_should_verify_blob_versioned_hashes_again
Substitute.For<IGetPayloadBodiesByRangeV1Handler>(),
Substitute.For<IHandler<TransitionConfigurationV1, TransitionConfigurationV1>>(),
Substitute.For<IHandler<IEnumerable<string>, IEnumerable<string>>>(),
Substitute.For<IAsyncHandler<byte[][], GetBlobsV1Result>>(),
chain.SpecProvider,
new GCKeeper(NoGCStrategy.Instance, chain.LogManager),
Substitute.For<ILogManager>()));
Expand Down Expand Up @@ -508,6 +510,145 @@ static void MarkAsUnprocessed(MergeTestBlockchain chain, int blockNumber)
Assert.That(res2.Data.PayloadStatus.Status, Is.EqualTo(PayloadStatus.Valid));
}

[Test]
public async Task GetBlobsV1_should_throw_if_more_than_128_requested_blobs([Values(128, 129)] int requestSize)
{
MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Cancun.Instance);
IEngineRpcModule rpcModule = CreateEngineModule(chain, null, TimeSpan.FromDays(1));

List<byte[]> request = new List<byte[]>(requestSize);
for (int i = 0; i < requestSize; i++)
{
request.Add(Bytes.FromHexString(i.ToString("X64")));
}

ResultWrapper<GetBlobsV1Result> result = await rpcModule.engine_getBlobsV1(request.ToArray());

if (requestSize > 128)
{
result.Result.Should().BeEquivalentTo(Result.Fail($"The number of requested blobs must not exceed 128"));
result.ErrorCode.Should().Be(MergeErrorCodes.TooLargeRequest);
}
else
{
result.Result.Should().Be(Result.Success);
result.Data.BlobsAndProofs.Length.Should().Be(requestSize);
}
}

[Test]
public async Task GetBlobsV1_should_return_requested_blobs([Values(1, 2, 3, 4, 5, 6)] int numberOfBlobs)
{
MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Cancun.Instance);
IEngineRpcModule rpcModule = CreateEngineModule(chain, null, TimeSpan.FromDays(1));

Transaction blobTx = Build.A.Transaction
.WithShardBlobTxTypeAndFields(numberOfBlobs)
.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<BlobAndProofV1?> blobsAndProofs = new(numberOfBlobs);
for (int i = 0; i < numberOfBlobs; i++)
{
blobsAndProofs.Add(new BlobAndProofV1(blobTx, i));
}
GetBlobsV1Result expected = new(blobsAndProofs.ToArray());

ResultWrapper<GetBlobsV1Result> result = await rpcModule.engine_getBlobsV1(blobTx.BlobVersionedHashes!);

result.Data.Should().BeEquivalentTo(expected);
result.Data.BlobsAndProofs.Length.Should().Be(numberOfBlobs);
for (int i = 0; i < numberOfBlobs; i++)
{
result.Data.BlobsAndProofs[i]!.Blob.Should().BeEquivalentTo(((ShardBlobNetworkWrapper)blobTx.NetworkWrapper!).Blobs[i]);
result.Data.BlobsAndProofs[i]!.Proof.Should().BeEquivalentTo(((ShardBlobNetworkWrapper)blobTx.NetworkWrapper!).Proofs[i]);
}
}

[Test]
public async Task GetBlobsV1_should_return_nulls_when_blobs_not_found([Values(1, 2, 3, 4, 5, 6)] int numberOfRequestedBlobs)
{
MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Cancun.Instance);
IEngineRpcModule rpcModule = CreateEngineModule(chain, null, TimeSpan.FromDays(1));

// we are not adding this tx
Transaction blobTx = Build.A.Transaction
.WithShardBlobTxTypeAndFields(numberOfRequestedBlobs)
.WithMaxFeePerGas(1.GWei())
.WithMaxPriorityFeePerGas(1.GWei())
.WithMaxFeePerBlobGas(1000.Wei())
.SignedAndResolved(chain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject;

List<BlobAndProofV1?> blobsAndProofs = new(numberOfRequestedBlobs);
for (int i = 0; i < numberOfRequestedBlobs; i++)
{
blobsAndProofs.Add(null);
}
GetBlobsV1Result expected = new(blobsAndProofs.ToArray());

// requesting hashes that are not present in TxPool
ResultWrapper<GetBlobsV1Result> result = await rpcModule.engine_getBlobsV1(blobTx.BlobVersionedHashes!);

result.Data.Should().BeEquivalentTo(expected);
result.Data.BlobsAndProofs.Length.Should().Be(numberOfRequestedBlobs);
for (int i = 0; i < numberOfRequestedBlobs; i++)
{
result.Data.BlobsAndProofs[i]!.Should().BeNull();
}
}

[Test]
public async Task GetBlobsV1_should_return_mix_of_blobs_and_nulls([Values(1, 2, 3, 4, 5, 6)] int numberOfBlobs)
{
int requestSize = 10 * numberOfBlobs;

MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Cancun.Instance);
IEngineRpcModule rpcModule = CreateEngineModule(chain, null, TimeSpan.FromDays(1));

Transaction blobTx = Build.A.Transaction
.WithShardBlobTxTypeAndFields(numberOfBlobs)
.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<byte[]> blobVersionedHashesRequest = new List<byte[]>(requestSize);
List<BlobAndProofV1?> blobsAndProofs = new(requestSize);

int actualIndex = 0;
for (int i = 0; i < requestSize; i++)
{
bool addActualHash = i % 10 == 0;

blobsAndProofs.Add(addActualHash ? new BlobAndProofV1(blobTx, actualIndex) : null);
blobVersionedHashesRequest.Add(addActualHash ? blobTx.BlobVersionedHashes![actualIndex++]! : Bytes.FromHexString(i.ToString("X64")));
}
GetBlobsV1Result expected = new(blobsAndProofs.ToArray());

ResultWrapper<GetBlobsV1Result> result = await rpcModule.engine_getBlobsV1(blobVersionedHashesRequest.ToArray());

result.Data.Should().BeEquivalentTo(expected);
result.Data.BlobsAndProofs.Length.Should().Be(requestSize);
for (int i = 0; i < requestSize; i++)
{
if (i % 10 == 0)
{
result.Data.BlobsAndProofs[i]!.Blob.Should().BeEquivalentTo(((ShardBlobNetworkWrapper)blobTx.NetworkWrapper!).Blobs[i / 10]);
result.Data.BlobsAndProofs[i]!.Proof.Should().BeEquivalentTo(((ShardBlobNetworkWrapper)blobTx.NetworkWrapper!).Proofs[i / 10]);
}
else
{
result.Data.BlobsAndProofs[i].Should().BeNull();
}
}
}

public static IEnumerable<TestCaseData> ForkchoiceUpdatedV3DeclinedTestCaseSource
{
get
Expand Down
24 changes: 24 additions & 0 deletions src/Nethermind/Nethermind.Merge.Plugin/Data/BlobAndProofV1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only


using System;
using Nethermind.Core;

namespace Nethermind.Merge.Plugin.Data;

public class BlobAndProofV1
{
public BlobAndProofV1(Transaction blobTx, int index)
{
if (blobTx is not { NetworkWrapper: ShardBlobNetworkWrapper wrapper })
{
throw new ArgumentException("Shard blob transaction should contain network wrapper data");
}

Blob = wrapper.Blobs[index];
Proof = wrapper.Proofs[index];
}
marcindsobczak marked this conversation as resolved.
Show resolved Hide resolved
public byte[] Blob { get; set; }
public byte[] Proof { get; set; }
}
11 changes: 11 additions & 0 deletions src/Nethermind/Nethermind.Merge.Plugin/Data/GetBlobsV1Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Generic;

namespace Nethermind.Merge.Plugin.Data;

public class GetBlobsV1Result(BlobAndProofV1?[] blobsAndProofs)
{
public readonly BlobAndProofV1?[] BlobsAndProofs = blobsAndProofs;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Nethermind.Merge.Plugin;
public partial class EngineRpcModule : IEngineRpcModule
{
private readonly IAsyncHandler<byte[], GetPayloadV3Result?> _getPayloadHandlerV3;
private readonly IAsyncHandler<byte[][], GetBlobsV1Result> _getBlobsHandler;

public Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV3(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null)
=> ForkchoiceUpdated(forkchoiceState, payloadAttributes, EngineApiVersions.Cancun);
Expand All @@ -23,4 +24,7 @@ public Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV3(ExecutionPayload

public async Task<ResultWrapper<GetPayloadV3Result?>> engine_getPayloadV3(byte[] payloadId) =>
await _getPayloadHandlerV3.HandleAsync(payloadId);

public async Task<ResultWrapper<GetBlobsV1Result>> engine_getBlobsV1(byte[][] blobVersionedHashes) =>
await _getBlobsHandler.HandleAsync(blobVersionedHashes);
}
2 changes: 2 additions & 0 deletions src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public EngineRpcModule(
IGetPayloadBodiesByRangeV1Handler executionGetPayloadBodiesByRangeV1Handler,
IHandler<TransitionConfigurationV1, TransitionConfigurationV1> transitionConfigurationHandler,
IHandler<IEnumerable<string>, IEnumerable<string>> capabilitiesHandler,
IAsyncHandler<byte[][], GetBlobsV1Result> getBlobsHandler,
ISpecProvider specProvider,
GCKeeper gcKeeper,
ILogManager logManager)
Expand All @@ -43,6 +44,7 @@ public EngineRpcModule(
_executionGetPayloadBodiesByHashV1Handler = executionGetPayloadBodiesByHashV1Handler;
_executionGetPayloadBodiesByRangeV1Handler = executionGetPayloadBodiesByRangeV1Handler;
_transitionConfigurationHandler = transitionConfigurationHandler;
_getBlobsHandler = getBlobsHandler;
_specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider));
_gcKeeper = gcKeeper;
_logger = logManager.GetClassLogger();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public EngineRpcCapabilitiesProvider(ISpecProvider specProvider)
_capabilities[nameof(IEngineRpcModule.engine_getPayloadV3)] = (spec.IsEip4844Enabled, spec.IsEip4844Enabled);
_capabilities[nameof(IEngineRpcModule.engine_forkchoiceUpdatedV3)] = (spec.IsEip4844Enabled, spec.IsEip4844Enabled);
_capabilities[nameof(IEngineRpcModule.engine_newPayloadV3)] = (spec.IsEip4844Enabled, spec.IsEip4844Enabled);
_capabilities[nameof(IEngineRpcModule.engine_getBlobsV1)] = (spec.IsEip4844Enabled, spec.IsEip4844Enabled);
#endregion
}

Expand Down
60 changes: 60 additions & 0 deletions src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nethermind.Core;
using Nethermind.Core.Collections;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
using Nethermind.JsonRpc;
using Nethermind.Merge.Plugin.Data;
using Nethermind.TxPool;

namespace Nethermind.Merge.Plugin.Handlers;

public class GetBlobsHandler(ITxPool txPool) : IAsyncHandler<byte[][], GetBlobsV1Result>
{
private const int MaxRequest = 128;

private readonly ConcurrentDictionary<string, List<Hash256>> _blobIndex = txPool.GetBlobIndex();

public Task<ResultWrapper<GetBlobsV1Result>> HandleAsync(byte[][] request)
{
if (request.Length > MaxRequest)
{
var error = $"The number of requested blobs must not exceed {MaxRequest}";
return ResultWrapper<GetBlobsV1Result>.Fail(error, MergeErrorCodes.TooLargeRequest);
}

ArrayPoolList<BlobAndProofV1?> blobsAndProofs = new(request.Length);

foreach (byte[] requestedBlobVersionedHash in request)
{
bool isBlobFound = false;
if (_blobIndex.TryGetValue(requestedBlobVersionedHash.ToHexString(), out List<Hash256>? txHashes)
&& txPool.TryGetPendingBlobTransaction(txHashes.First(), out Transaction? blobTx)
&& blobTx.BlobVersionedHashes?.Length > 0)
{
for (int indexOfBlob = 0; indexOfBlob < blobTx.BlobVersionedHashes.Length; indexOfBlob++)
{
if (blobTx.BlobVersionedHashes[indexOfBlob] == requestedBlobVersionedHash)
{
isBlobFound = true;
blobsAndProofs.Add(new BlobAndProofV1(blobTx, indexOfBlob));
break;
}
}
}

if (!isBlobFound)
{
blobsAndProofs.Add(null);
}
}

return ResultWrapper<GetBlobsV1Result>.Success(new GetBlobsV1Result(blobsAndProofs.ToArray()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ public partial interface IEngineRpcModule : IRpcModule
IsSharable = true,
IsImplemented = true)]
public Task<ResultWrapper<GetPayloadV3Result?>> engine_getPayloadV3(byte[] payloadId);

[JsonRpcMethod(
Description = "Returns requested blobs and proofs.",
IsSharable = true,
IsImplemented = true)]
public Task<ResultWrapper<GetBlobsV1Result>> engine_getBlobsV1(byte[][] blobVersionedHashes);
}
2 changes: 2 additions & 0 deletions src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ public Task InitRpcModules()
if (_api.Sealer is null) throw new ArgumentNullException(nameof(_api.Sealer));
if (_api.BlockValidator is null) throw new ArgumentNullException(nameof(_api.BlockValidator));
if (_api.BlockProcessingQueue is null) throw new ArgumentNullException(nameof(_api.BlockProcessingQueue));
if (_api.TxPool is null) throw new ArgumentNullException(nameof(_api.TxPool));
if (_api.SpecProvider is null) throw new ArgumentNullException(nameof(_api.SpecProvider));
if (_api.StateReader is null) throw new ArgumentNullException(nameof(_api.StateReader));
if (_beaconPivot is null) throw new ArgumentNullException(nameof(_beaconPivot));
Expand Down Expand Up @@ -354,6 +355,7 @@ public Task InitRpcModules()
new GetPayloadBodiesByRangeV1Handler(_api.BlockTree, _api.LogManager),
new ExchangeTransitionConfigurationV1Handler(_poSSwitcher, _api.LogManager),
new ExchangeCapabilitiesHandler(_api.RpcCapabilitiesProvider, _api.LogManager),
new GetBlobsHandler(_api.TxPool),
_api.SpecProvider,
new GCKeeper(new NoSyncGcRegionStrategy(_api.SyncModeSelector, _mergeConfig), _api.LogManager),
_api.LogManager);
Expand Down
2 changes: 2 additions & 0 deletions src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ public async Task InitRpcModules()
ArgumentNullException.ThrowIfNull(_api.BlockValidator);
ArgumentNullException.ThrowIfNull(_api.RpcModuleProvider);
ArgumentNullException.ThrowIfNull(_api.BlockProducer);
ArgumentNullException.ThrowIfNull(_api.TxPool);

ArgumentNullException.ThrowIfNull(_beaconSync);
ArgumentNullException.ThrowIfNull(_beaconPivot);
Expand Down Expand Up @@ -268,6 +269,7 @@ public async Task InitRpcModules()
new GetPayloadBodiesByRangeV1Handler(_api.BlockTree, _api.LogManager),
new ExchangeTransitionConfigurationV1Handler(_api.PoSSwitcher, _api.LogManager),
new ExchangeCapabilitiesHandler(_api.RpcCapabilitiesProvider, _api.LogManager),
new GetBlobsHandler(_api.TxPool),
_api.SpecProvider,
new GCKeeper(
initConfig.DisableGcOnNewPayload
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ public int GetLength(OptimismTxReceipt item, RlpBehaviors rlpBehaviors)

TxReceipt IRlpStreamDecoder<TxReceipt>.Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors)
{
return Decode(rlpStream, rlpBehaviors); ;
return Decode(rlpStream, rlpBehaviors);
}

public void Encode(RlpStream stream, TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
Expand Down
Loading