Skip to content

Commit

Permalink
[ETCM-43] Add opt-out field
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Tallar committed Sep 16, 2020
1 parent 7743ce1 commit da98f44
Show file tree
Hide file tree
Showing 39 changed files with 345 additions and 119 deletions.
Expand Up @@ -41,7 +41,8 @@ object BlockchainTestConfig {
ethCompatibleStorage = true,
atlantisBlockNumber = Long.MaxValue,
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue
phoenixBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
)

val FrontierConfig = BaseBlockchainConfig.copy(
Expand Down
Expand Up @@ -56,7 +56,7 @@ case class BlockHeaderDef(

def toBlockHeader: BlockHeader =
BlockHeader(parentHash, uncleHash, coinbase, stateRoot, transactionsTrie, receiptTrie, bloom, difficulty, number,
gasLimit, gasUsed, timestamp, extraData, mixHash, nonce
gasLimit, gasUsed, timestamp, extraData, mixHash, nonce, None
)
}

Expand Down
3 changes: 2 additions & 1 deletion src/ets/scala/io/iohk/ethereum/ets/vm/ScenarioBuilder.scala
Expand Up @@ -51,7 +51,8 @@ object ScenarioBuilder {
env.currentTimestamp,
bEmpty,
bEmpty,
bEmpty
bEmpty,
None
)

def prepareWorld(accounts: Map[Address, AccountState], blockNumber: BigInt, exec: Exec): MockWorldState = {
Expand Down
3 changes: 2 additions & 1 deletion src/it/scala/io/iohk/ethereum/txExecTest/ECIP1017Test.scala
Expand Up @@ -46,7 +46,8 @@ class ECIP1017Test extends FlatSpec with Matchers {
atlantisBlockNumber = Long.MaxValue,
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue,
petersburgBlockNumber = Long.MaxValue
petersburgBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
)
val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4))

Expand Down
3 changes: 2 additions & 1 deletion src/it/scala/io/iohk/ethereum/txExecTest/ForksTest.scala
Expand Up @@ -43,7 +43,8 @@ class ForksTest extends FlatSpec with Matchers {
atlantisBlockNumber = Long.MaxValue,
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue,
petersburgBlockNumber = Long.MaxValue
petersburgBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
)

