Skip to content

Commit a1de42a

Browse files
committed
[ETCM-213] Fix bug in pivot block selector
1 parent 54e75c1 commit a1de42a

File tree

2 files changed

+46
-7
lines changed

2 files changed

+46
-7
lines changed

src/main/scala/io/iohk/ethereum/blockchain/sync/PivotBlockSelector.scala

+15-7
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,15 @@ class PivotBlockSelector(
109109
timeout: Cancellable,
110110
headers: Map[ByteString, BlockHeaderWithVotes]
111111
): Unit = {
112-
val BlockHeaderWithVotes(mostPopularBlockHeader, updatedVotes) = headers.mostVotedHeader
112+
// most voted header can return empty if we asked one peer and it returned us non expected block. Then headers map is empty
113+
// so there is no most voted header
114+
val maybeBlockHeaderWithVotes = headers.mostVotedHeader
113115
// All peers responded - consensus reached
114-
if (peersToAsk.isEmpty && updatedVotes >= minPeersToChoosePivotBlock) {
116+
if (peersToAsk.isEmpty && maybeBlockHeaderWithVotes.exists(hWv => hWv.votes >= minPeersToChoosePivotBlock)) {
115117
timeout.cancel()
116-
sendResponseAndCleanup(mostPopularBlockHeader)
118+
sendResponseAndCleanup(maybeBlockHeaderWithVotes.get.header)
117119
// Consensus could not be reached - ask additional peer if available
118-
} else if (!isPossibleToReachConsensus(peersToAsk.size, updatedVotes)) {
120+
} else if (!isPossibleToReachConsensus(peersToAsk.size, maybeBlockHeaderWithVotes.map(_.votes).getOrElse(0))) {
119121
timeout.cancel()
120122
if (waitingPeers.nonEmpty) { // There are more peers to ask
121123
val newTimeout = scheduler.scheduleOnce(peerResponseTimeout, self, ElectionPivotBlockTimeout)
@@ -208,9 +210,15 @@ object PivotBlockSelector {
208210
}
209211

210212
implicit class SortableHeadersMap(headers: Map[ByteString, BlockHeaderWithVotes]) {
211-
def mostVotedHeader: BlockHeaderWithVotes = headers.maxBy { case (_, headerWithVotes) =>
212-
headerWithVotes.votes
213-
}._2
213+
def mostVotedHeader: Option[BlockHeaderWithVotes] = {
214+
if (headers.isEmpty) {
215+
None
216+
} else {
217+
Some(headers.maxBy { case (_, headerWithVotes) =>
218+
headerWithVotes.votes
219+
}._2)
220+
}
221+
}
214222
}
215223

216224
case class ElectionDetails(participants: List[Peer], expectedPivotBlock: BigInt) {

src/test/scala/io/iohk/ethereum/blockchain/sync/PivotBlockSelectorSpec.scala

+31
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,37 @@ class PivotBlockSelectorSpec
188188
)
189189
}
190190

191+
it should "handle case when one peer responded with wrong block header" in new TestSetup {
192+
override def minPeersToChoosePivotBlock: Int = 1
193+
194+
updateHandshakedPeers(HandshakedPeers(singlePeer))
195+
196+
pivotBlockSelector ! SelectPivotBlock
197+
198+
peerMessageBus.expectMsgAllOf(
199+
Subscribe(MessageClassifier(Set(BlockHeaders.code), PeerSelector.WithId(peer1.id)))
200+
)
201+
202+
etcPeerManager.expectMsgAllOf(
203+
EtcPeerManagerActor.SendMessage(GetBlockHeaders(Left(expectedPivotBlock), 1, 0, reverse = false), peer1.id)
204+
)
205+
206+
// peer responds with block header number
207+
pivotBlockSelector ! MessageFromPeer(
208+
BlockHeaders(Seq(pivotBlockHeader.copy(number = expectedPivotBlock + 1))),
209+
peer1.id
210+
)
211+
212+
peerMessageBus.expectMsgAllOf(
213+
Unsubscribe(MessageClassifier(Set(BlockHeaders.code), PeerSelector.WithId(peer1.id))),
214+
Unsubscribe()
215+
)
216+
time.advance(syncConfig.syncRetryInterval)
217+
218+
fastSync.expectNoMessage() // consensus not reached - process have to be repeated
219+
peerMessageBus.expectNoMessage()
220+
}
221+
191222
it should "not ask additional peers if not needed" in new TestSetup {
192223
override val minPeersToChoosePivotBlock = 2
193224
override val peersToChoosePivotBlockMargin = 1

0 commit comments

Comments
 (0)