1
1
package io .iohk .ethereum .blockchain .sync
2
2
3
3
import akka .actor .{Actor , ActorLogging , ActorRef , Cancellable , Props , Scheduler }
4
+ import akka .util .ByteString
4
5
import io .iohk .ethereum .domain .BlockHeader
5
- import io .iohk .ethereum .network .{EtcPeerManagerActor , Peer , PeerId }
6
6
import io .iohk .ethereum .network .EtcPeerManagerActor .PeerInfo
7
7
import io .iohk .ethereum .network .PeerEventBusActor .PeerEvent .MessageFromPeer
8
- import io .iohk .ethereum .network .PeerEventBusActor .{PeerSelector , Subscribe , Unsubscribe }
9
8
import io .iohk .ethereum .network .PeerEventBusActor .SubscriptionClassifier .MessageClassifier
9
+ import io .iohk .ethereum .network .PeerEventBusActor .{PeerSelector , Subscribe , Unsubscribe }
10
10
import io .iohk .ethereum .network .p2p .messages .PV62 .{BlockHeaders , GetBlockHeaders }
11
+ import io .iohk .ethereum .network .{EtcPeerManagerActor , PeerId }
11
12
import io .iohk .ethereum .utils .Config .SyncConfig
12
- import scala .concurrent .duration .FiniteDuration
13
13
import scala .concurrent .ExecutionContext .Implicits .global
14
+ import scala .concurrent .duration .FiniteDuration
14
15
15
16
class FastSyncPivotBlockSelector (
16
17
val etcPeerManager : ActorRef ,
@@ -36,79 +37,88 @@ class FastSyncPivotBlockSelector(
36
37
(peer, maxBlockNumber)
37
38
}
38
39
39
- if (peersUsedToChooseTarget.size >= minPeersToChoosePivotBlock) {
40
- tryChooseTargetBlock(peersUsedToChooseTarget)
40
+ val peersSortedByBestNumber = peersUsedToChooseTarget.toList.sortBy { case (_, number) => - number }
41
+ val bestPeerBestBlockNumber = peersSortedByBestNumber.headOption
42
+ .map { case (_, bestPeerBestBlockNumber) => bestPeerBestBlockNumber }
43
+ .getOrElse(BigInt (0 ))
44
+ val expectedPivotBlock = (bestPeerBestBlockNumber - syncConfig.pivotBlockOffset).max(0 )
45
+ val peersToAsk = peersSortedByBestNumber
46
+ .takeWhile { case (_, number) => number >= expectedPivotBlock }
47
+ .map { case (peer, _) => peer }
48
+
49
+ if (peersToAsk.size >= minPeersToChoosePivotBlock) {
50
+ log.info(
51
+ " Trying to choose fast sync pivot block using {} peers. The best block is {}. Ask {} peers for block nr {}" ,
52
+ peersUsedToChooseTarget.size,
53
+ bestPeerBestBlockNumber,
54
+ peersToAsk.size,
55
+ expectedPivotBlock
56
+ )
57
+
58
+ peersToAsk.foreach { peer =>
59
+ peerEventBus ! Subscribe (MessageClassifier (Set (BlockHeaders .code), PeerSelector .WithId (peer.id)))
60
+ etcPeerManager ! EtcPeerManagerActor .SendMessage (
61
+ GetBlockHeaders (Left (expectedPivotBlock), 1 , 0 , reverse = false ),
62
+ peer.id
63
+ )
64
+ }
65
+
66
+ val timeout = scheduler.scheduleOnce(peerResponseTimeout, self, PivotBlockTimeout )
67
+ context become waitingForPivotBlock(peersToAsk.map(_.id).toSet, expectedPivotBlock, timeout, Map .empty)
41
68
} else {
42
69
log.info(
43
- " Cannot pick pivot block. Need at least {} peers, but there are only {} available at the moment. Retrying in {}" ,
70
+ " Cannot pick pivot block. Need at least {} peers, but there are only {} which meet the criteria ({} all available at the moment) . Retrying in {}" ,
44
71
minPeersToChoosePivotBlock,
72
+ peersToAsk.size,
45
73
peersUsedToChooseTarget.size,
46
74
startRetryInterval
47
75
)
48
76
scheduleRetry(startRetryInterval)
49
- context become idle
50
77
}
51
78
}
52
79
53
- def tryChooseTargetBlock (peersWithBestBlockNumbers : Map [Peer , BigInt ]): Unit = {
54
- val peersSortedByBestNumber = peersWithBestBlockNumbers.toList.sortBy(- _._2)
55
- val bestPeerBestBlockNumber = peersSortedByBestNumber.head._2
56
- val expectedPivotBlock = (bestPeerBestBlockNumber - syncConfig.pivotBlockOffset).max(0 )
57
- val peersToAsk = peersSortedByBestNumber.takeWhile(_._2 >= expectedPivotBlock).map(_._1)
58
-
59
- log.info(
60
- " Trying to choose fast sync pivot block using {} peers. The best block is {}. Ask {} peers for block nr {}" ,
61
- peersWithBestBlockNumbers.size,
62
- bestPeerBestBlockNumber,
63
- peersToAsk.size,
64
- expectedPivotBlock
65
- )
66
-
67
- peersToAsk.foreach { peer =>
68
- peerEventBus ! Subscribe (MessageClassifier (Set (BlockHeaders .code), PeerSelector .WithId (peer.id)))
69
- etcPeerManager ! EtcPeerManagerActor .SendMessage (
70
- GetBlockHeaders (Left (expectedPivotBlock), 1 , 0 , reverse = false ),
71
- peer.id
72
- )
73
- }
74
-
75
- val timeout = scheduler.scheduleOnce(peerResponseTimeout, self, PivotBlockTimeout )
76
- context become waitingForPivotBlock(peersToAsk.map(_.id).toSet, expectedPivotBlock, timeout, Map .empty)
77
- }
78
-
79
80
def waitingForPivotBlock (
80
81
peersToAsk : Set [PeerId ],
81
82
targetBlockNumber : BigInt ,
82
83
timeout : Cancellable ,
83
- headers : Map [BlockHeader , Int ]
84
+ headers : Map [ByteString , BlockHeaderWithVotes ]
84
85
): Receive =
85
86
handleCommonMessages orElse {
86
87
case MessageFromPeer (blockHeaders : BlockHeaders , peerId) =>
87
88
peerEventBus ! Unsubscribe (MessageClassifier (Set (BlockHeaders .code), PeerSelector .WithId (peerId)))
88
89
val updatedPeersToAsk = peersToAsk - peerId
89
- val targetBlockHeaderOpt = blockHeaders.headers.find(header => header.number == targetBlockNumber)
90
-
90
+ val targetBlockHeaderOpt =
91
+ if (blockHeaders.headers.size != 1 ) None
92
+ else
93
+ blockHeaders.headers.find(header => header.number == targetBlockNumber)
91
94
targetBlockHeaderOpt match {
92
95
case Some (targetBlockHeader) =>
93
96
log.info(" Received vote for {} from {}" , targetBlockHeader.hashAsHexString, peerId.value)
94
- val newValue = headers.find(_._1 == targetBlockHeader).map(_._2 + 1 ).getOrElse(1 )
95
- val updatedHeaders = headers.updated(targetBlockHeader, newValue)
96
- val (mostPopularBlockHeader, votes) = updatedHeaders.maxBy(_._2)
97
- if (votes >= minPeersToChoosePivotBlock) {
97
+ val newValue =
98
+ headers.get(targetBlockHeader.hash).map(_.vote).getOrElse(BlockHeaderWithVotes (targetBlockHeader))
99
+ val updatedHeaders = headers.updated(targetBlockHeader.hash, newValue)
100
+ val BlockHeaderWithVotes (mostPopularBlockHeader, updatedVotes) = updatedHeaders.mostVotedHeader
101
+ if (updatedVotes >= minPeersToChoosePivotBlock) {
98
102
timeout.cancel()
99
103
sendResponseAndCleanup(mostPopularBlockHeader)
100
- } else if (updatedPeersToAsk.size + votes < minPeersToChoosePivotBlock ) {
104
+ } else if (! isPossibleToReachConsensus( updatedPeersToAsk.size, updatedVotes) ) {
101
105
timeout.cancel()
102
106
peerEventBus ! Unsubscribe ()
103
107
log.info(" Not enough votes for pivot block. Retrying in {}" , startRetryInterval)
104
108
scheduleRetry(startRetryInterval)
105
- context become idle
106
109
} else {
107
110
context become waitingForPivotBlock(updatedPeersToAsk, targetBlockNumber, timeout, updatedHeaders)
108
111
}
109
112
case None =>
110
113
blacklist(peerId, blacklistDuration, " Did not respond with pivot block header, blacklisting" )
111
- context become waitingForPivotBlock(updatedPeersToAsk, targetBlockNumber, timeout, headers)
114
+ val BlockHeaderWithVotes (_, votes) = headers.mostVotedHeader
115
+ if (! isPossibleToReachConsensus(updatedPeersToAsk.size, votes)) {
116
+ timeout.cancel()
117
+ log.info(" Not enough votes for pivot block. Retrying in {}" , startRetryInterval)
118
+ peerEventBus ! Unsubscribe ()
119
+ scheduleRetry(startRetryInterval)
120
+ } else
121
+ context become waitingForPivotBlock(updatedPeersToAsk, targetBlockNumber, timeout, headers)
112
122
}
113
123
case PivotBlockTimeout =>
114
124
peersToAsk.foreach { peerId =>
@@ -117,11 +127,14 @@ class FastSyncPivotBlockSelector(
117
127
peerEventBus ! Unsubscribe ()
118
128
log.info(" Pivot block header receive timeout. Retrying in {}" , startRetryInterval)
119
129
scheduleRetry(startRetryInterval)
120
- context become idle
121
130
}
122
131
132
+ private def isPossibleToReachConsensus (peersLeft : Int , bestHeaderVotes : Int ): Boolean =
133
+ peersLeft + bestHeaderVotes >= minPeersToChoosePivotBlock
134
+
123
135
def scheduleRetry (interval : FiniteDuration ): Unit = {
124
136
scheduler.scheduleOnce(interval, self, ChoosePivotBlock )
137
+ context become idle
125
138
}
126
139
127
140
def sendResponseAndCleanup (pivotBlockHeader : BlockHeader ): Unit = {
@@ -140,5 +153,15 @@ object FastSyncPivotBlockSelector {
140
153
case object ChoosePivotBlock
141
154
case class Result (targetBlockHeader : BlockHeader )
142
155
143
- private case object PivotBlockTimeout
156
+ case object PivotBlockTimeout
157
+
158
+ case class BlockHeaderWithVotes (header : BlockHeader , votes : Int = 1 ) {
159
+ def vote : BlockHeaderWithVotes = copy(votes = votes + 1 )
160
+ }
161
+
162
+ implicit class SortableHeadersMap (headers : Map [ByteString , BlockHeaderWithVotes ]) {
163
+ def mostVotedHeader : BlockHeaderWithVotes = headers.maxBy { case (_, headerWithVotes) =>
164
+ headerWithVotes.votes
165
+ }._2
166
+ }
144
167
}
0 commit comments