Skip to content

Commit 414f1cc

Browse files
committed
[ETCM-77] checkpoint sync
1 parent 8c4496b commit 414f1cc

27 files changed

+634
-163
lines changed

src/main/scala/io/iohk/ethereum/blockchain/sync/SyncController.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.iohk.ethereum.blockchain.sync
22

33
import akka.actor.{Actor, ActorLogging, ActorRef, PoisonPill, Props, Scheduler}
44
import io.iohk.ethereum.blockchain.sync.regular.RegularSync
5+
import io.iohk.ethereum.consensus.blocks.CheckpointBlockGenerator
56
import io.iohk.ethereum.consensus.validators.Validators
67
import io.iohk.ethereum.db.storage.{AppStateStorage, FastSyncStateStorage}
78
import io.iohk.ethereum.domain.Blockchain
@@ -16,6 +17,7 @@ class SyncController(
1617
validators: Validators,
1718
peerEventBus: ActorRef,
1819
pendingTransactionsManager: ActorRef,
20+
checkpointBlockGenerator: CheckpointBlockGenerator,
1921
ommersPool: ActorRef,
2022
etcPeerManager: ActorRef,
2123
syncConfig: SyncConfig,
@@ -102,6 +104,7 @@ class SyncController(
102104
syncConfig,
103105
ommersPool,
104106
pendingTransactionsManager,
107+
checkpointBlockGenerator,
105108
scheduler
106109
),
107110
"regular-sync"
@@ -122,6 +125,7 @@ object SyncController {
122125
validators: Validators,
123126
peerEventBus: ActorRef,
124127
pendingTransactionsManager: ActorRef,
128+
checkpointBlockGenerator: CheckpointBlockGenerator,
125129
ommersPool: ActorRef,
126130
etcPeerManager: ActorRef,
127131
syncConfig: SyncConfig
@@ -135,6 +139,7 @@ object SyncController {
135139
validators,
136140
peerEventBus,
137141
pendingTransactionsManager,
142+
checkpointBlockGenerator,
138143
ommersPool,
139144
etcPeerManager,
140145
syncConfig

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

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,40 @@ package io.iohk.ethereum.blockchain.sync.regular
22

33
import akka.actor.Actor.Receive
44
import akka.actor.{Actor, ActorLogging, ActorRef, NotInfluenceReceiveTimeout, Props, ReceiveTimeout}
5+
import akka.util.ByteString
56
import cats.data.NonEmptyList
67
import cats.instances.future._
78
import cats.instances.list._
89
import cats.syntax.apply._
910
import io.iohk.ethereum.blockchain.sync.regular.BlockBroadcasterActor.BroadcastBlocks
10-
import io.iohk.ethereum.crypto.kec256
11-
import io.iohk.ethereum.domain.{Block, Blockchain, SignedTransaction}
11+
import io.iohk.ethereum.consensus.blocks.CheckpointBlockGenerator
12+
import io.iohk.ethereum.crypto.{ECDSASignature, kec256}
13+
import io.iohk.ethereum.domain.{Block, Blockchain, Checkpoint, SignedTransaction}
1214
import io.iohk.ethereum.ledger._
1315
import io.iohk.ethereum.mpt.MerklePatriciaTrie.MissingNodeException
1416
import io.iohk.ethereum.network.PeerId
1517
import io.iohk.ethereum.network.p2p.messages.CommonMessages.NewBlock
1618
import io.iohk.ethereum.ommers.OmmersPool.{AddOmmers, RemoveOmmers}
1719
import io.iohk.ethereum.transactions.PendingTransactionsManager
1820
import io.iohk.ethereum.transactions.PendingTransactionsManager.{AddUncheckedTransactions, RemoveTransactions}
21+
import io.iohk.ethereum.utils.ByteStringUtils
1922
import io.iohk.ethereum.utils.Config.SyncConfig
2023
import io.iohk.ethereum.utils.FunctorOps._
2124

2225
import scala.concurrent.{ExecutionContext, Future}
26+
import scala.concurrent.duration._
2327
import scala.util.{Failure, Success}
2428

29+
// scalastyle:off cyclomatic.complexity
2530
class BlockImporter(
2631
fetcher: ActorRef,
2732
ledger: Ledger,
2833
blockchain: Blockchain,
2934
syncConfig: SyncConfig,
3035
ommersPool: ActorRef,
3136
broadcaster: ActorRef,
32-
pendingTransactionsManager: ActorRef
37+
pendingTransactionsManager: ActorRef,
38+
checkpointBlockGenerator: CheckpointBlockGenerator
3339
) extends Actor
3440
with ActorLogging {
3541
import BlockImporter._
@@ -56,15 +62,35 @@ class BlockImporter(
5662

5763
private def running(state: ImporterState): Receive = handleTopMessages(state, running) orElse {
5864
case ReceiveTimeout => self ! PickBlocks
65+
5966
case PrintStatus => log.info("Block: {}, is on top?: {}", blockchain.getBestBlockNumber(), state.isOnTop)
67+
6068
case BlockFetcher.PickedBlocks(blocks) =>
6169
SignedTransaction.retrieveSendersInBackGround(blocks.toList.map(_.body))
6270
importBlocks(blocks)(state)
71+
6372
case MinedBlock(block) =>
6473
if (!state.importing) {
6574
importMinedBlock(block, state)
6675
}
76+
77+
case nc @ NewCheckpoint(parentHash, signatures) =>
78+
if (state.importing) {
79+
//TODO: is this ok? What delay?
80+
context.system.scheduler.scheduleOnce(100.millis, self, nc)
81+
} else {
82+
ledger.getBlockByHash(parentHash) match {
83+
case Some(parent) =>
84+
val checkpointBlock = checkpointBlockGenerator.generate(parent, Checkpoint(signatures))
85+
importCheckpointBlock(checkpointBlock, state)
86+
87+
case None =>
88+
log.error(s"Could not find parent (${ByteStringUtils.hash2string(parentHash)}) for new checkpoint block")
89+
}
90+
}
91+
6792
case ImportNewBlock(block, peerId) if state.isOnTop && !state.importing => importNewBlock(block, peerId, state)
93+
6894
case ImportDone(newBehavior) =>
6995
val newState = state.notImportingBlocks().branchResolved()
7096
val behavior: Behavior = getBehavior(newBehavior)
@@ -177,6 +203,9 @@ class BlockImporter(
177203
private def importMinedBlock(block: Block, state: ImporterState): Unit =
178204
importBlock(block, new MinedBlockImportMessages(block), informFetcherOnFail = false)(state)
179205

206+
private def importCheckpointBlock(block: Block, state: ImporterState): Unit =
207+
importBlock(block, new CheckpointBlockImportMessages(block), informFetcherOnFail = false)(state)
208+
180209
private def importNewBlock(block: Block, peerId: PeerId, state: ImporterState): Unit =
181210
importBlock(block, new NewBlockImportMessages(block, peerId), informFetcherOnFail = true)(state)
182211

@@ -247,7 +276,7 @@ class BlockImporter(
247276

248277
// Either block from which we try resolve branch or list of blocks to be imported
249278
private def resolveBranch(blocks: NonEmptyList[Block]): Either[BigInt, List[Block]] =
250-
ledger.resolveBranch(blocks.map(_.header).toList) match {
279+
ledger.resolveBranch(blocks.map(_.header)) match {
251280
case NewBetterBranch(oldBranch) =>
252281
val transactionsToAdd = oldBranch.flatMap(_.body.transactionList)
253282
pendingTransactionsManager ! PendingTransactionsManager.AddUncheckedTransactions(transactionsToAdd)
@@ -294,10 +323,20 @@ object BlockImporter {
294323
syncConfig: SyncConfig,
295324
ommersPool: ActorRef,
296325
broadcaster: ActorRef,
297-
pendingTransactionsManager: ActorRef
326+
pendingTransactionsManager: ActorRef,
327+
checkpointBlockGenerator: CheckpointBlockGenerator
298328
): Props =
299329
Props(
300-
new BlockImporter(fetcher, ledger, blockchain, syncConfig, ommersPool, broadcaster, pendingTransactionsManager)
330+
new BlockImporter(
331+
fetcher,
332+
ledger,
333+
blockchain,
334+
syncConfig,
335+
ommersPool,
336+
broadcaster,
337+
pendingTransactionsManager,
338+
checkpointBlockGenerator
339+
)
301340
)
302341

303342
type Behavior = ImporterState => Receive
@@ -308,6 +347,7 @@ object BlockImporter {
308347
case object OnTop extends ImporterMsg
309348
case object NotOnTop extends ImporterMsg
310349
case class MinedBlock(block: Block) extends ImporterMsg
350+
case class NewCheckpoint(parentHash: ByteString, signatures: Seq[ECDSASignature]) extends ImporterMsg
311351
case class ImportNewBlock(block: Block, peerId: PeerId) extends ImporterMsg
312352
case class ImportDone(newBehavior: NewBehavior) extends ImporterMsg
313353
case object PickBlocks extends ImporterMsg

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ class MinedBlockImportMessages(block: Block) extends ImportMessages(block) {
5454
(ErrorLevel, s"Ignoring mined block $exception")
5555
}
5656

57+
class CheckpointBlockImportMessages(block: Block) extends ImportMessages(block) {
58+
import ImportMessages._
59+
override def preImport(): LogEntry = (DebugLevel, s"Importing new checkpoint block (${block.idTag})")
60+
override def importedToTheTop(): LogEntry =
61+
(DebugLevel, s"Added new checkpoint block $number to top of the chain")
62+
override def enqueued(): LogEntry = (DebugLevel, s"Checkpoint block $number was added to the queue")
63+
override def duplicated(): LogEntry =
64+
(DebugLevel, "Ignoring duplicate checkpoint block")
65+
override def orphaned(): LogEntry =
66+
(ErrorLevel, "Checkpoint block has no parent. This should never happen")
67+
override def reorganisedChain(newBranch: List[Block]): LogEntry =
68+
(DebugLevel, s"Addition of new checkpoint block $number resulting in chain reorganization")
69+
override def importFailed(error: String): LogEntry =
70+
(WarningLevel, s"Failed to execute checkpoint block because of $error")
71+
override def missingStateNode(exception: MissingNodeException): LogEntry =
72+
(ErrorLevel, s"Ignoring checkpoint block: $exception")
73+
}
74+
5775
class NewBlockImportMessages(block: Block, peerId: PeerId) extends ImportMessages(block) {
5876
import ImportMessages._
5977
override def preImport(): LogEntry = (DebugLevel, s"Handling NewBlock message for block (${block.idTag})")

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package io.iohk.ethereum.blockchain.sync.regular
33
import akka.actor.{Actor, ActorLogging, ActorRef, AllForOneStrategy, Cancellable, Props, Scheduler, SupervisorStrategy}
44
import akka.util.ByteString
55
import io.iohk.ethereum.blockchain.sync.BlockBroadcast
6+
import io.iohk.ethereum.consensus.blocks.CheckpointBlockGenerator
67
import io.iohk.ethereum.crypto.ECDSASignature
78
import io.iohk.ethereum.domain.{Block, Blockchain}
89
import io.iohk.ethereum.ledger.Ledger
10+
import io.iohk.ethereum.utils.ByteStringUtils
911
import io.iohk.ethereum.utils.Config.SyncConfig
1012

1113
class RegularSync(
@@ -17,6 +19,7 @@ class RegularSync(
1719
syncConfig: SyncConfig,
1820
ommersPool: ActorRef,
1921
pendingTransactionsManager: ActorRef,
22+
checkpointBlockGenerator: CheckpointBlockGenerator,
2023
scheduler: Scheduler
2124
) extends Actor
2225
with ActorLogging {
@@ -31,7 +34,16 @@ class RegularSync(
3134
)
3235
val importer: ActorRef =
3336
context.actorOf(
34-
BlockImporter.props(fetcher, ledger, blockchain, syncConfig, ommersPool, broadcaster, pendingTransactionsManager),
37+
BlockImporter.props(
38+
fetcher,
39+
ledger,
40+
blockchain,
41+
syncConfig,
42+
ommersPool,
43+
broadcaster,
44+
pendingTransactionsManager,
45+
checkpointBlockGenerator
46+
),
3547
"block-importer"
3648
)
3749

@@ -57,6 +69,10 @@ class RegularSync(
5769
case MinedBlock(block) =>
5870
log.info(s"Block mined [number = {}, hash = {}]", block.number, block.header.hashAsHexString)
5971
importer ! BlockImporter.MinedBlock(block)
72+
73+
case NewCheckpoint(parentHash, signatures) =>
74+
log.info(s"Received new checkpoint for block ${ByteStringUtils.hash2string(parentHash)}")
75+
importer ! BlockImporter.NewCheckpoint(parentHash, signatures)
6076
}
6177

6278
override def supervisorStrategy: SupervisorStrategy = AllForOneStrategy()(SupervisorStrategy.defaultDecider)
@@ -78,6 +94,7 @@ object RegularSync {
7894
syncConfig: SyncConfig,
7995
ommersPool: ActorRef,
8096
pendingTransactionsManager: ActorRef,
97+
checkpointBlockGenerator: CheckpointBlockGenerator,
8198
scheduler: Scheduler
8299
): Props =
83100
Props(
@@ -90,6 +107,7 @@ object RegularSync {
90107
syncConfig,
91108
ommersPool,
92109
pendingTransactionsManager,
110+
checkpointBlockGenerator,
93111
scheduler
94112
)
95113
)

src/main/scala/io/iohk/ethereum/consensus/Consensus.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package io.iohk.ethereum.consensus
22

33
import io.iohk.ethereum.consensus.blocks.{BlockGenerator, TestBlockGenerator}
4+
import io.iohk.ethereum.consensus.difficulty.DifficultyCalculator
45
import io.iohk.ethereum.consensus.ethash.{MinerProtocol, MinerResponse}
56
import io.iohk.ethereum.consensus.validators.Validators
67
import io.iohk.ethereum.ledger.BlockPreparator
78
import io.iohk.ethereum.ledger.Ledger.VMImpl
89
import io.iohk.ethereum.nodebuilder.Node
10+
911
import scala.concurrent.Future
1012

1113
/**
@@ -46,6 +48,8 @@ trait Consensus {
4648
*/
4749
def blockGenerator: BlockGenerator
4850

51+
def difficultyCalculator: DifficultyCalculator
52+
4953
/**
5054
* Starts the consensus protocol on the current `node`.
5155
*/

src/main/scala/io/iohk/ethereum/consensus/blocks/BlockGeneratorSkeleton.scala

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,12 @@ import io.iohk.ethereum.utils.ByteUtils.or
2121

2222
/**
2323
* This is a skeleton for a generic [[io.iohk.ethereum.consensus.blocks.BlockGenerator BlockGenerator]].
24-
*
25-
* @param blockchain
26-
* @param blockchainConfig
27-
* @param _blockTimestampProvider
2824
*/
2925
abstract class BlockGeneratorSkeleton(
3026
blockchain: Blockchain,
3127
blockchainConfig: BlockchainConfig,
3228
consensusConfig: ConsensusConfig,
33-
blockPreparator: BlockPreparator,
29+
difficultyCalc: DifficultyCalculator,
3430
_blockTimestampProvider: BlockTimestampProvider = DefaultBlockTimestampProvider
3531
) extends TestBlockGenerator {
3632

@@ -42,8 +38,6 @@ abstract class BlockGeneratorSkeleton(
4238

4339
protected def newBlockBody(transactions: Seq[SignedTransaction], x: X): BlockBody
4440

45-
protected def difficulty: DifficultyCalculator
46-
4741
protected def defaultPrepareHeader(
4842
blockNumber: BigInt,
4943
parent: Block,
@@ -66,7 +60,7 @@ abstract class BlockGeneratorSkeleton(
6660
transactionsRoot = ByteString.empty,
6761
receiptsRoot = ByteString.empty,
6862
logsBloom = ByteString.empty,
69-
difficulty = difficulty.calculateDifficulty(blockNumber, blockTimestamp, parent.header),
63+
difficulty = difficultyCalc.calculateDifficulty(blockNumber, blockTimestamp, parent.header),
7064
number = blockNumber,
7165
gasLimit = calculateGasLimit(parent.header.gasLimit),
7266
gasUsed = 0,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.iohk.ethereum.consensus.blocks
2+
3+
import akka.util.ByteString
4+
import io.iohk.ethereum.domain.BlockHeader.HeaderExtraFields.HefPostEcip1097
5+
import io.iohk.ethereum.domain._
6+
import io.iohk.ethereum.ledger.BloomFilter
7+
8+
class CheckpointBlockGenerator {
9+
10+
def generate(parent: Block, checkpoint: Checkpoint): Block = {
11+
val blockNumber = parent.number + 1
12+
// we are using a predictable value for timestamp so that each federation node generates identical block
13+
// see ETCM-173
14+
val timestamp = parent.header.unixTimestamp + 1
15+
16+
val header = BlockHeader(
17+
parentHash = parent.hash,
18+
ommersHash = BlockHeader.EmptyOmmers,
19+
beneficiary = BlockHeader.EmptyBeneficiary,
20+
difficulty = parent.header.difficulty,
21+
number = blockNumber,
22+
gasLimit = parent.header.gasLimit,
23+
unixTimestamp = timestamp,
24+
extraData = ByteString.empty,
25+
stateRoot = parent.header.stateRoot,
26+
transactionsRoot = BlockHeader.EmptyMpt,
27+
receiptsRoot = BlockHeader.EmptyMpt,
28+
logsBloom = BloomFilter.EmptyBloomFilter,
29+
gasUsed = UInt256.Zero,
30+
mixHash = ByteString.empty,
31+
nonce = ByteString.empty,
32+
extraFields = HefPostEcip1097(false, Some(checkpoint))
33+
)
34+
35+
Block(header, BlockBody.empty)
36+
}
37+
}

src/main/scala/io/iohk/ethereum/consensus/blocks/NoOmmersBlockGenerator.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.iohk.ethereum.consensus.blocks
22

33
import io.iohk.ethereum.consensus.ConsensusConfig
4+
import io.iohk.ethereum.consensus.difficulty.DifficultyCalculator
45
import io.iohk.ethereum.domain._
56
import io.iohk.ethereum.ledger.{BlockPreparationError, BlockPreparator}
67
import io.iohk.ethereum.utils.BlockchainConfig
@@ -10,12 +11,13 @@ abstract class NoOmmersBlockGenerator(
1011
blockchainConfig: BlockchainConfig,
1112
consensusConfig: ConsensusConfig,
1213
blockPreparator: BlockPreparator,
14+
difficultyCalc: DifficultyCalculator,
1315
blockTimestampProvider: BlockTimestampProvider = DefaultBlockTimestampProvider
1416
) extends BlockGeneratorSkeleton(
1517
blockchain,
1618
blockchainConfig,
1719
consensusConfig,
18-
blockPreparator,
20+
difficultyCalc,
1921
blockTimestampProvider
2022
) {
2123

0 commit comments

Comments
 (0)