Skip to content

Commit

Permalink
ETCM-284 block with checkpoint will not generate coinbase reward
Browse files Browse the repository at this point in the history
  • Loading branch information
pslaski committed Oct 29, 2020
1 parent 663f0b8 commit 28ed295
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 53 deletions.
15 changes: 14 additions & 1 deletion src/main/scala/io/iohk/ethereum/jsonrpc/BlockResponse.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.iohk.ethereum.jsonrpc

import akka.util.ByteString
import io.iohk.ethereum.domain.{Block, BlockHeader, BlockBody}
import io.iohk.ethereum.crypto.ECDSASignature
import io.iohk.ethereum.domain.{Block, BlockBody, BlockHeader}

case class CheckpointResponse(signatures: Seq[ECDSASignature], signers: Seq[ByteString])

case class BlockResponse(
number: BigInt,
Expand All @@ -21,6 +24,8 @@ case class BlockResponse(
gasLimit: BigInt,
gasUsed: BigInt,
timestamp: BigInt,
checkpoint: Option[CheckpointResponse],
treasuryOptOut: Option[Boolean],
transactions: Either[Seq[ByteString], Seq[TransactionResponse]],
uncles: Seq[ByteString]
)
Expand All @@ -41,6 +46,12 @@ object BlockResponse {
else
Left(block.body.transactionList.map(_.hash))

val checkpoint = block.header.checkpoint.map { checkpoint =>
val signers = checkpoint.signatures.flatMap(_.publicKey(block.header.parentHash))

CheckpointResponse(checkpoint.signatures, signers)
}

BlockResponse(
number = block.header.number,
hash = if (pendingBlock) None else Some(block.header.hash),
Expand All @@ -59,6 +70,8 @@ object BlockResponse {
gasLimit = block.header.gasLimit,
gasUsed = block.header.gasUsed,
timestamp = block.header.unixTimestamp,
checkpoint = checkpoint,
treasuryOptOut = block.header.treasuryOptOut,
transactions = transactions,
uncles = block.body.uncleNodesList.map(_.hash)
)
Expand Down
89 changes: 47 additions & 42 deletions src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,61 +51,66 @@ class BlockPreparator(
* Pay 20% of it to the treasury contract
* 2. Miner is payed a reward for the inclusion of ommers
* 3. Ommers's miners are payed a reward for their inclusion in this block
* 4. There is no reward for block with checkpoint
*
* @param block the block being processed
* @param worldStateProxy the initial state
* @return the state after paying the appropriate reward to who corresponds
*/
// scalastyle:off method.length
private[ledger] def payBlockReward(
block: Block,
worldStateProxy: InMemoryWorldStateProxy
): InMemoryWorldStateProxy = {
val blockNumber = block.header.number

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

val minerAddress = Address(block.header.beneficiary)
val treasuryAddress = blockchainConfig.treasuryAddress
val existsTreasuryContract = worldStateProxy.getAccount(treasuryAddress).isDefined

val worldAfterPayingBlockReward =
if (block.header.treasuryOptOut.isEmpty || !existsTreasuryContract) {
val minerReward = minerRewardForOmmers + minerRewardForBlock
val worldAfterMinerReward = increaseAccountBalance(minerAddress, UInt256(minerReward))(worldStateProxy)
log.debug(s"Paying block $blockNumber reward of $minerReward to miner with address $minerAddress")
worldAfterMinerReward
} else if (block.header.treasuryOptOut.get) {
val minerReward = minerRewardForOmmers + minerRewardForBlock * MinerRewardPercentageAfterECIP1098 / 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 + minerRewardForBlock * MinerRewardPercentageAfterECIP1098 / 100
val worldAfterMinerReward = increaseAccountBalance(minerAddress, UInt256(minerReward))(worldStateProxy)
val treasuryReward = minerRewardForBlock * TreasuryRewardPercentageAfterECIP1098 / 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"
)
worldAfterTreasuryReward
}
if (block.hasCheckpoint) worldStateProxy
else {
val blockNumber = block.header.number

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

val minerAddress = Address(block.header.beneficiary)
val treasuryAddress = blockchainConfig.treasuryAddress
val existsTreasuryContract = worldStateProxy.getAccount(treasuryAddress).isDefined

val worldAfterPayingBlockReward =
if (block.header.treasuryOptOut.isEmpty || !existsTreasuryContract) {
val minerReward = minerRewardForOmmers + minerRewardForBlock
val worldAfterMinerReward = increaseAccountBalance(minerAddress, UInt256(minerReward))(worldStateProxy)
log.debug(s"Paying block $blockNumber reward of $minerReward to miner with address $minerAddress")
worldAfterMinerReward
} else if (block.header.treasuryOptOut.get) {
val minerReward = minerRewardForOmmers + minerRewardForBlock * MinerRewardPercentageAfterECIP1098 / 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 + minerRewardForBlock * MinerRewardPercentageAfterECIP1098 / 100
val worldAfterMinerReward = increaseAccountBalance(minerAddress, UInt256(minerReward))(worldStateProxy)
val treasuryReward = minerRewardForBlock * TreasuryRewardPercentageAfterECIP1098 / 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"
)
worldAfterTreasuryReward
}

block.body.uncleNodesList.foldLeft(worldAfterPayingBlockReward) { (ws, ommer) =>
val ommerAddress = Address(ommer.beneficiary)
val ommerReward = blockRewardCalculator.calculateOmmerRewardForInclusion(blockNumber, ommer.number)
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")
increaseAccountBalance(ommerAddress, UInt256(ommerReward))(ws)
log.debug(s"Paying block $blockNumber reward of $ommerReward to ommer with account address $ommerAddress")
increaseAccountBalance(ommerAddress, UInt256(ommerReward))(ws)
}
}
}

// scalastyle:on
/**
* v0 ≡ Tg (Tx gas limit) * Tp (Tx gas price). See YP equation number (68)
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@ import io.iohk.ethereum.crypto.kec256
import io.iohk.ethereum.domain._
import io.iohk.ethereum.jsonrpc.EthService._
import io.iohk.ethereum.jsonrpc.FilterManager.LogFilterLogs
import io.iohk.ethereum.jsonrpc.serialization.JsonSerializers.{
OptionNoneToJNullSerializer,
QuantitiesSerializer,
UnformattedDataJsonSerializer
}
import io.iohk.ethereum.jsonrpc.PersonalService._
import io.iohk.ethereum.jsonrpc.serialization.JsonSerializers.{OptionNoneToJNullSerializer, QuantitiesSerializer, UnformattedDataJsonSerializer}
import io.iohk.ethereum.ommers.OmmersPool
import io.iohk.ethereum.ommers.OmmersPool.Ommers
import io.iohk.ethereum.testing.ActorsTesting.simpleAutoPilot
Expand Down Expand Up @@ -110,6 +106,48 @@ class JsonRpcControllerEthSpec
response should haveResult(expectedBlockResponse)
}

it should "handle eth_getBlockByHash request (block with checkpoint)" in new JsonRpcControllerFixture {
val blockToRequest = blockWithCheckpoint
val blockTd = blockToRequest.header.difficulty

blockchain
.storeBlock(blockToRequest)
.and(blockchain.storeTotalDifficulty(blockToRequest.header.hash, blockTd))
.commit()

val request = newJsonRpcRequest(
"eth_getBlockByHash",
List(JString(s"0x${blockToRequest.header.hashAsHexString}"), JBool(false))
)
val response = jsonRpcController.handleRequest(request).futureValue

val expectedBlockResponse =
Extraction.decompose(BlockResponse(blockToRequest, fullTxs = false, totalDifficulty = Some(blockTd)))

response should haveResult(expectedBlockResponse)
}

it should "handle eth_getBlockByHash request (block with treasuryOut)" in new JsonRpcControllerFixture {
val blockToRequest = blockWithTreasuryOut
val blockTd = blockToRequest.header.difficulty

blockchain
.storeBlock(blockToRequest)
.and(blockchain.storeTotalDifficulty(blockToRequest.header.hash, blockTd))
.commit()

val request = newJsonRpcRequest(
"eth_getBlockByHash",
List(JString(s"0x${blockToRequest.header.hashAsHexString}"), JBool(false))
)
val response = jsonRpcController.handleRequest(request).futureValue

val expectedBlockResponse =
Extraction.decompose(BlockResponse(blockToRequest, fullTxs = false, totalDifficulty = Some(blockTd)))

response should haveResult(expectedBlockResponse)
}

it should "handle eth_getBlockByNumber request" in new JsonRpcControllerFixture {
val blockToRequest = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body)
val blockTd = blockToRequest.header.difficulty
Expand All @@ -131,6 +169,48 @@ class JsonRpcControllerEthSpec
response should haveResult(expectedBlockResponse)
}

it should "handle eth_getBlockByNumber request (block with treasuryOut)" in new JsonRpcControllerFixture {
val blockToRequest = blockWithTreasuryOut
val blockTd = blockToRequest.header.difficulty

blockchain
.storeBlock(blockToRequest)
.and(blockchain.storeTotalDifficulty(blockToRequest.header.hash, blockTd))
.commit()

val request = newJsonRpcRequest(
"eth_getBlockByNumber",
List(JString(s"0x${Hex.toHexString(blockToRequest.header.number.toByteArray)}"), JBool(false))
)
val response = jsonRpcController.handleRequest(request).futureValue

val expectedBlockResponse =
Extraction.decompose(BlockResponse(blockToRequest, fullTxs = false, totalDifficulty = Some(blockTd)))

response should haveResult(expectedBlockResponse)
}

it should "handle eth_getBlockByNumber request (block with checkpoint)" in new JsonRpcControllerFixture {
val blockToRequest = blockWithCheckpoint
val blockTd = blockToRequest.header.difficulty

blockchain
.storeBlock(blockToRequest)
.and(blockchain.storeTotalDifficulty(blockToRequest.header.hash, blockTd))
.commit()

val request = newJsonRpcRequest(
"eth_getBlockByNumber",
List(JString(s"0x${Hex.toHexString(blockToRequest.header.number.toByteArray)}"), JBool(false))
)
val response = jsonRpcController.handleRequest(request).futureValue

val expectedBlockResponse =
Extraction.decompose(BlockResponse(blockToRequest, fullTxs = false, totalDifficulty = Some(blockTd)))

response should haveResult(expectedBlockResponse)
}

it should "handle eth_getUncleByBlockHashAndIndex request" in new JsonRpcControllerFixture {
val uncle = Fixtures.Blocks.DaoForkBlock.header
val blockToRequest = Block(Fixtures.Blocks.Block3125369.header, BlockBody(Nil, Seq(uncle)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package io.iohk.ethereum.jsonrpc
import akka.actor.ActorSystem
import akka.testkit.TestProbe
import akka.util.ByteString
import io.iohk.ethereum.{Fixtures, Timeouts}
import io.iohk.ethereum.{Fixtures, ObjectGenerators, Timeouts}
import io.iohk.ethereum.blockchain.sync.EphemBlockchainTestSetup
import io.iohk.ethereum.checkpointing.CheckpointingTestHelpers
import io.iohk.ethereum.consensus.{ConsensusConfigs, TestConsensus}
import io.iohk.ethereum.consensus.ethash.blocks.EthashBlockGenerator
import io.iohk.ethereum.consensus.ethash.validators.ValidatorsExecutor
import io.iohk.ethereum.crypto.ECDSASignature
import io.iohk.ethereum.db.storage.AppStateStorage
import io.iohk.ethereum.domain.BlockHeader.HeaderExtraFields.HefPostEcip1098
import io.iohk.ethereum.domain.{Block, BlockBody, SignedTransaction}
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonRpcConfig
import io.iohk.ethereum.keystore.KeyStore
Expand Down Expand Up @@ -113,6 +115,11 @@ class JsonRpcControllerFixture(implicit system: ActorSystem)
unixTimestamp = 0
)

val checkpoint = ObjectGenerators.fakeCheckpointGen(2, 5).sample.get
val blockWithCheckpoint = CheckpointingTestHelpers.createBlockWithCheckpoint(Fixtures.Blocks.Block3125369.header, checkpoint)
val blockWithTreasuryOut =
Block(Fixtures.Blocks.Block3125369.header.copy(extraFields = HefPostEcip1098(true)), Fixtures.Blocks.Block3125369.body)

val parentBlock = Block(blockHeader.copy(number = 1), BlockBody.empty)

val r: ByteString = ByteString(Hex.decode("a3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a1"))
Expand Down
26 changes: 22 additions & 4 deletions src/test/scala/io/iohk/ethereum/ledger/BlockExecutionSpec.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package io.iohk.ethereum.ledger

import akka.util.ByteString
import io.iohk.ethereum.Mocks
import io.iohk.ethereum.Mocks.{MockVM, MockValidatorsFailOnSpecificBlockNumber}
import io.iohk.ethereum.Mocks.{MockVM, MockValidatorsAlwaysSucceed, MockValidatorsFailOnSpecificBlockNumber}
import io.iohk.ethereum.checkpointing.CheckpointingTestHelpers
import io.iohk.ethereum.consensus.TestConsensus
import io.iohk.ethereum.crypto.ECDSASignature
import io.iohk.ethereum.domain._
import io.iohk.ethereum.ledger.Ledger.BlockResult
import io.iohk.ethereum.vm.OutOfGas
import org.scalatest.prop.TableFor4
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import io.iohk.ethereum.{Mocks, ObjectGenerators}
import org.scalatest.matchers.should.Matchers
import org.scalatest.prop.TableFor4
import org.scalatest.wordspec.AnyWordSpec
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks

// scalastyle:off magic.number
class BlockExecutionSpec extends AnyWordSpec with Matchers with ScalaCheckPropertyChecks {
Expand Down Expand Up @@ -89,6 +90,23 @@ class BlockExecutionSpec extends AnyWordSpec with Matchers with ScalaCheckProper
blocks.head.block shouldBe block1
error.isDefined shouldBe true
}

"block with checkpoint and without txs" in new BlockchainSetup {
val checkpoint = ObjectGenerators.fakeCheckpointGen(2, 5).sample.get
val blockWithCheckpoint = CheckpointingTestHelpers.createBlockWithCheckpoint(validBlockParentHeader, checkpoint)

val mockValidators = MockValidatorsAlwaysSucceed
val newConsensus: TestConsensus = consensus.withVM(vm).withValidators(mockValidators)
val blockValidation = new BlockValidation(newConsensus, blockchain, BlockQueue(blockchain, syncConfig))
val blockExecution =
new BlockExecution(blockchain, blockchainConfig, newConsensus.blockPreparator, blockValidation)

val (blocks, error) = blockExecution.executeBlocks(List(blockWithCheckpoint), defaultBlockHeader.difficulty)

blocks.size shouldBe 1
blocks.head.block shouldBe blockWithCheckpoint
error.isDefined shouldBe false
}
}

"correctly run executeBlockTransactions" when {
Expand Down

0 comments on commit 28ed295

Please sign in to comment.