Skip to content

Commit

Permalink
[ETCM-44] Treasury block reward distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Tallar committed Sep 22, 2020
1 parent 071d36a commit 6d8f6b9
Show file tree
Hide file tree
Showing 20 changed files with 117 additions and 67 deletions.
Expand Up @@ -43,7 +43,8 @@ object BlockchainTestConfig {
atlantisBlockNumber = Long.MaxValue,
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
ecip1098BlockNumber = Long.MaxValue,
treasuryAddress = Address(0)
)

val FrontierConfig = BaseBlockchainConfig.copy(
Expand Down
9 changes: 5 additions & 4 deletions src/it/scala/io/iohk/ethereum/txExecTest/ECIP1017Test.scala
Expand Up @@ -2,11 +2,11 @@ package io.iohk.ethereum.txExecTest

import java.util.concurrent.Executors

import io.iohk.ethereum.domain.{ BlockchainImpl, Receipt, UInt256 }
import io.iohk.ethereum.domain.{Address, BlockchainImpl, Receipt, UInt256}
import io.iohk.ethereum.ledger._
import io.iohk.ethereum.txExecTest.util.FixtureProvider
import io.iohk.ethereum.utils.{ BlockchainConfig, MonetaryPolicyConfig }
import org.scalatest.{ FlatSpec, Matchers }
import io.iohk.ethereum.utils.{BlockchainConfig, MonetaryPolicyConfig}
import org.scalatest.{FlatSpec, Matchers}

import scala.concurrent.ExecutionContext

Expand Down Expand Up @@ -47,7 +47,8 @@ class ECIP1017Test extends FlatSpec with Matchers {
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue,
petersburgBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
ecip1098BlockNumber = Long.MaxValue,
treasuryAddress = Address(0)
)
val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4))

Expand Down
11 changes: 6 additions & 5 deletions src/it/scala/io/iohk/ethereum/txExecTest/ForksTest.scala
Expand Up @@ -2,11 +2,11 @@ package io.iohk.ethereum.txExecTest

import java.util.concurrent.Executors

import io.iohk.ethereum.domain.{ BlockchainImpl, Receipt, UInt256 }
import io.iohk.ethereum.ledger.{ BlockExecution, BlockQueue, BlockValidation }
import io.iohk.ethereum.domain.{Address, BlockchainImpl, Receipt, UInt256}
import io.iohk.ethereum.ledger.{BlockExecution, BlockQueue, BlockValidation}
import io.iohk.ethereum.txExecTest.util.FixtureProvider
import io.iohk.ethereum.utils.{ BlockchainConfig, MonetaryPolicyConfig }
import org.scalatest.{ FlatSpec, Matchers }
import io.iohk.ethereum.utils.{BlockchainConfig, MonetaryPolicyConfig}
import org.scalatest.{FlatSpec, Matchers}

import scala.concurrent.ExecutionContext

Expand Down Expand Up @@ -44,7 +44,8 @@ class ForksTest extends FlatSpec with Matchers {
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue,
petersburgBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
ecip1098BlockNumber = Long.MaxValue,
treasuryAddress = Address(0)
)

val noErrors = a[Right[_, Seq[Receipt]]]
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/chains/base.conf
Expand Up @@ -80,6 +80,7 @@

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

# DAO fork configuration (Ethereum HF/Classic split)
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/chains/eth.conf
Expand Up @@ -47,6 +47,7 @@

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

