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

RBuilder rpc #7985

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions src/Nethermind/Nethermind.Core/Crypto/Hash256Converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,16 @@ public override void Write(
{
ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false);
}

// Confusingly these two method is needed only in test
public override Hash256 ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
byte[]? bytes = ByteArrayConverter.Convert(ref reader);
return bytes is null ? null! : new Hash256(bytes);
}

public override void WriteAsPropertyName(Utf8JsonWriter writer, Hash256 value, JsonSerializerOptions options)
{
writer.WritePropertyName(value.ToString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class ByteArrayConverter : JsonConverter<byte[]>
JsonTokenType tokenType = reader.TokenType;
if (tokenType == JsonTokenType.None || tokenType == JsonTokenType.Null)
return null;
else if (tokenType != JsonTokenType.String)
else if (tokenType != JsonTokenType.String && tokenType != JsonTokenType.PropertyName)
{
ThrowInvalidOperationException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public override Transaction ToTransaction()

tx.GasPrice = MaxPriorityFeePerGas ?? 0;
tx.DecodedMaxFeePerGas = MaxFeePerGas ?? 0;
tx.GasLimit = Gas ?? 0;

return tx;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private bool TrySimulate(
{
IBlockTree blockTree = env.BlockTree;
IWorldState stateProvider = env.WorldState;
parent = GetParent(parent, payload, blockTree);
// parent = GetParent(parent, payload, blockTree);
IReleaseSpec spec = env.SpecProvider.GetSpec(parent);

if (payload.BlockStateCalls is not null)
Expand Down
4 changes: 4 additions & 0 deletions src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Nethermind.JsonRpc.Modules.Parity;
using Nethermind.JsonRpc.Modules.Personal;
using Nethermind.JsonRpc.Modules.Proof;
using Nethermind.JsonRpc.Modules.RBuilder;
using Nethermind.JsonRpc.Modules.Rpc;
using Nethermind.JsonRpc.Modules.Subscribe;
using Nethermind.JsonRpc.Modules.Trace;
Expand Down Expand Up @@ -191,6 +192,9 @@ public virtual async Task Execute(CancellationToken cancellationToken)
RpcRpcModule rpcRpcModule = new(rpcModuleProvider.Enabled);
rpcModuleProvider.RegisterSingle<IRpcRpcModule>(rpcRpcModule);

RbuilderRpcModule rbuilderRpcModule = new(_api.BlockTree, _api.SpecProvider, _api.WorldStateManager);
rpcModuleProvider.RegisterSingle<IRbuilderRpcModule>(rbuilderRpcModule);

if (logger.IsDebug) logger.Debug($"RPC modules : {string.Join(", ", rpcModuleProvider.Enabled.OrderBy(static x => x))}");
ThisNodeInfo.AddInfo("RPC modules :", $"{string.Join(", ", rpcModuleProvider.Enabled.OrderBy(static x => x))}");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Nethermind.Blockchain;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Test.Builders;
using Nethermind.Db;
using Nethermind.Int256;
using Nethermind.JsonRpc.Modules.RBuilder;
using Nethermind.Logging;
using Nethermind.Specs;
using Nethermind.Specs.Forks;
using Nethermind.State;
using NUnit.Framework;
using Bytes = Nethermind.Core.Extensions.Bytes;

namespace Nethermind.JsonRpc.Test.Modules.Rbuilder;

public class RbuilderRpcModuleTests
{
private IRbuilderRpcModule _rbuilderRpcModule;
private WorldStateManager _worldStateManager;

[SetUp]
public async Task Setup()
{
_worldStateManager = WorldStateManager.CreateForTest(await TestMemDbProvider.InitAsync(), LimboLogs.Instance);
IBlockTree blockTree = Build.A.BlockTree()
.OfChainLength(10)
.TestObject;

_rbuilderRpcModule = new RbuilderRpcModule(blockTree, MainnetSpecProvider.Instance, _worldStateManager);
}

[Test]
public async Task Test_getCodeByHash()
{
string theCode = "01234567012345670123456701234567012345670123456701234567012345670123456701234567012345670123456701234567012345670123456701234567";
byte[] theCodeBytes = Bytes.FromHexString(theCode);
Hash256 theHash = Keccak.Compute(theCodeBytes);
Console.Error.WriteLine(theHash.ToString());

IWorldState worldState = _worldStateManager.GlobalWorldState;
worldState.CreateAccount(TestItem.AddressA, 100000);
worldState.InsertCode(TestItem.AddressA, theCodeBytes, London.Instance);
worldState.Commit(London.Instance);
worldState.CommitTree(0);

string response = await RpcTest.TestSerializedRequest(_rbuilderRpcModule, "rbuilder_getCodeByHash", theHash);

response.Should().Contain(theCode);
}

[Test]
public async Task Test_getCodeByHashNotFound()
{
string theCode = "01234567012345670123456701234567012345670123456701234567012345670123456701234567012345670123456701234567012345670123456701234567";
byte[] theCodeBytes = Bytes.FromHexString(theCode);
Hash256 theHash = Keccak.Compute(theCodeBytes);
string response = await RpcTest.TestSerializedRequest(_rbuilderRpcModule, "rbuilder_getCodeByHash", theHash);
response.Should().Contain("null");
}

[Test]
public async Task Test_calculateStateRoot()
{
Dictionary<Address, AccountChange> accountDiff = new Dictionary<Address, AccountChange>();
accountDiff[TestItem.AddressA] = new AccountChange()
{
Nonce = 10,
Balance = 20,
SelfDestructed = true,
ChangedSlots = new Dictionary<UInt256, UInt256>()
{
{0,0}
}
};

string response = await RpcTest.TestSerializedRequest(_rbuilderRpcModule, "rbuilder_calculateStateRoot", "LATEST", accountDiff);
response.Should().Contain("0x4dab99008d5b6a24037cf0b601adf7526af44e89c1cef06ccf220b07c497bcd5");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ protected override SimulatePayload<TransactionWithSourceDetails> Prepare(Simulat
bool hadNonceInRequest = asLegacy?.Nonce is not null;
asLegacy!.EnsureDefaults(_gasCapBudget);
_gasCapBudget -= asLegacy.Gas!.Value;
if (_gasCapBudget < 0) _gasCapBudget = 0;

Transaction tx = callTransactionModel.ToTransaction();
tx.ChainId = _blockchainBridge.GetChainId();
Expand Down
2 changes: 2 additions & 0 deletions src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static class ModuleType
public const string Deposit = nameof(Deposit);
public const string Health = nameof(Health);
public const string Rpc = nameof(Rpc);
public const string Rbuilder = nameof(Rbuilder);

public static IEnumerable<string> AllBuiltInModules { get; } = new List<string>()
{
Expand All @@ -52,6 +53,7 @@ public static class ModuleType
Deposit,
Health,
Rpc,
Rbuilder,
};

public static IEnumerable<string> DefaultModules { get; } = new List<string>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Generic;
using System.Text.Json.Serialization;
using Nethermind.Blockchain.Find;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Int256;

namespace Nethermind.JsonRpc.Modules.RBuilder;

[RpcModule(ModuleType.Rbuilder)]
public interface IRbuilderRpcModule: IRpcModule
{
[JsonRpcMethod(IsImplemented = true,
Description = "Returns bytecode based on hash.",
IsSharable = true,
ExampleResponse = "0xffff")]
ResultWrapper<byte[]> rbuilder_getCodeByHash(Hash256 hash);

[JsonRpcMethod(IsImplemented = true,
Description = "Calculate the state root on top of the state trie at specified block given a set of change.",
IsSharable = true,
ExampleResponse = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")]
ResultWrapper<Hash256> rbuilder_calculateStateRoot(BlockParameter block, IDictionary<Address, AccountChange> accountDiff);
}

public class AccountChange
{
[JsonPropertyName("nonce")]
public UInt256? Nonce { get; set; }

[JsonPropertyName("balance")]
public UInt256? Balance { get; set; }

[JsonPropertyName("code_hash")]
public Hash256? CodeHash { get; set; }

[JsonPropertyName("self_destructed")]
public bool SelfDestructed { get; set; }

[JsonPropertyName("changed_slots")]
public IDictionary<UInt256, UInt256>? ChangedSlots { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using Microsoft.Extensions.ObjectPool;
using Nethermind.Blockchain.Find;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
using Nethermind.Core.Specs;
using Nethermind.Int256;
using Nethermind.State;

namespace Nethermind.JsonRpc.Modules.RBuilder;

public class RbuilderRpcModule(IBlockFinder blockFinder, ISpecProvider specProvider, IWorldStateManager worldStateManager): IRbuilderRpcModule
{
private readonly ObjectPool<IOverridableWorldScope> _overridableWorldScopePool = new DefaultObjectPool<IOverridableWorldScope>(new PooledIWorldStatePolicy(worldStateManager));

public ResultWrapper<byte[]> rbuilder_getCodeByHash(Hash256 hash)
{
return ResultWrapper<byte[]>.Success(worldStateManager.GlobalStateReader.GetCode(hash));
}

public ResultWrapper<Hash256> rbuilder_calculateStateRoot(BlockParameter blockParam, IDictionary<Address, AccountChange> accountDiff)
{
BlockHeader? blockHeader = blockFinder.FindHeader(blockParam);
if (blockHeader is null)
{
return ResultWrapper<Hash256>.Fail("Block not found");
}

IOverridableWorldScope worldScope = _overridableWorldScopePool.Get();
try
{
IWorldState worldState = worldScope.WorldState;
IReleaseSpec releaseSpec = specProvider.GetSpec(blockHeader);
worldState.StateRoot = blockHeader.StateRoot!;

foreach (KeyValuePair<Address, AccountChange> kv in accountDiff)
{
Address address = kv.Key;
AccountChange accountChange = kv.Value;

if (accountChange.SelfDestructed)
{
worldState.DeleteAccount(address);
}

bool hasAccountChange = accountChange.Balance is not null
|| accountChange.Nonce is not null
|| accountChange.CodeHash is not null
|| accountChange.ChangedSlots?.Count > 0;
if (!hasAccountChange) continue;

if (worldState.TryGetAccount(address, out AccountStruct account))
{
// IWorldState does not actually have set nonce or set balance.
// Set, its either this or changing `IWorldState` which is somewhat risky.
if (accountChange.Nonce is not null)
{
worldState.SetNonce(address, accountChange.Nonce.Value);
}

if (accountChange.Balance is not null)
{
UInt256 originalBalance = account.Balance;
if (accountChange.Balance.Value > originalBalance)
{
worldState.AddToBalance(address, accountChange.Balance.Value - originalBalance, releaseSpec);
}
else if (accountChange.Balance.Value == originalBalance)
{
}
else
{
worldState.SubtractFromBalance(address, originalBalance - accountChange.Balance.Value, releaseSpec);
}

if (worldState.GetBalance(address) != accountChange.Balance.Value)
{
throw new Exception("Balance is not same!? Why?");
}
}
}
else
{
worldState.CreateAccountIfNotExists(address, accountChange.Balance ?? 0, accountChange.Nonce ?? 0);
}

if (accountChange.CodeHash is not null)
{
// Note, this set CodeDb, but since this is a read only world state, it should do nothing.
worldState.InsertCode(address, accountChange.CodeHash, Array.Empty<byte>(), releaseSpec, false);
}

if (accountChange.ChangedSlots is not null)
{
foreach (KeyValuePair<UInt256, UInt256> changedSlot in accountChange.ChangedSlots)
{
ReadOnlySpan<byte> bytes = changedSlot.Value.ToBigEndian();
bool newIsZero = bytes.IsZero();
bytes = !newIsZero ? bytes.WithoutLeadingZeros() : [0];
worldState.Set(new StorageCell(address, changedSlot.Key), bytes.ToArray());
}
}
}

worldState.Commit(releaseSpec);
worldState.CommitTree(blockHeader.Number + 1);
return ResultWrapper<Hash256>.Success(worldState.StateRoot);
}
finally
{
_overridableWorldScopePool.Return(worldScope);
}
}

private class PooledIWorldStatePolicy(IWorldStateManager worldStateManager): IPooledObjectPolicy<IOverridableWorldScope>
{
public IOverridableWorldScope Create()
{
return worldStateManager.CreateOverridableWorldScope();
}

public bool Return(IOverridableWorldScope obj)
{
obj.WorldState.Reset();
obj.WorldState.ResetOverrides();
return true;
}
}
}
Loading