Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d6d1a42
Fix block improvement check
alexb5dh Mar 5, 2026
8b14a1c
Log details about skipping vote
alexb5dh Mar 5, 2026
942b009
Improve some nullability checks
alexb5dh Mar 5, 2026
d8e7903
Use correct round number for `HighestCommitBlock`
alexb5dh Mar 5, 2026
461d9eb
Code cleanup
alexb5dh Mar 5, 2026
3bd3d6f
Fix default `HighestTC`
alexb5dh Mar 5, 2026
c23ba7d
Fix invalid header being used for voting
alexb5dh Mar 5, 2026
90f3970
Merge branch 'master' into fix/xdc-block-proposal
ak88 Mar 6, 2026
90796c2
Try alternative TD check
alexb5dh Mar 6, 2026
9b8b4ef
Simplify if-time-to-propose block
alexb5dh Mar 6, 2026
d13a0ba
Allow head-override with self-mined blocks
alexb5dh Mar 9, 2026
5f65eb9
Set highest-voted-round from head on startup
alexb5dh Mar 9, 2026
c2664e8
Revert to voting on top of head, but force-vote if enough time has pa…
alexb5dh Mar 9, 2026
cd1e8d6
Merge remote-tracking branch 'origin/master' into fix/xdc-block-proposal
alexb5dh Mar 9, 2026
e94b080
Fix non-disposed buffer
alexb5dh Mar 9, 2026
20a01d8
Fix logging "Round 0 completed"
alexb5dh Mar 9, 2026
0ea5004
Fallback comparison fix
alexb5dh Mar 9, 2026
cb892eb
Fix epoch-switch caching
alexb5dh Mar 9, 2026
97e70d1
`IsSelfMined` fix
alexb5dh Mar 9, 2026
c619ffb
Code cleanup
alexb5dh Mar 9, 2026
7d38178
Formatting
alexb5dh Mar 9, 2026
6ffdcca
Merge remote-tracking branch 'origin/master' into fix/xdc-block-proposal
alexb5dh Mar 10, 2026
c2b4253
Remove deprecated TODO
alexb5dh Mar 10, 2026
8384276
Override `IsBetterThanHead` in `XdcBlockTree`
alexb5dh Mar 11, 2026
ecc907e
hash equality base class
ak88 Mar 11, 2026
6fcba09
Fix `ArrayPoolList` disposal in `WarmupTransactions`
alexb5dh Mar 12, 2026
868d72f
Disable XDC P2P processing when syncing
alexb5dh Mar 12, 2026
820b42f
Merge branch 'fix/xdc-block-proposal' of https://github.com/nethermin…
ak88 Mar 12, 2026
7c83019
dont run when syncing and xdc payload fields
ak88 Mar 12, 2026
706c253
Merge branch 'master' into fix/xdc-block-building
ak88 Mar 12, 2026
463bc19
always vote on self build
ak88 Mar 12, 2026
058a067
adjusted log statements
ak88 Mar 12, 2026
22bd2b2
Merge branch 'master' into fix/xdc-block-building
ak88 Mar 12, 2026
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
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Blockchain/BlockTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ public void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainC
}


public bool IsBetterThanHead(BlockHeader? header) =>
public virtual bool IsBetterThanHead(BlockHeader? header) =>
header is not null // null is never better
&& ((header.IsGenesis && Genesis is null) // is genesis
|| header.TotalDifficulty >= SpecProvider.TerminalTotalDifficulty // is post-merge block, we follow engine API
Expand Down Expand Up @@ -1227,7 +1227,7 @@ protected virtual bool HeadImprovementRequirementsSatisfied(BlockHeader header)
return preMergeImprovementRequirementSatisfied || postMergeImprovementRequirementSatisfied;
}

