@@ -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,8 @@ 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 ] =
217
+ new AtomicReference (BestBlockLatestCheckpointNumbers (BigInt (0 ), BigInt (0 )))
213
218
214
219
override def getBlockHeaderByHash (hash : ByteString ): Option [BlockHeader ] =
215
220
blockHeadersStorage.get(hash)
@@ -225,12 +230,20 @@ class BlockchainImpl(
225
230
226
231
override def getBestBlockNumber (): BigInt = {
227
232
val bestBlockNum = appStateStorage.getBestBlockNumber()
228
- if (bestKnownBlock .get() > bestBlockNum)
229
- bestKnownBlock .get()
233
+ if (bestKnownBlockAndLatestCheckpoint .get().bestBlockNumber > bestBlockNum)
234
+ bestKnownBlockAndLatestCheckpoint .get().bestBlockNumber
230
235
else
231
236
bestBlockNum
232
237
}
233
238
239
+ override def getLatestCheckpointBlockNumber (): BigInt = {
240
+ val latestCheckpointNumberInStorage = appStateStorage.getLatestCheckpointBlockNumber()
241
+ if (bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber > latestCheckpointNumberInStorage)
242
+ bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber
243
+ else
244
+ latestCheckpointNumberInStorage
245
+ }
246
+
234
247
override def getBestBlock (): Block =
235
248
getBlockByNumber(getBestBlockNumber()).get
236
249
@@ -252,8 +265,10 @@ class BlockchainImpl(
252
265
ByteString (mpt.get(position).getOrElse(BigInt (0 )).toByteArray)
253
266
}
254
267
255
- def saveBestBlock (bestBlock : Option [BigInt ]): Unit = {
256
- bestBlock.fold(appStateStorage.putBestBlockNumber(getBestBlockNumber()).commit())(best => appStateStorage.putBestBlockNumber(best).commit())
268
+ private def persistBestBlocksData (): Unit = {
269
+ appStateStorage.putBestBlockNumber(getBestBlockNumber())
270
+ .and(appStateStorage.putLatestCheckpointBlockNumber(getLatestCheckpointBlockNumber()))
271
+ .commit()
257
272
}
258
273
259
274
def save (block : Block , receipts : Seq [Receipt ], totalDifficulty : BigInt , saveAsBestBlock : Boolean ): Unit = {
@@ -263,8 +278,12 @@ class BlockchainImpl(
263
278
.commit()
264
279
265
280
// not transactional part
266
- stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(saveBestBlock)
267
- if (saveAsBestBlock) {
281
+ // the best blocks data will be persisted only when the cache will be persisted
282
+ stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(persistBestBlocksData())
283
+
284
+ if (saveAsBestBlock && block.hasCheckpoint) {
285
+ saveBestKnownBlockAndLatestCheckpointNumber(block.header.number, block.header.number)
286
+ } else if (saveAsBestBlock) {
268
287
saveBestKnownBlock(block.header.number)
269
288
}
270
289
}
@@ -290,7 +309,15 @@ class BlockchainImpl(
290
309
evmCodeStorage.put(hash, evmCode)
291
310
292
311
override def saveBestKnownBlock (number : BigInt ): Unit = {
293
- bestKnownBlock.set(number)
312
+ bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(bestBlockNumber = number))
313
+ }
314
+
315
+ def saveBestKnownBlockAndLatestCheckpointNumber (number : BigInt , latestCheckpointNumber : BigInt ): Unit = {
316
+ bestKnownBlockAndLatestCheckpoint.set(BestBlockLatestCheckpointNumbers (number, latestCheckpointNumber))
317
+ }
318
+
319
+ def saveLatestCheckpointNumber (latestCheckpointNumber : BigInt ): Unit = {
320
+ bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(latestCheckpointNumber = latestCheckpointNumber))
294
321
}
295
322
296
323
def storeTotalDifficulty (blockhash : ByteString , td : BigInt ): DataSourceBatchUpdate =
@@ -310,6 +337,7 @@ class BlockchainImpl(
310
337
blockNumberMappingStorage.remove(number)
311
338
}
312
339
340
+ // scalastyle:off method.length
313
341
override def removeBlock (blockHash : ByteString , withState : Boolean ): Unit = {
314
342
val maybeBlockHeader = getBlockHeaderByHash(blockHash)
315
343
val maybeTxList = getBlockBodyByHash(blockHash).map(_.transactionList)
@@ -323,20 +351,64 @@ class BlockchainImpl(
323
351
)
324
352
}
325
353
354
+ val checkpointUpdates = maybeBlockHeader match {
355
+ case Some (header) =>
356
+ if (header.hasCheckpoint && header.number == getLatestCheckpointBlockNumber()) {
357
+ val prev = findPreviousCheckpointBlockNumber(header.number, header.number)
358
+ prev.map { num =>
359
+ // side effect
360
+ saveLatestCheckpointNumber(num)
361
+ appStateStorage.putLatestCheckpointBlockNumber(num)
362
+ }.getOrElse {
363
+ // side effect
364
+ saveLatestCheckpointNumber(0 )
365
+ appStateStorage.removeLatestCheckpointBlockNumber()
366
+ }
367
+ } else appStateStorage.emptyBatchUpdate
368
+ case None =>
369
+ appStateStorage.emptyBatchUpdate
370
+ }
371
+
326
372
blockHeadersStorage.remove(blockHash)
327
373
.and(blockBodiesStorage.remove(blockHash))
328
374
.and(totalDifficultyStorage.remove(blockHash))
329
375
.and(receiptStorage.remove(blockHash))
330
376
.and(maybeTxList.fold(transactionMappingStorage.emptyBatchUpdate)(removeTxsLocations))
331
377
.and(blockNumberMappingUpdates)
378
+ // immediate last checkpoint save to not need to manage it from outside
379
+ .and(checkpointUpdates)
332
380
.commit()
333
381
334
382
// not transactional part
335
383
maybeBlockHeader.foreach { h =>
336
384
if (withState)
337
- stateStorage.onBlockRollback(h.number, bestSavedBlock)(saveBestBlock )
385
+ stateStorage.onBlockRollback(h.number, bestSavedBlock)(persistBestBlocksData() )
338
386
}
339
387
}
388
+ // scalastyle:on method.length
389
+
390
+ /**
391
+ * Recursive function which try to find the previous checkpoint by traversing blocks from top to the bottom.
392
+ * In case of finding the checkpoint block number, the function will finish the job and return result
393
+ */
394
+ @ tailrec
395
+ private def findPreviousCheckpointBlockNumber (
396
+ blockNumberToCheck : BigInt ,
397
+ latestCheckpointBlockNumber : BigInt
398
+ ): Option [BigInt ] = {
399
+ if (blockNumberToCheck > 0 ) {
400
+ val maybePreviousCheckpointBlockNumber = for {
401
+ currentBlock <- getBlockByNumber(blockNumberToCheck)
402
+ if currentBlock.hasCheckpoint &&
403
+ currentBlock.number < latestCheckpointBlockNumber
404
+ } yield currentBlock.number
405
+
406
+ maybePreviousCheckpointBlockNumber match {
407
+ case Some (_) => maybePreviousCheckpointBlockNumber
408
+ case None => findPreviousCheckpointBlockNumber(blockNumberToCheck - 1 , latestCheckpointBlockNumber)
409
+ }
410
+ } else None
411
+ }
340
412
341
413
private def saveTxsLocations (blockHash : ByteString , blockBody : BlockBody ): DataSourceBatchUpdate =
342
414
blockBody.transactionList.zipWithIndex.foldLeft(transactionMappingStorage.emptyBatchUpdate) {
@@ -423,4 +495,6 @@ object BlockchainImpl {
423
495
appStateStorage = storages.appStateStorage,
424
496
stateStorage = storages.stateStorage
425
497
)
498
+
499
+ private case class BestBlockLatestCheckpointNumbers (bestBlockNumber : BigInt , latestCheckpointNumber : BigInt )
426
500
}
0 commit comments