Skip to content

Commit 8e45269

Browse files
authored
[ETCM-177] Fix Ommers pool (#724)
- 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 327d295 commit 8e45269

File tree

7 files changed

+277
-80
lines changed

7 files changed

+277
-80
lines changed

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ class BlockImporter(
4545
start()
4646
}
4747

48-
private def idle: Receive = {
49-
case Start => start()
48+
private def idle: Receive = { case Start =>
49+
start()
5050
}
5151

5252
private def handleTopMessages(state: ImporterState, currentBehavior: Behavior): Receive = {
@@ -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) =>
@@ -96,9 +94,10 @@ class BlockImporter(
9694
}
9795

9896
private def pickBlocks(state: ImporterState): Unit = {
99-
val msg = state.resolvingBranchFrom.fold[BlockFetcher.FetchMsg](
100-
BlockFetcher.PickBlocks(syncConfig.blocksBatchSize))(
101-
from => BlockFetcher.StrictPickBlocks(from, startingBlockNumber))
97+
val msg =
98+
state.resolvingBranchFrom.fold[BlockFetcher.FetchMsg](BlockFetcher.PickBlocks(syncConfig.blocksBatchSize))(from =>
99+
BlockFetcher.StrictPickBlocks(from, startingBlockNumber)
100+
)
102101

103102
fetcher ! msg
104103
}
@@ -146,8 +145,9 @@ class BlockImporter(
146145
}
147146
}
148147

149-
private def tryImportBlocks(blocks: List[Block], importedBlocks: List[Block] = Nil)(
150-
implicit ec: ExecutionContext): Future[(List[Block], Option[Any])] =
148+
private def tryImportBlocks(blocks: List[Block], importedBlocks: List[Block] = Nil)(implicit
149+
ec: ExecutionContext
150+
): Future[(List[Block], Option[Any])] =
151151
if (blocks.isEmpty) {
152152
Future.successful((importedBlocks, None))
153153
} else {
@@ -294,9 +294,11 @@ object BlockImporter {
294294
syncConfig: SyncConfig,
295295
ommersPool: ActorRef,
296296
broadcaster: ActorRef,
297-
pendingTransactionsManager: ActorRef): Props =
297+
pendingTransactionsManager: ActorRef
298+
): Props =
298299
Props(
299-
new BlockImporter(fetcher, ledger, blockchain, syncConfig, ommersPool, broadcaster, pendingTransactionsManager))
300+
new BlockImporter(fetcher, ledger, blockchain, syncConfig, ommersPool, broadcaster, pendingTransactionsManager)
301+
)
300302

301303
type Behavior = ImporterState => Receive
302304
type ImportFn = ImporterState => Unit

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

Lines changed: 12 additions & 14 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,23 +28,21 @@ 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 {
32-
case (ommers, pendingTxs) =>
33-
blockGenerator
34-
.generateBlock(parentBlock, pendingTxs.pendingTransactions.map(_.stx.tx), coinbase, ommers.headers) match {
35-
case Right(pb) => Future.successful(pb)
36-
case Left(err) => Future.failed(new RuntimeException(s"Error while generating block for mining: $err"))
37-
}
31+
getOmmersFromPool(parentBlock.hash).zip(transactions).flatMap { case (ommers, pendingTxs) =>
32+
blockGenerator
33+
.generateBlock(parentBlock, pendingTxs.pendingTransactions.map(_.stx.tx), coinbase, ommers.headers) match {
34+
case Right(pb) => Future.successful(pb)
35+
case Left(err) => Future.failed(new RuntimeException(s"Error while generating block for mining: $err"))
36+
}
3837
}
3938
}
4039

41-
private def getOmmersFromPool(blockNumber: BigInt): Future[OmmersPool.Ommers] = {
42-
(ommersPool ? OmmersPool.GetOmmers(blockNumber))(Timeout(miningConfig.ommerPoolQueryTimeout))
40+
private def getOmmersFromPool(parentBlockHash: ByteString): Future[OmmersPool.Ommers] = {
41+
(ommersPool ? OmmersPool.GetOmmers(parentBlockHash))(Timeout(miningConfig.ommerPoolQueryTimeout))
4342
.mapTo[OmmersPool.Ommers]
44-
.recover {
45-
case ex =>
46-
log.error("Failed to get ommers, mining block with empty ommers list", ex)
47-
OmmersPool.Ommers(Nil)
43+
.recover { case ex =>
44+
log.error("Failed to get ommers, mining block with empty ommers list", ex)
45+
OmmersPool.Ommers(Nil)
4846
}
4947
}
5048

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: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,79 @@
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, returnedOmmersSizeLimit: 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
12-
val ommerSizeLimit: Int = 2
13-
1416
override def receive: Receive = {
1517
case AddOmmers(ommers) =>
1618
ommersPool = (ommers ++ ommersPool).take(ommersPoolSize).distinct
19+
logStatus(event = "Ommers after add", ommers = ommersPool)
1720

1821
case RemoveOmmers(ommers) =>
1922
val toDelete = ommers.map(_.hash).toSet
2023
ommersPool = ommersPool.filter(b => !toDelete.contains(b.hash))
24+
logStatus(event = "Ommers after remove", ommers = ommersPool)
2125

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)
26+
case GetOmmers(parentBlockHash) =>
27+
val ancestors = collectAncestors(parentBlockHash, ommerGenerationLimit)
28+
val ommers = ommersPool
29+
.filter { b =>
30+
val notAncestor = ancestors.find(_.hash == b.hash).isEmpty
31+
ancestors.find(_.hash == b.parentHash).isDefined && notAncestor
32+
}
33+
.take(returnedOmmersSizeLimit)
34+
logStatus(event = s"Ommers given parent block ${Hex.toHexString(parentBlockHash.toArray)}", ommers)
2935
sender() ! OmmersPool.Ommers(ommers)
3036
}
37+
38+
private def collectAncestors(parentHash: ByteString, generationLimit: Int): List[BlockHeader] = {
39+
@tailrec
40+
def rec(hash: ByteString, limit: Int, acc: List[BlockHeader]): List[BlockHeader] = {
41+
if (limit > 0) {
42+
blockchain.getBlockHeaderByHash(hash) match {
43+
case Some(bh) => rec(bh.parentHash, limit - 1, acc :+ bh)
44+
case None => acc
45+
}
46+
} else {
47+
acc
48+
}
49+
}
50+
rec(parentHash, generationLimit, List.empty)
51+
}
52+
53+
private def logStatus(event: String, ommers: Seq[BlockHeader]): Unit = {
54+
lazy val ommersAsString: Seq[String] = ommers.map { bh => s"[number = ${bh.number}, hash = ${bh.hashAsHexString}]" }
55+
log.debug(s"$event ${ommersAsString}")
56+
}
3157
}
3258

