Skip to content

Commit a071ad4

Browse files
committed
[Fix] Fix handling of empty recipts
1 parent 992196d commit a071ad4

File tree

3 files changed

+101
-34
lines changed

3 files changed

+101
-34
lines changed

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

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -360,35 +360,36 @@ class FastSync(
360360
}
361361

362362
private def handleReceipts(peer: Peer, requestedHashes: Seq[ByteString], receipts: Seq[Seq[Receipt]]) = {
363-
validateReceipts(requestedHashes, receipts) match {
364-
case ReceiptsValidationResult.Valid(blockHashesWithReceipts) =>
365-
blockHashesWithReceipts.map { case (hash, receiptsForBlock) =>
366-
blockchain.storeReceipts(hash, receiptsForBlock)
367-
}.reduce(_.and(_))
368-
.commit()
369-
370-
val receivedHashes = blockHashesWithReceipts.unzip._1
371-
updateBestBlockIfNeeded(receivedHashes)
372-
373-
if (receipts.isEmpty) {
374-
val reason = s"got empty receipts for known hashes: ${requestedHashes.map(h => Hex.toHexString(h.toArray[Byte]))}"
375-
blacklist(peer.id, blacklistDuration, reason)
376-
}
377-
378-
val remainingReceipts = requestedHashes.drop(receipts.size)
379-
if (remainingReceipts.nonEmpty) {
380-
syncState = syncState.enqueueReceipts(remainingReceipts)
381-
}
363+
if (receipts.isEmpty) {
364+
val reason = s"got empty receipts for known hashes: ${requestedHashes.map(h => Hex.toHexString(h.toArray[Byte]))}"
365+
blacklist(peer.id, blacklistDuration, reason)
366+
syncState = syncState.enqueueReceipts(requestedHashes)
367+
} else {
368+
validateReceipts(requestedHashes, receipts) match {
369+
case ReceiptsValidationResult.Valid(blockHashesWithReceipts) =>
370+
blockHashesWithReceipts.map { case (hash, receiptsForBlock) =>
371+
blockchain.storeReceipts(hash, receiptsForBlock)
372+
}.reduce(_.and(_))
373+
.commit()
374+
375+
val receivedHashes = blockHashesWithReceipts.unzip._1
376+
updateBestBlockIfNeeded(receivedHashes)
377+
378+
val remainingReceipts = requestedHashes.drop(receipts.size)
379+
if (remainingReceipts.nonEmpty) {
380+
syncState = syncState.enqueueReceipts(remainingReceipts)
381+
}
382382

383-
case ReceiptsValidationResult.Invalid(error) =>
384-
val reason =
385-
s"got invalid receipts for known hashes: ${requestedHashes.map(h => Hex.toHexString(h.toArray[Byte]))}" +
386-
s" due to: $error"
387-
blacklist(peer.id, blacklistDuration, reason)
388-
syncState = syncState.enqueueReceipts(requestedHashes)
383+
case ReceiptsValidationResult.Invalid(error) =>
384+
val reason =
385+
s"got invalid receipts for known hashes: ${requestedHashes.map(h => Hex.toHexString(h.toArray[Byte]))}" +
386+
s" due to: $error"
387+
blacklist(peer.id, blacklistDuration, reason)
388+
syncState = syncState.enqueueReceipts(requestedHashes)
389389

390-
case ReceiptsValidationResult.DbError =>
391-
redownloadBlockchain()
390+
case ReceiptsValidationResult.DbError =>
391+
redownloadBlockchain()
392+
}
392393
}
393394

394395
processSyncing()

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,68 @@ class SyncControllerSpec extends AnyFlatSpec with Matchers with BeforeAndAfter w
145145
peerMessageBus.expectMsg(Subscribe(MessageClassifier(Set(BlockHeaders.code), PeerSelector.WithId(peer1.id))))
146146
}
147147

148+
it should "gracefully handle receiving empty receipts while syncing" in new TestSetup() {
149+
150+
val newSafeTarget = defaultExpectedTargetBlock + syncConfig.fastSyncBlockValidationX
151+
val bestBlockNumber = defaultExpectedTargetBlock
152+
val firstNewBlock = bestBlockNumber + 1
153+
154+
startWithState(defaultState.copy(
155+
bestBlockHeaderNumber = bestBlockNumber,
156+
safeDownloadTarget = newSafeTarget)
157+
)
158+
159+
Thread.sleep(1.seconds.toMillis)
160+
161+
syncController ! SyncController.Start
162+
163+
val handshakedPeers = HandshakedPeers(singlePeer)
164+
updateHandshakedPeers(handshakedPeers)
165+
etcPeerManager.setAutoPilot(new AutoPilot {
166+
def run(sender: ActorRef, msg: Any): AutoPilot = {
167+
if (msg == EtcPeerManagerActor.GetHandshakedPeers) {
168+
sender ! handshakedPeers
169+
}
170+
171+
this
172+
}
173+
})
174+
175+
val watcher = TestProbe()
176+
watcher.watch(syncController)
177+
178+
val newBlocks = getHeaders(firstNewBlock, syncConfig.blockHeadersPerRequest)
179+
val newReceipts = newBlocks.map(_.hash).map(_ => Seq.empty[Receipt])
180+
val newBodies = newBlocks.map(_ => BlockBody.empty)
181+
182+
//wait for peers throttle
183+
Thread.sleep(syncConfig.fastSyncThrottle.toMillis)
184+
sendBlockHeaders(firstNewBlock, newBlocks, peer1, newBlocks.size)
185+
186+
Thread.sleep(syncConfig.fastSyncThrottle.toMillis)
187+
sendNewTargetBlock(defaultTargetBlockHeader.copy(number = defaultTargetBlockHeader.number + 1), peer1, peer1Status, handshakedPeers)
188+
189+
Thread.sleep(1.second.toMillis)
190+
sendReceipts(newBlocks.map(_.hash), Seq(), peer1)
191+
192+
// Peer will be blacklisted for empty response, so wait he is blacklisted
193+
Thread.sleep(6.second.toMillis)
194+
sendReceipts(newBlocks.map(_.hash), newReceipts, peer1)
195+
196+
Thread.sleep(syncConfig.fastSyncThrottle.toMillis)
197+
sendBlockBodies(newBlocks.map(_.hash), newBodies, peer1)
198+
199+
Thread.sleep(syncConfig.fastSyncThrottle.toMillis)
200+
sendNodes(Seq(defaultTargetBlockHeader.stateRoot), Seq(defaultStateMptLeafWithAccount), peer1)
201+
202+
//switch to regular download
203+
etcPeerManager.expectMsg(EtcPeerManagerActor.SendMessage(
204+
GetBlockHeaders(Left(defaultTargetBlockHeader.number + 1), syncConfig.blockHeadersPerRequest, 0, reverse = false),
205+
peer1.id))
206+
peerMessageBus.expectMsg(Subscribe(MessageClassifier(Set(BlockHeaders.code), PeerSelector.WithId(peer1.id))))
207+
}
208+
209+
148210
it should "handle blocks that fail validation" in new TestSetup(_validators = new Mocks.MockValidatorsAlwaysSucceed {
149211
override val blockHeaderValidator: BlockHeaderValidator = { (blockHeader, getBlockHeaderByHash) => Left(HeaderPoWError) }
150212
}) {

src/test/scala/io/iohk/ethereum/consensus/ethash/EthashUtilsSpec.scala

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
88
import org.scalatest.flatspec.AnyFlatSpec
99
import org.scalatest.matchers.should.Matchers
1010

11+
import scala.annotation.tailrec
12+
1113
class EthashUtilsSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyChecks {
1214

1315
import io.iohk.ethereum.consensus.ethash.EthashUtils._
@@ -115,13 +117,15 @@ class EthashUtilsSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyC
115117
}
116118

117119
def seedForBlockReference(blockNumber: BigInt): ByteString = {
118-
if (blockNumber < EPOCH_LENGTH) {
119-
//wrong version from YP:
120-
//ByteString(kec256(Hex.decode("00" * 32)))
121-
//working version:
122-
ByteString(Hex.decode("00" * 32))
123-
} else {
124-
kec256(seedForBlockReference(blockNumber - EPOCH_LENGTH))
120+
@tailrec
121+
def go(current: BigInt, currentHash: ByteString): ByteString = {
122+
if (current < EPOCH_LENGTH) {
123+
currentHash
124+
} else {
125+
go(current - EPOCH_LENGTH, kec256(currentHash))
126+
}
125127
}
128+
129+
go(blockNumber, ByteString(Hex.decode("00" * 32)))
126130
}
127131
}

0 commit comments

Comments
 (0)