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

Make chainspec extendable by plugins #7540

Merged
merged 53 commits into from
Nov 4, 2024
Merged
Changes from 4 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
2ada5d8
Custom chainspec engine
deffrian Oct 1, 2024
d9d4f2a
Optimism & add more types to decoder
deffrian Oct 2, 2024
d61acc3
Update src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs
deffrian Oct 3, 2024
6ea1a10
Update src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs
deffrian Oct 3, 2024
33f8a6c
Small fixes
deffrian Oct 5, 2024
f7b8e32
Update src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecParame…
deffrian Oct 6, 2024
32cbfa4
Small fixes
deffrian Oct 6, 2024
70d2227
Custom chainspec engine
deffrian Oct 1, 2024
da4313b
Optimism & add more types to decoder
deffrian Oct 2, 2024
3c39505
Update src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs
deffrian Oct 3, 2024
c44f8e8
Update src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs
deffrian Oct 3, 2024
7f8ae03
Small fixes
deffrian Oct 5, 2024
be56036
Update src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecParame…
deffrian Oct 6, 2024
b6f1a53
Small fixes
deffrian Oct 6, 2024
bed5240
Merge remote-tracking branch 'origin/refactor/chainspec-v2' into refa…
deffrian Oct 6, 2024
4b5d291
NethDev
deffrian Oct 6, 2024
777c49a
Clique
deffrian Oct 7, 2024
4261b1e
Fix tests
deffrian Oct 7, 2024
2946160
Runner tests
deffrian Oct 7, 2024
6d0ece3
Merge branch 'master' into refactor/chainspec-v2
deffrian Oct 11, 2024
ddbcc2d
Fix build
deffrian Oct 11, 2024
15cb899
Ethash & switch to json serialization
deffrian Oct 14, 2024
a9dd55e
Fix tests
deffrian Oct 15, 2024
c3d296f
Merge branch 'master' into refactor/chainspec-v2
deffrian Oct 15, 2024
67d89e1
Aura
deffrian Oct 21, 2024
7705a5c
Merge branch 'master' into refactor/chainspec-v2
deffrian Oct 21, 2024
0a7f661
Fix build
deffrian Oct 21, 2024
dde5690
Merge branch 'master' into refactor/chainspec-v2
deffrian Oct 21, 2024
18ce8a2
Fix tests
deffrian Oct 22, 2024
931b4ac
Fix merge tests
deffrian Oct 23, 2024
8e9ab40
Merge branch 'master' into refactor/chainspec-v2
deffrian Oct 23, 2024
300dd01
AuraParameters
deffrian Oct 23, 2024
d488037
Remove comments
deffrian Oct 23, 2024
5c40e24
TODOs
deffrian Oct 24, 2024
24c91c1
Fix spec tests
deffrian Oct 24, 2024
afef052
Merge branch 'master' into refactor/chainspec-v2
deffrian Oct 27, 2024
407de4f
Fix Taiko
deffrian Oct 27, 2024
e39085b
Formatting & tests
deffrian Oct 27, 2024
728ee3a
EngineName
deffrian Oct 27, 2024
7e3e146
TODO
deffrian Oct 27, 2024
3a004be
Merge branch 'master' into refactor/chainspec-v2
deffrian Oct 30, 2024
5030e93
Fix suggestions
deffrian Oct 30, 2024
92d7253
refactors
LukaszRozmej Oct 31, 2024
c7b4d63
more refactors
LukaszRozmej Oct 31, 2024
fed3fdd
fix for strings
LukaszRozmej Oct 31, 2024
bbff932
Remove TestSealEngineType.cs and JsonConverter
deffrian Nov 1, 2024
27de2f5
Merge branch 'master' into refactor/chainspec-v2
deffrian Nov 1, 2024
d0b5b08
Rollback JsonConverter removal
deffrian Nov 1, 2024
9ea7527
Handle null
deffrian Nov 1, 2024
c9c38bf
Remove DifficultyBombDelaysConverter
deffrian Nov 1, 2024
69d8c21
Merge branch 'master' into refactor/chainspec-v2
deffrian Nov 1, 2024
98c8178
Make discovery of ChainSpecEngineParameters explicit
LukaszRozmej Nov 4, 2024
18a3619
Merge branch 'master' into refactor/chainspec-v2
deffrian Nov 4, 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
95 changes: 87 additions & 8 deletions src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs
Original file line number Diff line number Diff line change
@@ -8,13 +8,21 @@
using Nethermind.Int256;
using System.Text.Json;
using System.Text.Json.Serialization;
using Nethermind.Core;
using Nethermind.Core.Crypto;