3359
object OmmersPool {
34-
def props(blockchain: Blockchain, ommersPoolSize: Int): Props = Props(new OmmersPool(blockchain, ommersPoolSize))
60+
61+
/**
62+
* As is stated on section 11.1, eq. (143) of the YP
63+
*
64+
* @param ommerGenerationLimit should be === 6
65+
* @param returnedOmmersSizeLimit should be === 2
66+
*
67+
* ^ Probably not worthy but those params could be placed in consensus config.
68+
*/
69+
def props(
70+
blockchain: Blockchain,
71+
ommersPoolSize: Int,
72+
ommerGenerationLimit: Int = 6,
73+
returnedOmmersSizeLimit: Int = 2
74+
): Props = Props(
75+
new OmmersPool(blockchain, ommersPoolSize, ommerGenerationLimit, returnedOmmersSizeLimit)
76+
)
3577

3678
case class AddOmmers(ommers: List[BlockHeader])
3779

@@ -45,7 +87,7 @@ object OmmersPool {
4587
def apply(b: BlockHeader*): RemoveOmmers = RemoveOmmers(b.toList)
4688
}
4789

48-
case class GetOmmers(blockNumber: BigInt)
90+
case class GetOmmers(parentBlockHash: ByteString)
4991

5092
case class Ommers(headers: Seq[BlockHeader])
5193
}

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(parentBlock.hash))
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: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ class JsonRpcControllerSpec
224224
val blockToRequest = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body)
225225
val blockTd = blockToRequest.header.difficulty
226226

227-
blockchain.storeBlock(blockToRequest)
227+
blockchain
228+
.storeBlock(blockToRequest)
228229
.and(blockchain.storeTotalDifficulty(blockToRequest.header.hash, blockTd))
229230
.commit()
230231

@@ -250,7 +251,8 @@ class JsonRpcControllerSpec
250251
val blockToRequest = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body)
251252
val blockTd = blockToRequest.header.difficulty
252253

253-
blockchain.storeBlock(blockToRequest)
254+
blockchain
255+
.storeBlock(blockToRequest)
254256
.and(blockchain.storeTotalDifficulty(blockToRequest.header.hash, blockTd))
255257
.commit()
256258

@@ -628,7 +630,7 @@ class JsonRpcControllerSpec
628630
pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions)
629631
pendingTransactionsManager.reply(PendingTransactionsManager.PendingTransactionsResponse(Nil))
630632

631-
ommersPool.expectMsg(OmmersPool.GetOmmers(2))
633+
ommersPool.expectMsg(OmmersPool.GetOmmers(parentBlock.hash))
632634
ommersPool.reply(Ommers(Nil))
633635

634636
val response = result.futureValue
@@ -674,7 +676,7 @@ class JsonRpcControllerSpec
674676
val result: Future[JsonRpcResponse] = jsonRpcController.handleRequest(request)
675677

676678
pendingTransactionsManager.expectMsg(PendingTransactionsManager.GetPendingTransactions)
677-
ommersPool.expectMsg(OmmersPool.GetOmmers(2))
679+
ommersPool.expectMsg(OmmersPool.GetOmmers(parentBlock.hash))
678680
//on time out it should respond with empty list
679681

680682
val response = result.futureValue(timeout(Timeouts.longTimeout))
@@ -786,7 +788,9 @@ class JsonRpcControllerSpec
786788
}
787789

788790
it should "eth_gasPrice" in new TestSetup {
789-
blockchain.storeBlock(Block(Fixtures.Blocks.Block3125369.header.copy(number = 42), Fixtures.Blocks.Block3125369.body)).commit()
791+
blockchain
792+
.storeBlock(Block(Fixtures.Blocks.Block3125369.header.copy(number = 42), Fixtures.Blocks.Block3125369.body))
793+
.commit()
790794
blockchain.saveBestKnownBlock(42)
791795

792796
val request: JsonRpcRequest = JsonRpcRequest(

0 commit comments

Comments
 (0)