Skip to content

Commit 3f66fea

Browse files
committed
Fix cross-block false double-sign detection in sequential consensus
The _proposalsByView dictionary was keyed by (view, proposer) without block number. After a view change bumps view to V for block N-1, and then StartRound(N) sets view = N where N == V, the old entry from block N-1 collides with the new proposal for block N. This causes false double-sign detection because the hashes differ (different blocks). Fix: include block number in the key → (view, block, proposer). Proposals for different blocks at the same view no longer collide. Genuine double- signs (same view + same block + same proposer + different hashes) are still detected correctly.
1 parent e0871bf commit 3f66fea

File tree

1 file changed

+9
-6
lines changed

1 file changed

+9
-6
lines changed

src/node/Basalt.Node/NodeCoordinator.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,12 @@ public sealed class NodeCoordinator : IAsyncDisposable
9898
// N-14: Cap block request count to prevent resource exhaustion
9999
private const int MaxBlockRequestCount = 100;
100100

101-
// N-17: Thread-safe double-sign detection: keyed by (view, proposer) to avoid false positives
102-
// when different proposers propose for the same view after a view change.
103-
private readonly ConcurrentDictionary<(ulong View, PeerId Proposer), Hash256> _proposalsByView = new();
101+
// N-17: Thread-safe double-sign detection: keyed by (view, block, proposer).
102+
// Block number is included because view numbers can collide across blocks:
103+
// after a view change bumps view to V, and then StartRound(V) reuses the same
104+
// view number for the next block, old entries from the previous block would
105+
// cause false double-sign detection without the block dimension.
106+
private readonly ConcurrentDictionary<(ulong View, ulong Block, PeerId Proposer), Hash256> _proposalsByView = new();
104107

105108
// Identity
106109
private byte[] _privateKey = [];
@@ -1146,7 +1149,7 @@ private void HandleConsensusProposal(PeerId sender, ConsensusProposalMessage pro
11461149

11471150
if (proposal.BlockNumber == currentBlock)
11481151
{
1149-
var proposalKey = (proposal.ViewNumber, proposal.SenderId);
1152+
var proposalKey = (proposal.ViewNumber, proposal.BlockNumber, proposal.SenderId);
11501153
if (_slashingEngine != null && _proposalsByView.TryGetValue(proposalKey, out var existingHash))
11511154
{
11521155
if (existingHash != proposal.BlockHash)
@@ -1155,8 +1158,8 @@ private void HandleConsensusProposal(PeerId sender, ConsensusProposalMessage pro
11551158
if (proposerInfo != null)
11561159
{
11571160
_slashingEngine.SlashDoubleSign(proposerInfo.Address, proposal.BlockNumber, existingHash, proposal.BlockHash);
1158-
_logger.LogWarning("Double-sign detected from validator {Address} at view {View}",
1159-
proposerInfo.Address, proposal.ViewNumber);
1161+
_logger.LogWarning("Double-sign detected from validator {Address} at view {View} block {Block}",
1162+
proposerInfo.Address, proposal.ViewNumber, proposal.BlockNumber);
11601163
}
11611164
}
11621165
}

0 commit comments

Comments
 (0)