namespace Nethermind.Config
{
internal static class ConfigSourceHelper
public static class ConfigSourceHelper
{
public static object ParseValue(Type valueType, string valueString, string category, string name)
{
if (Nullable.GetUnderlyingType(valueType) is { } nullableType)
{
return !string.IsNullOrEmpty(valueString) && !valueString.Equals("null", StringComparison.InvariantCultureIgnoreCase)
? ParseValue(nullableType, valueString, category, name)
: null;
}
try
{
object value;
@@ -23,8 +31,17 @@ public static object ParseValue(Type valueType, string valueString, string categ
//supports Arrays, e.g int[] and generic IEnumerable<T>, IList<T>
var itemType = valueType.IsGenericType ? valueType.GetGenericArguments()[0] : valueType.GetElementType();

if (itemType == typeof(byte) && !valueString.AsSpan().TrimStart().StartsWith("["))
{
// hex encoded byte array
string hex = valueString.Trim().RemoveStart('0').RemoveStart('x').TrimEnd();
value = Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bytes.FromHexString
AsSpan().Trim() if needed

}
//In case of collection of objects (more complex config models) we parse entire collection
if (itemType.IsClass && typeof(IConfigModel).IsAssignableFrom(itemType))
else if (itemType.IsClass && typeof(IConfigModel).IsAssignableFrom(itemType))
{
var objCollection = JsonSerializer.Deserialize(valueString, valueType);
value = objCollection;
@@ -86,13 +103,76 @@ public static object GetDefault(Type type)
return type.IsValueType ? (false, Activator.CreateInstance(type)) : (false, null);
}

public static bool TryFromHex(Type type, string itemValue, out object value)
{
if (!itemValue.StartsWith("0x"))
{
value = null;
return false;
}
switch (Type.GetTypeCode(type))
LukaszRozmej marked this conversation as resolved.
Show resolved Hide resolved
{
case TypeCode.Byte:
value = Convert.ToByte(itemValue, 16);
return true;
case TypeCode.SByte:
value = Convert.ToSByte(itemValue, 16);
return true;
case TypeCode.UInt16:
value = Convert.ToUInt16(itemValue, 16);
return true;
case TypeCode.UInt32:
value = Convert.ToUInt32(itemValue, 16);
return true;
case TypeCode.UInt64:
value = Convert.ToUInt64(itemValue, 16);
return true;
case TypeCode.Int16:
value = Convert.ToInt16(itemValue, 16);
return true;
case TypeCode.Int32:
value = Convert.ToInt32(itemValue, 16);
return true;
case TypeCode.Int64:
value = Convert.ToInt64(itemValue, 16);
return true;
default:
value = null;
return false;
}
}

private static object GetValue(Type valueType, string itemValue)
{
if (Nullable.GetUnderlyingType(valueType) is var nullableType && nullableType is not null)
{
if (string.IsNullOrEmpty(itemValue) || itemValue.Equals("null", StringComparison.InvariantCultureIgnoreCase))
{
return null;
}

return GetValue(nullableType, itemValue);
}
deffrian marked this conversation as resolved.
Show resolved Hide resolved

if (valueType == typeof(UInt256))
{
return UInt256.Parse(itemValue);
}

if (valueType == typeof(Address))
{
if (Address.TryParse(itemValue, out var address))
{
return address;
}
throw new FormatException($"Could not parse {itemValue} to {typeof(Address)}");
}

if (valueType == typeof(Hash256))
{
return new Hash256(itemValue);
}

if (valueType.IsEnum)
{
if (Enum.TryParse(valueType, itemValue, true, out var enumValue))
@@ -103,13 +183,12 @@ private static object GetValue(Type valueType, string itemValue)
throw new FormatException($"Cannot parse enum value: {itemValue}, type: {valueType.Name}");
}

var nullableType = Nullable.GetUnderlyingType(valueType);
if (TryFromHex(valueType, itemValue, out object value))
{
return value;
}

return nullableType is null
? Convert.ChangeType(itemValue, valueType)
: !string.IsNullOrEmpty(itemValue) && !itemValue.Equals("null", StringComparison.InvariantCultureIgnoreCase)
? Convert.ChangeType(itemValue, nullableType)
: null;
return Convert.ChangeType(itemValue, valueType);
}
}
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ namespace Nethermind.Optimism;

public interface IOptimismSpecHelper
{
Address L1FeeReceiver { get; }
Address? L1FeeReceiver { get; }

bool IsBedrock(BlockHeader header);
bool IsRegolith(BlockHeader header);
Original file line number Diff line number Diff line change
@@ -22,10 +22,13 @@ public class InitializeBlockchainOptimism(OptimismNethermindApi api) : Initializ
{
private readonly IBlocksConfig _blocksConfig = api.Config<IBlocksConfig>();

private readonly OptimismChainSpecEngineParameters _chainSpecParameters = api.ChainSpec
.EngineChainSpecParametersProvider.GetChainSpecParameters<OptimismChainSpecEngineParameters>();

protected override async Task InitBlockchain()
{
api.SpecHelper = new(api.ChainSpec.Optimism);
api.L1CostHelper = new(api.SpecHelper, api.ChainSpec.Optimism.L1BlockAddress);
api.SpecHelper = new(_chainSpecParameters);
api.L1CostHelper = new(api.SpecHelper, _chainSpecParameters.L1BlockAddress!);

await base.InitBlockchain();
}
8 changes: 4 additions & 4 deletions src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs
Original file line number Diff line number Diff line change
@@ -6,16 +6,16 @@

namespace Nethermind.Optimism;

public class OptimismSpecHelper(OptimismParameters parameters) : IOptimismSpecHelper
public class OptimismSpecHelper(OptimismChainSpecEngineParameters parameters) : IOptimismSpecHelper
{
private readonly long _bedrockBlockNumber = parameters.BedrockBlockNumber;
private readonly ulong _regolithTimestamp = parameters.RegolithTimestamp;
private readonly long? _bedrockBlockNumber = parameters.BedrockBlockNumber;
private readonly ulong? _regolithTimestamp = parameters.RegolithTimestamp;
Comment on lines +11 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to make stuff nullable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be no value in config, so better to make it nullable imo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deffrian shouldn't we always have a value for these hardforks?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On op-sepolia which actually does not have a bedrock block we're using 0x0, for example.

I'm in favor of letting it default to 0 rather than making it null

private readonly ulong? _canyonTimestamp = parameters.CanyonTimestamp;
private readonly ulong? _ecotoneTimestamp = parameters.EcotoneTimestamp;
private readonly ulong? _fjordTimestamp = parameters.FjordTimestamp;
private readonly ulong? _graniteTimestamp = parameters.GraniteTimestamp;

public Address L1FeeReceiver { get; init; } = parameters.L1FeeRecipient;
public Address? L1FeeReceiver { get; init; } = parameters.L1FeeRecipient;

public bool IsRegolith(BlockHeader header)
{
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using Nethermind.Core;
using Nethermind.Int256;
using Nethermind.Specs;
using Nethermind.Specs.ChainSpecStyle;

namespace Nethermind.Optimism;

public class OptimismChainSpecEngineParameters : IChainSpecEngineParameters
{
public string? SealEngineType => "Optimism";

public ulong? RegolithTimestamp { get; set; }

public long? BedrockBlockNumber { get; set; }

public ulong? CanyonTimestamp { get; set; }

public ulong? EcotoneTimestamp { get; set; }

public ulong? FjordTimestamp { get; set; }

public ulong? GraniteTimestamp { get; set; }

public Address? L1FeeRecipient { get; set; }

public Address? L1BlockAddress { get; set; }

public UInt256? CanyonBaseFeeChangeDenominator { get; set; }

public Address? Create2DeployerAddress { get; set; }

public byte[]? Create2DeployerCode { get; set; }

public void AddTransitions(SortedSet<long> blockNumbers, SortedSet<ulong> timestamps)
{
ArgumentNullException.ThrowIfNull(RegolithTimestamp);
ArgumentNullException.ThrowIfNull(BedrockBlockNumber);
ArgumentNullException.ThrowIfNull(CanyonTimestamp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these fields (like CanyonTimestamp) used to be nullable and still are here, but others have changed (like RegolithTimestamp).
Which fields are actually required and which are not?

ArgumentNullException.ThrowIfNull(EcotoneTimestamp);
ArgumentNullException.ThrowIfNull(FjordTimestamp);
ArgumentNullException.ThrowIfNull(GraniteTimestamp);
ArgumentNullException.ThrowIfNull(L1FeeRecipient);
ArgumentNullException.ThrowIfNull(L1BlockAddress);
ArgumentNullException.ThrowIfNull(CanyonBaseFeeChangeDenominator);
ArgumentNullException.ThrowIfNull(Create2DeployerAddress);
ArgumentNullException.ThrowIfNull(Create2DeployerCode);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it have some sort of Validate method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to remove. Was for debug purposes

}

public void AdjustReleaseSpec(ReleaseSpec spec, long startBlock, ulong? startTimestamp)
{
if (CanyonTimestamp <= startTimestamp)
{
// TODO: check
spec.BaseFeeMaxChangeDenominator = CanyonBaseFeeChangeDenominator!.Value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible risk of null dereference?

}
}
}
Original file line number Diff line number Diff line change
@@ -146,7 +146,7 @@ protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec
if (opSpecHelper.IsBedrock(header))
{
UInt256 l1Cost = _currentTxL1Cost ??= l1CostHelper.ComputeL1Cost(tx, header, WorldState);
WorldState.AddToBalanceAndCreateIfNotExists(opSpecHelper.L1FeeReceiver, l1Cost, spec);
WorldState.AddToBalanceAndCreateIfNotExists(opSpecHelper.L1FeeReceiver!, l1Cost, spec);
}
}
}
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs
Original file line number Diff line number Diff line change
@@ -40,10 +40,10 @@ public class ChainSpec

public EthashParameters Ethash { get; set; }

public OptimismParameters Optimism { get; set; }

public ChainParameters Parameters { get; set; }

public IChainSpecParametersProvider EngineChainSpecParametersProvider { get; set; }

public Dictionary<Address, ChainSpecAllocation> Allocations { get; set; }

public long? FixedDifficulty { get; set; }
Original file line number Diff line number Diff line change
@@ -41,6 +41,11 @@ private void BuildTransitions()
}
}

foreach (IChainSpecEngineParameters item in _chainSpec.EngineChainSpecParametersProvider.AllChainSpecParameters)
{
item.AddTransitions(transitionBlockNumbers, transitionTimestamps);
}

AddTransitions(transitionBlockNumbers, _chainSpec, n => n.EndsWith("BlockNumber") && n != "TerminalPoWBlockNumber");
AddTransitions(transitionBlockNumbers, _chainSpec.Parameters, n => n.EndsWith("Transition"));
AddTransitions(transitionBlockNumbers, _chainSpec.Ethash, n => n.EndsWith("Transition"));
@@ -107,7 +112,7 @@ static void Add(SortedSet<T> transitions, T value, T? minValueExclusive)
TerminalTotalDifficulty = _chainSpec.Parameters.TerminalTotalDifficulty;
}

private static (ForkActivation, IReleaseSpec Spec)[] CreateTransitions(
private (ForkActivation, IReleaseSpec Spec)[] CreateTransitions(
ChainSpec chainSpec,
SortedSet<long> transitionBlockNumbers,
SortedSet<ulong> transitionTimestamps)
@@ -153,7 +158,7 @@ private static ForkActivation[] CreateTransitionActivations(SortedSet<long> tran
return transitionActivations;
}

private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseStartBlock, ulong? releaseStartTimestamp = null)
private ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseStartBlock, ulong? releaseStartTimestamp = null)
{
ReleaseSpec releaseSpec = new();
releaseSpec.MaximumUncleCount = (int)(releaseStartBlock >= (chainSpec.AuRa?.MaximumUncleCountTransition ?? long.MaxValue) ? chainSpec.AuRa?.MaximumUncleCount ?? 2 : 2);
@@ -213,10 +218,6 @@ private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseSt
releaseSpec.ForkBaseFee = chainSpec.Parameters.Eip1559BaseFeeInitialValue ?? Eip1559Constants.DefaultForkBaseFee;
releaseSpec.BaseFeeMaxChangeDenominator = chainSpec.Parameters.Eip1559BaseFeeMaxChangeDenominator ?? Eip1559Constants.DefaultBaseFeeMaxChangeDenominator;

if (chainSpec.Optimism?.CanyonTimestamp <= releaseStartTimestamp)
{
releaseSpec.BaseFeeMaxChangeDenominator = chainSpec.Optimism.CanyonBaseFeeChangeDenominator;
}


if (chainSpec.Ethash is not null)
@@ -262,6 +263,11 @@ private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseSt
releaseSpec.IsEip7002Enabled = (chainSpec.Parameters.Eip7002TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp;
releaseSpec.Eip7002ContractAddress = chainSpec.Parameters.Eip7002ContractAddress;

foreach (IChainSpecEngineParameters item in _chainSpec.EngineChainSpecParametersProvider.AllChainSpecParameters)
{
item.AdjustReleaseSpec(releaseSpec, releaseStartBlock, releaseStartTimestamp);
}

return releaseSpec;
}

38 changes: 19 additions & 19 deletions src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
Original file line number Diff line number Diff line change
@@ -336,25 +336,6 @@ static AuRaParameters.Validator LoadValidator(ChainSpecJson.AuRaValidatorJson va
}
}
}
else if (chainSpecJson.Engine?.Optimism is not null)
{
chainSpec.SealEngineType = SealEngineType.Optimism;
chainSpec.Optimism = new OptimismParameters
{
RegolithTimestamp = chainSpecJson.Engine.Optimism.RegolithTimestamp,
BedrockBlockNumber = chainSpecJson.Engine.Optimism.BedrockBlockNumber,
CanyonTimestamp = chainSpecJson.Engine.Optimism.CanyonTimestamp,
EcotoneTimestamp = chainSpecJson.Engine.Optimism.EcotoneTimestamp,
FjordTimestamp = chainSpecJson.Engine.Optimism.FjordTimestamp,
GraniteTimestamp = chainSpecJson.Engine.Optimism.GraniteTimestamp,

L1FeeRecipient = chainSpecJson.Engine.Optimism.L1FeeRecipient,
L1BlockAddress = chainSpecJson.Engine.Optimism.L1BlockAddress,
CanyonBaseFeeChangeDenominator = chainSpecJson.Engine.Optimism.CanyonBaseFeeChangeDenominator,
Create2DeployerAddress = chainSpecJson.Engine.Optimism.Create2DeployerAddress,
Create2DeployerCode = chainSpecJson.Engine.Optimism.Create2DeployerCode
};
}
else if (chainSpecJson.Engine?.NethDev is not null)
{
chainSpec.SealEngineType = SealEngineType.NethDev;
@@ -367,6 +348,25 @@ static AuRaParameters.Validator LoadValidator(ChainSpecJson.AuRaValidatorJson va
chainSpec.SealEngineType = customEngineType;
}

Dictionary<string, JsonElement> engineParameters = new();
foreach (KeyValuePair<string, JsonElement> engine in chainSpecJson.Engine.CustomEngineData)
{
if (engine.Value.TryGetProperty("params", out JsonElement value))
{
engineParameters.Add(engine.Key, value);
}
else
{
engineParameters.Add(engine.Key, engine.Value);
}
}

chainSpec.EngineChainSpecParametersProvider = new ChainSpecParametersProvider(engineParameters);
if (string.IsNullOrEmpty(chainSpec.SealEngineType))
{
chainSpec.SealEngineType = chainSpec.EngineChainSpecParametersProvider.SealEngineType;
}

if (string.IsNullOrEmpty(chainSpec.SealEngineType))
{
throw new NotSupportedException("unknown seal engine in chainspec");
Loading