From 3b50c5563cdf5efff6b5148a45aecaa3aaa4fb91 Mon Sep 17 00:00:00 2001 From: ak88 Date: Mon, 20 Oct 2025 16:09:22 +0200 Subject: [PATCH 01/11] block producer --- .../Nethermind.Xdc/XdcBlockProducer.cs | 102 ++++++++++++++++++ src/Nethermind/Nethermind.Xdc/XdcConstants.cs | 2 + 2 files changed, 104 insertions(+) create mode 100644 src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs new file mode 100644 index 000000000000..ff852115bcc2 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Consensus.Transactions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.Xdc; +internal class XdcBlockProducer : BlockProducerBase +{ + private readonly IEpochSwitchManager epochSwitchManager; + private readonly ISnapshotManager snapshotManager; + private readonly XdcContext xdcContext; + private readonly ISealer sealer; + private readonly ISpecProvider specProvider; + private readonly ILogManager logManager; + private static readonly ExtraConsensusDataDecoder _extraConsensusDataDecoder = new(); + + public XdcBlockProducer(IEpochSwitchManager epochSwitchManager, ISnapshotManager snapshotManager, XdcContext xdcContext, ITxSource txSource, IBlockchainProcessor processor, ISealer sealer, IBlockTree blockTree, IWorldState stateProvider, IGasLimitCalculator? gasLimitCalculator, ITimestamper? timestamper, ISpecProvider specProvider, ILogManager logManager, IDifficultyCalculator? difficultyCalculator, IBlocksConfig? blocksConfig) : base(txSource, processor, sealer, blockTree, stateProvider, gasLimitCalculator, timestamper, specProvider, logManager, difficultyCalculator, blocksConfig) + { + this.epochSwitchManager = epochSwitchManager; + this.snapshotManager = snapshotManager; + this.xdcContext = xdcContext; + this.sealer = sealer; + this.specProvider = specProvider; + this.logManager = logManager; + } + + protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes? payloadAttributes) + { + if (parent is not XdcBlockHeader xdcParent) + throw new ArgumentException("Only XDC header are supported."); + + QuorumCertificate highestCert = xdcContext.HighestQC; + var currentRound = xdcContext.CurrentRound; + + //TODO maybe some sanity checks here for round and hash + + byte[] extra = [2, .._extraConsensusDataDecoder.Encode(new ExtraFieldsV2(currentRound, highestCert)).Bytes]; + + Address blockAuthor = sealer.Address; + XdcBlockHeader xdcBlockHeader = new( + parent.Hash!, + Keccak.OfAnEmptySequenceRlp, + blockAuthor, + UInt256.Zero, + parent.Number + 1, + XdcConstants.TargetGasLimit, + 0, + extra) + { + Author = blockAuthor, + }; + + IXdcReleaseSpec spec = specProvider.GetXdcSpec(xdcBlockHeader, currentRound); + + xdcBlockHeader.Timestamp = parent.Timestamp + (ulong)spec.MinePeriod; + + parent.Difficulty = 1; + + xdcBlockHeader.BaseFeePerGas = BaseFeeCalculator.Calculate(parent, spec); + + if (epochSwitchManager.IsEpochSwitch(xdcBlockHeader, out _)) + { + (Address[] masternodes, Address[] penalties) = snapshotManager.CalculateNextEpochMasternodes(xdcBlockHeader, spec); + + xdcBlockHeader.Validators = new byte[masternodes.Length * Address.Size]; + + for (int i = 0; i < masternodes.Length; i++) + { + Array.Copy(masternodes[i].Bytes, 0, xdcBlockHeader.Validators, i * Address.Size, Address.Size); + } + + xdcBlockHeader.Penalties = new byte[penalties.Length * Address.Size]; + + for (int i = 0; i < penalties.Length; i++) + { + Array.Copy(penalties[i].Bytes, 0, xdcBlockHeader.Penalties, i * Address.Size, Address.Size); + } + } + return xdcBlockHeader; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcConstants.cs b/src/Nethermind/Nethermind.Xdc/XdcConstants.cs index b12d5aa348f2..a8242b5497c8 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcConstants.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcConstants.cs @@ -26,6 +26,8 @@ internal static class XdcConstants public static readonly int InMemoryRound2Epochs = 65536; // One epoch ~ 0.5h, 65536 epochs ~ 3.7y, ~10MB memory + public const long TargetGasLimit = 84000000; // XDC default gas limit per block + // --- Compile-time constants --- public const int InMemorySnapshots = 128; // Number of recent vote snapshots to keep in memory public const int BlockSignersCacheLimit = 9000; From 6fe8a955f514c926acd7de3308109f0b2f4cb0c1 Mon Sep 17 00:00:00 2001 From: ak88 Date: Mon, 20 Oct 2025 16:41:55 +0200 Subject: [PATCH 02/11] format --- src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs index ff852115bcc2..4ac5183effa9 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs @@ -55,7 +55,7 @@ protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAtt //TODO maybe some sanity checks here for round and hash - byte[] extra = [2, .._extraConsensusDataDecoder.Encode(new ExtraFieldsV2(currentRound, highestCert)).Bytes]; + byte[] extra = [2, .. _extraConsensusDataDecoder.Encode(new ExtraFieldsV2(currentRound, highestCert)).Bytes]; Address blockAuthor = sealer.Address; XdcBlockHeader xdcBlockHeader = new( From 0bc715269bf49957e4591b56fe29a06912b97d7c Mon Sep 17 00:00:00 2001 From: ak88 Date: Tue, 21 Oct 2025 14:38:33 +0200 Subject: [PATCH 03/11] test --- .../Helpers/XdcTestHelper.cs | 45 +++++++++++ .../QuorumCertificateManagerTest.cs | 37 ++------- .../XdcBlockProducerTest.cs | 77 +++++++++++++++++++ 3 files changed, 129 insertions(+), 30 deletions(-) create mode 100644 src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs create mode 100644 src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs new file mode 100644 index 000000000000..1455135ebba1 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; +internal class XdcTestHelper +{ + public static PrivateKey[] GeneratePrivateKeys(int count) + { + var keyBuilder = new PrivateKeyGenerator(); + return keyBuilder.Generate(count).ToArray(); + } + + public static QuorumCertificate CreateQc(BlockRoundInfo roundInfo, ulong gapNumber, PrivateKey[] keys) + { + EthereumEcdsa ecdsa = new EthereumEcdsa(0); + var qcEncoder = new VoteDecoder(); + + IEnumerable signatures = CreateVoteSignatures(roundInfo, gapNumber, keys); + + return new QuorumCertificate(roundInfo, signatures.ToArray(), gapNumber); + } + + public static Signature[] CreateVoteSignatures(BlockRoundInfo roundInfo, ulong gapnumber, PrivateKey[] keys) + { + EthereumEcdsa ecdsa = new EthereumEcdsa(0); + var encoder = new VoteDecoder(); + IEnumerable signatures = keys.Select(k => + { + var stream = new KeccakRlpStream(); + encoder.Encode(stream, new Vote(roundInfo, gapnumber), RlpBehaviors.ForSealing); + return ecdsa.Sign(k, stream.GetValueHash()); + }).ToArray(); + return signatures.ToArray(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateManagerTest.cs b/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateManagerTest.cs index 124cf3fa07d0..e8c6d762ab4d 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateManagerTest.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateManagerTest.cs @@ -55,25 +55,25 @@ public static IEnumerable QcCases() //Base valid control case PrivateKey[] keys = keyBuilder.Generate(20).ToArray(); IEnumerable
masterNodes = keys.Select(k => k.Address); - yield return new TestCaseData(CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys), headerBuilder, keys.Select(k => k.Address), true); + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys), headerBuilder, keys.Select(k => k.Address), true); //Not enough signatures - yield return new TestCaseData(CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys.Take(13).ToArray()), headerBuilder, keys.Select(k => k.Address), false); + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys.Take(13).ToArray()), headerBuilder, keys.Select(k => k.Address), false); //1 Vote is not master node - yield return new TestCaseData(CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys), headerBuilder, keys.Skip(1).Select(k => k.Address), false); + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys), headerBuilder, keys.Skip(1).Select(k => k.Address), false); //Wrong gap number - yield return new TestCaseData(CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 1, keys), headerBuilder, masterNodes, false); + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 1, keys), headerBuilder, masterNodes, false); //Wrong block number in QC - yield return new TestCaseData(CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 2), 0, keys), headerBuilder, masterNodes, false); + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 2), 0, keys), headerBuilder, masterNodes, false); //Wrong hash in QC - yield return new TestCaseData(CreateQc(new BlockRoundInfo(Hash256.Zero, 1, 1), 0, keys), headerBuilder, masterNodes, false); + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(Hash256.Zero, 1, 1), 0, keys), headerBuilder, masterNodes, false); //Wrong round number in QC - yield return new TestCaseData(CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 0, 1), 0, keys), headerBuilder, masterNodes, false); + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 0, 1), 0, keys), headerBuilder, masterNodes, false); } [TestCaseSource(nameof(QcCases))] @@ -98,27 +98,4 @@ public void VerifyCertificate_QcWithDifferentParameters_ReturnsExpected(QuorumCe Assert.That(quorumCertificateManager.VerifyCertificate(quorumCert, xdcBlockHeaderBuilder.TestObject, out _), Is.EqualTo(expected)); } - - private static QuorumCertificate CreateQc(BlockRoundInfo roundInfo, ulong gapNumber, PrivateKey[] keys) - { - EthereumEcdsa ecdsa = new EthereumEcdsa(0); - var qcEncoder = new VoteDecoder(); - - IEnumerable signatures = CreateVoteSignatures(roundInfo, gapNumber, keys); - - return new QuorumCertificate(roundInfo, signatures.ToArray(), gapNumber); - } - - private static Signature[] CreateVoteSignatures(BlockRoundInfo roundInfo, ulong gapnumber, PrivateKey[] keys) - { - EthereumEcdsa ecdsa = new EthereumEcdsa(0); - var encoder = new VoteDecoder(); - IEnumerable signatures = keys.Select(k => - { - var stream = new KeccakRlpStream(); - encoder.Encode(stream, new Vote(roundInfo, gapnumber), RlpBehaviors.ForSealing); - return ecdsa.Sign(k, stream.GetValueHash()); - }).ToArray(); - return signatures.ToArray(); - } } diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs new file mode 100644 index 000000000000..5aff8e61a171 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Transactions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Logging; +using Nethermind.Xdc.Spec; +using NSubstitute; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; +internal class XdcBlockProducerTest +{ + [Test] + public async Task SampleTest() + { + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + xdcReleaseSpec.MinePeriod.Returns(2); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + + var epochManager = Substitute.For(); + ISealer sealer = Substitute.For(); + sealer.CanSeal(Arg.Any(), Arg.Any()).Returns(true); + sealer.Address.Returns(TestItem.AddressA); + + IWorldState stateProvider = Substitute.For(); + stateProvider.HasStateForBlock(Arg.Any()).Returns(true); + + PrivateKey[] masterNodes = XdcTestHelper.GeneratePrivateKeys(108); + epochManager + .GetEpochSwitchInfo(Arg.Any(), Arg.Any()) + .Returns(new Types.EpochSwitchInfo(masterNodes.Select(m=>m.Address).ToArray(), [], new Types.BlockRoundInfo(Hash256.Zero, 0, 0))); + + XdcBlockHeader parent = Build.A.XdcBlockHeader().TestObject; + + var xdcContext = new XdcContext(); + xdcContext.CurrentRound = 1; + xdcContext.HighestQC = XdcTestHelper.CreateQc(new Types.BlockRoundInfo(parent.Hash!, 0, parent.Number), 0, masterNodes); + + IBlockchainProcessor processor = Substitute.For(); + processor.Process(Arg.Any(), Arg.Any(), Arg.Any()).Returns(args => args.ArgAt(0)); + + XdcBlockProducer producer = new XdcBlockProducer( + epochManager, + Substitute.For(), + xdcContext, + Substitute.For(), + processor, + sealer, + Substitute.For(), + stateProvider, + Substitute.For(), + Substitute.For(), + specProvider, + Substitute.For(), + Substitute.For(), + Substitute.For()); + + Block? block = await producer.BuildBlock(parent); + } +} From 4e63d9ee84538e5f3b131cc9884e1b4dc998661d Mon Sep 17 00:00:00 2001 From: ak88 Date: Tue, 21 Oct 2025 16:04:25 +0200 Subject: [PATCH 04/11] block production test --- .../Helpers/XdcBlockHeaderBuilder.cs | 2 +- .../Nethermind.Xdc.Test/XdcBlockProducerTest.cs | 14 +++++++++----- src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs | 5 ++++- src/Nethermind/Nethermind.Xdc/XdcConstants.cs | 2 ++ .../Nethermind.Xdc/XdcHeaderValidator.cs | 2 +- src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs | 6 ++++-- src/Nethermind/Nethermind.Xdc/XdcSealer.cs | 2 ++ 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcBlockHeaderBuilder.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcBlockHeaderBuilder.cs index fa9bdc05a386..08c73ddd43e8 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcBlockHeaderBuilder.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcBlockHeaderBuilder.cs @@ -29,7 +29,7 @@ public XdcBlockHeaderBuilder() Address.Zero, UInt256.One, 1, - 30_000_000, + XdcConstants.TargetGasLimit, 1_700_000_000, new byte[] { 1, 2, 3 }) { diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs index 5aff8e61a171..e8ccbfabee99 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs @@ -32,13 +32,10 @@ public async Task SampleTest() ISpecProvider specProvider = Substitute.For(); IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); xdcReleaseSpec.MinePeriod.Returns(2); + xdcReleaseSpec.EpochLength.Returns(900); + xdcReleaseSpec.GasLimitBoundDivisor.Returns(1); specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); - var epochManager = Substitute.For(); - ISealer sealer = Substitute.For(); - sealer.CanSeal(Arg.Any(), Arg.Any()).Returns(true); - sealer.Address.Returns(TestItem.AddressA); - IWorldState stateProvider = Substitute.For(); stateProvider.HasStateForBlock(Arg.Any()).Returns(true); @@ -47,6 +44,8 @@ public async Task SampleTest() .GetEpochSwitchInfo(Arg.Any(), Arg.Any()) .Returns(new Types.EpochSwitchInfo(masterNodes.Select(m=>m.Address).ToArray(), [], new Types.BlockRoundInfo(Hash256.Zero, 0, 0))); + ISealer sealer = new XdcSealer(new Signer(0, new ProtectedPrivateKey(masterNodes[1], ""), NullLogManager.Instance)); + XdcBlockHeader parent = Build.A.XdcBlockHeader().TestObject; var xdcContext = new XdcContext(); @@ -71,7 +70,12 @@ public async Task SampleTest() Substitute.For(), Substitute.For(), Substitute.For()); + XdcHeaderValidator headerValidator = new XdcHeaderValidator(Substitute.For(), new XdcSealValidator(Substitute.For(), epochManager, specProvider), specProvider, NullLogManager.Instance); Block? block = await producer.BuildBlock(parent); + + Assert.That(block, Is.Not.Null); + bool actual = headerValidator.Validate(block.Header, parent, false, out string? error); + Assert.That(actual, Is.True); } } diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs index 4ac5183effa9..eb669145e3e2 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs @@ -75,10 +75,13 @@ protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAtt xdcBlockHeader.Timestamp = parent.Timestamp + (ulong)spec.MinePeriod; - parent.Difficulty = 1; + xdcBlockHeader.Difficulty = 1; + xdcBlockHeader.TotalDifficulty = 1; xdcBlockHeader.BaseFeePerGas = BaseFeeCalculator.Calculate(parent, spec); + xdcBlockHeader.MixHash = Hash256.Zero; + if (epochSwitchManager.IsEpochSwitch(xdcBlockHeader, out _)) { (Address[] masternodes, Address[] penalties) = snapshotManager.CalculateNextEpochMasternodes(xdcBlockHeader, spec); diff --git a/src/Nethermind/Nethermind.Xdc/XdcConstants.cs b/src/Nethermind/Nethermind.Xdc/XdcConstants.cs index f3e3efa570aa..c1936b13a9b6 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcConstants.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcConstants.cs @@ -30,6 +30,8 @@ internal static class XdcConstants public const byte ConsensusVersion = 0x02; + public const int GasLimitBoundDivisor = 1024; // The bound divisor of gas limit adjustment per block + // --- Compile-time constants --- public const int InMemorySnapshots = 128; // Number of recent vote snapshots to keep in memory public const int BlockSignersCacheLimit = 9000; diff --git a/src/Nethermind/Nethermind.Xdc/XdcHeaderValidator.cs b/src/Nethermind/Nethermind.Xdc/XdcHeaderValidator.cs index a27f13914255..d5ed9f1c5cc8 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcHeaderValidator.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcHeaderValidator.cs @@ -66,7 +66,7 @@ protected override bool Validate(BlockHeader header, BlockHeader? par protected override bool ValidateSeal(BlockHeader header, BlockHeader parent, bool isUncle, ref string? error) { if (_sealValidator is XdcSealValidator xdcSealValidator) - return xdcSealValidator.ValidateParams(header, parent, out error); + return xdcSealValidator.ValidateParams(parent, header, out error); if (!_sealValidator.ValidateParams(parent, header, isUncle)) { diff --git a/src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs b/src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs index f39a2f880460..8cdbe648325a 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs @@ -87,12 +87,14 @@ public bool ValidateParams(BlockHeader parent, BlockHeader header, out string er } else { - if (xdcHeader.Validators?.Length != 0) + if (xdcHeader.Validators is not null && + xdcHeader.Validators.Length != 0) { error = "Validators are not empty in non-epoch switch header."; return false; } - if (xdcHeader.Penalties?.Length != 0) + if (xdcHeader.Penalties is not null && + xdcHeader.Penalties?.Length != 0) { error = "Penalties are not empty in non-epoch switch header."; return false; diff --git a/src/Nethermind/Nethermind.Xdc/XdcSealer.cs b/src/Nethermind/Nethermind.Xdc/XdcSealer.cs index a08026a7dd84..49ce730e2459 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcSealer.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcSealer.cs @@ -34,6 +34,8 @@ public Task SealBlock(Block block, CancellationToken cancellationToken) KeccakRlpStream hashStream = new KeccakRlpStream(); _xdcHeaderDecoder.Encode(hashStream, xdcBlockHeader, RlpBehaviors.ForSealing); xdcBlockHeader.Validator = signer.Sign(hashStream.GetValueHash()).BytesWithRecovery; + + xdcBlockHeader.Hash = xdcBlockHeader.CalculateHash().ToHash256(); return Task.FromResult(block); } } From fda2b9b4f164c0f75dfb07f397489e922ca311e9 Mon Sep 17 00:00:00 2001 From: ak88 Date: Tue, 21 Oct 2025 16:12:45 +0200 Subject: [PATCH 05/11] format --- src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs index e8ccbfabee99..18fe7fc04d36 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs @@ -42,7 +42,7 @@ public async Task SampleTest() PrivateKey[] masterNodes = XdcTestHelper.GeneratePrivateKeys(108); epochManager .GetEpochSwitchInfo(Arg.Any(), Arg.Any()) - .Returns(new Types.EpochSwitchInfo(masterNodes.Select(m=>m.Address).ToArray(), [], new Types.BlockRoundInfo(Hash256.Zero, 0, 0))); + .Returns(new Types.EpochSwitchInfo(masterNodes.Select(m => m.Address).ToArray(), [], new Types.BlockRoundInfo(Hash256.Zero, 0, 0))); ISealer sealer = new XdcSealer(new Signer(0, new ProtectedPrivateKey(masterNodes[1], ""), NullLogManager.Instance)); From c80df36dd9787ed95f9220ca44a429be8f74a1a4 Mon Sep 17 00:00:00 2001 From: ak88 Date: Thu, 23 Oct 2025 15:48:28 +0200 Subject: [PATCH 06/11] use the constant --- src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs index eb669145e3e2..8730acf9f03f 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs @@ -55,7 +55,7 @@ protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAtt //TODO maybe some sanity checks here for round and hash - byte[] extra = [2, .. _extraConsensusDataDecoder.Encode(new ExtraFieldsV2(currentRound, highestCert)).Bytes]; + byte[] extra = [XdcConstants.ConsensusVersion, .. _extraConsensusDataDecoder.Encode(new ExtraFieldsV2(currentRound, highestCert)).Bytes]; Address blockAuthor = sealer.Address; XdcBlockHeader xdcBlockHeader = new( @@ -64,6 +64,7 @@ protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAtt blockAuthor, UInt256.Zero, parent.Number + 1, + //This should probably use TargetAdjustedGasLimitCalculator XdcConstants.TargetGasLimit, 0, extra) From 83e7fbd2a941a86f2b8b910950bfed08b99f0715 Mon Sep 17 00:00:00 2001 From: ak88 Date: Thu, 23 Oct 2025 16:21:17 +0200 Subject: [PATCH 07/11] use timestamp from attributes --- src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs index 8730acf9f03f..64af0bad6b4c 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs @@ -45,7 +45,7 @@ public XdcBlockProducer(IEpochSwitchManager epochSwitchManager, ISnapshotManager this.logManager = logManager; } - protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes? payloadAttributes) + protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes payloadAttributes) { if (parent is not XdcBlockHeader xdcParent) throw new ArgumentException("Only XDC header are supported."); @@ -74,7 +74,7 @@ protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAtt IXdcReleaseSpec spec = specProvider.GetXdcSpec(xdcBlockHeader, currentRound); - xdcBlockHeader.Timestamp = parent.Timestamp + (ulong)spec.MinePeriod; + xdcBlockHeader.Timestamp = payloadAttributes.Timestamp; xdcBlockHeader.Difficulty = 1; xdcBlockHeader.TotalDifficulty = 1; From f746609eb2513f31bbee462d7be2925a7b85aee7 Mon Sep 17 00:00:00 2001 From: ak88 Date: Thu, 23 Oct 2025 17:10:45 +0200 Subject: [PATCH 08/11] default for timestamp --- src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs index 64af0bad6b4c..03e9cc93312d 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs @@ -74,7 +74,7 @@ protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAtt IXdcReleaseSpec spec = specProvider.GetXdcSpec(xdcBlockHeader, currentRound); - xdcBlockHeader.Timestamp = payloadAttributes.Timestamp; + xdcBlockHeader.Timestamp = payloadAttributes?.Timestamp ?? parent.Timestamp + (ulong)spec.MinePeriod; xdcBlockHeader.Difficulty = 1; xdcBlockHeader.TotalDifficulty = 1; From 42ced141c84727ba555cce6798179a5e532bfb2f Mon Sep 17 00:00:00 2001 From: ak88 Date: Fri, 24 Oct 2025 12:39:46 +0200 Subject: [PATCH 09/11] name --- src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs index 18fe7fc04d36..7ce8b3e26bd4 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs @@ -17,17 +17,14 @@ using Nethermind.Xdc.Spec; using NSubstitute; using NUnit.Framework; -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Nethermind.Xdc.Test; internal class XdcBlockProducerTest { [Test] - public async Task SampleTest() + public async Task BuildBlock_HasCorrectQC_ProducesValidHeader() { ISpecProvider specProvider = Substitute.For(); IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); From 8a3745a79f60e4df6b3eaf1bb5302b0af9237a12 Mon Sep 17 00:00:00 2001 From: ak88 Date: Fri, 24 Oct 2025 12:54:38 +0200 Subject: [PATCH 10/11] merged --- src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs index 7ce8b3e26bd4..14abbf3656b4 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs @@ -38,8 +38,8 @@ public async Task BuildBlock_HasCorrectQC_ProducesValidHeader() PrivateKey[] masterNodes = XdcTestHelper.GeneratePrivateKeys(108); epochManager - .GetEpochSwitchInfo(Arg.Any(), Arg.Any()) - .Returns(new Types.EpochSwitchInfo(masterNodes.Select(m => m.Address).ToArray(), [], new Types.BlockRoundInfo(Hash256.Zero, 0, 0))); + .GetEpochSwitchInfo(Arg.Any()) + .Returns(new Types.EpochSwitchInfo(masterNodes.Select(m => m.Address).ToArray(), [], [], new Types.BlockRoundInfo(Hash256.Zero, 0, 0))); ISealer sealer = new XdcSealer(new Signer(0, new ProtectedPrivateKey(masterNodes[1], ""), NullLogManager.Instance)); From 9cd3e4d43c7ad5fcc67649b0db8a9fc03bc365d8 Mon Sep 17 00:00:00 2001 From: ak88 Date: Fri, 24 Oct 2025 13:00:13 +0200 Subject: [PATCH 11/11] merge fix --- src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs index 03e9cc93312d..eefdcb9f9be1 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs @@ -83,7 +83,7 @@ protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAtt xdcBlockHeader.MixHash = Hash256.Zero; - if (epochSwitchManager.IsEpochSwitch(xdcBlockHeader, out _)) + if (epochSwitchManager.IsEpochSwitchAtBlock(xdcBlockHeader)) { (Address[] masternodes, Address[] penalties) = snapshotManager.CalculateNextEpochMasternodes(xdcBlockHeader, spec);