Skip to content
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
29 changes: 29 additions & 0 deletions src/Nethermind/Nethermind.Xdc.Test/SnapshotManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using FluentAssertions;
using Nethermind.Blockchain;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.Core.Test.Builders;
using Nethermind.Db;
Expand All @@ -12,6 +13,7 @@
using Nethermind.Xdc.Types;
using NSubstitute;
using NUnit.Framework;
using System;

namespace Nethermind.Xdc.Test;

Expand Down Expand Up @@ -174,4 +176,31 @@ public void NewHeadBlock_(int gapNumber)
blockTree.NewHeadBlock += Raise.EventWith(new BlockEventArgs(new Block(header)));
snapshotManager.GetSnapshotByGapNumber(header.Number)!.HeaderHash.Should().Be(header.Hash!);
}

[Test]
public void CalculateNextEpochMasternodes_AllPenalized_Throws()
{
IXdcReleaseSpec releaseSpec = Substitute.For<IXdcReleaseSpec>();
releaseSpec.EpochLength.Returns(900);
releaseSpec.Gap.Returns(450);
releaseSpec.MaxMasternodes.Returns(100);
releaseSpec.SwitchBlock.Returns(0);

IPenaltyHandler penaltyHandler = Substitute.For<IPenaltyHandler>();
IBlockTree blockTree = Substitute.For<IBlockTree>();
ISpecProvider specProvider = Substitute.For<ISpecProvider>();
IMasternodeVotingContract votingContract = Substitute.For<IMasternodeVotingContract>();
SnapshotManager snapshotManager = new SnapshotManager(new MemDb(), blockTree, penaltyHandler, votingContract, specProvider);

XdcBlockHeader header = Build.A.XdcBlockHeader().WithNumber(0).TestObject;
blockTree.FindHeader(0).Returns(header);
Snapshot snapshot = new Snapshot(0, header.Hash!, new[] { TestItem.AddressA, TestItem.AddressB });
snapshotManager.StoreSnapshot(snapshot);

penaltyHandler.HandlePenalties(Arg.Any<long>(), Arg.Any<Hash256>(), Arg.Any<Address[]>())
.Returns(snapshot.NextEpochCandidates);

Assert.Throws<InvalidOperationException>(() =>
snapshotManager.CalculateNextEpochMasternodes(2, header.ParentHash ?? Hash256.Zero, releaseSpec));
}
}
64 changes: 64 additions & 0 deletions src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,70 @@ public void ValidateParams_HeaderHasDifferentSealParameters_ReturnsExpected(XdcB
Assert.That(validator.ValidateParams(parent, header), Is.EqualTo(expected));
}

[Test]
public void ValidateParams_MissingEpochInfo_ReturnsFalse()
{
XdcBlockHeader parent = Build.A.XdcBlockHeader().TestObject;

PrivateKeyGenerator keyBuilder = new PrivateKeyGenerator();
PrivateKey[] masterSigners = Enumerable.Range(0, 3).Select(_ => keyBuilder.Generate()).ToArray();
QuorumCertificate qc = CreateQc(new BlockRoundInfo(Hash256.Zero, 1, 1), masterSigners, 1);
ExtraFieldsV2 extraFields = new ExtraFieldsV2(2, qc);

XdcBlockHeaderBuilder headerBuilder = Build.A.XdcBlockHeader();
headerBuilder.WithParent(parent);
headerBuilder.WithExtraFieldsV2(extraFields);
XdcBlockHeader header = headerBuilder.TestObject;
header.Validators = Array.Empty<byte>();
header.Penalties = Array.Empty<byte>();

IXdcReleaseSpec releaseSpec = Substitute.For<IXdcReleaseSpec>();
releaseSpec.EpochLength.Returns(900);
ISpecProvider specProvider = Substitute.For<ISpecProvider>();
specProvider.GetSpec(Arg.Any<ForkActivation>()).Returns(releaseSpec);

IEpochSwitchManager epochSwitchManager = Substitute.For<IEpochSwitchManager>();
epochSwitchManager.IsEpochSwitchAtBlock(header).Returns(false);
epochSwitchManager.GetEpochSwitchInfo(header).Returns((EpochSwitchInfo?)null);

XdcSealValidator validator = new XdcSealValidator(Substitute.For<ISnapshotManager>(), epochSwitchManager, specProvider);

Assert.That(validator.ValidateParams(parent, header, out var error), Is.False);
Assert.That(error, Does.Contain("Epoch switch info"));
}

