Skip to content

Commit 4fe8dfd

Browse files
committed
[ETCM-177] Improve Ommers pool
- Fix ommers calculation at pool level - Query by parentBlockHash instead of blockNumber - Remove Block inclusion as ommer for mined blocks that are not been imported (Testnet issue) - Increase OmmersPool test coverage
1 parent ba42652 commit 4fe8dfd

File tree

7 files changed

+226
-54
lines changed

7 files changed

+226
-54
lines changed

src/main/scala/io/iohk/ethereum/blockchain/sync/regular/BlockImporter.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ class BlockImporter(
6363
case MinedBlock(block) =>
6464
if (!state.importing) {
6565
importMinedBlock(block, state)
66-
} else {
67-
ommersPool ! AddOmmers(block.header)
6866
}
6967
case ImportNewBlock(block, peerId) if state.isOnTop && !state.importing => importNewBlock(block, peerId, state)
7068
case ImportDone(newBehavior) =>

src/main/scala/io/iohk/ethereum/consensus/ethash/EthashBlockCreator.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package io.iohk.ethereum.consensus.ethash
22

33
import akka.actor.ActorRef
44
import akka.pattern.ask
5-
import akka.util.Timeout
5+
import akka.util.{Timeout, ByteString}
66
import io.iohk.ethereum.consensus.blocks.PendingBlock
77
import io.iohk.ethereum.consensus.ethash.blocks.EthashBlockGenerator
88
import io.iohk.ethereum.domain.{Address, Block}
@@ -28,7 +28,7 @@ class EthashBlockCreator(
2828
def getBlockForMining(parentBlock: Block, withTransactions: Boolean = true): Future[PendingBlock] = {
2929
val transactions =
3030
if (withTransactions) getTransactionsFromPool else Future.successful(PendingTransactionsResponse(Nil))
31-
getOmmersFromPool(parentBlock.header.number + 1).zip(transactions).flatMap {
31+
getOmmersFromPool(parentBlock.hash).zip(transactions).flatMap {
3232
case (ommers, pendingTxs) =>
3333
blockGenerator
3434
.generateBlock(parentBlock, pendingTxs.pendingTransactions.map(_.stx.tx), coinbase, ommers.headers) match {
@@ -38,8 +38,8 @@ class EthashBlockCreator(
3838
}
3939
}
4040

41-
private def getOmmersFromPool(blockNumber: BigInt): Future[OmmersPool.Ommers] = {
42-
(ommersPool ? OmmersPool.GetOmmers(blockNumber))(Timeout(miningConfig.ommerPoolQueryTimeout))
41+
private def getOmmersFromPool(parentBlockHash: ByteString): Future[OmmersPool.Ommers] = {
42+
(ommersPool ? OmmersPool.GetOmmers(parentBlockHash))(Timeout(miningConfig.ommerPoolQueryTimeout))
4343
.mapTo[OmmersPool.Ommers]
4444
.recover {
4545
case ex =>

src/main/scala/io/iohk/ethereum/jsonrpc/EthService.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ class EthService(
507507
import io.iohk.ethereum.consensus.ethash.EthashUtils.{epoch, seed}
508508

509509
val bestBlock = blockchain.getBestBlock()
510-
getOmmersFromPool(bestBlock.header.number + 1).zip(getTransactionsFromPool).map { case (ommers, pendingTxs) =>
510+
getOmmersFromPool(bestBlock.hash).zip(getTransactionsFromPool).map { case (ommers, pendingTxs) =>
511511
val blockGenerator = ethash.blockGenerator
512512
blockGenerator.generateBlock(
513513
bestBlock,
@@ -530,12 +530,12 @@ class EthService(
530530
}
531531
})(Future.successful(Left(JsonRpcErrors.ConsensusIsNotEthash)))
532532

533-
private def getOmmersFromPool(blockNumber: BigInt): Future[OmmersPool.Ommers] =
533+
private def getOmmersFromPool(parentBlockHash: ByteString): Future[OmmersPool.Ommers] =
534534
consensus.ifEthash(ethash {
535535
val miningConfig = ethash.config.specific
536536
implicit val timeout: Timeout = Timeout(miningConfig.ommerPoolQueryTimeout)
537537

538-
(ommersPool ? OmmersPool.GetOmmers(blockNumber))
538+
(ommersPool ? OmmersPool.GetOmmers(parentBlockHash))
539539
.mapTo[OmmersPool.Ommers]
540540
.recover { case ex =>
541541
log.error("failed to get ommer, mining block with empty ommers list", ex)
Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,66 @@
11
package io.iohk.ethereum.ommers
22

3-
import akka.actor.{Actor, Props}
3+
import akka.util.ByteString
4+
import akka.actor.{Actor, ActorLogging, Props}
5+
import org.bouncycastle.util.encoders.Hex
46
import io.iohk.ethereum.domain.{BlockHeader, Blockchain}
57
import io.iohk.ethereum.ommers.OmmersPool.{AddOmmers, GetOmmers, RemoveOmmers}
8+
import scala.annotation.tailrec
69

7-
class OmmersPool(blockchain: Blockchain, ommersPoolSize: Int) extends Actor {
10+
class OmmersPool(blockchain: Blockchain, ommersPoolSize: Int, ommerGenerationLimit: Int)
11+
extends Actor
12+
with ActorLogging {
813

914
var ommersPool: Seq[BlockHeader] = Nil
1015

11-
val ommerGenerationLimit: Int = 6 //Stated on section 11.1, eq. (143) of the YP
1216
val ommerSizeLimit: Int = 2
1317

1418
override def receive: Receive = {
1519
case AddOmmers(ommers) =>
1620
ommersPool = (ommers ++ ommersPool).take(ommersPoolSize).distinct
21+
logStatus(event = "Ommers after add", ommers = ommersPool)
1722

1823
case RemoveOmmers(ommers) =>
1924
val toDelete = ommers.map(_.hash).toSet
2025
ommersPool = ommersPool.filter(b => !toDelete.contains(b.hash))
26+
logStatus(event = "Ommers after remove", ommers = ommersPool)
2127

22-
case GetOmmers(blockNumber) =>
23-
val ommers = ommersPool.filter { b =>
24-
val generationDifference = blockNumber - b.number
25-
generationDifference > 0 && generationDifference <= ommerGenerationLimit
26-
}.filter { b =>
27-
blockchain.getBlockHeaderByHash(b.parentHash).isDefined
28-
}.take(ommerSizeLimit)
28+
case GetOmmers(parentBlockHash) =>
29+
val ancestors = collectAncestors(parentBlockHash, ommerGenerationLimit)
30+
val ommers = ommersPool
31+
.filter { b =>
32+
val notAncestor = ancestors.find(_.hash == b.hash).isEmpty
33+
ancestors.find(_.hash == b.parentHash).isDefined && notAncestor
34+
}
35+
.take(ommerSizeLimit)
36+
logStatus(event = s"Ommers given parent block ${Hex.toHexString(parentBlockHash.toArray)}", ommers)
2937
sender() ! OmmersPool.Ommers(ommers)
3038
}
39+
40+
private def collectAncestors(parentHash: ByteString, generationLimit: Int): List[BlockHeader] = {
41+
assert(generationLimit > 0)
42+
@tailrec
43+
def rec(hash: ByteString, limit: Int, acc: List[BlockHeader]): List[BlockHeader] = {
44+
blockchain.getBlockHeaderByHash(hash) match {
45+
case Some(bh) if (limit >= 0) => rec(bh.parentHash, limit - 1, acc ++ List(bh))
46+
case _ => acc
47+
}
48+
}
49+
rec(parentHash, generationLimit - 1, List.empty)
50+
}
51+
52+
private def logStatus(event: String, ommers: Seq[BlockHeader]): Unit = {
53+
val ommersAsString: Seq[String] = ommers.map { bh => s"[number = ${bh.number}, hash = ${bh.hashAsHexString}]" }
54+
log.debug(s"$event ${ommersAsString}")
55+
}
3156
}
3257

3358
object OmmersPool {
34-
def props(blockchain: Blockchain, ommersPoolSize: Int): Props = Props(new OmmersPool(blockchain, ommersPoolSize))
59+
60+
// ommerGenerationLimit should be === 6 as is stated on section 11.1, eq. (143) of the YP
61+
def props(blockchain: Blockchain, ommersPoolSize: Int, ommerGenerationLimit: Int = 6): Props = Props(
62+
new OmmersPool(blockchain, ommersPoolSize, ommerGenerationLimit)
63+
)
3564

3665
case class AddOmmers(ommers: List[BlockHeader])
3766

@@ -45,7 +74,7 @@ object OmmersPool {
4574
def apply(b: BlockHeader*): RemoveOmmers = RemoveOmmers(b.toList)
4675
}
4776

48-
case class GetOmmers(blockNumber: BigInt)
77+
case class GetOmmers(parentBlockHash: ByteString)
4978

5079
case class Ommers(headers: Seq[BlockHeader])
5180
}

src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ class EthServiceSpec
427427
pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions)
428428
pendingTransactionsManager.reply(PendingTransactionsManager.PendingTransactionsResponse(Nil))
429429

430-
ommersPool.expectMsg(OmmersPool.GetOmmers(1))
430+
ommersPool.expectMsg(OmmersPool.GetOmmers(ByteString.empty))
431431
ommersPool.reply(OmmersPool.Ommers(Nil))
432432

433433
response.futureValue shouldEqual Right(GetWorkResponse(powHash, seedHash, target))

src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerSpec.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ class JsonRpcControllerSpec
628628
pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions)
629629
pendingTransactionsManager.reply(PendingTransactionsManager.PendingTransactionsResponse(Nil))
630630

631-
ommersPool.expectMsg(OmmersPool.GetOmmers(2))
631+
ommersPool.expectMsg(OmmersPool.GetOmmers(parentBlock.hash))
632632
ommersPool.reply(Ommers(Nil))
633633

634634
val response = result.futureValue
@@ -674,7 +674,7 @@ class JsonRpcControllerSpec
674674
val result: Future[JsonRpcResponse] = jsonRpcController.handleRequest(request)
675675

676676
pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions)
677-
ommersPool.expectMsg(OmmersPool.GetOmmers(2))
677+
ommersPool.expectMsg(OmmersPool.GetOmmers(parentBlock.hash))
678678
//on time out it should respond with empty list
679679

680680
val response = result.futureValue(timeout(Timeouts.longTimeout))

0 commit comments

Comments
 (0)