-
Notifications
You must be signed in to change notification settings - Fork 75
/
Copy pathBranchResolution.scala
96 lines (75 loc) · 3.12 KB
/
BranchResolution.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package io.iohk.ethereum.ledger
import cats.data.NonEmptyList
import io.iohk.ethereum.domain.{Block, BlockHeader, Blockchain}
class BranchResolution(blockchain: Blockchain) {
def resolveBranch(headers: NonEmptyList[BlockHeader]): BranchResolutionResult = {
if (!doHeadersFormChain(headers)) {
InvalidBranch
} else {
val knownParentOrGenesis = blockchain
.getBlockHeaderByHash(headers.head.parentHash)
.isDefined || headers.head.hash == blockchain.genesisHeader.hash
if (!knownParentOrGenesis)
UnknownBranch
else
compareBranch(headers)
}
}
private[ledger] def doHeadersFormChain(headers: NonEmptyList[BlockHeader]): Boolean =
headers.toList.zip(headers.tail).forall { case (parent, child) =>
parent.hash == child.parentHash && parent.number + 1 == child.number
}
private[ledger] def compareBranch(headers: NonEmptyList[BlockHeader]): BranchResolutionResult = {
val headersList = headers.toList
val oldBlocksWithCommonPrefix = getTopBlocksFromNumber(headers.head.number)
val commonPrefixLength = oldBlocksWithCommonPrefix
.zip(headersList)
.takeWhile { case (oldBlock, newHeader) => oldBlock.header == newHeader }
.length
val oldBlocks = oldBlocksWithCommonPrefix.drop(commonPrefixLength)
val newHeaders = headersList.drop(commonPrefixLength)
if (compareByCheckpoints(newHeaders, oldBlocks.map(_.header)))
NewBetterBranch(oldBlocks)
else
NoChainSwitch
}
/**
* @return true if newBranch is better than oldBranch
*/
private def compareByCheckpoints(newBranch: Seq[BlockHeader], oldBranch: Seq[BlockHeader]): Boolean =
(branchLatestCheckpoint(newBranch), branchLatestCheckpoint(oldBranch)) match {
case (Some(newCheckpoint), Some(oldCheckpoint)) =>
if (newCheckpoint.number == oldCheckpoint.number)
compareByDifficulty(newBranch, oldBranch)
else
newCheckpoint.number > oldCheckpoint.number
case (Some(_), None) =>
true
case (None, Some(_)) =>
false
case (None, None) =>
compareByDifficulty(newBranch, oldBranch)
}
/**
* @return true if newBranch is better than oldBranch
*/
private def compareByDifficulty(newBranch: Seq[BlockHeader], oldBranch: Seq[BlockHeader]): Boolean = {
val newDifficulty = newBranch.map(_.difficulty).sum
val oldDifficulty = oldBranch.map(_.difficulty).sum
newDifficulty > oldDifficulty
}
private def getTopBlocksFromNumber(from: BigInt): List[Block] =
(from to blockchain.getBestBlockNumber())
.flatMap(blockchain.getBlockByNumber)
.toList
private def branchLatestCheckpoint(headers: Seq[BlockHeader]): Option[BlockHeader] =
headers.filter(_.hasCheckpoint) match {
case Seq() => None
case checkpoints => Some(checkpoints.maxBy(_.number))
}
}
sealed trait BranchResolutionResult
case class NewBetterBranch(oldBranch: Seq[Block]) extends BranchResolutionResult
case object NoChainSwitch extends BranchResolutionResult
case object UnknownBranch extends BranchResolutionResult
case object InvalidBranch extends BranchResolutionResult