[Test]
public void ValidateParams_EmptyMasternodes_ReturnsFalse()
{
XdcBlockHeader parent = Build.A.XdcBlockHeader().TestObject;

PrivateKeyGenerator keyBuilder = new PrivateKeyGenerator();
PrivateKey[] masterSigners = Enumerable.Range(0, 3).Select(_ => keyBuilder.Generate()).ToArray();
QuorumCertificate qc = CreateQc(new BlockRoundInfo(Hash256.Zero, 1, 1), masterSigners, 1);
ExtraFieldsV2 extraFields = new ExtraFieldsV2(2, qc);

XdcBlockHeaderBuilder headerBuilder = Build.A.XdcBlockHeader();
headerBuilder.WithParent(parent);
headerBuilder.WithExtraFieldsV2(extraFields);
XdcBlockHeader header = headerBuilder.TestObject;
header.Validators = Array.Empty<byte>();
header.Penalties = Array.Empty<byte>();

IXdcReleaseSpec releaseSpec = Substitute.For<IXdcReleaseSpec>();
releaseSpec.EpochLength.Returns(900);
ISpecProvider specProvider = Substitute.For<ISpecProvider>();
specProvider.GetSpec(Arg.Any<ForkActivation>()).Returns(releaseSpec);

IEpochSwitchManager epochSwitchManager = Substitute.For<IEpochSwitchManager>();
epochSwitchManager.IsEpochSwitchAtBlock(header).Returns(false);
epochSwitchManager.GetEpochSwitchInfo(header).Returns(new EpochSwitchInfo(Array.Empty<Address>(), [], [], new BlockRoundInfo(Hash256.Zero, 1, 1)));

XdcSealValidator validator = new XdcSealValidator(Substitute.For<ISnapshotManager>(), epochSwitchManager, specProvider);

Assert.That(validator.ValidateParams(parent, header, out var error), Is.False);
Assert.That(error, Does.Contain("Snapshot returned no master nodes"));
}

private static QuorumCertificate CreateQc(BlockRoundInfo roundInfo, PrivateKey[] keys, ulong gapNumber)
{
EthereumEcdsa ecdsa = new EthereumEcdsa(0);
Expand Down
11 changes: 11 additions & 0 deletions src/Nethermind/Nethermind.Xdc/SnapshotManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@ public void StoreSnapshot(Snapshot snapshot)
if (candidates.Length > maxMasternodes)
{
Array.Resize(ref candidates, maxMasternodes);
EnsureMasternodesAvailable(candidates, blockNumber);
return (candidates, []);
}

EnsureMasternodesAvailable(candidates, blockNumber);
return (candidates, []);
}

Expand All @@ -112,9 +114,18 @@ public void StoreSnapshot(Snapshot snapshot)
.Take(maxMasternodes) // enforce max cap
.ToArray();

EnsureMasternodesAvailable(candidates, blockNumber);
return (candidates, penalties);
}

private static void EnsureMasternodesAvailable(Address[] masternodes, long blockNumber)
{
if (masternodes.Length == 0)
{
throw new InvalidOperationException($"No masternodes available for block #{blockNumber} after applying penalties.");
}
}

private void OnNewHeadBlock(object? sender, BlockEventArgs e)
{
UpdateMasterNodes((XdcBlockHeader)e.Block.Header);
Expand Down
7 changes: 6 additions & 1 deletion src/Nethermind/Nethermind.Xdc/XdcHotStuff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,12 @@ public Address GetLeaderAddress(XdcBlockHeader currentHead, ulong round, IXdcRel
else
{
var epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(currentHead);
currentMasternodes = epochSwitchInfo.Masternodes;
currentMasternodes = epochSwitchInfo?.Masternodes;
}

if (currentMasternodes is null || currentMasternodes.Length == 0)
{
throw new InvalidOperationException($"No masternodes available for leader selection at round {round}.");
}

int currentLeaderIndex = ((int)round % spec.EpochLength % currentMasternodes.Length);
Expand Down
13 changes: 11 additions & 2 deletions src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,19 @@ public bool ValidateParams(BlockHeader parent, BlockHeader header, out string er
return false;
}
//TODO get masternodes from snapshot
EpochSwitchInfo epochSwitchInfo = epochSwitchManager.GetEpochSwitchInfo(xdcHeader);
EpochSwitchInfo? epochSwitchInfo = epochSwitchManager.GetEpochSwitchInfo(xdcHeader);
if (epochSwitchInfo is null)
{
error = "Epoch switch info not found for header.";
return false;
}

masternodes = epochSwitchInfo.Masternodes;
if (masternodes is null || masternodes.Length == 0)
throw new InvalidOperationException($"Snap shot returned no master nodes for header \n{xdcHeader.ToString()}");
{
error = $"Snapshot returned no master nodes for header \n{xdcHeader}";
return false;
}
}

ulong currentLeaderIndex = (xdcHeader.ExtraConsensusData.BlockRound % (ulong)xdcSpec.EpochLength % (ulong)masternodes.Length);
Expand Down