Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.Core.Test.Builders;
using Nethermind.Consensus;
using Nethermind.Crypto;
using Nethermind.Xdc.RLP;
using Nethermind.Xdc.Spec;
using Nethermind.Xdc.Types;

Expand Down Expand Up @@ -47,10 +47,13 @@ public void VerifyTC_SnapshotMissing_ReturnsFalse()
XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject;
blockTree.FindHeader(Arg.Any<long>()).Returns(header);
var tcManager = new TimeoutCertificateManager(
new XdcContext(),
snapshotManager,
Substitute.For<IEpochSwitchManager>(),
Substitute.For<ISpecProvider>(),
blockTree);
blockTree,
Substitute.For<ISyncInfoManager>(),
Substitute.For<ISigner>());

var ok = tcManager.VerifyTimeoutCertificate(tc, out var err);
Assert.That(ok, Is.False);
Expand All @@ -68,10 +71,13 @@ public void VerifyTC_EmptyCandidates_ReturnsFalse()
XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject;
blockTree.FindHeader(Arg.Any<long>()).Returns(header);
var tcManager = new TimeoutCertificateManager(
new XdcContext(),
snapshotManager,
Substitute.For<IEpochSwitchManager>(),
Substitute.For<ISpecProvider>(),
blockTree);
blockTree,
Substitute.For<ISyncInfoManager>(),
Substitute.For<ISigner>());

var ok = tcManager.VerifyTimeoutCertificate(tc, out var err);
Assert.That(ok, Is.False);
Expand Down Expand Up @@ -128,18 +134,26 @@ public void VerifyTCWithDifferentParameters_ReturnsExpected(TimeoutCertificate t
blockTree.Head.Returns(new Block(header, new BlockBody()));
blockTree.FindHeader(Arg.Any<long>()).Returns(header);

var tcManager = new TimeoutCertificateManager(snapshotManager, epochSwitchManager, specProvider, blockTree);
var context = new XdcContext();
ISyncInfoManager syncInfoManager = Substitute.For<ISyncInfoManager>();
ISigner signer = Substitute.For<ISigner>();

var tcManager = new TimeoutCertificateManager(context, snapshotManager, epochSwitchManager, specProvider,
blockTree, syncInfoManager, signer);

Assert.That(tcManager.VerifyTimeoutCertificate(timeoutCertificate, out _), Is.EqualTo(expected));
}

private TimeoutCertificateManager BuildTimeoutCertificateManager()
{
return new TimeoutCertificateManager(
new XdcContext(),
Substitute.For<ISnapshotManager>(),
Substitute.For<IEpochSwitchManager>(),
Substitute.For<ISpecProvider>(),
Substitute.For<IBlockTree>());
Substitute.For<IBlockTree>(),
Substitute.For<ISyncInfoManager>(),
Substitute.For<ISigner>());
}

private static TimeoutCertificate BuildTimeoutCertificate(PrivateKey[] keys, ulong round = 1, ulong gap = 0)
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Xdc/ISyncInfoManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Threading.Tasks;