monetary-policy {
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/chains/mordor.conf
Expand Up @@ -80,6 +80,7 @@

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

# DAO fork configuration (Ethereum HF/Classic split)
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/chains/private.conf
Expand Up @@ -12,6 +12,7 @@
agharta-block-number = "0"
phoenix-block-number = "0"
# FIXME: Once the testnet is up an running we should determine this value
treasury-address = "0011223344556677889900112233445566778899"
ecip1098-block-number = "1000000000000000000"

chain-id = "0x2A"
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/chains/ropsten.conf
Expand Up @@ -21,6 +21,7 @@

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

max-code-size = 24576
Expand Down
54 changes: 39 additions & 15 deletions src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala
Expand Up @@ -45,26 +45,47 @@ class BlockPreparator(
* @return
*/
private[ledger] def payBlockReward(block: Block, worldStateProxy: InMemoryWorldStateProxy): InMemoryWorldStateProxy = {
def getAccountToPay(address: Address, ws: InMemoryWorldStateProxy): Account =
ws.getAccount(address).getOrElse(Account.empty(blockchainConfig.accountStartNonce))

val blockNumber = block.header.number

val rewardForBlock = blockRewardCalculator.calculateMiningRewardForBlock(blockNumber)
val minerRewardForOmmers = blockRewardCalculator.calculateMiningRewardForOmmers(blockNumber, block.body.uncleNodesList.size)

val minerAddress = Address(block.header.beneficiary)
val minerAccount = getAccountToPay(minerAddress, worldStateProxy)
val minerReward = blockRewardCalculator.calcBlockMinerReward(blockNumber, block.body.uncleNodesList.size)
val treasuryAddress = blockchainConfig.treasuryAddress
val existsTreasuryContract = worldStateProxy.getAccount(treasuryAddress).isDefined

val afterMinerReward = worldStateProxy.saveAccount(minerAddress, minerAccount.increaseBalance(UInt256(minerReward)))
log.debug(s"Paying block $blockNumber reward of $minerReward to miner with account address $minerAddress")
val worldAfterPayingBlockReward =
if (block.header.treasuryOptOut.isEmpty || !existsTreasuryContract) {
val minerReward = minerRewardForOmmers + rewardForBlock
val worldAfterMinerReward = pay(minerAddress, UInt256(minerReward), withTouch = false)(worldStateProxy)
log.debug(s"Paying block $blockNumber reward of $minerReward to miner with address $minerAddress")

block.body.uncleNodesList.foldLeft(afterMinerReward) { (ws, ommer) =>
val ommerAddress = Address(ommer.beneficiary)
val account = getAccountToPay(ommerAddress, ws)
worldAfterMinerReward
} else if (block.header.treasuryOptOut.get) {
val minerReward = minerRewardForOmmers + rewardForBlock * 80 / 100
val worldAfterMinerReward = increaseAccountBalance(minerAddress, UInt256(minerReward))(worldStateProxy)
log.debug(s"Paying block $blockNumber reward of $minerReward to miner with address $minerAddress, miner opted-out of treasury")

worldAfterMinerReward
} else {
val minerReward = minerRewardForOmmers + rewardForBlock * 80 / 100
val worldAfterMinerReward = increaseAccountBalance(minerAddress, UInt256(minerReward))(worldStateProxy)

val treasuryReward = rewardForBlock * 20 / 100
val worldAfterTreasuryReward = increaseAccountBalance(treasuryAddress, UInt256(treasuryReward))(worldAfterMinerReward)

log.debug(s"Paying block $blockNumber reward of $minerReward to miner with address $minerAddress" +
s"paying treasury reward of $treasuryReward to treasury with address $treasuryAddress")

val ommerReward = blockRewardCalculator.calcOmmerMinerReward(blockNumber, ommer.number)
worldAfterTreasuryReward
}

block.body.uncleNodesList.foldLeft(worldAfterPayingBlockReward) { (ws, ommer) =>
val ommerAddress = Address(ommer.beneficiary)
val ommerReward = blockRewardCalculator.calculateOmmerRewardForInclusion(blockNumber, ommer.number)

log.debug(s"Paying block $blockNumber reward of $ommerReward to ommer with account address $ommerAddress")
ws.saveAccount(ommerAddress, account.increaseBalance(UInt256(ommerReward)))
increaseAccountBalance(ommerAddress, UInt256(ommerReward))(ws)
}
}

Expand Down Expand Up @@ -120,13 +141,16 @@ class BlockPreparator(
}
}

private[ledger] def increaseAccountBalance(address: Address, value: UInt256)(world: InMemoryWorldStateProxy): InMemoryWorldStateProxy = {
val account = world.getAccount(address).getOrElse(Account.empty(blockchainConfig.accountStartNonce)).increaseBalance(value)
world.saveAccount(address, account)
}

private[ledger] def pay(address: Address, value: UInt256, withTouch: Boolean)(world: InMemoryWorldStateProxy): InMemoryWorldStateProxy = {
if (world.isZeroValueTransferToNonExistentAccount(address, value)) {
world
} else {
val account = world.getAccount(address).getOrElse(Account.empty(blockchainConfig.accountStartNonce)).increaseBalance(value)
val savedWorld = world.saveAccount(address, account)

val savedWorld = increaseAccountBalance(address, value)(world)
if (withTouch) savedWorld.touchAccounts(address) else savedWorld
}
}
Expand Down
17 changes: 7 additions & 10 deletions src/main/scala/io/iohk/ethereum/ledger/BlockRewardCalculator.scala
Expand Up @@ -44,20 +44,17 @@ class BlockRewardCalculator(config: MonetaryPolicyConfig, byzantiumBlockNumber:
val firstEraOmmerMiningRewardDenom: BigInt = 8


def calcBlockMinerReward(blockNumber: BigInt, ommersCount: Int): BigInt = {
val baseReward = calcMinerBaseReward(blockNumber)
val ommersReward = calcMinerRewardPerOmmer(blockNumber) * ommersCount
baseReward + ommersReward
}
def calculateMiningRewardForOmmers(blockNumber: BigInt, ommersCount: Int): BigInt =
calculateMiningRewardPerOmmer(blockNumber) * ommersCount

def calcOmmerMinerReward(blockNumber: BigInt, ommerNumber: BigInt): BigInt = {
def calculateOmmerRewardForInclusion(blockNumber: BigInt, ommerNumber: BigInt): BigInt = {
val era = eraNumber(blockNumber)

if (era == 0) {
val number = firstEraOmmerMiningRewardMaxNumer - (blockNumber - ommerNumber - 1)
(newBlockReward(blockNumber) * number) / firstEraOmmerMiningRewardDenom
} else
calcMinerBaseReward(blockNumber) * ommerMiningRewardNumer / ommerMiningRewardDenom
calculateMiningRewardForBlock(blockNumber) * ommerMiningRewardNumer / ommerMiningRewardDenom
}

/**
Expand All @@ -66,7 +63,7 @@ class BlockRewardCalculator(config: MonetaryPolicyConfig, byzantiumBlockNumber:
* @param blockNumber mined block
* @return miner base reward
*/
private def calcMinerBaseReward(blockNumber: BigInt): BigInt = {
def calculateMiningRewardForBlock(blockNumber: BigInt): BigInt = {
val era = eraNumber(blockNumber)
val eraMultiplier = rewardReductionRateNumer.pow(era)
val eraDivisor = rewardReductionRateDenom.pow(era)
Expand All @@ -79,8 +76,8 @@ class BlockRewardCalculator(config: MonetaryPolicyConfig, byzantiumBlockNumber:
* @param blockNumber mined block
* @return reward given to the miner for each ommer included
*/
private def calcMinerRewardPerOmmer(blockNumber: BigInt): BigInt = {
calcMinerBaseReward(blockNumber) * ommerInclusionRewardNumer / ommerInclusionRewardDenom
private def calculateMiningRewardPerOmmer(blockNumber: BigInt): BigInt = {
calculateMiningRewardForBlock(blockNumber) * ommerInclusionRewardNumer / ommerInclusionRewardDenom
}

/** era number counting from 0 */
Expand Down
6 changes: 4 additions & 2 deletions src/main/scala/io/iohk/ethereum/utils/BlockchainConfig.scala
@@ -1,10 +1,9 @@
package io.iohk.ethereum.utils

import io.iohk.ethereum.domain.UInt256
import io.iohk.ethereum.domain.{Address, UInt256}
import io.iohk.ethereum.utils.NumericUtils._

import scala.collection.JavaConverters._

import com.typesafe.config.{Config => TypesafeConfig}

import scala.util.Try
Expand All @@ -25,6 +24,7 @@ case class BlockchainConfig(
aghartaBlockNumber: BigInt,
phoenixBlockNumber: BigInt,
petersburgBlockNumber: BigInt,
treasuryAddress: Address,
ecip1098BlockNumber: BigInt,

maxCodeSize: Option[BigInt],
Expand Down Expand Up @@ -69,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 treasuryAddress = Address(blockchainConfig.getString("treasury-address"))
val ecip1098BlockNumber: BigInt = BigInt(blockchainConfig.getString("ecip1098-block-number"))

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

maxCodeSize = maxCodeSize,
Expand Down
Expand Up @@ -220,7 +220,8 @@ class BlockGeneratorSpec extends FlatSpec with Matchers with ScalaCheckPropertyC
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue,
petersburgBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
ecip1098BlockNumber = Long.MaxValue,
treasuryAddress = Address(0)
)

override lazy val blockExecution =
Expand Down Expand Up @@ -489,7 +490,8 @@ class BlockGeneratorSpec extends FlatSpec with Matchers with ScalaCheckPropertyC
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue,
petersburgBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
ecip1098BlockNumber = Long.MaxValue,
treasuryAddress = Address(0)
)
override lazy val blockchainConfig = baseBlockchainConfig

Expand Down
Expand Up @@ -430,7 +430,8 @@ class BlockHeaderValidatorSpec
aghartaBlockNumber = Long.MaxValue,
phoenixBlockNumber = Long.MaxValue,
petersburgBlockNumber = Long.MaxValue,
ecip1098BlockNumber = Long.MaxValue
ecip1098BlockNumber = Long.MaxValue,
treasuryAddress = Address(0)
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/test/scala/io/iohk/ethereum/jsonrpc/EthServiceSpec.scala
Expand Up @@ -876,7 +876,7 @@ class EthServiceSpec extends FlatSpec with Matchers with ScalaFutures with MockF
override lazy val ledger = mock[Ledger]
override lazy val stxLedger = mock[StxLedger]

override lazy val blockchainConfig = BlockchainConfig (
/*override lazy val blockchainConfig = BlockchainConfig (
ethCompatibleStorage = true,
//unused
Expand Down Expand Up @@ -907,7 +907,7 @@ class EthServiceSpec extends FlatSpec with Matchers with ScalaFutures with MockF
phoenixBlockNumber = 0,
petersburgBlockNumber = 0,
ecip1098BlockNumber = 0
)
)*/

override lazy val consensus: TestConsensus = buildTestConsensus().withBlockGenerator(blockGenerator)

Expand Down
Expand Up @@ -466,7 +466,8 @@ class PersonalServiceSpec
aghartaBlockNumber = 0,
phoenixBlockNumber = 0,
petersburgBlockNumber = 0,
ecip1098BlockNumber = 0
ecip1098BlockNumber = 0,
treasuryAddress = Address(0)
)

val wallet = Wallet(address, prvKey)
Expand Down
Expand Up @@ -7,7 +7,7 @@ import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
// scalastyle:off magic.number
class BlockRewardCalculatorSpec extends FlatSpec with Matchers with ScalaCheckPropertyChecks {

"BlockRewardCalculator" should "correctly calculate block and ommer rewards" in {
"BlockRewardCalculator" should "correctly calculate block and ommer rewards" in new TestSetup {
val standardMP =
MonetaryPolicyConfig(5000000, 0.2, 5000000000000000000L, 3000000000000000000L, 2000000000000000000L)

Expand Down Expand Up @@ -123,15 +123,15 @@ class BlockRewardCalculatorSpec extends FlatSpec with Matchers with ScalaCheckPr
) =>
val calculator = new BlockRewardCalculator(config, byzantiumBlockNumber, constantinopleBlockNumber)

val blockReward = calculator.calcBlockMinerReward(blockNumber, ommersNumbers.size)
val ommersRewards = ommersNumbers.map(calculator.calcOmmerMinerReward(blockNumber, _))
val blockReward = fullMinerReward(calculator)(blockNumber, ommersNumbers.size)
val ommersRewards = ommersNumbers.map(calculator.calculateOmmerRewardForInclusion(blockNumber, _))

blockReward shouldEqual expectedBlockReward
ommersRewards shouldEqual expectedOmmersRewards
}
}

it should "be compliant with ECIP1039 for block reward including zero, one or two, uncles until 50 era" in {
it should "be compliant with ECIP1039 for block reward including zero, one or two, uncles until 50 era" in new TestSetup {

val standardEraDuration = 5000000

Expand Down Expand Up @@ -197,17 +197,17 @@ class BlockRewardCalculatorSpec extends FlatSpec with Matchers with ScalaCheckPr
(config, blockNumber, expectedBlockReward, expectedWinnerOneUncleReward, expectedWinnerTwoUnclesReward) =>
val calculator = new BlockRewardCalculator(config, byzantiumBlockNumber, constantinopleBlockNumber)

val blockReward = calculator.calcBlockMinerReward(blockNumber, 0)
val winnerOneUncleReward = calculator.calcBlockMinerReward(blockNumber, 1)
val winnerTwoUnclesReward = calculator.calcBlockMinerReward(blockNumber, 2)
val blockReward = fullMinerReward(calculator)(blockNumber, 0)
val winnerOneUncleReward = fullMinerReward(calculator)(blockNumber, 1)
val winnerTwoUnclesReward = fullMinerReward(calculator)(blockNumber, 2)

blockReward shouldEqual expectedBlockReward
winnerOneUncleReward shouldEqual expectedWinnerOneUncleReward
winnerTwoUnclesReward shouldEqual expectedWinnerTwoUnclesReward
}
}

it should "be compliant with ECIP1039 for block reward including two uncles until 200 era" in {
it should "be compliant with ECIP1039 for block reward including two uncles until 200 era" in new TestSetup {

val standardEraDuration = 5000000

Expand Down Expand Up @@ -423,9 +423,17 @@ class BlockRewardCalculatorSpec extends FlatSpec with Matchers with ScalaCheckPr
forAll(ecip1039table) { (config, blockNumber, expectedWinnerTwoUnclesReward) =>
val calculator = new BlockRewardCalculator(config, byzantiumBlockNumber, constantinopleBlockNumber)

val winnerTwoUnclesReward = calculator.calcBlockMinerReward(blockNumber, 2)
val winnerTwoUnclesReward = fullMinerReward(calculator)(blockNumber, 2)

winnerTwoUnclesReward shouldEqual expectedWinnerTwoUnclesReward
}
}

trait TestSetup {
def fullMinerReward(blockRewardCalculator: BlockRewardCalculator)(blockNumber: BigInt, numberOfOmmers: Int): BigInt = {
val rewardForBlock = blockRewardCalculator.calculateMiningRewardForBlock(blockNumber)
val rewardForOmmers = blockRewardCalculator.calculateMiningRewardForOmmers(blockNumber, numberOfOmmers)
rewardForBlock + rewardForOmmers
}
}
}

0 comments on commit 6d8f6b9

Please sign in to comment.