-
Notifications
You must be signed in to change notification settings - Fork 75
Feature/etcm 354/forkid #1018
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Merged
Merged
Feature/etcm 354/forkid #1018
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
ccf182c
[ETCM-354] Initialize the forkid package
lukasz-golebiewski 6f252ac
[ETCM-354] Create ForkId using genesis hash and forks
lukasz-golebiewski 40c2fa5
[ETCM-354] Encode ForkId using RLP
lukasz-golebiewski af7faa1
[ETCM-354] Roundtrip ForkIds
lukasz-golebiewski 63f0d96
[ETCM-354] Do not always use the DAO block for fork id validation
lukasz-golebiewski d696af3
[ETCM-354] Print ForkId hash as hex string
lukasz-golebiewski a0696a7
[ETCM-354] Add ForkId calculation tests for Mordor
lukasz-golebiewski 017780c
[ECIM-354] Apply review suggestions
lukasz-golebiewski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package io.iohk.ethereum.forkid | ||
|
||
import java.util.zip.CRC32 | ||
import java.nio.ByteBuffer | ||
|
||
import akka.util.ByteString | ||
import io.iohk.ethereum.utils.BlockchainConfig | ||
import io.iohk.ethereum.utils.BigIntExtensionMethods._ | ||
import io.iohk.ethereum.utils.ByteUtils._ | ||
import io.iohk.ethereum.utils.Hex | ||
import io.iohk.ethereum.rlp._ | ||
|
||
import RLPImplicitConversions._ | ||
|
||
case class ForkId(hash: BigInt, next: Option[BigInt]) { | ||
override def toString(): String = s"ForkId(0x${Hex.toHexString(hash.toUnsignedByteArray)}, $next)" | ||
} | ||
|
||
object ForkId { | ||
|
||
def create(genesisHash: ByteString, config: BlockchainConfig)(head: BigInt): ForkId = { | ||
val crc = new CRC32() | ||
crc.update(genesisHash.asByteBuffer) | ||
val next = gatherForks(config).find { fork => | ||
if (fork <= head) { | ||
crc.update(bigIntToBytes(fork, 8)) | ||
} | ||
fork > head | ||
} | ||
new ForkId(crc.getValue(), next) | ||
} | ||
|
||
val noFork = BigInt("1000000000000000000") | ||
|
||
def gatherForks(config: BlockchainConfig): List[BigInt] = { | ||
val maybeDaoBlock: Option[BigInt] = config.daoForkConfig.flatMap { daoConf => | ||
if (daoConf.includeOnForkIdList) Some(daoConf.forkBlockNumber) | ||
else None | ||
} | ||
|
||
(maybeDaoBlock.toList ++ config.forkBlockNumbers.all) | ||
.filterNot(v => v == 0 || v == noFork) | ||
.distinct | ||
.sorted | ||
} | ||
|
||
implicit class ForkIdEnc(forkId: ForkId) extends RLPSerializable { | ||
import RLPImplicits._ | ||
|
||
import io.iohk.ethereum.utils.ByteUtils._ | ||
override def toRLPEncodable: RLPEncodeable = { | ||
val hash: Array[Byte] = bigIntToBytes(forkId.hash, 4).takeRight(4) | ||
val next: Array[Byte] = bigIntToUnsignedByteArray(forkId.next.getOrElse(BigInt(0))).takeRight(8) | ||
RLPList(hash, next) | ||
} | ||
|
||
} | ||
|
||
implicit val forkIdEnc = new RLPDecoder[ForkId] { | ||
|
||
def decode(rlp: RLPEncodeable): ForkId = rlp match { | ||
case RLPList(hash, next) => { | ||
val i = bigIntFromEncodeable(next) | ||
ForkId(bigIntFromEncodeable(hash), if (i == 0) None else Some(i)) | ||
} | ||
case _ => throw new RuntimeException("Error when decoding ForkId") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
src/test/scala/io/iohk/ethereum/forkid/ForkIdSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package io.iohk.ethereum.forkid | ||
|
||
import akka.util.ByteString | ||
import io.iohk.ethereum.forkid.ForkId._ | ||
import io.iohk.ethereum.utils.ForkBlockNumbers | ||
import io.iohk.ethereum.utils.Config._ | ||
|
||
import org.scalatest.wordspec.AnyWordSpec | ||
import org.scalatest.matchers.should._ | ||
import org.bouncycastle.util.encoders.Hex | ||
|
||
import io.iohk.ethereum.rlp._ | ||
import io.iohk.ethereum.rlp.RLPImplicits._ | ||
|
||
|
||
class ForkIdSpec extends AnyWordSpec with Matchers { | ||
|
||
val config = blockchains | ||
|
||
"ForkId" must { | ||
"gatherForks for all chain configurations without errors" in { | ||
config.blockchains.map { case (name, conf) => (name, gatherForks(conf)) } | ||
} | ||
"gatherForks for the etc chain correctly" in { | ||
val res = config.blockchains.map { case (name, conf) => (name, gatherForks(conf)) } | ||
res("etc") shouldBe List(1150000, 2500000, 3000000, 5000000, 5900000, 8772000, 9573000, 10500839, 11700000) | ||
} | ||
|
||
"gatherForks for the eth chain correctly" in { | ||
val res = config.blockchains.map { case (name, conf) => (name, gatherForks(conf)) } | ||
res("eth") shouldBe List(1150000, 1920000, 2463000, 2675000, 4370000, 7280000, 9069000) | ||
} | ||
|
||
"create correct ForkId for ETH mainnet blocks" in { | ||
val ethConf = config.blockchains("eth") | ||
val ethGenesisHash = ByteString(Hex.decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")) | ||
def create(head: BigInt) = ForkId.create(ethGenesisHash, ethConf)(head) | ||
|
||
create(0) shouldBe ForkId(0xfc64ec04L, Some(1150000)) // Unsynced | ||
create(1149999) shouldBe ForkId(0xfc64ec04L, Some(1150000)) // Last Frontier block | ||
create(1150000) shouldBe ForkId(0x97c2c34cL, Some(1920000)) // First Homestead block | ||
create(1919999) shouldBe ForkId(0x97c2c34cL, Some(1920000)) // Last Homestead block | ||
create(1920000) shouldBe ForkId(0x91d1f948L, Some(2463000)) // First DAO block | ||
create(2462999) shouldBe ForkId(0x91d1f948L, Some(2463000)) // Last DAO block | ||
create(2463000) shouldBe ForkId(0x7a64da13L, Some(2675000)) // First Tangerine block | ||
create(2674999) shouldBe ForkId(0x7a64da13L, Some(2675000)) // Last Tangerine block | ||
create(2675000) shouldBe ForkId(0x3edd5b10L, Some(4370000)) // First Spurious block | ||
create(4369999) shouldBe ForkId(0x3edd5b10L, Some(4370000)) // Last Spurious block | ||
create(4370000) shouldBe ForkId(0xa00bc324L, Some(7280000)) // First Byzantium block | ||
create(7279999) shouldBe ForkId(0xa00bc324L, Some(7280000)) // Last Byzantium block | ||
create(7280000) shouldBe ForkId(0x668db0afL, Some(9069000)) // First and last Constantinople, first Petersburg block | ||
create(9068999) shouldBe ForkId(0x668db0afL, Some(9069000)) // Last Petersburg block | ||
// TODO: Add Muir Glacier and Berlin | ||
create(9069000) shouldBe ForkId(0x879d6e30L, None) // First Istanbul block | ||
create(12644529) shouldBe ForkId(0x879d6e30L, None) // Today Istanbul block | ||
} | ||
|
||
"create correct ForkId for ETC mainnet blocks" in { | ||
val etcConf = config.blockchains("etc") | ||
val etcGenesisHash = ByteString(Hex.decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")) | ||
def create(head: BigInt) = ForkId.create(etcGenesisHash, etcConf)(head) | ||
|
||
create(0) shouldBe ForkId(0xfc64ec04L, Some(1150000)) // Unsynced | ||
create(1149999) shouldBe ForkId(0xfc64ec04L, Some(1150000)) // Last Frontier block | ||
create(1150000) shouldBe ForkId(0x97c2c34cL, Some(2500000)) // First Homestead block | ||
create(1919999) shouldBe ForkId(0x97c2c34cL, Some(2500000)) // Last Homestead block | ||
create(2500000) shouldBe ForkId(0xdb06803fL, Some(3000000)) | ||
create(3000000-1) shouldBe ForkId(0xdb06803fL, Some(3000000)) | ||
create(3000000) shouldBe ForkId(0xaff4bed4L, Some(5000000)) | ||
create(5000000-1) shouldBe ForkId(0xaff4bed4L, Some(5000000)) | ||
create(5000000) shouldBe ForkId(0xf79a63c0L, Some(5900000)) | ||
create(5900000-1) shouldBe ForkId(0xf79a63c0L, Some(5900000)) | ||
create(5900000) shouldBe ForkId(0x744899d6L, Some(8772000)) | ||
create(8772000-1) shouldBe ForkId(0x744899d6L, Some(8772000)) | ||
create(8772000) shouldBe ForkId(0x518b59c6L, Some(9573000)) | ||
create(9573000-1) shouldBe ForkId(0x518b59c6L, Some(9573000)) | ||
create(9573000) shouldBe ForkId(0x7ba22882L, Some(10500839)) | ||
create(10500839-1) shouldBe ForkId(0x7ba22882L, Some(10500839)) | ||
create(10500839) shouldBe ForkId(0x9007bfccL, Some(11700000)) | ||
create(11700000-1) shouldBe ForkId(0x9007bfccL, Some(11700000)) | ||
create(11700000) shouldBe ForkId(0xdb63a1caL, None) | ||
} | ||
|
||
"create correct ForkId for mordor blocks" in { | ||
val mordorConf = config.blockchains("mordor") | ||
val mordorGenesisHash = ByteString(Hex.decode("a68ebde7932eccb177d38d55dcc6461a019dd795a681e59b5a3e4f3a7259a3f1")) | ||
def create(head: BigInt) = ForkId.create(mordorGenesisHash, mordorConf)(head) | ||
|
||
create(0) shouldBe ForkId(0x175782aaL, Some(301243)) // Unsynced | ||
create(301242) shouldBe ForkId(0x175782aaL, Some(301243)) | ||
create(301243) shouldBe ForkId(0x604f6ee1L, Some(999983)) | ||
create(999982) shouldBe ForkId(0x604f6ee1L, Some(999983)) | ||
create(999983) shouldBe ForkId(0xf42f5539L, Some(2520000)) | ||
create(2519999) shouldBe ForkId(0xf42f5539L, Some(2520000)) | ||
create(2520000) shouldBe ForkId(0x66b5c286L, None) | ||
// TODO: Add Magneto | ||
// create(2520000) shouldBe ForkId(0x66b5c286L, Some(3985893)) | ||
// create(3985893) shouldBe ForkId(0x66b5c286L, Some(3985893)) | ||
// create(3985894) shouldBe ForkId(0x92b323e0L, None) | ||
} | ||
|
||
// Here’s a couple of tests to verify the proper RLP encoding (since FORK_HASH is a 4 byte binary but FORK_NEXT is an 8 byte quantity): | ||
"be correctly encoded via rlp" in { | ||
roundTrip(ForkId(0, None), "c6840000000080") | ||
roundTrip(ForkId(0xdeadbeefL, Some(0xBADDCAFEL)), "ca84deadbeef84baddcafe") | ||
|
||
val maxUInt64 = (BigInt(0x7FFFFFFFFFFFFFFFL) << 1) + 1 | ||
maxUInt64.toByteArray shouldBe Array(0, -1, -1, -1, -1, -1, -1, -1, -1) | ||
val maxUInt32 = BigInt(0xFFFFFFFFL) | ||
maxUInt32.toByteArray shouldBe Array(0, -1, -1, -1, -1) | ||
|
||
roundTrip(ForkId(maxUInt32, Some(maxUInt64)), "ce84ffffffff88ffffffffffffffff") | ||
} | ||
} | ||
|
||
private def roundTrip(forkId: ForkId, hex: String) = { | ||
encode(forkId.toRLPEncodable) shouldBe Hex.decode(hex) | ||
decode[ForkId](Hex.decode(hex)) shouldBe forkId | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get why we filter out
noFork
. It does not seem to be used somewhere elseThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same configuration structure
BlockchainConfig
is re-used for multiple chains. Forks which shouldn't occur in a given chain are configured to happen at block height "1000000000000000000" which is equivalent to "never". We don't want those fork numbers on our fork id list