Skip to content

Commit

Permalink
[Fix] Fix handling of empty recipts
Browse files Browse the repository at this point in the history
  • Loading branch information
KonradStaniec committed Sep 28, 2020
1 parent 992196d commit aa71fbe
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 36 deletions.
55 changes: 28 additions & 27 deletions src/main/scala/io/iohk/ethereum/blockchain/sync/FastSync.scala
Original file line number Diff line number Diff line change
Expand Up @@ -360,35 +360,36 @@ class FastSync(
}

private def handleReceipts(peer: Peer, requestedHashes: Seq[ByteString], receipts: Seq[Seq[Receipt]]) = {
validateReceipts(requestedHashes, receipts) match {
case ReceiptsValidationResult.Valid(blockHashesWithReceipts) =>
blockHashesWithReceipts.map { case (hash, receiptsForBlock) =>
blockchain.storeReceipts(hash, receiptsForBlock)
}.reduce(_.and(_))
.commit()

val receivedHashes = blockHashesWithReceipts.unzip._1
updateBestBlockIfNeeded(receivedHashes)

if (receipts.isEmpty) {
val reason = s"got empty receipts for known hashes: ${requestedHashes.map(h => Hex.toHexString(h.toArray[Byte]))}"
blacklist(peer.id, blacklistDuration, reason)
}

val remainingReceipts = requestedHashes.drop(receipts.size)
if (remainingReceipts.nonEmpty) {
syncState = syncState.enqueueReceipts(remainingReceipts)
}
if (receipts.isEmpty) {
val reason = s"got empty receipts for known hashes: ${requestedHashes.map(h => Hex.toHexString(h.toArray[Byte]))}"
blacklist(peer.id, blacklistDuration, reason)
syncState = syncState.enqueueReceipts(requestedHashes)
} else {
validateReceipts(requestedHashes, receipts) match {
case ReceiptsValidationResult.Valid(blockHashesWithReceipts) =>
blockHashesWithReceipts.map { case (hash, receiptsForBlock) =>
blockchain.storeReceipts(hash, receiptsForBlock)
}.reduce(_.and(_))
.commit()

val receivedHashes = blockHashesWithReceipts.unzip._1
updateBestBlockIfNeeded(receivedHashes)

val remainingReceipts = requestedHashes.drop(receipts.size)
if (remainingReceipts.nonEmpty) {
syncState = syncState.enqueueReceipts(remainingReceipts)
}

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

case ReceiptsValidationResult.DbError =>
redownloadBlockchain()
case ReceiptsValidationResult.DbError =>
redownloadBlockchain()
}
}

processSyncing()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,68 @@ class SyncControllerSpec extends AnyFlatSpec with Matchers with BeforeAndAfter w
peerMessageBus.expectMsg(Subscribe(MessageClassifier(Set(BlockHeaders.code), PeerSelector.WithId(peer1.id))))
}

it should "gracefully handle receiving empty receipts while syncing" in new TestSetup() {

val newSafeTarget = defaultExpectedTargetBlock + syncConfig.fastSyncBlockValidationX
val bestBlockNumber = defaultExpectedTargetBlock
val firstNewBlock = bestBlockNumber + 1

startWithState(defaultState.copy(
bestBlockHeaderNumber = bestBlockNumber,
safeDownloadTarget = newSafeTarget)
)

Thread.sleep(1.seconds.toMillis)

syncController ! SyncController.Start

val handshakedPeers = HandshakedPeers(singlePeer)
updateHandshakedPeers(handshakedPeers)
etcPeerManager.setAutoPilot(new AutoPilot {
def run(sender: ActorRef, msg: Any): AutoPilot = {
if (msg == EtcPeerManagerActor.GetHandshakedPeers) {
sender ! handshakedPeers
}

this
}
})

val watcher = TestProbe()
watcher.watch(syncController)

val newBlocks = getHeaders(firstNewBlock, syncConfig.blockHeadersPerRequest)
val newReceipts = newBlocks.map(_.hash).map(_ => Seq.empty[Receipt])
val newBodies = newBlocks.map(_ => BlockBody.empty)

//wait for peers throttle
Thread.sleep(syncConfig.fastSyncThrottle.toMillis)
sendBlockHeaders(firstNewBlock, newBlocks, peer1, newBlocks.size)

Thread.sleep(syncConfig.fastSyncThrottle.toMillis)
sendNewTargetBlock(defaultTargetBlockHeader.copy(number = defaultTargetBlockHeader.number + 1), peer1, peer1Status, handshakedPeers)

Thread.sleep(1.second.toMillis)
sendReceipts(newBlocks.map(_.hash), Seq(), peer1)

// Peer will be blacklisted for empty response, so wait he is blacklisted
Thread.sleep(6.second.toMillis)
sendReceipts(newBlocks.map(_.hash), newReceipts, peer1)

Thread.sleep(syncConfig.fastSyncThrottle.toMillis)
sendBlockBodies(newBlocks.map(_.hash), newBodies, peer1)

Thread.sleep(syncConfig.fastSyncThrottle.toMillis)
sendNodes(Seq(defaultTargetBlockHeader.stateRoot), Seq(defaultStateMptLeafWithAccount), peer1)

//switch to regular download
etcPeerManager.expectMsg(EtcPeerManagerActor.SendMessage(
GetBlockHeaders(Left(defaultTargetBlockHeader.number + 1), syncConfig.blockHeadersPerRequest, 0, reverse = false),
peer1.id))
peerMessageBus.expectMsg(Subscribe(MessageClassifier(Set(BlockHeaders.code), PeerSelector.WithId(peer1.id))))
}


it should "handle blocks that fail validation" in new TestSetup(_validators = new Mocks.MockValidatorsAlwaysSucceed {
override val blockHeaderValidator: BlockHeaderValidator = { (blockHeader, getBlockHeaderByHash) => Left(HeaderPoWError) }
}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package io.iohk.ethereum.consensus.ethash

import akka.util.ByteString
import io.iohk.ethereum.crypto.kec256
import org.scalacheck.Arbitrary
import org.scalacheck.{Gen}
import org.bouncycastle.util.encoders.Hex
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.annotation.tailrec

class EthashUtilsSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyChecks {

import io.iohk.ethereum.consensus.ethash.EthashUtils._

"Ethash" should "generate correct hash" in {
forAll(Arbitrary.arbitrary[Long].filter(_ < 15000000)) { blockNumber =>
forAll(Gen.choose[Long](0, 15000000L)) { blockNumber =>
seed(epoch(blockNumber)) shouldBe seedForBlockReference(blockNumber)
}
}
Expand Down Expand Up @@ -115,13 +117,15 @@ class EthashUtilsSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyC
}

def seedForBlockReference(blockNumber: BigInt): ByteString = {
if (blockNumber < EPOCH_LENGTH) {
//wrong version from YP:
//ByteString(kec256(Hex.decode("00" * 32)))
//working version:
ByteString(Hex.decode("00" * 32))
} else {
kec256(seedForBlockReference(blockNumber - EPOCH_LENGTH))
@tailrec
def go(current: BigInt, currentHash: ByteString): ByteString = {
if (current < EPOCH_LENGTH) {
currentHash
} else {
go(current - EPOCH_LENGTH, kec256(currentHash))
}
}

go(blockNumber, ByteString(Hex.decode("00" * 32)))
}
}

0 comments on commit aa71fbe

Please sign in to comment.