Skip to content

Commit 2e078af

Browse files
author
Aurélien Richez
authored
[ETCM-969] simple Branch implementation (#1039)
* create Branch class with getBlockByNumber and getHashByBlockNumber functions * make getBlockByNumber private in blockchain reader * Create EmptyBlochainBranch and make getBestChain return it if there no known best block * Rephrase scaladoc comment and remove TODO
1 parent 2940e56 commit 2e078af

27 files changed

+175
-49
lines changed

src/it/scala/io/iohk/ethereum/sync/RegularSyncItSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class RegularSyncItSpec extends FreeSpecBase with Matchers with BeforeAndAfterAl
124124
_ <- peer2.importBlocksUntil(30)(IdentityUpdate)
125125
_ <- peer1.startRegularSync()
126126
_ <- peer2.startRegularSync()
127-
_ <- peer2.addCheckpointedBlock(peer2.blockchainReader.getBlockByNumber(25).get)
127+
_ <- peer2.addCheckpointedBlock(peer2.blockchainReader.getBestBranch().getBlockByNumber(25).get)
128128
_ <- peer2.waitForRegularSyncLoadLastBlock(length)
129129
_ <- peer1.connectToPeers(Set(peer2.node))
130130
_ <- peer1.waitForRegularSyncLoadLastBlock(length)
@@ -181,8 +181,8 @@ class RegularSyncItSpec extends FreeSpecBase with Matchers with BeforeAndAfterAl
181181
)
182182
)
183183
(
184-
peer1.blockchainReader.getBlockByNumber(blockNumer + 1),
185-
peer2.blockchainReader.getBlockByNumber(blockNumer + 1)
184+
peer1.blockchainReader.getBestBranch().getBlockByNumber(blockNumer + 1),
185+
peer2.blockchainReader.getBestBranch().getBlockByNumber(blockNumer + 1)
186186
) match {
187187
case (Some(blockP1), Some(blockP2)) =>
188188
assert(peer1.bl.getChainWeightByHash(blockP1.hash) == peer2.bl.getChainWeightByHash(blockP2.hash))

src/it/scala/io/iohk/ethereum/sync/util/RegularSyncItSpecUtils.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ object RegularSyncItSpecUtils {
187187
Task(blockNumber match {
188188
case Some(bNumber) =>
189189
blockchainReader
190+
.getBestBranch()
190191
.getBlockByNumber(bNumber)
191192
.getOrElse(throw new RuntimeException(s"block by number: $bNumber doesn't exist"))
192193
case None => blockchainReader.getBestBlock().get

src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.iohk.ethereum.db.storage.pruning.PruningMode
2626
import io.iohk.ethereum.domain.BlockHeader.HeaderExtraFields.HefEmpty
2727
import io.iohk.ethereum.domain.Blockchain
2828
import io.iohk.ethereum.domain._
29+
import io.iohk.ethereum.domain.branch.Branch
2930
import io.iohk.ethereum.jsonrpc.ProofService.EmptyStorageValueProof
3031
import io.iohk.ethereum.jsonrpc.ProofService.StorageProof
3132
import io.iohk.ethereum.jsonrpc.ProofService.StorageProofKey
@@ -101,7 +102,9 @@ object DumpChainApp
101102

102103
val blockchain: Blockchain = new BlockchainMock(genesisHash)
103104
val blockchainReader: BlockchainReader = mock[BlockchainReader]
104-
(blockchainReader.getHashByBlockNumber _).expects(*).returning(Some(genesisHash))
105+
val bestChain: Branch = mock[Branch]
106+
(blockchainReader.getBestBranch _).expects().anyNumberOfTimes().returning(bestChain)
107+
(bestChain.getHashByBlockNumber _).expects(*).returning(Some(genesisHash))
105108

106109
val nodeStatus: NodeStatus =
107110
NodeStatus(key = nodeKey, serverStatus = ServerStatus.NotListening, discoveryStatus = ServerStatus.NotListening)

src/main/scala/io/iohk/ethereum/consensus/pow/validators/OmmersValidator.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ trait OmmersValidator {
2929
): Either[OmmersError, OmmersValid] = {
3030

3131
val getBlockHeaderByHash: ByteString => Option[BlockHeader] = blockchainReader.getBlockHeaderByHash
32+
val bestBranch = blockchainReader.getBestBranch()
3233
val getNBlocksBack: (ByteString, Int) => List[Block] =
33-
(_, n) => ((blockNumber - n) until blockNumber).toList.flatMap(blockchainReader.getBlockByNumber)
34+
(_, n) =>
35+
((blockNumber - n) until blockNumber).toList
36+
.flatMap(nb => bestBranch.getBlockByNumber(nb))
3437

3538
validate(parentHash, blockNumber, ommers, getBlockHeaderByHash, getNBlocksBack)
3639
}

src/main/scala/io/iohk/ethereum/domain/Blockchain.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class BlockchainImpl(
103103
override def isInChain(hash: ByteString): Boolean =
104104
(for {
105105
header <- blockchainReader.getBlockHeaderByHash(hash) if header.number <= blockchainReader.getBestBlockNumber()
106-
hash <- blockchainReader.getHashByBlockNumber(header.number)
106+
hash <- blockchainReader.getBestBranch().getHashByBlockNumber(header.number)
107107
} yield header.hash == hash).getOrElse(false)
108108

109109
override def getChainWeightByHash(blockhash: ByteString): Option[ChainWeight] = chainWeightStorage.get(blockhash)
@@ -223,7 +223,7 @@ class BlockchainImpl(
223223
val latestCheckpointNumber = getLatestCheckpointBlockNumber()
224224

225225
val blockNumberMappingUpdates =
226-
if (blockchainReader.getHashByBlockNumber(block.number).contains(blockHash))
226+
if (blockchainReader.getBestBranch().getHashByBlockNumber(block.number).contains(blockHash))
227227
removeBlockNumberMapping(block.number)
228228
else blockNumberMappingStorage.emptyBatchUpdate
229229

@@ -292,7 +292,7 @@ class BlockchainImpl(
292292
): BigInt =
293293
if (blockNumberToCheck > 0) {
294294
val maybePreviousCheckpointBlockNumber = for {
295-
currentBlock <- blockchainReader.getBlockByNumber(blockNumberToCheck)
295+
currentBlock <- blockchainReader.getBestBranch().getBlockByNumber(blockNumberToCheck)
296296
if currentBlock.hasCheckpoint &&
297297
currentBlock.number < latestCheckpointBlockNumber
298298
} yield currentBlock.number

src/main/scala/io/iohk/ethereum/domain/BlockchainReader.scala

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import io.iohk.ethereum.db.storage.BlockHeadersStorage
88
import io.iohk.ethereum.db.storage.BlockNumberMappingStorage
99
import io.iohk.ethereum.db.storage.ReceiptStorage
1010
import io.iohk.ethereum.db.storage.StateStorage
11+
import io.iohk.ethereum.domain.branch.BestBranch
12+
import io.iohk.ethereum.domain.branch.Branch
13+
import io.iohk.ethereum.domain.branch.EmptyBranch$
1114
import io.iohk.ethereum.mpt.MptNode
1215
import io.iohk.ethereum.utils.Logger
1316

@@ -48,31 +51,12 @@ class BlockchainReader(
4851
body <- getBlockBodyByHash(hash)
4952
} yield Block(header, body)
5053

51-
/** Returns a block hash given a block number
52-
*
53-
* @param number Number of the searchead block
54-
* @return Block hash if found
55-
*/
56-
def getHashByBlockNumber(number: BigInt): Option[ByteString] =
57-
blockNumberMappingStorage.get(number)
58-
5954
def getBlockHeaderByNumber(number: BigInt): Option[BlockHeader] =
6055
for {
6156
hash <- getHashByBlockNumber(number)
6257
header <- getBlockHeaderByHash(hash)
6358
} yield header
6459

65-
/** Allows to query for a block based on it's number
66-
*
67-
* @param number Block number
68-
* @return Block if it exists
69-
*/
70-
def getBlockByNumber(number: BigInt): Option[Block] =
71-
for {
72-
hash <- getHashByBlockNumber(number)
73-
block <- getBlockByHash(hash)
74-
} yield block
75-
7660
/** Returns MPT node searched by it's hash
7761
* @param hash Node Hash
7862
* @return MPT node
@@ -86,6 +70,18 @@ class BlockchainReader(
8670
*/
8771
def getReceiptsByHash(blockhash: ByteString): Option[Seq[Receipt]] = receiptStorage.get(blockhash)
8872

73+
/** get the current best stored branch */
74+
def getBestBranch(): Branch =
75+
getBestBlock()
76+
.map { block =>
77+
new BestBranch(
78+
block.header,
79+
blockNumberMappingStorage,
80+
this
81+
)
82+
}
83+
.getOrElse(EmptyBranch$)
84+
8985
def getBestBlockNumber(): BigInt = {
9086
val bestSavedBlockNumber = appStateStorage.getBestBlockNumber()
9187
val bestKnownBlockNumber = blockchainMetadata.bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber
@@ -116,6 +112,25 @@ class BlockchainReader(
116112

117113
def genesisBlock: Block =
118114
getBlockByNumber(0).get
115+
116+
/** Allows to query for a block based on it's number
117+
*
118+
* @param number Block number
119+
* @return Block if it exists
120+
*/
121+
private def getBlockByNumber(number: BigInt): Option[Block] =
122+
for {
123+
hash <- getHashByBlockNumber(number)
124+
block <- getBlockByHash(hash)
125+
} yield block
126+
127+
/** Returns a block hash given a block number
128+
*
129+
* @param number Number of the searchead block
130+
* @return Block hash if found
131+
*/
132+
private def getHashByBlockNumber(number: BigInt): Option[ByteString] =
133+
blockNumberMappingStorage.get(number)
119134
}
120135

121136
object BlockchainReader {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.iohk.ethereum.domain.branch
2+
import akka.util.ByteString
3+
4+
import io.iohk.ethereum.db.storage.BlockNumberMappingStorage
5+
import io.iohk.ethereum.domain.Block
6+
import io.iohk.ethereum.domain.BlockHeader
7+
import io.iohk.ethereum.domain.BlockchainReader
8+
9+
/** A Branch instance which only works for the best canonical branch or a subset of this branch.
10+
* This implementation uses the existing storage indexes to access blocks by number more efficiently.
11+
*/
12+
class BestBranch(
13+
tipBlockHeader: BlockHeader,
14+
bestChainBlockNumberMappingStorage: BlockNumberMappingStorage,
15+
blockchainReader: BlockchainReader
16+
) extends Branch {
17+
18+
/* The following assumptions are made in this class :
19+
* - The whole branch exists in storage
20+
* - The various metadata and index are consistent
21+
*/
22+
23+
override def getBlockByNumber(number: BigInt): Option[Block] =
24+
if (tipBlockHeader.number >= number && number >= 0) {
25+
for {
26+
hash <- getHashByBlockNumber(number)
27+
block <- blockchainReader.getBlockByHash(hash)
28+
} yield block
29+
} else None
30+
31+
override def getHashByBlockNumber(number: BigInt): Option[ByteString] =
32+
if (tipBlockHeader.number >= number && number >= 0) {
33+
bestChainBlockNumberMappingStorage.get(number)
34+
} else None
35+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.iohk.ethereum.domain.branch
2+
3+
import akka.util.ByteString
4+
5+
import io.iohk.ethereum.domain.Block
6+
7+
/** An interface to manipulate blockchain branches */
8+
trait Branch {
9+
10+
/** Returns a block inside this branch based on its number */
11+
def getBlockByNumber(number: BigInt): Option[Block]
12+
13+
/** Returns a block hash for the block at the given height if any */
14+
def getHashByBlockNumber(number: BigInt): Option[ByteString]
15+
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.iohk.ethereum.domain.branch
2+
import akka.util.ByteString
3+
4+
import io.iohk.ethereum.domain.Block
5+
6+
object EmptyBranch$ extends Branch {
7+
8+
override def getBlockByNumber(number: BigInt): Option[Block] = None
9+
10+
override def getHashByBlockNumber(number: BigInt): Option[ByteString] = None
11+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class CheckpointingService(
3636
req.parentCheckpoint.forall(blockchainReader.getBlockHeaderByHash(_).exists(_.number < blockToReturnNum))
3737

3838
Task {
39-
blockchainReader.getBlockByNumber(blockToReturnNum)
39+
blockchainReader.getBestBranch().getBlockByNumber(blockToReturnNum)
4040
}.flatMap {
4141
case Some(b) if isValidParent =>
4242
Task.now(Right(GetLatestBlockResponse(Some(BlockInfo(b.hash, b.number)))))

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ class EthProofService(
205205
private def resolveBlock(blockParam: BlockParam): Either[JsonRpcError, ResolvedBlock] = {
206206
def getBlock(number: BigInt): Either[JsonRpcError, Block] =
207207
blockchainReader
208+
.getBestBranch()
208209
.getBlockByNumber(number)
209210
.toRight(JsonRpcError.InvalidParams(s"Block $number not found"))
210211

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,9 @@ class EthTxService(
156156
val bestBlock = blockchainReader.getBestBlockNumber()
157157

158158
Task {
159+
val bestBranch = blockchainReader.getBestBranch()
159160
val gasPrice = ((bestBlock - blockDifference) to bestBlock)
160-
.flatMap(blockchainReader.getBlockByNumber)
161+
.flatMap(nb => bestBranch.getBlockByNumber(nb))
161162
.flatMap(_.body.transactionList)
162163
.map(_.tx.gasPrice)
163164
if (gasPrice.nonEmpty) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ trait ResolveBlock {
3434

3535
private def getBlock(number: BigInt): Either[JsonRpcError, Block] =
3636
blockchainReader
37+
.getBestBranch()
3738
.getBlockByNumber(number)
3839
.toRight(JsonRpcError.InvalidParams(s"Block $number not found"))
3940

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ class TestService(
372372

373373
val blockOpt = request.parameters.blockHashOrNumber
374374
.fold(
375-
number => blockchainReader.getBlockByNumber(number),
375+
number => blockchainReader.getBestBranch().getBlockByNumber(number),
376376
blockHash => blockchainReader.getBlockByHash(blockHash)
377377
)
378378

@@ -411,7 +411,10 @@ class TestService(
411411
def storageRangeAt(request: StorageRangeRequest): ServiceResponse[StorageRangeResponse] = {
412412

413413
val blockOpt = request.parameters.blockHashOrNumber
414-
.fold(number => blockchainReader.getBlockByNumber(number), hash => blockchainReader.getBlockByHash(hash))
414+
.fold(
415+
number => blockchainReader.getBestBranch().getBlockByNumber(number),
416+
hash => blockchainReader.getBlockByHash(hash)
417+
)
415418

416419
(for {
417420
block <- blockOpt.toRight(StorageRangeResponse(complete = false, Map.empty, None))

src/main/scala/io/iohk/ethereum/ledger/BlockImport.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ class BlockImport(
305305
private def removeBlocksUntil(parent: ByteString, fromNumber: BigInt): List[BlockData] = {
306306
@tailrec
307307
def removeBlocksUntil(parent: ByteString, fromNumber: BigInt, acc: List[BlockData]): List[BlockData] =
308-
blockchainReader.getBlockByNumber(fromNumber) match {
308+
blockchainReader.getBestBranch().getBlockByNumber(fromNumber) match {
309309
case Some(block) if block.header.hash == parent || fromNumber == 0 =>
310310
acc
311311

src/main/scala/io/iohk/ethereum/ledger/BlockValidation.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ class BlockValidation(
4141
val remaining = n - queuedBlocks.length - 1
4242

4343
val numbers = (block.header.number - remaining) until block.header.number
44-
val blocks = (numbers.toList.flatMap(blockchainReader.getBlockByNumber) :+ block) ::: queuedBlocks
44+
val bestBranch = blockchainReader.getBestBranch()
45+
val blocks =
46+
(numbers.toList.flatMap(nb => bestBranch.getBlockByNumber(nb)) :+ block) ::: queuedBlocks
4547
blocks
4648
}
4749
}

src/main/scala/io/iohk/ethereum/ledger/BranchResolution.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,12 @@ class BranchResolution(blockchain: Blockchain, blockchainReader: BlockchainReade
7272
}
7373
}
7474

75-
private def getTopBlocksFromNumber(from: BigInt): List[Block] =
75+
private def getTopBlocksFromNumber(from: BigInt): List[Block] = {
76+
val bestBranch = blockchainReader.getBestBranch()
7677
(from to blockchainReader.getBestBlockNumber())
77-
.flatMap(blockchainReader.getBlockByNumber)
78+
.flatMap(nb => bestBranch.getBlockByNumber(nb))
7879
.toList
80+
}
7981
}
8082

8183
sealed trait BranchResolutionResult

src/main/scala/io/iohk/ethereum/testmode/TestEthBlockServiceWrapper.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ class TestEthBlockServiceWrapper(
7575
.getBlockByNumber(request)
7676
.map(
7777
_.map { blockByBlockResponse =>
78-
val fullBlock = blockchainReader.getBlockByNumber(blockByBlockResponse.blockResponse.get.number).get
78+
val bestBranch = blockchainReader.getBestBranch()
79+
val fullBlock = bestBranch.getBlockByNumber(blockByBlockResponse.blockResponse.get.number).get
7980
BlockByNumberResponse(blockByBlockResponse.blockResponse.map(response => toEthResponse(fullBlock, response)))
8081
}
8182
)

src/main/scala/io/iohk/ethereum/transactions/TransactionHistoryService.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class TransactionHistoryService(
3333
val getLastCheckpoint = Task(blockchain.getLatestCheckpointBlockNumber()).memoizeOnSuccess
3434
val txnsFromBlocks = Observable
3535
.from(fromBlocks.reverse)
36-
.mapParallelOrdered(10)(blockNr => Task(blockchainReader.getBlockByNumber(blockNr)))(
36+
.mapParallelOrdered(10)(blockNr => Task(blockchainReader.getBestBranch().getBlockByNumber(blockNr)))(
3737
OverflowStrategy.Unbounded
3838
)
3939
.collect { case Some(block) => block }

src/test/scala/io/iohk/ethereum/consensus/pow/validators/StdOmmersValidatorSpec.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ class StdOmmersValidatorSpec extends AnyFlatSpec with Matchers with ScalaCheckPr
7979

8080
it should "report failure if there is an ommer which that is not parent of an ancestor" in new BlockUtils {
8181
val getNBlocksBack: (ByteString, Int) => List[Block] =
82-
(_, n) => ((ommersBlockNumber - n) until ommersBlockNumber).toList.flatMap(blockchainReader.getBlockByNumber)
82+
(_, n) =>
83+
((ommersBlockNumber - n) until ommersBlockNumber).toList
84+
.flatMap(nb => blockchainReader.getBestBranch().getBlockByNumber(nb))
8385

8486
ommersValidator.validateOmmersAncestors(
8587
ommersBlockParentHash,
@@ -462,6 +464,7 @@ class StdOmmersValidatorSpec extends AnyFlatSpec with Matchers with ScalaCheckPr
462464
.and(blockchainWriter.storeBlock(block95))
463465
.and(blockchainWriter.storeBlock(block96))
464466
.commit()
467+
blockchain.saveBestKnownBlocks(block96.number)
465468

466469
}
467470
}

src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh
4444
it should "be able to store a block and retrieve it by number" in new EphemBlockchainTestSetup {
4545
val validBlock = Fixtures.Blocks.ValidBlock.block
4646
blockchainWriter.storeBlock(validBlock).commit()
47-
val block = blockchainReader.getBlockByNumber(validBlock.header.number)
47+
blockchain.saveBestKnownBlocks(validBlock.number)
48+
val block = blockchainReader.getBestBranch().getBlockByNumber(validBlock.header.number)
4849
block.isDefined should ===(true)
4950
validBlock should ===(block.get)
5051
}
@@ -67,8 +68,10 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh
6768
}
6869

6970
it should "not return a value if not stored" in new EphemBlockchainTestSetup {
70-
blockchainReader.getBlockByNumber(Fixtures.Blocks.ValidBlock.header.number).isEmpty should ===(true)
71-
blockchainReader.getBlockByHash(Fixtures.Blocks.ValidBlock.header.hash).isEmpty should ===(true)
71+
blockchainReader
72+
.getBestBranch()
73+
.getBlockByNumber(Fixtures.Blocks.ValidBlock.header.number) shouldBe None
74+
blockchainReader.getBlockByHash(Fixtures.Blocks.ValidBlock.header.hash) shouldBe None
7275
}
7376

7477
it should "be able to store a block with checkpoint and retrieve it and checkpoint" in new EphemBlockchainTestSetup {

0 commit comments

Comments
 (0)