@@ -10,10 +10,13 @@ import io.iohk.ethereum.db.storage.TransactionMappingStorage.TransactionLocation
10
10
import io .iohk .ethereum .db .storage ._
11
11
import io .iohk .ethereum .db .storage .pruning .PruningMode
12
12
import io .iohk .ethereum .domain
13
+ import io .iohk .ethereum .domain .BlockchainImpl .BestBlockLatestCheckpointNumbers
13
14
import io .iohk .ethereum .ledger .{InMemoryWorldStateProxy , InMemoryWorldStateProxyStorage }
14
15
import io .iohk .ethereum .mpt .{MerklePatriciaTrie , MptNode }
15
16
import io .iohk .ethereum .vm .{Storage , WorldStateProxy }
16
17
18
+ import scala .annotation .tailrec
19
+
17
20
/**
18
21
* Entity to be used to persist and query Blockchain related objects (blocks, transactions, ommers)
19
22
*/
@@ -123,6 +126,7 @@ trait Blockchain {
123
126
124
127
def getBestBlock (): Block
125
128
129
+ def getLatestCheckpointBlockNumber (): BigInt
126
130
127
131
/**
128
132
* Persists full block along with receipts and total difficulty
@@ -209,7 +213,7 @@ class BlockchainImpl(
209
213
210
214
// There is always only one writer thread (ensured by actor), but can by many readers (api calls)
211
215
// to ensure visibility of writes, needs to be volatile or atomic ref
212
- private val bestKnownBlock : AtomicReference [BigInt ] = new AtomicReference (BigInt (0 ))
216
+ private val bestKnownBlockAndLatestCheckpoint : AtomicReference [BestBlockLatestCheckpointNumbers ] = new AtomicReference (BestBlockLatestCheckpointNumbers ( BigInt (0 ), BigInt ( 0 ) ))
213
217
214
218
override def getBlockHeaderByHash (hash : ByteString ): Option [BlockHeader ] =
215
219
blockHeadersStorage.get(hash)
@@ -225,12 +229,20 @@ class BlockchainImpl(
225
229
226
230
override def getBestBlockNumber (): BigInt = {
227
231
val bestBlockNum = appStateStorage.getBestBlockNumber()
228
- if (bestKnownBlock .get() > bestBlockNum)
229
- bestKnownBlock .get()
232
+ if (bestKnownBlockAndLatestCheckpoint .get().bestBlockNumber > bestBlockNum)
233
+ bestKnownBlockAndLatestCheckpoint .get().bestBlockNumber
230
234
else
231
235
bestBlockNum
232
236
}
233
237
238
+ override def getLatestCheckpointBlockNumber (): BigInt = {
239
+ val latestCheckpointNumberInStorage = appStateStorage.getLatestCheckpointBlockNumber()
240
+ if (bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber > latestCheckpointNumberInStorage)
241
+ bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber
242
+ else
243
+ latestCheckpointNumberInStorage
244
+ }
245
+
234
246
override def getBestBlock (): Block =
235
247
getBlockByNumber(getBestBlockNumber()).get
236
248
@@ -252,8 +264,10 @@ class BlockchainImpl(
252
264
ByteString (mpt.get(position).getOrElse(BigInt (0 )).toByteArray)
253
265
}
254
266
255
- def saveBestBlock (bestBlock : Option [BigInt ]): Unit = {
256
- bestBlock.fold(appStateStorage.putBestBlockNumber(getBestBlockNumber()).commit())(best => appStateStorage.putBestBlockNumber(best).commit())
267
+ private def persistBestBlocksData (): Unit = {
268
+ appStateStorage.putBestBlockNumber(getBestBlockNumber())
269
+ .and(appStateStorage.putLatestCheckpointBlockNumber(getLatestCheckpointBlockNumber()))
270
+ .commit()
257
271
}
258
272
259
273
def save (block : Block , receipts : Seq [Receipt ], totalDifficulty : BigInt , saveAsBestBlock : Boolean ): Unit = {
@@ -263,8 +277,12 @@ class BlockchainImpl(
263
277
.commit()
264
278
265
279
// not transactional part
266
- stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(saveBestBlock)
267
- if (saveAsBestBlock) {
280
+ // the best blocks data will be persisted only when the cache will be persisted
281
+ stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(persistBestBlocksData())
282
+
283
+ if (saveAsBestBlock && block.hasCheckpoint) {
284
+ saveBestKnownBlockAndLatestCheckpointNumber(block.header.number, block.header.number)
285
+ } else if (saveAsBestBlock) {
268
286
saveBestKnownBlock(block.header.number)
269
287
}
270
288
}
@@ -290,7 +308,15 @@ class BlockchainImpl(
290
308
evmCodeStorage.put(hash, evmCode)
291
309
292
310
override def saveBestKnownBlock (number : BigInt ): Unit = {
293
- bestKnownBlock.set(number)
311
+ bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(bestBlockNumber = number))
312
+ }
313
+
314
+ def saveBestKnownBlockAndLatestCheckpointNumber (number : BigInt , latestCheckpointNumber : BigInt ): Unit = {
315
+ bestKnownBlockAndLatestCheckpoint.set(BestBlockLatestCheckpointNumbers (number, latestCheckpointNumber))
316
+ }
317
+
318
+ def saveLatestCheckpointNumber (latestCheckpointNumber : BigInt ): Unit = {
319
+ bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(latestCheckpointNumber = latestCheckpointNumber))
294
320
}
295
321
296
322
def storeTotalDifficulty (blockhash : ByteString , td : BigInt ): DataSourceBatchUpdate =
@@ -310,6 +336,7 @@ class BlockchainImpl(
310
336
blockNumberMappingStorage.remove(number)
311
337
}
312
338
339
+ // scalastyle:off method.length
313
340
override def removeBlock (blockHash : ByteString , withState : Boolean ): Unit = {
314
341
val maybeBlockHeader = getBlockHeaderByHash(blockHash)
315
342
val maybeTxList = getBlockBodyByHash(blockHash).map(_.transactionList)
@@ -323,20 +350,64 @@ class BlockchainImpl(
323
350
)
324
351
}
325
352
353
+ val checkpointUpdates = maybeBlockHeader match {
354
+ case Some (header) =>
355
+ if (header.hasCheckpoint && header.number == getLatestCheckpointBlockNumber()) {
356
+ val prev = findPreviousCheckpointBlockNumber(header.number, header.number)
357
+ prev.map { num =>
358
+ // side effect
359
+ saveLatestCheckpointNumber(num)
360
+ appStateStorage.putLatestCheckpointBlockNumber(num)
361
+ }.getOrElse {
362
+ // side effect
363
+ saveLatestCheckpointNumber(0 )
364
+ appStateStorage.removeLatestCheckpointBlockNumber()
365
+ }
366
+ } else appStateStorage.emptyBatchUpdate
367
+ case None =>
368
+ appStateStorage.emptyBatchUpdate
369
+ }
370
+
326
371
blockHeadersStorage.remove(blockHash)
327
372
.and(blockBodiesStorage.remove(blockHash))
328
373
.and(totalDifficultyStorage.remove(blockHash))
329
374
.and(receiptStorage.remove(blockHash))
330
375
.and(maybeTxList.fold(transactionMappingStorage.emptyBatchUpdate)(removeTxsLocations))
331
376
.and(blockNumberMappingUpdates)
377
+ // immediate last checkpoint save to not need to manage it from outside
378
+ .and(checkpointUpdates)
332
379
.commit()
333
380
334
381
// not transactional part
335
382
maybeBlockHeader.foreach { h =>
336
383
if (withState)
337
- stateStorage.onBlockRollback(h.number, bestSavedBlock)(saveBestBlock )
384
+ stateStorage.onBlockRollback(h.number, bestSavedBlock)(persistBestBlocksData() )
338
385
}
339
386
}
387
+ // scalastyle:on method.length
388
+
389
+ /**
390
+ * Recursive function which try to find the previous checkpoint by traversing blocks from top to the bottom.
391
+ * In case of finding the checkpoint block number, the function will finish the job and return result
392
+ */
393
+ @ tailrec
394
+ private def findPreviousCheckpointBlockNumber (
395
+ blockNumberToCheck : BigInt ,
396
+ latestCheckpointBlockNumber : BigInt
397
+ ): Option [BigInt ] = {
398
+ if (blockNumberToCheck > 0 ) {
399
+ val maybePreviousCheckpointBlockNumber = for {
400
+ currentBlock <- getBlockByNumber(blockNumberToCheck)
401
+ if currentBlock.hasCheckpoint &&
402
+ currentBlock.number < latestCheckpointBlockNumber
403
+ } yield currentBlock.number
404
+
405
+ maybePreviousCheckpointBlockNumber match {
406
+ case Some (_) => maybePreviousCheckpointBlockNumber
407
+ case None => findPreviousCheckpointBlockNumber(blockNumberToCheck - 1 , latestCheckpointBlockNumber)
408
+ }
409
+ } else None
410
+ }
340
411
341
412
private def saveTxsLocations (blockHash : ByteString , blockBody : BlockBody ): DataSourceBatchUpdate =
342
413
blockBody.transactionList.zipWithIndex.foldLeft(transactionMappingStorage.emptyBatchUpdate) {
@@ -423,4 +494,6 @@ object BlockchainImpl {
423
494
appStateStorage = storages.appStateStorage,
424
495
stateStorage = storages.stateStorage
425
496
)
497
+
498
+ private case class BestBlockLatestCheckpointNumbers (bestBlockNumber : BigInt , latestCheckpointNumber : BigInt )
426
499
}
0 commit comments