namespace Nethermind.Xdc;
internal interface ISyncInfoManager
public interface ISyncInfoManager
{
void ProcessSyncInfo(SyncInfo syncInfo);
bool VerifySyncInfo(SyncInfo syncInfo);
Expand Down
7 changes: 4 additions & 3 deletions src/Nethermind/Nethermind.Xdc/ITimeoutCertificateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Xdc.Types;
using System;
using System.Threading.Tasks;

namespace Nethermind.Xdc;
public interface ITimeoutCertificateManager
{
void HandleTimeout(Timeout timeout);
void OnCountdownTimer(DateTime time);
Task OnReceiveTimeout(Timeout timeout);
Task HandleTimeout(Timeout timeout);
void OnCountdownTimer();
void ProcessTimeoutCertificate(TimeoutCertificate timeoutCertificate);
bool VerifyTimeoutCertificate(TimeoutCertificate timeoutCertificate, out string errorMessage);
}
11 changes: 11 additions & 0 deletions src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ public void Encode(RlpStream stream, Vote item, RlpBehaviors rlpBehaviors = RlpB
stream.Encode(item.GapNumber);
}

public Rlp Encode(Vote item, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
{
if (item is null)
return Rlp.OfEmptySequence;

RlpStream rlpStream = new(GetLength(item, rlpBehaviors));
Encode(rlpStream, item, rlpBehaviors);

return new Rlp(rlpStream.Data.ToArray());
}

public int GetLength(Vote item, RlpBehaviors rlpBehaviors)
{
return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors));
Expand Down
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Nethermind.Xdc.Spec;
public class XdcReleaseSpec : ReleaseSpec, IXdcReleaseSpec
{
public int EpochLength { get; set; }
public long Gap { get; set; }
public int Gap { get; set; }
public int SwitchEpoch { get; set; }
public UInt256 SwitchBlock { get; set; }
public int MaxMasternodes { get; set; } // v2 max masternodes
Expand Down Expand Up @@ -57,7 +57,7 @@ internal static V2ConfigParams GetConfigAtRound(List<V2ConfigParams> list, ulong
public interface IXdcReleaseSpec : IReleaseSpec
{
public int EpochLength { get; }
public long Gap { get; }
public int Gap { get; }
public int SwitchEpoch { get; set; }
public UInt256 SwitchBlock { get; set; }
public int MaxMasternodes { get; set; } // v2 max masternodes
Expand Down
158 changes: 148 additions & 10 deletions src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,88 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nethermind.Blockchain;
using Nethermind.Consensus;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.Crypto;
using Nethermind.Serialization.Rlp;
using Nethermind.Xdc.RLP;
using Nethermind.Xdc.Errors;
using Nethermind.Xdc.Types;
using Nethermind.Xdc.Spec;

namespace Nethermind.Xdc;

public class TimeoutCertificateManager(ISnapshotManager snapshotManager, IEpochSwitchManager epochSwitchManager, ISpecProvider specProvider, IBlockTree blockTree) : ITimeoutCertificateManager
public class TimeoutCertificateManager(XdcContext context, ISnapshotManager snapshotManager, IEpochSwitchManager epochSwitchManager, ISpecProvider specProvider, IBlockTree blockTree, ISyncInfoManager syncInfoManager, ISigner signer) : ITimeoutCertificateManager
{
private XdcContext _ctx = context;
private ISnapshotManager _snapshotManager = snapshotManager;
private IEpochSwitchManager _epochSwitchManager = epochSwitchManager;
private ISpecProvider _specProvider = specProvider;
private IBlockTree _blockTree = blockTree;
private ISyncInfoManager _syncInfoManager = syncInfoManager;
private ISigner _signer = signer;

private EthereumEcdsa _ethereumEcdsa = new EthereumEcdsa(0);
private static readonly TimeoutDecoder _timeoutDecoder = new();
private XdcPool<Timeout> _timeouts = new();

public void HandleTimeout(Timeout timeout)
public Task HandleTimeout(Timeout timeout)
{
throw new NotImplementedException();
if (timeout.Round != _ctx.CurrentRound)
{
// Not interested in processing timeout for round different from the current one
return Task.CompletedTask;
}

var count = _timeouts.Add(timeout);
var collectedTimeouts = _timeouts.GetItems(timeout);

var xdcHeader = _blockTree.Head?.Header as XdcBlockHeader;
EpochSwitchInfo epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(xdcHeader, xdcHeader.Hash);
if (epochSwitchInfo is null)
{
// Failed to get epoch switch info, cannot process timeout
return Task.CompletedTask;
}

IXdcReleaseSpec spec = _specProvider.GetXdcSpec(xdcHeader, timeout.Round);
var certThreshold = spec.CertThreshold;
if (count >= epochSwitchInfo.Masternodes.Length * certThreshold)
{
OnTimeoutPoolThresholdReached(collectedTimeouts, timeout);
}
return Task.CompletedTask;
}

public void OnCountdownTimer(DateTime time)
private void OnTimeoutPoolThresholdReached(IEnumerable<Timeout> timeouts, Timeout timeout)
{
throw new NotImplementedException();
Signature[] signatures = timeouts.Select(t => t.Signature).ToArray();

var timeoutCertificate = new TimeoutCertificate(timeout.Round, signatures, timeout.GapNumber);

ProcessTimeoutCertificate(timeoutCertificate);

SyncInfo syncInfo = _syncInfoManager.GetSyncInfo();
Comment thread
cicr99 marked this conversation as resolved.
//TODO: Broadcast syncInfo
}

public void ProcessTimeoutCertificate(TimeoutCertificate timeoutCertificate)
{
throw new NotImplementedException();
if (timeoutCertificate.Round > _ctx.HighestTC.Round)
{
_ctx.HighestTC = timeoutCertificate;
}

if (timeoutCertificate.Round >= _ctx.CurrentRound)
{
//TODO Check how this new round is set
_ctx.SetNewRound(_blockTree, timeoutCertificate.Round + 1);
}
}

public bool VerifyTimeoutCertificate(TimeoutCertificate timeoutCertificate, out string errorMessage)
Expand All @@ -60,9 +107,7 @@ public bool VerifyTimeoutCertificate(TimeoutCertificate timeoutCertificate, out
var nextEpochCandidates = new HashSet<Address>(snapshot.NextEpochCandidates);

var signatures = new HashSet<Signature>(timeoutCertificate.Signatures);
BlockHeader header = _blockTree.Head?.Header;
if (header is not XdcBlockHeader xdcHeader)
throw new InvalidOperationException($"Only type of {nameof(XdcBlockHeader)} is allowed");
var xdcHeader = _blockTree.Head?.Header as XdcBlockHeader;
IXdcReleaseSpec spec = _specProvider.GetXdcSpec(xdcHeader, timeoutCertificate.Round);
EpochSwitchInfo epochInfo = _epochSwitchManager.GetTimeoutCertificateEpochInfo(timeoutCertificate);
if (epochInfo is null)
Expand Down Expand Up @@ -98,11 +143,104 @@ public bool VerifyTimeoutCertificate(TimeoutCertificate timeoutCertificate, out
return true;
}

public void OnCountdownTimer()
{
if (!AllowedToSend())
return;

SendTimeout();
_ctx.TimeoutCounter++;

var xdcHeader = _blockTree.Head?.Header as XdcBlockHeader;
IXdcReleaseSpec spec = _specProvider.GetXdcSpec(xdcHeader!, _ctx.CurrentRound);

if (_ctx.TimeoutCounter % spec.TimeoutSyncThreshold == 0)
{
SyncInfo syncInfo = _syncInfoManager.GetSyncInfo();
//TODO: Broadcast syncInfo
}
}

public Task OnReceiveTimeout(Timeout timeout)
{
var currentBlock = _blockTree.Head ?? throw new InvalidOperationException("Failed to get current block");
var currentHeader = currentBlock.Header as XdcBlockHeader;
var currentBlockNumber = currentBlock.Number;
var epochLenth = _specProvider.GetXdcSpec(currentHeader, timeout.Round).EpochLength;
if (Math.Abs((long)timeout.GapNumber - currentBlockNumber) > 3 * epochLenth)
{
// Discarded propagated timeout, too far away
return Task.CompletedTask;
}

if (FilterTimeout(timeout))
{
//TODO: Broadcast Timeout
return HandleTimeout(timeout);
}
return Task.CompletedTask;
}

private bool FilterTimeout(Timeout timeout)
{
if (timeout.Round < _ctx.CurrentRound) return false;
Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber(_blockTree, timeout.GapNumber);
if (snapshot is null || snapshot.NextEpochCandidates.Length == 0) return false;

// Verify msg signature
ValueHash256 timeoutMsgHash = ComputeTimeoutMsgHash(timeout.Round, timeout.GapNumber);
Address signer = _ethereumEcdsa.RecoverAddress(timeout.Signature, in timeoutMsgHash);
timeout.Signer = signer;

return snapshot.NextEpochCandidates.Contains(signer);
}

private void SendTimeout()
{
ulong gapNumber = 0;
var currentHeader = (XdcBlockHeader)_blockTree.Head?.Header;
if (currentHeader is null) throw new InvalidOperationException("Failed to retrieve current header");
IXdcReleaseSpec spec = _specProvider.GetXdcSpec(currentHeader, _ctx.CurrentRound);
if (_epochSwitchManager.IsEpochSwitchAtRound(_ctx.CurrentRound, currentHeader, out ulong epochNumber))
{
ulong currentNumber = (ulong)currentHeader.Number + 1;
gapNumber = Math.Max(0, currentNumber - currentNumber % (ulong)spec.EpochLength - (ulong)spec.Gap);
}
else
{
EpochSwitchInfo epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(currentHeader, currentHeader.Hash);
if (epochSwitchInfo is null)
throw new ConsensusHeaderDataExtractionException(nameof(EpochSwitchInfo));

ulong currentNumber = (ulong)epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber;
gapNumber = Math.Max(0, currentNumber - currentNumber % (ulong)spec.EpochLength - (ulong)spec.Gap);
}

ValueHash256 msgHash = ComputeTimeoutMsgHash(_ctx.CurrentRound, gapNumber);
Signature signedHash = _signer.Sign(msgHash);
var timeoutMsg = new Timeout(_ctx.CurrentRound, signedHash, gapNumber);
timeoutMsg.Signer = _signer.Address;

HandleTimeout(timeoutMsg);

//TODO: Broadcast _ctx.HighestTC
}

// Returns true if the signer is within the master node list
private bool AllowedToSend()
{
var currentHeader = (XdcBlockHeader)_blockTree.Head?.Header;
EpochSwitchInfo epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(currentHeader, currentHeader.Hash);
if (epochSwitchInfo is null)
return false;
return epochSwitchInfo.Masternodes.Contains(_signer.Address);
}

internal static ValueHash256 ComputeTimeoutMsgHash(ulong round, ulong gap)
{
var timeout = new Timeout(round, null, gap);
Rlp encoded = _timeoutDecoder.Encode(timeout, RlpBehaviors.ForSealing);
return Keccak.Compute(encoded.Bytes).ValueHash256;
}
}

}
8 changes: 4 additions & 4 deletions src/Nethermind/Nethermind.Xdc/Types/Timeout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@

namespace Nethermind.Xdc.Types;

public class Timeout(ulong round, Signature? signature, ulong gapNumber)
public class Timeout(ulong round, Signature? signature, ulong gapNumber) : IXdcPoolItem
{
private Address signer;
private readonly TimeoutDecoder _decoder = new();

public ulong Round { get; set; } = round;
public Signature? Signature { get; set; } = signature;
public ulong GapNumber { get; set; } = gapNumber;
public Address? Signer { get; set; }

public override string ToString() => $"{Round}:{GapNumber}";

public void SetSigner(Address signer) => this.signer = signer;
public (ulong Round, Hash256 hash) PoolKey() => (Round, Keccak.Compute(_decoder.Encode(this, RlpBehaviors.ForSealing).Bytes));
}
2 changes: 0 additions & 2 deletions src/Nethermind/Nethermind.Xdc/Types/TimeoutCertificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Core.Crypto;
using Nethermind.Serialization.Rlp;
using Nethermind.Xdc.RLP;

namespace Nethermind.Xdc.Types;

Expand Down
8 changes: 5 additions & 3 deletions src/Nethermind/Nethermind.Xdc/Types/Vote.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Core;
using Nethermind.Core.Crypto;
using System.Text.Json.Serialization;
using RlpBehaviors = Nethermind.Serialization.Rlp.RlpBehaviors;

namespace Nethermind.Xdc.Types;

public class Vote(BlockRoundInfo proposedBlockInfo, ulong gapNumber, Signature signature = null)
public class Vote(BlockRoundInfo proposedBlockInfo, ulong gapNumber, Signature signature = null) : IXdcPoolItem
{
private readonly VoteDecoder _decoder = new();
public BlockRoundInfo ProposedBlockInfo { get; set; } = proposedBlockInfo;
public ulong GapNumber { get; set; } = gapNumber;
public Signature? Signature { get; set; } = signature;

public override string ToString() =>
$"{ProposedBlockInfo.Round}:{GapNumber}:{ProposedBlockInfo.BlockNumber}";

public (ulong Round, Hash256 hash) PoolKey() => (ProposedBlockInfo.Round, Keccak.Compute(_decoder.Encode(this, RlpBehaviors.ForSealing).Bytes));
}
Loading