val noErrors = a[Right[_, Seq[Receipt]]]
Expand Down
Expand Up @@ -102,7 +102,7 @@ object DumpChainApp extends App with NodeKeyBuilder with SecureRandomBuilder wit
class BlockchainMock(genesisHash: ByteString) extends Blockchain {

class FakeHeader() extends BlockHeader(ByteString.empty, ByteString.empty, ByteString.empty, ByteString.empty,
ByteString.empty, ByteString.empty, ByteString.empty, 0, 0, 0, 0, 0, ByteString.empty, ByteString.empty, ByteString.empty) {
ByteString.empty, ByteString.empty, ByteString.empty, 0, 0, 0, 0, 0, ByteString.empty, ByteString.empty, ByteString.empty, None) {
override lazy val hash: ByteString = genesisHash
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/application.conf
Expand Up @@ -235,6 +235,11 @@ mantis {
# In the case of ethash PoW, this means mining new blocks, as specified by Ethereum.
# In the general case, the semantics are due to the specific consensus implementation.
mining-enabled = false

# Whether or not as a miner we want to support the proto-treasury and send 20% of the block reward to it
# If false then that 20% gets burned
# Doesn't have any effect is ecip1098 is not yet activated
opt-out = false
}

# This is the section dedicated to Ethash mining.
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/chains/base.conf
Expand Up @@ -78,6 +78,10 @@
# https://eips.ethereum.org/EIPS/eip-1679
istanbul-block-number = "1000000000000000000"

# Proto-treasury fork block number (ETC only, but deactivated for now)
# https://ecips.ethereumclassic.org/ECIPs/ecip-1098
ecip1098-block-number = "1000000000000000000"

# DAO fork configuration (Ethereum HF/Classic split)
# https://blog.ethereum.org/2016/07/20/hard-fork-completed/
dao {
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/chains/eth.conf
Expand Up @@ -45,6 +45,10 @@
# https://eips.ethereum.org/EIPS/eip-1679
istanbul-block-number = "9069000"

# Proto-treasury fork block number (ETC only, but deactivated for now)
# https://ecips.ethereumclassic.org/ECIPs/ecip-1098
ecip1098-block-number = "1000000000000000000"

monetary-policy {

# Setting era-duration for eth and ropsten chain to big number is necessery to ensure
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/chains/mordor.conf
Expand Up @@ -78,6 +78,10 @@
# https://eips.ethereum.org/EIPS/eip-1679
istanbul-block-number = "1000000000000000000"

# Proto-treasury fork block number (ETC only, but deactivated for now)
# https://ecips.ethereumclassic.org/ECIPs/ecip-1098
ecip1098-block-number = "1000000000000000000"

# DAO fork configuration (Ethereum HF/Classic split)
# https://blog.ethereum.org/2016/07/20/hard-fork-completed/
dao = null
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/chains/private.conf
Expand Up @@ -11,6 +11,8 @@
atlantis-block-number = "0"
agharta-block-number = "0"
phoenix-block-number = "0"
# FIXME: Once the testnet is up an running we should determine this value
ecip1098-block-number = "1000000000000000000"

chain-id = "0x2A"
network-id = 42
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/chains/ropsten.conf
Expand Up @@ -19,6 +19,10 @@
# https://github.com/ethereum/pm/issues/53
constantinople-block-number = "4230000"

# Proto-treasury fork block number (ETC only, but deactivated for now)
# https://ecips.ethereumclassic.org/ECIPs/ecip-1098
ecip1098-block-number = "1000000000000000000"

max-code-size = 24576

monetary-policy {
Expand Down
Expand Up @@ -134,7 +134,8 @@ class GenesisDataLoader(
unixTimestamp = BigInt(genesisData.timestamp.replace("0x", ""), 16).toLong,
extraData = genesisData.extraData,
mixHash = genesisData.mixHash.getOrElse(zeros(hashLength)),
nonce = genesisData.nonce
nonce = genesisData.nonce,
optOut = None
)

private def zeros(length: Int) =
Expand Down
Expand Up @@ -21,7 +21,8 @@ final case class ConsensusConfig(
coinbase: Address,
headerExtraData: ByteString, // only used in BlockGenerator
blockCacheSize: Int, // only used in BlockGenerator
miningEnabled: Boolean
miningEnabled: Boolean,
optOut: Boolean
)

object ConsensusConfig extends Logger {
Expand All @@ -32,6 +33,7 @@ object ConsensusConfig extends Logger {
final val HeaderExtraData = "header-extra-data"
final val BlockCacheSize = "block-cashe-size"
final val MiningEnabled = "mining-enabled"
final val OptOut = "opt-out"
}


Expand Down Expand Up @@ -68,13 +70,15 @@ object ConsensusConfig extends Logger {
.take(BlockHeaderValidator.MaxExtraDataSize)
val blockCacheSize = config.getInt(Keys.BlockCacheSize)
val miningEnabled = config.getBoolean(Keys.MiningEnabled)
val optOut = config.getBoolean(Keys.OptOut)

new ConsensusConfig(
protocol = protocol,
coinbase = coinbase,
headerExtraData = headerExtraData,
blockCacheSize = blockCacheSize,
miningEnabled = miningEnabled
miningEnabled = miningEnabled,
optOut = optOut
)
}
}
Expand Up @@ -50,7 +50,9 @@ abstract class BlockGeneratorSkeleton(
beneficiary: Address,
blockTimestamp: Long,
x: Ommers
): BlockHeader =
): BlockHeader = {
val optOut = if(blockNumber >= blockchainConfig.ecip1098BlockNumber) Some(consensusConfig.optOut) else None

BlockHeader(
parentHash = parent.header.hash,
ommersHash = ByteString(kec256(x.toBytes: Array[Byte])),
Expand All @@ -67,8 +69,10 @@ abstract class BlockGeneratorSkeleton(
unixTimestamp = blockTimestamp,
extraData = blockchainConfig.daoForkConfig.flatMap(daoForkConfig => daoForkConfig.getExtraData(blockNumber)).getOrElse(headerExtraData),
mixHash = ByteString.empty,
nonce = ByteString.empty
nonce = ByteString.empty,
optOut = optOut
)
}

protected def prepareHeader(
blockNumber: BigInt, parent: Block,
Expand Down
Expand Up @@ -32,6 +32,7 @@ object BlockHeaderError {
case object HeaderGasLimitError extends BlockHeaderError
case object HeaderNumberError extends BlockHeaderError
case object HeaderPoWError extends BlockHeaderError
case object HeaderOptOutError extends BlockHeaderError
}

sealed trait BlockHeaderValid
Expand Down
Expand Up @@ -48,6 +48,7 @@ abstract class BlockHeaderValidatorSkeleton(blockchainConfig: BlockchainConfig)
_ <- validateGasUsed(blockHeader)
_ <- validateGasLimit(blockHeader, parentHeader)
_ <- validateNumber(blockHeader, parentHeader)
_ <- validateOptOut(blockHeader)
_ <- validateEvenMore(blockHeader, parentHeader)
} yield BlockHeaderValid
}
Expand Down Expand Up @@ -163,6 +164,21 @@ abstract class BlockHeaderValidatorSkeleton(blockchainConfig: BlockchainConfig)
if(blockHeader.number == parentHeader.number + 1) Right(BlockHeaderValid)
else Left(HeaderNumberError)

/**
* Validates [[io.iohk.ethereum.domain.BlockHeader.optOut]] is only defined if ECIP1098 is enabled at the block's number
*
* @param blockHeader BlockHeader to validate.
* @return BlockHeader if valid, an [[HeaderOptOutError]] otherwise
*/
private def validateOptOut(blockHeader: BlockHeader): Either[BlockHeaderError, BlockHeaderValid] = {
val isEcip1098Activated = blockHeader.number >= blockchainConfig.ecip1098BlockNumber
val isOptOutDefined = blockHeader.optOut.isDefined

if(isEcip1098Activated && isOptOutDefined) Right(BlockHeaderValid)
else if (!isEcip1098Activated && !isOptOutDefined) Right(BlockHeaderValid)
else Left(HeaderOptOutError)
}

}


Expand Down
Expand Up @@ -55,15 +55,16 @@ object BlockHeadersStorage {
Long,
ByteString,
ByteString,
ByteString
ByteString,
Option[Boolean]
)

import boopickle.DefaultBasic._

implicit val byteStringPickler: Pickler[ByteString] = transformPickler[ByteString, Array[Byte]](ByteString(_))(_.toArray[Byte])
implicit val blockHeaderPickler: Pickler[BlockHeader] = transformPickler[BlockHeader, BlockHeaderBody]
{ case (ph, oh, b, sr, txr, rr, lb, d, no, gl, gu, ut, ed, mh, n) =>
new BlockHeader(ph, oh, b, sr, txr, rr, lb, d, no, gl, gu, ut, ed, mh, n)
{ case (ph, oh, b, sr, txr, rr, lb, d, no, gl, gu, ut, ed, mh, n, oo) =>
new BlockHeader(ph, oh, b, sr, txr, rr, lb, d, no, gl, gu, ut, ed, mh, n, oo)
}{ blockHeader => (
blockHeader.parentHash,
blockHeader.ommersHash,
Expand All @@ -79,6 +80,7 @@ object BlockHeadersStorage {
blockHeader.unixTimestamp,
blockHeader.extraData,
blockHeader.mixHash,
blockHeader.nonce
blockHeader.nonce,
blockHeader.optOut
)}
}
41 changes: 35 additions & 6 deletions src/main/scala/io/iohk/ethereum/domain/BlockHeader.scala
Expand Up @@ -22,7 +22,8 @@ case class BlockHeader(
unixTimestamp: Long,
extraData: ByteString,
mixHash: ByteString,
nonce: ByteString) {
nonce: ByteString,
optOut: Option[Boolean]) {

override def toString: String = {
s"""BlockHeader {
Expand All @@ -40,7 +41,8 @@ case class BlockHeader(
|unixTimestamp: $unixTimestamp,
|extraData: ${Hex.toHexString(extraData.toArray[Byte])}
|mixHash: ${Hex.toHexString(mixHash.toArray[Byte])}
|nonce: ${Hex.toHexString(nonce.toArray[Byte])}
|nonce: ${Hex.toHexString(nonce.toArray[Byte])},
|optOut: $optOut
|}""".stripMargin
}

Expand All @@ -58,13 +60,18 @@ case class BlockHeader(

object BlockHeader {

private val NumberOfFields = 16

val emptyOmmerHash = ByteString(Hex.decode("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"))

def getEncodedWithoutNonce(blockHeader: BlockHeader): Array[Byte] = {
val rlpEncoded = blockHeader.toRLPEncodable match {
case rlpList: RLPList =>
case rlpList: RLPList if rlpList.items.length == (NumberOfFields - 1) =>
RLPList(rlpList.items.dropRight(2): _*)

case rlpList: RLPList if rlpList.items.length == NumberOfFields =>
val rlpItemsWithoutNonce = rlpList.items.dropRight(3) :+ rlpList.items.last
RLPList(rlpItemsWithoutNonce: _*)
case _ => throw new Exception("BlockHeader cannot be encoded without nonce and mixHash")
}
rlpEncode(rlpEncoded)
Expand All @@ -73,8 +80,17 @@ object BlockHeader {
implicit class BlockHeaderEnc(blockHeader: BlockHeader) extends RLPSerializable {
override def toRLPEncodable: RLPEncodeable = {
import blockHeader._
RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce)
optOut match {
case Some(definedOptOut) =>
val encodedOptOut = if(definedOptOut) 1 else 0

RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, RLPList(encodedOptOut))

case None =>
RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce)
}
}
}

Expand All @@ -88,7 +104,20 @@ object BlockHeader {
case RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce) =>
BlockHeader(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce)
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, None)

case RLPList(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, RLPList(encodedOptOut)) =>
val booleanOptOut =
if ((encodedOptOut: Int) == 1) true
else if ((encodedOptOut: Int) == 0) false
else throw new Exception("BlockHeader cannot be decoded with an invalid opt-out")

BlockHeader(parentHash, ommersHash, beneficiary, stateRoot, transactionsRoot, receiptsRoot,
logsBloom, difficulty, number, gasLimit, gasUsed, unixTimestamp, extraData, mixHash, nonce, Some(booleanOptOut))

case _ =>
throw new Exception("BlockHeader cannot be decoded")
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/io/iohk/ethereum/extvm/VMServer.scala
Expand Up @@ -106,7 +106,8 @@ class VMServer(messageHandler: MessageHandler)
contextMsg.blockHeader.get.unixTimestamp,
irrelevant,
irrelevant,
irrelevant
irrelevant,
None
)

val blockchainConfig = contextMsg.config.ethereumConfig.map(constructBlockchainConfig).getOrElse(defaultBlockchainConfig)
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/io/iohk/ethereum/utils/BlockchainConfig.scala
Expand Up @@ -25,6 +25,7 @@ case class BlockchainConfig(
aghartaBlockNumber: BigInt,
phoenixBlockNumber: BigInt,
petersburgBlockNumber: BigInt,
ecip1098BlockNumber: BigInt,

maxCodeSize: Option[BigInt],
difficultyBombPauseBlockNumber: BigInt,
Expand Down Expand Up @@ -68,6 +69,7 @@ object BlockchainConfig {
val aghartaBlockNumber: BigInt = BigInt(blockchainConfig.getString("agharta-block-number"))
val phoenixBlockNumber: BigInt = BigInt(blockchainConfig.getString("phoenix-block-number"))
val petersburgBlockNumber: BigInt = BigInt(blockchainConfig.getString("petersburg-block-number"))
val ecip1098BlockNumber: BigInt = BigInt(blockchainConfig.getString("ecip1098-block-number"))

val maxCodeSize: Option[BigInt] = Try(BigInt(blockchainConfig.getString("max-code-size"))).toOption
val difficultyBombPauseBlockNumber: BigInt = BigInt(blockchainConfig.getString("difficulty-bomb-pause-block-number"))
Expand Down Expand Up @@ -111,6 +113,7 @@ object BlockchainConfig {
aghartaBlockNumber = aghartaBlockNumber,
phoenixBlockNumber = phoenixBlockNumber,
petersburgBlockNumber = petersburgBlockNumber,
ecip1098BlockNumber = ecip1098BlockNumber,

maxCodeSize = maxCodeSize,
difficultyBombPauseBlockNumber = difficultyBombPauseBlockNumber,
Expand Down
2 changes: 2 additions & 0 deletions src/test/resources/application.conf
Expand Up @@ -35,6 +35,8 @@ mantis {
# todo change
constantinople-block-number = "1000000000000000000"

ecip1098-block-number = "1000000000000000000"

difficulty-bomb-pause-block-number = "3000000"

difficulty-bomb-continue-block-number = "5000000"
Expand Down

0 comments on commit da98f44

Please sign in to comment.