private bool BestSuggestedImprovementRequirementsSatisfied(BlockHeader header)
protected virtual bool BestSuggestedImprovementRequirementsSatisfied(BlockHeader header)
{
if (BestSuggestedHeader is null) return true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ private void WarmupTransactions(BlockState blockState, ParallelOptions parallelO
try
{
// Convert to array for parallel iteration
ArrayPoolList<ArrayPoolList<(int Index, Transaction Tx)>> groupArray = senderGroups.Values.ToPooledList();
using ArrayPoolList<ArrayPoolList<(int Index, Transaction Tx)>> groupArray = senderGroups.Values.ToPooledList();

// Parallel across different senders, sequential within the same sender
ParallelUnbalancedWork.For(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected override void Encode(IChannelHandlerContext context, IByteBuffer input

int maxLength = Snappy.GetMaxCompressedLength(input.ReadableBytes);
output.EnsureWritable(packetLength + maxLength);
output.WriteBytes(input.ReadBytes(packetLength));
output.WriteBytes(input, packetLength);

if (_logger.IsTrace) _logger.Trace($"Compressing with Snappy a message of length {input.ReadableBytes}");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public void VerifyVotingRules_FirstChecks_ReturnsExpected(ulong currentRound, ul
var blockInfo = new BlockRoundInfo(Hash256.Zero, blockInfoRound, 100);
var qc = new QuorumCertificate(blockInfo, null, 0);

Assert.That(votesManager.VerifyVotingRules(blockInfo, qc), Is.EqualTo(expected));
Assert.That(votesManager.VerifyVotingRules(blockInfo, qc, out _), Is.EqualTo(expected));
}

[TestCase]
Expand All @@ -223,7 +223,7 @@ public async Task VerifyVotingRules_RoundWasVotedOn_ReturnsFalse()
var qc = new QuorumCertificate(blockInfo, null, 0);
await votesManager.CastVote(blockInfo);

Assert.That(votesManager.VerifyVotingRules(blockInfo, qc), Is.False);
Assert.That(votesManager.VerifyVotingRules(blockInfo, qc, out _), Is.False);
}

[Test]
Expand All @@ -236,7 +236,7 @@ public void VerifyVotingRules_QcNewerThanLockQc_ReturnsTrue()
var blockInfo = new BlockRoundInfo(Hash256.Zero, 5, 100);
var qc = new QuorumCertificate(blockInfo, null, 0);

Assert.That(votesManager.VerifyVotingRules(blockInfo, qc), Is.True);
Assert.That(votesManager.VerifyVotingRules(blockInfo, qc, out _), Is.True);
}

public static IEnumerable<TestCaseData> ExtendingFromAncestorCases()
Expand Down Expand Up @@ -271,7 +271,7 @@ public void VerifyVotingRules_CheckExtendingFromAncestor_ReturnsExpected(IBlockT
VotesManager votesManager = BuildVoteManager(ctx, tree);
var qc = new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 3, 99), null, 0);

Assert.That(votesManager.VerifyVotingRules(blockInfo, qc), Is.EqualTo(expected));
Assert.That(votesManager.VerifyVotingRules(blockInfo, qc, out _), Is.EqualTo(expected));
}

private static PrivateKey[] MakeKeys(int n)
Expand Down
5 changes: 5 additions & 0 deletions src/Nethermind/Nethermind.Xdc.Test/XdcProtocolHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using NSubstitute;
using NUnit.Framework;
using System;
using Nethermind.Blockchain;

namespace Nethermind.Xdc.Test.P2P;

Expand All @@ -41,10 +42,14 @@ private static (XdcProtocolHandler handler, IMessageSerializationService seriali
INodeStatsManager nodeStatsManager = Substitute.For<INodeStatsManager>();
nodeStatsManager.GetOrAdd(Arg.Any<Node>()).Returns(Substitute.For<INodeStats>());

IBlockTree blockTree = Substitute.For<IBlockTree>();
blockTree.IsSyncing().Returns((false, 0, 0));

XdcProtocolHandler handler = new(
timeoutManager,
votesManager,
syncInfoManager,
blockTree,
session,
serializer,
nodeStatsManager,
Expand Down
5 changes: 3 additions & 2 deletions src/Nethermind/Nethermind.Xdc/EpochSwitchManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ public bool IsEpochSwitchAtRound(ulong currentRound, XdcBlockHeader parent)

public EpochSwitchInfo? GetEpochSwitchInfo(XdcBlockHeader header)
{
if (_epochSwitches.TryGet(header.Hash, out var epochSwitchInfo))
Hash256 headerHash = header.Hash;
if (_epochSwitches.TryGet(headerHash, out var epochSwitchInfo))
{
return epochSwitchInfo;
}
Expand Down Expand Up @@ -150,7 +151,7 @@ public bool IsEpochSwitchAtRound(ulong currentRound, XdcBlockHeader parent)
epochSwitchInfo.EpochSwitchParentBlockInfo = header.ExtraConsensusData.QuorumCert.ProposedBlockInfo;
}

_epochSwitches.Set(header.Hash, epochSwitchInfo);
_epochSwitches.Set(headerHash, epochSwitchInfo);
return epochSwitchInfo;
}

Expand Down
7 changes: 4 additions & 3 deletions src/Nethermind/Nethermind.Xdc/IVotesManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Diagnostics.CodeAnalysis;
using Nethermind.Core.Crypto;
using Nethermind.Xdc.Types;
using System.Threading.Tasks;
Expand All @@ -12,7 +13,7 @@ public interface IVotesManager
Task CastVote(BlockRoundInfo blockInfo);
Task HandleVote(Vote vote);
Task OnReceiveVote(Vote vote);
bool VerifyVotingRules(BlockRoundInfo roundInfo, QuorumCertificate certificate);
bool VerifyVotingRules(XdcBlockHeader header);
bool VerifyVotingRules(Hash256 blockHash, long blockNumber, ulong roundNumber, QuorumCertificate qc);
bool VerifyVotingRules(BlockRoundInfo roundInfo, QuorumCertificate certificate, [NotNullWhen(false)] out string? error);
bool VerifyVotingRules(XdcBlockHeader header, [NotNullWhen(false)] out string? error);
bool VerifyVotingRules(Hash256 blockHash, long blockNumber, ulong roundNumber, QuorumCertificate qc, [NotNullWhen(false)] out string? error);
}
1 change: 0 additions & 1 deletion src/Nethermind/Nethermind.Xdc/IXdcConsensusContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public interface IXdcConsensusContext
TimeoutCertificate? HighestTC { get; set; }
QuorumCertificate? LockQC { get; set; }
int TimeoutCounter { get; set; }
DateTime RoundStarted { get; }

event EventHandler<NewRoundEventArgs> NewRoundSetEvent;

Expand Down
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Xdc/NewRoundEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

namespace Nethermind.Xdc;

public class NewRoundEventArgs(ulong round, ulong previous, int previousRoundTimeouts, TimeSpan lastDuration) : EventArgs
public class NewRoundEventArgs(ulong round, ulong previous, int previousRoundTimeouts, TimeSpan? lastDuration) : EventArgs
{
public ulong NewRound { get; } = round;
public ulong PreviousRound { get; } = previous;
public int PreviousRoundTimeouts { get; } = previousRoundTimeouts;
public TimeSpan LastRoundDuration { get; } = lastDuration;
public TimeSpan? LastRoundDuration { get; } = lastDuration;
}
10 changes: 10 additions & 0 deletions src/Nethermind/Nethermind.Xdc/P2P/XdcProtocolHandler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Blockchain;
using Nethermind.Consensus;
using Nethermind.Consensus.Scheduler;
using Nethermind.Core.Caching;
Expand All @@ -24,6 +25,7 @@ internal class XdcProtocolHandler(
ITimeoutCertificateManager timeoutCertificateManager,
IVotesManager votesManager,
ISyncInfoManager syncInfoManager,
IBlockTree blockTree,
ISession session,
IMessageSerializationService serializer,
INodeStatsManager nodeStatsManager,
Expand All @@ -37,6 +39,7 @@ internal class XdcProtocolHandler(
{
private readonly ITimeoutCertificateManager _timeoutCertificateManager = timeoutCertificateManager;
private readonly IVotesManager _votesManager = votesManager;
private readonly IBlockTree _blockTree = blockTree;
private ClockKeyCache<ValueHash256> _notifiedVotes = new(MemoryAllowance.MemPoolSize / 2);
private ClockKeyCache<ValueHash256> _notifiedTimeouts = new(MemoryAllowance.MemPoolSize / 2);

Expand All @@ -56,6 +59,13 @@ public override void HandleMessage(ZeroPacket message)

int packetType = message.PacketType;

(bool isSyncing, _, _) = _blockTree.IsSyncing();
if (isSyncing) // ignore XDC updates while syncing
{
base.HandleMessage(message);
return;
}

switch (packetType)
{
case XdcMessageCode.VoteMsg:
Expand Down
33 changes: 24 additions & 9 deletions src/Nethermind/Nethermind.Xdc/QuorumCertificateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void CommitCertificate(QuorumCertificate qc)
_context.LockQC = parentQc;
}

if (!CommitBlock(_blockTree, proposedBlockHeader, proposedBlockHeader.ExtraConsensusData.BlockRound, qc, out string error))
if (!CommitBlock(proposedBlockHeader, proposedBlockHeader.ExtraConsensusData.BlockRound, qc, out string error))
{
if (_logger.IsWarn) _logger.Warn($"Could not commit block ({proposedBlockHeader.Hash}). {error}");
}
Expand All @@ -86,7 +86,7 @@ public void CommitCertificate(QuorumCertificate qc)
}
}

private bool CommitBlock(IBlockTree chain, XdcBlockHeader proposedBlockHeader, ulong proposedRound, QuorumCertificate proposedQuorumCert, [NotNullWhen(false)] out string? error)
private bool CommitBlock(XdcBlockHeader proposedBlockHeader, ulong proposedRound, QuorumCertificate proposedQuorumCert, [NotNullWhen(false)] out string? error)
{
IXdcReleaseSpec spec = _specProvider.GetXdcSpec(proposedBlockHeader);
//Can only commit a QC if the proposed block is at least 2 blocks after the switch block, since we want to check grandparent of proposed QC
Expand All @@ -97,7 +97,11 @@ private bool CommitBlock(IBlockTree chain, XdcBlockHeader proposedBlockHeader, u
return false;
}

XdcBlockHeader parentHeader = (XdcBlockHeader)_blockTree.FindHeader(proposedBlockHeader.ParentHash);
if (_blockTree.FindHeader(proposedBlockHeader.ParentHash!) is not XdcBlockHeader parentHeader)
{
error = $"Parent header {proposedBlockHeader.ParentHash} is missing.";
return false;
}

if (parentHeader.ExtraConsensusData is null)
{
Expand All @@ -111,11 +115,14 @@ private bool CommitBlock(IBlockTree chain, XdcBlockHeader proposedBlockHeader, u
return false;
}

XdcBlockHeader grandParentHeader = (XdcBlockHeader)_blockTree.FindHeader(parentHeader.ParentHash);
if (_blockTree.FindHeader(parentHeader.ParentHash!) is not XdcBlockHeader grandParentHeader)
{
error = $"Grandparent header {parentHeader.ParentHash} is missing.";
return false;
}

if (grandParentHeader.ExtraConsensusData is null)
{

error = $"QC grand parent ({grandParentHeader.ToString(BlockHeader.Format.FullHashAndNumber)}) does not have a QC.";
return false;
}
Expand All @@ -126,14 +133,22 @@ private bool CommitBlock(IBlockTree chain, XdcBlockHeader proposedBlockHeader, u
return false;
}

if (_context.HighestCommitBlock is not null && (_context.HighestCommitBlock.Round >= parentHeader.ExtraConsensusData.BlockRound || _context.HighestCommitBlock.BlockNumber > grandParentHeader.Number))
//We will normally commit twice - once when QC vote finished and once when we receive new block containing the same QC most likely
if (_context.HighestCommitBlock is not null && grandParentHeader.Hash == _context.HighestCommitBlock.Hash)
{
error = $"Committed block ({_context.HighestCommitBlock.Hash}) has higher round or block number.";
return false;
error = null;
return true;
}

_context.HighestCommitBlock = new BlockRoundInfo(grandParentHeader.Hash, parentHeader.ExtraConsensusData.BlockRound, grandParentHeader.Number);
if (_context.HighestCommitBlock is not null
&& (_context.HighestCommitBlock.Round >= grandParentHeader.ExtraConsensusData.BlockRound || _context.HighestCommitBlock.BlockNumber > grandParentHeader.Number))
{
error = $"Committed block (round={_context.HighestCommitBlock.Round} #{_context.HighestCommitBlock.BlockNumber} hash={_context.HighestCommitBlock.Hash}) has higher round or block number than proposed header grandparent #{grandParentHeader.Number} round={grandParentHeader.ExtraConsensusData.BlockRound} hash={grandParentHeader.Hash}.";
return false;
}

_context.HighestCommitBlock = new BlockRoundInfo(grandParentHeader.Hash, grandParentHeader.ExtraConsensusData.BlockRound, grandParentHeader.Number);
_logger.Info($"Committed block {grandParentHeader.ToString(BlockHeader.Format.Full)} round={grandParentHeader.ExtraConsensusData.BlockRound}");
//Mark grand parent as finalized
_blockTree.ForkChoiceUpdated(grandParentHeader.Hash, grandParentHeader.Hash);
error = null;
Expand Down
23 changes: 14 additions & 9 deletions src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ public class TimeoutCertificateManager : ITimeoutCertificateManager
private readonly ISigner _signer;
private readonly XdcPool<Timeout> _timeouts = new();

public TimeoutCertificateManager(IXdcConsensusContext context, ITimeoutTimer timeoutTimer, ISyncPeerPool syncPeerPool, ISnapshotManager snapshotManager, IEpochSwitchManager epochSwitchManager, ISpecProvider specProvider, IBlockTree blockTree, ISigner signer)
public TimeoutCertificateManager(
IXdcConsensusContext context,
ITimeoutTimer timeoutTimer,
ISyncPeerPool syncPeerPool,
ISnapshotManager snapshotManager,
IEpochSwitchManager epochSwitchManager,
ISpecProvider specProvider,
IBlockTree blockTree,
ISigner signer)
{
_consensusContext = context;
this._timeoutTimer = timeoutTimer;
Expand Down Expand Up @@ -95,8 +103,6 @@ private void OnTimeoutPoolThresholdReached(IEnumerable<Timeout> timeouts, Timeou
var timeoutCertificate = new TimeoutCertificate(timeout.Round, signatures, timeout.GapNumber);

ProcessTimeoutCertificate(timeoutCertificate);

SyncInfo syncInfo = GetSyncInfo();
}

public void ProcessTimeoutCertificate(TimeoutCertificate timeoutCertificate)
Expand Down Expand Up @@ -245,8 +251,9 @@ private void SendTimeout()
long 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, _consensusContext.CurrentRound);
if (_epochSwitchManager.IsEpochSwitchAtRound(_consensusContext.CurrentRound, currentHeader))
ulong currentRound = _consensusContext.CurrentRound;
IXdcReleaseSpec spec = _specProvider.GetXdcSpec(currentHeader, currentRound);
if (_epochSwitchManager.IsEpochSwitchAtRound(currentRound, currentHeader))
{
var currentNumber = currentHeader.Number + 1;
gapNumber = Math.Max(0, currentNumber - currentNumber % spec.EpochLength - spec.Gap);
Expand All @@ -261,14 +268,12 @@ private void SendTimeout()
gapNumber = Math.Max(0, currentNumber - currentNumber % spec.EpochLength - spec.Gap);
}

ValueHash256 msgHash = ComputeTimeoutMsgHash(_consensusContext.CurrentRound, (ulong)gapNumber);
ValueHash256 msgHash = ComputeTimeoutMsgHash(currentRound, (ulong)gapNumber);
Signature signedHash = _signer.Sign(msgHash);
var timeoutMsg = new Timeout(_consensusContext.CurrentRound, signedHash, (ulong)gapNumber, isMyVote: true);
var timeoutMsg = new Timeout(currentRound, signedHash, (ulong)gapNumber, isMyVote: true);
timeoutMsg.Signer = _signer.Address;

HandleTimeoutVote(timeoutMsg);

//TODO: Broadcast _ctx.HighestTC
}

// Returns true if the signer is within the master node list
Expand Down
15 changes: 15 additions & 0 deletions src/Nethermind/Nethermind.Xdc/Types/XdcPayloadAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Consensus.Producers;
using System;
using System.Collections.Generic;
using System.Text;

namespace Nethermind.Xdc.Types;

internal class XdcPayloadAttributes : PayloadAttributes
{
public ulong Round { get; set; }
public QuorumCertificate QuorumCertificate { get; set; }
}
22 changes: 18 additions & 4 deletions src/Nethermind/Nethermind.Xdc/VotesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;

Expand Down Expand Up @@ -138,35 +139,48 @@ private void CleanupVotes(ulong round)
if (key <= round) _qcBuildStartedByRound.TryRemove(key, out _);
}

public bool VerifyVotingRules(BlockRoundInfo roundInfo, QuorumCertificate qc) => VerifyVotingRules(roundInfo.Hash, roundInfo.BlockNumber, roundInfo.Round, qc);
public bool VerifyVotingRules(XdcBlockHeader header) => VerifyVotingRules(header.Hash, header.Number, header.ExtraConsensusData.BlockRound, header.ExtraConsensusData.QuorumCert);
public bool VerifyVotingRules(Hash256 blockHash, long blockNumber, ulong roundNumber, QuorumCertificate qc)
public bool VerifyVotingRules(BlockRoundInfo roundInfo, QuorumCertificate qc, out string? error) =>
VerifyVotingRules(roundInfo.Hash, roundInfo.BlockNumber, roundInfo.Round, qc, out error);

public bool VerifyVotingRules(XdcBlockHeader header, [NotNullWhen(false)] out string? error) =>
VerifyVotingRules(header.Hash, header.Number, header.ExtraConsensusData.BlockRound, header.ExtraConsensusData.QuorumCert, out error);

public bool VerifyVotingRules(Hash256 blockHash, long blockNumber, ulong roundNumber, QuorumCertificate qc, out string? error)
{
if ((long)_ctx.CurrentRound <= _highestVotedRound)
{
error = $"Already voted at round {_highestVotedRound}, current round {_ctx.CurrentRound}";
return false;
}

if (roundNumber != _ctx.CurrentRound)
{
error = $"Vote round {roundNumber} does not match current round {_ctx.CurrentRound}";
return false;
}

if (_ctx.LockQC is null)
{
error = null;
return true;
}

if (qc.ProposedBlockInfo.Round > _ctx.LockQC.ProposedBlockInfo.Round)
{
error = null;
return true;
}

if (!IsExtendingFromAncestor(blockHash, blockNumber, _ctx.LockQC.ProposedBlockInfo))
BlockRoundInfo locked = _ctx.LockQC.ProposedBlockInfo;
if (!IsExtendingFromAncestor(blockHash, blockNumber, locked))
{
error =
$"Block {blockHash} (number {blockNumber}, round {roundNumber}) does not extend from locked QC block " +
$"{locked.Hash}(number {locked.BlockNumber}, round {locked.Round})";
return false;
}

error = null;
return true;
}

Expand Down
Loading
Loading