From ba5a6e5fa078f46a8ea2ebd1bb8ddeb4ade12624 Mon Sep 17 00:00:00 2001 From: Michal Mrozek Date: Thu, 15 Oct 2020 17:39:12 +0200 Subject: [PATCH 1/3] [ECTM-212] Fix block preparation (taking into account EIP-161) --- .../io/iohk/ethereum/domain/Blockchain.scala | 99 +++-- .../ethereum/ledger/BlockPreparator.scala | 353 ++++++++++-------- .../io/iohk/ethereum/ledger/StxLedger.scala | 34 +- .../consensus/BlockGeneratorSpec.scala | 66 +++- 4 files changed, 336 insertions(+), 216 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala index d75c3e491f..b988c60c09 100644 --- a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala +++ b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala @@ -178,17 +178,21 @@ trait Blockchain { def genesisBlock: Block = getBlockByNumber(0).get - def getWorldStateProxy(blockNumber: BigInt, - accountStartNonce: UInt256, - stateRootHash: Option[ByteString], - noEmptyAccounts: Boolean, - ethCompatibleStorage: Boolean): WS - - def getReadOnlyWorldStateProxy(blockNumber: Option[BigInt], - accountStartNonce: UInt256, - stateRootHash: Option[ByteString], - noEmptyAccounts: Boolean, - ethCompatibleStorage: Boolean): WS + def getWorldStateProxy( + blockNumber: BigInt, + accountStartNonce: UInt256, + stateRootHash: Option[ByteString], + noEmptyAccounts: Boolean, + ethCompatibleStorage: Boolean + ): WS + + def getReadOnlyWorldStateProxy( + blockNumber: Option[BigInt], + accountStartNonce: UInt256, + stateRootHash: Option[ByteString], + noEmptyAccounts: Boolean, + ethCompatibleStorage: Boolean + ): WS def getStateStorage: StateStorage } @@ -259,7 +263,11 @@ class BlockchainImpl( mpt.get(address) } - override def getAccountStorageAt(rootHash: ByteString, position: BigInt, ethCompatibleStorage: Boolean): ByteString = { + override def getAccountStorageAt( + rootHash: ByteString, + position: BigInt, + ethCompatibleStorage: Boolean + ): ByteString = { val storage = stateStorage.getBackingStorage(0) val mpt = if (ethCompatibleStorage) domain.EthereumUInt256Mpt.storageMpt(rootHash, storage) @@ -268,7 +276,8 @@ class BlockchainImpl( } private def persistBestBlocksData(): Unit = { - appStateStorage.putBestBlockNumber(getBestBlockNumber()) + appStateStorage + .putBestBlockNumber(getBestBlockNumber()) .and(appStateStorage.putLatestCheckpointBlockNumber(getLatestCheckpointBlockNumber())) .commit() } @@ -298,7 +307,8 @@ class BlockchainImpl( override def getMptNodeByHash(hash: ByteString): Option[MptNode] = stateStorage.getNode(hash) - override def getTransactionLocation(txHash: ByteString): Option[TransactionLocation] = transactionMappingStorage.get(txHash) + override def getTransactionLocation(txHash: ByteString): Option[TransactionLocation] = + transactionMappingStorage.get(txHash) override def storeBlockBody(blockHash: ByteString, blockBody: BlockBody): DataSourceBatchUpdate = { blockBodiesStorage.put(blockHash, blockBody).and(saveTxsLocations(blockHash, blockBody)) @@ -351,14 +361,15 @@ class BlockchainImpl( val bestBlocks = bestKnownBlockAndLatestCheckpoint.get() // as we are decreasing block numbers in memory more often than in storage, // we can't use here getBestBlockNumber / getLatestCheckpointBlockNumber - val bestBlockNumber = if(bestBlocks.bestBlockNumber != 0) bestBlocks.bestBlockNumber else appStateStorage.getBestBlockNumber() + val bestBlockNumber = + if (bestBlocks.bestBlockNumber != 0) bestBlocks.bestBlockNumber else appStateStorage.getBestBlockNumber() val latestCheckpointNumber = { - if(bestBlocks.latestCheckpointNumber != 0) bestBlocks.latestCheckpointNumber + if (bestBlocks.latestCheckpointNumber != 0) bestBlocks.latestCheckpointNumber else appStateStorage.getLatestCheckpointBlockNumber() } val blockNumberMappingUpdates = { - maybeBlockHeader.fold(blockNumberMappingStorage.emptyBatchUpdate)( h => + maybeBlockHeader.fold(blockNumberMappingStorage.emptyBatchUpdate)(h => if (getHashByBlockNumber(h.number).contains(blockHash)) removeBlockNumberMapping(h.number) else blockNumberMappingStorage.emptyBatchUpdate @@ -369,19 +380,22 @@ class BlockchainImpl( case Some(header) => if (header.hasCheckpoint && header.number == latestCheckpointNumber) { val prev = findPreviousCheckpointBlockNumber(header.number, header.number) - prev.map { num => - (appStateStorage.putLatestCheckpointBlockNumber(num), Some(num)) - }.getOrElse { - (appStateStorage.removeLatestCheckpointBlockNumber(), Some(0)) - } + prev + .map { num => + (appStateStorage.putLatestCheckpointBlockNumber(num), Some(num)) + } + .getOrElse { + (appStateStorage.removeLatestCheckpointBlockNumber(), Some(0)) + } } else (appStateStorage.emptyBatchUpdate, None) case None => (appStateStorage.emptyBatchUpdate, None) } - val newBestBlockNumber: BigInt = if(bestBlockNumber >= 1) bestBlockNumber - 1 else 0 + val newBestBlockNumber: BigInt = if (bestBlockNumber >= 1) bestBlockNumber - 1 else 0 - blockHeadersStorage.remove(blockHash) + blockHeadersStorage + .remove(blockHash) .and(blockBodiesStorage.remove(blockHash)) .and(totalDifficultyStorage.remove(blockHash)) .and(receiptStorage.remove(blockHash)) @@ -394,7 +408,8 @@ class BlockchainImpl( maybeBlockHeader.foreach { h => if (withState) { - val bestBlocksUpdates = appStateStorage.putBestBlockNumber(newBestBlockNumber) + val bestBlocksUpdates = appStateStorage + .putBestBlockNumber(newBestBlockNumber) .and(checkpointUpdates) stateStorage.onBlockRollback(h.number, bestBlockNumber)(() => bestBlocksUpdates.commit()) } @@ -408,8 +423,8 @@ class BlockchainImpl( */ @tailrec private def findPreviousCheckpointBlockNumber( - blockNumberToCheck: BigInt, - latestCheckpointBlockNumber: BigInt + blockNumberToCheck: BigInt, + latestCheckpointBlockNumber: BigInt ): Option[BigInt] = { if (blockNumberToCheck > 0) { val maybePreviousCheckpointBlockNumber = for { @@ -432,19 +447,21 @@ class BlockchainImpl( } private def removeTxsLocations(stxs: Seq[SignedTransaction]): DataSourceBatchUpdate = { - stxs.map(_.hash).foldLeft(transactionMappingStorage.emptyBatchUpdate) { - case (updates, hash) => updates.and(transactionMappingStorage.remove(hash)) + stxs.map(_.hash).foldLeft(transactionMappingStorage.emptyBatchUpdate) { case (updates, hash) => + updates.and(transactionMappingStorage.remove(hash)) } } override type S = InMemoryWorldStateProxyStorage override type WS = InMemoryWorldStateProxy - override def getWorldStateProxy(blockNumber: BigInt, - accountStartNonce: UInt256, - stateRootHash: Option[ByteString], - noEmptyAccounts: Boolean, - ethCompatibleStorage: Boolean): InMemoryWorldStateProxy = + override def getWorldStateProxy( + blockNumber: BigInt, + accountStartNonce: UInt256, + stateRootHash: Option[ByteString], + noEmptyAccounts: Boolean, + ethCompatibleStorage: Boolean + ): InMemoryWorldStateProxy = InMemoryWorldStateProxy( evmCodeStorage, stateStorage.getBackingStorage(blockNumber), @@ -456,18 +473,20 @@ class BlockchainImpl( ) //FIXME Maybe we can use this one in regular execution too and persist underlying storage when block execution is successful - override def getReadOnlyWorldStateProxy(blockNumber: Option[BigInt], - accountStartNonce: UInt256, - stateRootHash: Option[ByteString], - noEmptyAccounts: Boolean, - ethCompatibleStorage: Boolean): InMemoryWorldStateProxy = + override def getReadOnlyWorldStateProxy( + blockNumber: Option[BigInt], + accountStartNonce: UInt256, + stateRootHash: Option[ByteString], + noEmptyAccounts: Boolean, + ethCompatibleStorage: Boolean + ): InMemoryWorldStateProxy = InMemoryWorldStateProxy( evmCodeStorage, stateStorage.getReadOnlyStorage, accountStartNonce, (number: BigInt) => getBlockHeaderByNumber(number).map(_.hash), stateRootHash, - noEmptyAccounts = false, + noEmptyAccounts = noEmptyAccounts, ethCompatibleStorage = ethCompatibleStorage ) diff --git a/src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala b/src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala index 91f9ad46be..520bb961a9 100644 --- a/src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala +++ b/src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala @@ -13,55 +13,58 @@ import io.iohk.ethereum.vm.{PC => _, _} import scala.annotation.tailrec /** - * This is used from a [[io.iohk.ethereum.consensus.blocks.BlockGenerator BlockGenerator]]. - * - * With its introduction, we: - * - * 1. Avoid a direct dependency of [[io.iohk.ethereum.consensus.Consensus Consensus]] on - * [[io.iohk.ethereum.ledger.Ledger Ledger]]. - * 2. Extract a substantial chunk of functionality outside [[io.iohk.ethereum.ledger.Ledger Ledger]], - * in an attempt to modularize it. - * - */ + * This is used from a [[io.iohk.ethereum.consensus.blocks.BlockGenerator BlockGenerator]]. + * + * With its introduction, we: + * + * 1. Avoid a direct dependency of [[io.iohk.ethereum.consensus.Consensus Consensus]] on + * [[io.iohk.ethereum.ledger.Ledger Ledger]]. + * 2. Extract a substantial chunk of functionality outside [[io.iohk.ethereum.ledger.Ledger Ledger]], + * in an attempt to modularize it. + */ class BlockPreparator( - vm: VMImpl, - signedTxValidator: SignedTransactionValidator, - blockchain: BlockchainImpl, // FIXME Depend on the interface - blockchainConfig: BlockchainConfig + vm: VMImpl, + signedTxValidator: SignedTransactionValidator, + blockchain: BlockchainImpl, // FIXME Depend on the interface + blockchainConfig: BlockchainConfig ) extends Logger { // NOTE We need a lazy val here, not a plain val, otherwise a mocked BlockChainConfig // in some irrelevant test can throw an exception. private[ledger] lazy val blockRewardCalculator = new BlockRewardCalculator( - blockchainConfig.monetaryPolicyConfig, - blockchainConfig.byzantiumBlockNumber, - blockchainConfig.constantinopleBlockNumber - ) + blockchainConfig.monetaryPolicyConfig, + blockchainConfig.byzantiumBlockNumber, + blockchainConfig.constantinopleBlockNumber + ) /** - * This function updates the state in order to pay rewards based on YP section 11.3 and with the required - * modifications due to ECIP1097: - * 1. Reward for block is distributed as: - * a. If treasury is disabled or it's has been selfdestructed: - * Pay 100% of it to the miner - * b. If a. isn't true and the miner opted out: - * Pay 80% of it to the miner - * Never generate the 20% else - * c. If a. isn't true and the miner opted in: - * Pay 80% of it to the miner - * 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 - * - * @param block the block being processed - * @param worldStateProxy the initial state - * @return the state after paying the apropiate reward to who corresponds - */ - private[ledger] def payBlockReward(block: Block, worldStateProxy: InMemoryWorldStateProxy): InMemoryWorldStateProxy = { + * This function updates the state in order to pay rewards based on YP section 11.3 and with the required + * modifications due to ECIP1097: + * 1. Reward for block is distributed as: + * a. If treasury is disabled or it's has been selfdestructed: + * Pay 100% of it to the miner + * b. If a. isn't true and the miner opted out: + * Pay 80% of it to the miner + * Never generate the 20% else + * c. If a. isn't true and the miner opted in: + * Pay 80% of it to the miner + * 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 + * + * @param block the block being processed + * @param worldStateProxy the initial state + * @return the state after paying the appropriate reward to who corresponds + */ + 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 minerRewardForOmmers = + blockRewardCalculator.calculateMiningRewardForOmmers(blockNumber, block.body.uncleNodesList.size) val minerAddress = Address(block.header.beneficiary) val treasuryAddress = blockchainConfig.treasuryAddress @@ -72,24 +75,25 @@ class BlockPreparator( 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") - + 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") + 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 } @@ -103,63 +107,75 @@ class BlockPreparator( } /** - * v0 ≡ Tg (Tx gas limit) * Tp (Tx gas price). See YP equation number (68) - * - * @param tx Target transaction - * @return Upfront cost - */ + * v0 ≡ Tg (Tx gas limit) * Tp (Tx gas price). See YP equation number (68) + * + * @param tx Target transaction + * @return Upfront cost + */ private[ledger] def calculateUpfrontGas(tx: Transaction): UInt256 = UInt256(tx.gasLimit * tx.gasPrice) /** - * v0 ≡ Tg (Tx gas limit) * Tp (Tx gas price) + Tv (Tx value). See YP equation number (65) - * - * @param tx Target transaction - * @return Upfront cost - */ + * v0 ≡ Tg (Tx gas limit) * Tp (Tx gas price) + Tv (Tx value). See YP equation number (65) + * + * @param tx Target transaction + * @return Upfront cost + */ private[ledger] def calculateUpfrontCost(tx: Transaction): UInt256 = UInt256(calculateUpfrontGas(tx) + tx.value) /** - * Increments account nonce by 1 stated in YP equation (69) and - * Pays the upfront Tx gas calculated as TxGasPrice * TxGasLimit from balance. YP equation (68) - * - * @param stx - * @param worldStateProxy - * @return - */ - private[ledger] def updateSenderAccountBeforeExecution(stx: SignedTransaction, - senderAddress: Address, - worldStateProxy: InMemoryWorldStateProxy): InMemoryWorldStateProxy = { + * Increments account nonce by 1 stated in YP equation (69) and + * Pays the upfront Tx gas calculated as TxGasPrice * TxGasLimit from balance. YP equation (68) + * + * @param stx + * @param worldStateProxy + * @return + */ + private[ledger] def updateSenderAccountBeforeExecution( + stx: SignedTransaction, + senderAddress: Address, + worldStateProxy: InMemoryWorldStateProxy + ): InMemoryWorldStateProxy = { val account = worldStateProxy.getGuaranteedAccount(senderAddress) worldStateProxy.saveAccount(senderAddress, account.increaseBalance(-calculateUpfrontGas(stx.tx)).increaseNonce()) } - private[ledger] def runVM(stx: SignedTransaction, senderAddress: Address, blockHeader: BlockHeader, world: InMemoryWorldStateProxy): PR = { + private[ledger] def runVM( + stx: SignedTransaction, + senderAddress: Address, + blockHeader: BlockHeader, + world: InMemoryWorldStateProxy + ): PR = { val evmConfig = EvmConfig.forBlock(blockHeader.number, blockchainConfig) val context: PC = ProgramContext(stx, blockHeader, senderAddress, world, evmConfig) vm.run(context) } /** - * Calculate total gas to be refunded - * See YP, eq (72) - */ + * Calculate total gas to be refunded + * See YP, eq (72) + */ private[ledger] def calcTotalGasToRefund(stx: SignedTransaction, result: PR): BigInt = { result.error.map(_.useWholeGas) match { - case Some(true) => 0 - case Some(false) => result.gasRemaining - case None => + case Some(true) => 0 + case Some(false) => result.gasRemaining + case None => val gasUsed = stx.tx.gasLimit - result.gasRemaining result.gasRemaining + (gasUsed / 2).min(result.gasRefund) } } - private[ledger] def increaseAccountBalance(address: Address, value: UInt256)(world: InMemoryWorldStateProxy): InMemoryWorldStateProxy = { - val account = world.getAccount(address).getOrElse(Account.empty(blockchainConfig.accountStartNonce)).increaseBalance(value) + 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 = { + private[ledger] def pay(address: Address, value: UInt256, withTouch: Boolean)( + world: InMemoryWorldStateProxy + ): InMemoryWorldStateProxy = { if (world.isZeroValueTransferToNonExistentAccount(address, value)) { world } else { @@ -169,37 +185,39 @@ class BlockPreparator( } /** - * Delete all accounts (that appear in SUICIDE list). YP eq (78). - * The contract storage should be cleared during pruning as nodes could be used in other tries. - * The contract code is also not deleted as there can be contracts with the exact same code, making it risky to delete - * the code of an account in case it is shared with another one. - * FIXME: [EC-242] - * Should we delete the storage associated with the deleted accounts? - * Should we keep track of duplicated contracts for deletion? - * - * @param addressesToDelete - * @param worldStateProxy - * @return a worldState equal worldStateProxy except that the accounts from addressesToDelete are deleted - */ - private[ledger] def deleteAccounts(addressesToDelete: Set[Address])(worldStateProxy: InMemoryWorldStateProxy): InMemoryWorldStateProxy = - addressesToDelete.foldLeft(worldStateProxy){ case (world, address) => world.deleteAccount(address) } + * Delete all accounts (that appear in SUICIDE list). YP eq (78). + * The contract storage should be cleared during pruning as nodes could be used in other tries. + * The contract code is also not deleted as there can be contracts with the exact same code, making it risky to delete + * the code of an account in case it is shared with another one. + * FIXME: [EC-242] + * Should we delete the storage associated with the deleted accounts? + * Should we keep track of duplicated contracts for deletion? + * + * @param addressesToDelete + * @param worldStateProxy + * @return a worldState equal worldStateProxy except that the accounts from addressesToDelete are deleted + */ + private[ledger] def deleteAccounts(addressesToDelete: Set[Address])( + worldStateProxy: InMemoryWorldStateProxy + ): InMemoryWorldStateProxy = + addressesToDelete.foldLeft(worldStateProxy) { case (world, address) => world.deleteAccount(address) } /** - * EIP161 - State trie clearing - * Delete all accounts that have been touched (involved in any potentially state-changing operation) during transaction execution. - * - * All potentially state-changing operation are: - * Account is the target or refund of a SUICIDE operation for zero or more value; - * Account is the source or destination of a CALL operation or message-call transaction transferring zero or more value; - * Account is the source or newly-creation of a CREATE operation or contract-creation transaction endowing zero or more value; - * as the block author ("miner") it is recipient of block-rewards or transaction-fees of zero or more. - * - * Deletion of touched account should be executed immediately following the execution of the suicide list - * - * @param world world after execution of all potentially state-changing operations - * @return a worldState equal worldStateProxy except that the accounts touched during execution are deleted and touched - * Set is cleared - */ + * EIP161 - State trie clearing + * Delete all accounts that have been touched (involved in any potentially state-changing operation) during transaction execution. + * + * All potentially state-changing operation are: + * Account is the target or refund of a SUICIDE operation for zero or more value; + * Account is the source or destination of a CALL operation or message-call transaction transferring zero or more value; + * Account is the source or newly-creation of a CREATE operation or contract-creation transaction endowing zero or more value; + * as the block author ("miner") it is recipient of block-rewards or transaction-fees of zero or more. + * + * Deletion of touched account should be executed immediately following the execution of the suicide list + * + * @param world world after execution of all potentially state-changing operations + * @return a worldState equal worldStateProxy except that the accounts touched during execution are deleted and touched + * Set is cleared + */ private[ledger] def deleteEmptyTouchedAccounts(world: InMemoryWorldStateProxy): InMemoryWorldStateProxy = { def deleteEmptyAccount(world: InMemoryWorldStateProxy, address: Address) = { if (world.getAccount(address).exists(_.isEmpty(blockchainConfig.accountStartNonce))) @@ -213,7 +231,12 @@ class BlockPreparator( .clearTouchedAccounts } - private[ledger] def executeTransaction(stx: SignedTransaction, senderAddress: Address, blockHeader: BlockHeader, world: InMemoryWorldStateProxy): TxResult = { + private[ledger] def executeTransaction( + stx: SignedTransaction, + senderAddress: Address, + blockHeader: BlockHeader, + world: InMemoryWorldStateProxy + ): TxResult = { log.debug(s"Transaction ${stx.hashAsHexString} execution start") val gasPrice = UInt256(stx.tx.gasPrice) val gasLimit = stx.tx.gasLimit @@ -232,7 +255,8 @@ class BlockPreparator( val executionGasToPayToMiner = gasLimit - totalGasToRefund val refundGasFn = pay(senderAddress, (totalGasToRefund * gasPrice).toUInt256, withTouch = false) _ - val payMinerForGasFn = pay(Address(blockHeader.beneficiary), (executionGasToPayToMiner * gasPrice).toUInt256, withTouch = true) _ + val payMinerForGasFn = + pay(Address(blockHeader.beneficiary), (executionGasToPayToMiner * gasPrice).toUInt256, withTouch = true) _ val worldAfterPayments = (refundGasFn andThen payMinerForGasFn)(resultWithErrorHandling.world) @@ -242,8 +266,7 @@ class BlockPreparator( val world2 = (deleteAccountsFn andThen deleteTouchedAccountsFn andThen persistStateFn)(worldAfterPayments) - log.debug( - s"""Transaction ${stx.hashAsHexString} execution end. Summary: + log.debug(s"""Transaction ${stx.hashAsHexString} execution end. Summary: | - Error: ${result.error}. | - Total Gas to Refund: $totalGasToRefund | - Execution gas paid to miner: $executionGasToPayToMiner""".stripMargin) @@ -253,58 +276,62 @@ class BlockPreparator( // scalastyle:off method.length /** - * This functions executes all the signed transactions from a block (till one of those executions fails) - * - * @param signedTransactions from the block that are left to execute - * @param world that will be updated by the execution of the signedTransactions - * @param blockHeader of the block we are currently executing - * @param acumGas, accumulated gas of the previoulsy executed transactions of the same block - * @param acumReceipts, accumulated receipts of the previoulsy executed transactions of the same block - * @return a BlockResult if the execution of all the transactions in the block was successful or a BlockExecutionError - * if one of them failed - */ + * This functions executes all the signed transactions from a block (till one of those executions fails) + * + * @param signedTransactions from the block that are left to execute + * @param world that will be updated by the execution of the signedTransactions + * @param blockHeader of the block we are currently executing + * @param acumGas, accumulated gas of the previoulsy executed transactions of the same block + * @param acumReceipts, accumulated receipts of the previoulsy executed transactions of the same block + * @return a BlockResult if the execution of all the transactions in the block was successful or a BlockExecutionError + * if one of them failed + */ @tailrec private[ledger] final def executeTransactions( - signedTransactions: Seq[SignedTransaction], - world: InMemoryWorldStateProxy, - blockHeader: BlockHeader, - acumGas: BigInt = 0, - acumReceipts: Seq[Receipt] = Nil + signedTransactions: Seq[SignedTransaction], + world: InMemoryWorldStateProxy, + blockHeader: BlockHeader, + acumGas: BigInt = 0, + acumReceipts: Seq[Receipt] = Nil ): Either[TxsExecutionError, BlockResult] = signedTransactions match { case Nil => Right(BlockResult(worldState = world, gasUsed = acumGas, receipts = acumReceipts)) - case Seq(stx, otherStxs@_*) => + case Seq(stx, otherStxs @ _*) => val upfrontCost = calculateUpfrontCost(stx.tx) val senderAddress = SignedTransaction.getSender(stx) - val accountDataOpt = senderAddress.map { address => - world - .getAccount(address) - .map(a => (a, address)) - .getOrElse((Account.empty(blockchainConfig.accountStartNonce), address)) - }.toRight(TransactionSignatureError) + val accountDataOpt = senderAddress + .map { address => + world + .getAccount(address) + .map(a => (a, address)) + .getOrElse((Account.empty(blockchainConfig.accountStartNonce), address)) + } + .toRight(TransactionSignatureError) val validatedStx = for { accData <- accountDataOpt - result <- signedTxValidator.validate(stx, accData._1, blockHeader, upfrontCost, acumGas) + result <- signedTxValidator.validate(stx, accData._1, blockHeader, upfrontCost, acumGas) } yield result validatedStx match { case Right(_) => val (account, address) = accountDataOpt.right.get - val TxResult(newWorld, gasUsed, logs, _, vmError) = executeTransaction(stx, address, blockHeader, world.saveAccount(address, account)) + val TxResult(newWorld, gasUsed, logs, _, vmError) = + executeTransaction(stx, address, blockHeader, world.saveAccount(address, account)) // spec: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md - val transactionOutcome = if ( - blockHeader.number >= blockchainConfig.byzantiumBlockNumber || - blockHeader.number >= blockchainConfig.atlantisBlockNumber - ) { - if (vmError.isDefined) FailureOutcome else SuccessOutcome - } else { - HashOutcome(newWorld.stateRootHash) - } + val transactionOutcome = + if ( + blockHeader.number >= blockchainConfig.byzantiumBlockNumber || + blockHeader.number >= blockchainConfig.atlantisBlockNumber + ) { + if (vmError.isDefined) FailureOutcome else SuccessOutcome + } else { + HashOutcome(newWorld.stateRootHash) + } val receipt = Receipt( postTransactionStateHash = transactionOutcome, @@ -316,18 +343,19 @@ class BlockPreparator( log.debug(s"Receipt generated for tx ${stx.hashAsHexString}, $receipt") executeTransactions(otherStxs, newWorld, blockHeader, receipt.cumulativeGasUsed, acumReceipts :+ receipt) - case Left(error) => Left(TxsExecutionError(stx, StateBeforeFailure(world, acumGas, acumReceipts), error.toString)) + case Left(error) => + Left(TxsExecutionError(stx, StateBeforeFailure(world, acumGas, acumReceipts), error.toString)) } } @tailrec private[ledger] final def executePreparedTransactions( - signedTransactions: Seq[SignedTransaction], - world: InMemoryWorldStateProxy, - blockHeader: BlockHeader, - acumGas: BigInt = 0, - acumReceipts: Seq[Receipt] = Nil, - executed: Seq[SignedTransaction] = Nil + signedTransactions: Seq[SignedTransaction], + world: InMemoryWorldStateProxy, + blockHeader: BlockHeader, + acumGas: BigInt = 0, + acumReceipts: Seq[Receipt] = Nil, + executed: Seq[SignedTransaction] = Nil ): (BlockResult, Seq[SignedTransaction]) = { val result = executeTransactions(signedTransactions, world, blockHeader, acumGas, acumReceipts) @@ -336,24 +364,39 @@ class BlockPreparator( case Left(TxsExecutionError(stx, StateBeforeFailure(worldState, gas, receipts), reason)) => log.debug(s"failure while preparing block because of $reason in transaction with hash ${stx.hashAsHexString}") val txIndex = signedTransactions.indexWhere(tx => tx.hash == stx.hash) - executePreparedTransactions(signedTransactions.drop(txIndex + 1), - worldState, blockHeader, gas, receipts, executed ++ signedTransactions.take(txIndex)) + executePreparedTransactions( + signedTransactions.drop(txIndex + 1), + worldState, + blockHeader, + gas, + receipts, + executed ++ signedTransactions.take(txIndex) + ) case Right(br) => (br, executed ++ signedTransactions) } } def prepareBlock(block: Block): BlockPreparationResult = { val parentStateRoot = blockchain.getBlockHeaderByHash(block.header.parentHash).map(_.stateRoot) - val initialWorld = blockchain.getReadOnlyWorldStateProxy(None, blockchainConfig.accountStartNonce, parentStateRoot, - noEmptyAccounts = false, - ethCompatibleStorage = blockchainConfig.ethCompatibleStorage) + val initialWorld = blockchain.getReadOnlyWorldStateProxy( + None, + blockchainConfig.accountStartNonce, + parentStateRoot, + noEmptyAccounts = EvmConfig.forBlock(block.header.number, blockchainConfig).noEmptyAccounts, + ethCompatibleStorage = blockchainConfig.ethCompatibleStorage + ) val prepared = executePreparedTransactions(block.body.transactionList, initialWorld, block.header) prepared match { - case (execResult@BlockResult(resultingWorldStateProxy, _, _), txExecuted) => + case (execResult @ BlockResult(resultingWorldStateProxy, _, _), txExecuted) => val worldToPersist = payBlockReward(block, resultingWorldStateProxy) val worldPersisted = InMemoryWorldStateProxy.persistState(worldToPersist) - BlockPreparationResult(block.copy(body = block.body.copy(transactionList = txExecuted)), execResult, worldPersisted.stateRootHash, worldToPersist) + BlockPreparationResult( + block.copy(body = block.body.copy(transactionList = txExecuted)), + execResult, + worldPersisted.stateRootHash, + worldToPersist + ) } } } diff --git a/src/main/scala/io/iohk/ethereum/ledger/StxLedger.scala b/src/main/scala/io/iohk/ethereum/ledger/StxLedger.scala index 0d289f941e..c03a8b7d50 100644 --- a/src/main/scala/io/iohk/ethereum/ledger/StxLedger.scala +++ b/src/main/scala/io/iohk/ethereum/ledger/StxLedger.scala @@ -1,6 +1,6 @@ package io.iohk.ethereum.ledger -import io.iohk.ethereum.domain.{ Account, BlockHeader, BlockchainImpl, SignedTransactionWithSender } +import io.iohk.ethereum.domain.{Account, BlockHeader, BlockchainImpl, SignedTransactionWithSender} import io.iohk.ethereum.ledger.Ledger.TxResult import io.iohk.ethereum.utils.BlockchainConfig import io.iohk.ethereum.vm.EvmConfig @@ -8,19 +8,21 @@ import io.iohk.ethereum.vm.EvmConfig class StxLedger(blockchain: BlockchainImpl, blockchainConfig: BlockchainConfig, blockPreparator: BlockPreparator) { def simulateTransaction( - stx: SignedTransactionWithSender, - blockHeader: BlockHeader, - world: Option[InMemoryWorldStateProxy] + stx: SignedTransactionWithSender, + blockHeader: BlockHeader, + world: Option[InMemoryWorldStateProxy] ): TxResult = { val tx = stx.tx - val world1 = world.getOrElse(blockchain.getReadOnlyWorldStateProxy( - blockNumber = None, - accountStartNonce = blockchainConfig.accountStartNonce, - stateRootHash = Some(blockHeader.stateRoot), - noEmptyAccounts = false, - ethCompatibleStorage = blockchainConfig.ethCompatibleStorage - )) + val world1 = world.getOrElse( + blockchain.getReadOnlyWorldStateProxy( + blockNumber = None, + accountStartNonce = blockchainConfig.accountStartNonce, + stateRootHash = Some(blockHeader.stateRoot), + noEmptyAccounts = EvmConfig.forBlock(blockHeader.number, blockchainConfig).noEmptyAccounts, + ethCompatibleStorage = blockchainConfig.ethCompatibleStorage + ) + ) val senderAddress = stx.senderAddress val world2 = @@ -30,7 +32,7 @@ class StxLedger(blockchain: BlockchainImpl, blockchainConfig: BlockchainConfig, world1 } - val worldForTx = blockPreparator.updateSenderAccountBeforeExecution(tx, senderAddress, world2) + val worldForTx = blockPreparator.updateSenderAccountBeforeExecution(tx, senderAddress, world2) val result = blockPreparator.runVM(tx, senderAddress, blockHeader, worldForTx) val totalGasToRefund = blockPreparator.calcTotalGasToRefund(tx, result) @@ -38,9 +40,9 @@ class StxLedger(blockchain: BlockchainImpl, blockchainConfig: BlockchainConfig, } def binarySearchGasEstimation( - stx: SignedTransactionWithSender, - blockHeader: BlockHeader, - world: Option[InMemoryWorldStateProxy] + stx: SignedTransactionWithSender, + blockHeader: BlockHeader, + world: Option[InMemoryWorldStateProxy] ): BigInt = { val lowLimit = EvmConfig.forBlock(blockHeader.number, blockchainConfig).feeSchedule.G_transaction val tx = stx.tx @@ -49,7 +51,7 @@ class StxLedger(blockchain: BlockchainImpl, blockchainConfig: BlockchainConfig, if (highLimit < lowLimit) { highLimit } else { - LedgerUtils.binaryChop(lowLimit, highLimit){ gasLimit => + LedgerUtils.binaryChop(lowLimit, highLimit) { gasLimit => simulateTransaction(stx.copy(tx = tx.copy(tx = tx.tx.copy(gasLimit = gasLimit))), blockHeader, world).vmError } } diff --git a/src/test/scala/io/iohk/ethereum/consensus/BlockGeneratorSpec.scala b/src/test/scala/io/iohk/ethereum/consensus/BlockGeneratorSpec.scala index d2fc499367..19f5218e46 100644 --- a/src/test/scala/io/iohk/ethereum/consensus/BlockGeneratorSpec.scala +++ b/src/test/scala/io/iohk/ethereum/consensus/BlockGeneratorSpec.scala @@ -258,6 +258,62 @@ class BlockGeneratorSpec extends AnyFlatSpec with Matchers with ScalaCheckProper fullBlock.right.foreach(b => b.header.extraData shouldBe headerExtraData) } + it should "generate correct block with (without empty accounts) after EIP-161" in new TestSetup { + override lazy val blockchainConfig = BlockchainConfig( + frontierBlockNumber = 0, + homesteadBlockNumber = 1150000, + difficultyBombPauseBlockNumber = 3000000, + difficultyBombContinueBlockNumber = 5000000, + difficultyBombRemovalBlockNumber = 5900000, + eip155BlockNumber = Long.MaxValue, + eip106BlockNumber = Long.MaxValue, + byzantiumBlockNumber = Long.MaxValue, + constantinopleBlockNumber = Long.MaxValue, + istanbulBlockNumber = Long.MaxValue, + chainId = 0x3d.toByte, + networkId = 1, + customGenesisFileOpt = Some("test-genesis.json"), + monetaryPolicyConfig = + MonetaryPolicyConfig(5000000, 0.2, 5000000000000000000L, 3000000000000000000L, 2000000000000000000L), + // unused + maxCodeSize = None, + eip160BlockNumber = Long.MaxValue, + eip150BlockNumber = Long.MaxValue, + eip161BlockNumber = 0, + accountStartNonce = UInt256.Zero, + daoForkConfig = None, + bootstrapNodes = Set(), + gasTieBreaker = false, + ethCompatibleStorage = true, + atlantisBlockNumber = Long.MaxValue, + aghartaBlockNumber = Long.MaxValue, + phoenixBlockNumber = Long.MaxValue, + petersburgBlockNumber = Long.MaxValue, + ecip1098BlockNumber = Long.MaxValue, + treasuryAddress = Address(0), + ecip1097BlockNumber = Long.MaxValue + ) + + override lazy val blockExecution = + new BlockExecution(blockchain, blockchainConfig, consensus.blockPreparator, blockValidation) + + val transaction1 = Transaction( + nonce = 0, + gasPrice = 1, + gasLimit = 1000000, + receivingAddress = None, + value = 0, + payload = ByteString.empty + ) + val generalTx = SignedTransaction.sign(transaction1, keyPair, None).tx + + val generatedBlock: Either[BlockPreparationError, PendingBlock] = + blockGenerator.generateBlock(bestBlock, Seq(generalTx), Address(testAddress), blockGenerator.emptyX) + + generatedBlock shouldBe a[Right[_, Block]] + generatedBlock.right.foreach(pb => blockExecution.executeBlock(pb.block, true) shouldBe a[Right[_, Seq[Receipt]]]) + } + it should "generate block after eip155 and allow both chain specific and general transactions" in new TestSetup { val generalTx = SignedTransaction.sign(transaction.copy(nonce = transaction.nonce + 1), keyPair, None).tx @@ -409,18 +465,17 @@ class BlockGeneratorSpec extends AnyFlatSpec with Matchers with ScalaCheckProper } it should "build blocks with the correct opt-out" in { - val table = Table[Boolean, Boolean, Option[Boolean]](("ecip1098Activated", "selectedOptOut", "expectedOptOut"), + val table = Table[Boolean, Boolean, Option[Boolean]]( + ("ecip1098Activated", "selectedOptOut", "expectedOptOut"), // Already activated (true, true, Some(true)), (true, false, Some(false)), - // Not yet activated (false, true, None), (false, false, None) ) forAll(table) { case (ecip1098Activated, selectedOptOut, expectedOptOut) => - val testSetup = new TestSetup { override lazy val blockchainConfig = baseBlockchainConfig.copy(ecip1098BlockNumber = 10000000) @@ -428,13 +483,14 @@ class BlockGeneratorSpec extends AnyFlatSpec with Matchers with ScalaCheckProper } import testSetup._ - val blockNumber = if (ecip1098Activated) blockchainConfig.ecip1098BlockNumber * 2 else blockchainConfig.ecip1098BlockNumber / 2 + val blockNumber = + if (ecip1098Activated) blockchainConfig.ecip1098BlockNumber * 2 else blockchainConfig.ecip1098BlockNumber / 2 val parentBlock = bestBlock.copy(header = bestBlock.header.copy(number = blockNumber - 1)) val generatedBlock: Either[BlockPreparationError, PendingBlock] = blockGenerator.generateBlock(parentBlock, Nil, Address(testAddress), blockGenerator.emptyX) generatedBlock shouldBe a[Right[_, Block]] - generatedBlock.right.foreach{ b => b.block.header.treasuryOptOut shouldBe expectedOptOut } + generatedBlock.right.foreach { b => b.block.header.treasuryOptOut shouldBe expectedOptOut } } } From b68fd567ee1be24560ffa163d17f5f67cb6d810d Mon Sep 17 00:00:00 2001 From: Michal Mrozek Date: Fri, 16 Oct 2020 10:17:04 +0200 Subject: [PATCH 2/3] [ECTM-212] Resolve fork in more deterministic manner --- .../scala/io/iohk/ethereum/vm/EvmConfig.scala | 230 +++++++++--------- 1 file changed, 121 insertions(+), 109 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala b/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala index 7ed1751c84..575d582c0c 100644 --- a/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala +++ b/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala @@ -7,8 +7,6 @@ import EvmConfig._ import io.iohk.ethereum import io.iohk.ethereum.vm -// scalastyle:off number.of.methods -// scalastyle:off number.of.types // scalastyle:off magic.number object EvmConfig { @@ -16,7 +14,9 @@ object EvmConfig { val MaxCallDepth: Int = 1024 - val MaxMemory: UInt256 = UInt256(Int.MaxValue) /* used to artificially limit memory usage by incurring maximum gas cost */ + val MaxMemory: UInt256 = UInt256( + Int.MaxValue + ) /* used to artificially limit memory usage by incurring maximum gas cost */ /** * returns the evm config that should be used for given block @@ -28,25 +28,26 @@ object EvmConfig { * returns the evm config that should be used for given block */ def forBlock(blockNumber: BigInt, blockchainConfig: BlockchainConfigForEvm): EvmConfig = { - val transitionBlockToConfigMapping: Map[BigInt, EvmConfigBuilder] = Map( - blockchainConfig.frontierBlockNumber -> FrontierConfigBuilder, - blockchainConfig.homesteadBlockNumber -> HomesteadConfigBuilder, - blockchainConfig.eip150BlockNumber -> PostEIP150ConfigBuilder, - blockchainConfig.eip160BlockNumber -> PostEIP160ConfigBuilder, - blockchainConfig.eip161BlockNumber -> PostEIP161ConfigBuilder, - blockchainConfig.byzantiumBlockNumber -> ByzantiumConfigBuilder, - blockchainConfig.constantinopleBlockNumber -> ConstantinopleConfigBuilder, - blockchainConfig.istanbulBlockNumber -> IstanbulConfigBuilder, - blockchainConfig.atlantisBlockNumber -> AtlantisConfigBuilder, - blockchainConfig.aghartaBlockNumber -> AghartaConfigBuilder, - blockchainConfig.petersburgBlockNumber -> PetersburgConfigBuilder, - blockchainConfig.phoenixBlockNumber -> PhoenixConfigBuilder + val transitionBlockToConfigWithPriorityMapping: Map[BigInt, (Int, EvmConfigBuilder)] = Map( + blockchainConfig.frontierBlockNumber -> (1, FrontierConfigBuilder), + blockchainConfig.homesteadBlockNumber -> (2, HomesteadConfigBuilder), + blockchainConfig.eip150BlockNumber -> (3, PostEIP150ConfigBuilder), + blockchainConfig.eip160BlockNumber -> (4, PostEIP160ConfigBuilder), + blockchainConfig.eip161BlockNumber -> (5, PostEIP161ConfigBuilder), + blockchainConfig.byzantiumBlockNumber -> (6, ByzantiumConfigBuilder), + blockchainConfig.atlantisBlockNumber -> (6, AtlantisConfigBuilder), + blockchainConfig.constantinopleBlockNumber -> (7, ConstantinopleConfigBuilder), + blockchainConfig.aghartaBlockNumber -> (7, AghartaConfigBuilder), + blockchainConfig.petersburgBlockNumber -> (8, PetersburgConfigBuilder), + blockchainConfig.istanbulBlockNumber -> (9, IstanbulConfigBuilder), + blockchainConfig.phoenixBlockNumber -> (9, PhoenixConfigBuilder) ) // highest transition block that is less/equal to `blockNumber` - val evmConfigBuilder = transitionBlockToConfigMapping + val evmConfigBuilder = transitionBlockToConfigWithPriorityMapping .filterKeys(_ <= blockNumber) - .maxBy(_._1) + .maxBy { case (number, (priority, _)) => (number, priority) } + ._2 ._2 evmConfigBuilder(blockchainConfig) } @@ -59,65 +60,75 @@ object EvmConfig { val AghartaOpCodes = ConstantinopleOpCodes val PhoenixOpCodes = OpCodeList(OpCodes.PhoenixOpCodes) - val FrontierConfigBuilder: EvmConfigBuilder = config => EvmConfig( - blockchainConfig = config, - feeSchedule = new FeeSchedule.FrontierFeeSchedule, - opCodeList = FrontierOpCodes, - exceptionalFailedCodeDeposit = false, - subGasCapDivisor = None, - chargeSelfDestructForNewAccount = false, - traceInternalTransactions = false) - - val HomesteadConfigBuilder: EvmConfigBuilder = config => EvmConfig( - blockchainConfig = config, - feeSchedule = new FeeSchedule.HomesteadFeeSchedule, - opCodeList = HomesteadOpCodes, - exceptionalFailedCodeDeposit = true, - subGasCapDivisor = None, - chargeSelfDestructForNewAccount = false, - traceInternalTransactions = false) - - val PostEIP150ConfigBuilder: EvmConfigBuilder = config => HomesteadConfigBuilder(config).copy( - feeSchedule = new FeeSchedule.PostEIP150FeeSchedule, - subGasCapDivisor = Some(64), - chargeSelfDestructForNewAccount = true) - - val PostEIP160ConfigBuilder: EvmConfigBuilder = config => PostEIP150ConfigBuilder(config).copy( - feeSchedule = new FeeSchedule.PostEIP160FeeSchedule) - - val PostEIP161ConfigBuilder: EvmConfigBuilder = config => PostEIP160ConfigBuilder(config).copy( - noEmptyAccounts = true) - - val ByzantiumConfigBuilder: EvmConfigBuilder = config => PostEIP161ConfigBuilder(config).copy( - feeSchedule = new FeeSchedule.ByzantiumFeeSchedule, - opCodeList = ByzantiumOpCodes - ) - - val ConstantinopleConfigBuilder: EvmConfigBuilder = config => ByzantiumConfigBuilder(config).copy( - feeSchedule = new vm.FeeSchedule.ConstantionopleFeeSchedule, - opCodeList = ConstantinopleOpCodes - ) + val FrontierConfigBuilder: EvmConfigBuilder = config => + EvmConfig( + blockchainConfig = config, + feeSchedule = new FeeSchedule.FrontierFeeSchedule, + opCodeList = FrontierOpCodes, + exceptionalFailedCodeDeposit = false, + subGasCapDivisor = None, + chargeSelfDestructForNewAccount = false, + traceInternalTransactions = false + ) + + val HomesteadConfigBuilder: EvmConfigBuilder = config => + EvmConfig( + blockchainConfig = config, + feeSchedule = new FeeSchedule.HomesteadFeeSchedule, + opCodeList = HomesteadOpCodes, + exceptionalFailedCodeDeposit = true, + subGasCapDivisor = None, + chargeSelfDestructForNewAccount = false, + traceInternalTransactions = false + ) + + val PostEIP150ConfigBuilder: EvmConfigBuilder = config => + HomesteadConfigBuilder(config).copy( + feeSchedule = new FeeSchedule.PostEIP150FeeSchedule, + subGasCapDivisor = Some(64), + chargeSelfDestructForNewAccount = true + ) + + val PostEIP160ConfigBuilder: EvmConfigBuilder = config => + PostEIP150ConfigBuilder(config).copy(feeSchedule = new FeeSchedule.PostEIP160FeeSchedule) + + val PostEIP161ConfigBuilder: EvmConfigBuilder = config => PostEIP160ConfigBuilder(config).copy(noEmptyAccounts = true) + + val ByzantiumConfigBuilder: EvmConfigBuilder = config => + PostEIP161ConfigBuilder(config).copy( + feeSchedule = new FeeSchedule.ByzantiumFeeSchedule, + opCodeList = ByzantiumOpCodes + ) + + val ConstantinopleConfigBuilder: EvmConfigBuilder = config => + ByzantiumConfigBuilder(config).copy( + feeSchedule = new vm.FeeSchedule.ConstantionopleFeeSchedule, + opCodeList = ConstantinopleOpCodes + ) val PetersburgConfigBuilder: EvmConfigBuilder = config => ConstantinopleConfigBuilder(config) val IstanbulConfigBuilder: EvmConfigBuilder = config => PhoenixConfigBuilder(config) // Ethereum classic forks only - val AtlantisConfigBuilder: EvmConfigBuilder = config => PostEIP160ConfigBuilder(config).copy( - feeSchedule = new FeeSchedule.AtlantisFeeSchedule, - opCodeList = AtlantisOpCodes, - noEmptyAccounts = true - ) - - val AghartaConfigBuilder: EvmConfigBuilder = config => AtlantisConfigBuilder(config).copy( - feeSchedule = new vm.FeeSchedule.ConstantionopleFeeSchedule, - opCodeList = AghartaOpCodes - ) - - val PhoenixConfigBuilder: EvmConfigBuilder = config => AghartaConfigBuilder(config).copy( - feeSchedule = new ethereum.vm.FeeSchedule.PhoenixFeeSchedule, - opCodeList = PhoenixOpCodes - ) + val AtlantisConfigBuilder: EvmConfigBuilder = config => + PostEIP160ConfigBuilder(config).copy( + feeSchedule = new FeeSchedule.AtlantisFeeSchedule, + opCodeList = AtlantisOpCodes, + noEmptyAccounts = true + ) + + val AghartaConfigBuilder: EvmConfigBuilder = config => + AtlantisConfigBuilder(config).copy( + feeSchedule = new vm.FeeSchedule.ConstantionopleFeeSchedule, + opCodeList = AghartaOpCodes + ) + + val PhoenixConfigBuilder: EvmConfigBuilder = config => + AghartaConfigBuilder(config).copy( + feeSchedule = new ethereum.vm.FeeSchedule.PhoenixFeeSchedule, + opCodeList = PhoenixOpCodes + ) case class OpCodeList(opCodes: List[OpCode]) { val byteToOpCode: Map[Byte, OpCode] = @@ -134,7 +145,8 @@ case class EvmConfig( subGasCapDivisor: Option[Long], chargeSelfDestructForNewAccount: Boolean, traceInternalTransactions: Boolean, - noEmptyAccounts: Boolean = false) { + noEmptyAccounts: Boolean = false +) { import feeSchedule._ import EvmConfig._ @@ -154,6 +166,7 @@ case class EvmConfig( * @return gas cost */ def calcMemCost(memSize: BigInt, offset: BigInt, dataSize: BigInt): BigInt = { + /** See YP H.1 (222) */ def c(m: BigInt): BigInt = { val a = wordsForBytes(m) @@ -171,7 +184,6 @@ case class EvmConfig( /** * Calculates transaction intrinsic gas. See YP section 6.2 - * */ def calcTransactionIntrinsicGas(txData: ByteString, isContractCreation: Boolean): BigInt = { val txDataZero = txData.count(_ == 0) @@ -206,41 +218,41 @@ case class EvmConfig( object FeeSchedule { class FrontierFeeSchedule extends FeeSchedule { - override val G_zero = 0 - override val G_base = 2 - override val G_verylow = 3 - override val G_low = 5 - override val G_mid = 8 - override val G_high = 10 - override val G_balance = 20 - override val G_sload = 50 - override val G_jumpdest = 1 - override val G_sset = 20000 - override val G_sreset = 5000 - override val R_sclear = 15000 - override val R_selfdestruct = 24000 - override val G_selfdestruct = 0 - override val G_create = 32000 - override val G_codedeposit = 200 - override val G_call = 40 - override val G_callvalue = 9000 - override val G_callstipend = 2300 - override val G_newaccount = 25000 - override val G_exp = 10 - override val G_expbyte = 10 - override val G_memory = 3 - override val G_txcreate = 0 - override val G_txdatazero = 4 - override val G_txdatanonzero = 68 - override val G_transaction = 21000 - override val G_log = 375 - override val G_logdata = 8 - override val G_logtopic = 375 - override val G_sha3 = 30 - override val G_sha3word = 6 - override val G_copy = 3 - override val G_blockhash = 20 - override val G_extcode = 20 + override val G_zero = 0 + override val G_base = 2 + override val G_verylow = 3 + override val G_low = 5 + override val G_mid = 8 + override val G_high = 10 + override val G_balance = 20 + override val G_sload = 50 + override val G_jumpdest = 1 + override val G_sset = 20000 + override val G_sreset = 5000 + override val R_sclear = 15000 + override val R_selfdestruct = 24000 + override val G_selfdestruct = 0 + override val G_create = 32000 + override val G_codedeposit = 200 + override val G_call = 40 + override val G_callvalue = 9000 + override val G_callstipend = 2300 + override val G_newaccount = 25000 + override val G_exp = 10 + override val G_expbyte = 10 + override val G_memory = 3 + override val G_txcreate = 0 + override val G_txdatazero = 4 + override val G_txdatanonzero = 68 + override val G_transaction = 21000 + override val G_log = 375 + override val G_logdata = 8 + override val G_logtopic = 375 + override val G_sha3 = 30 + override val G_sha3word = 6 + override val G_copy = 3 + override val G_blockhash = 20 + override val G_extcode = 20 } class HomesteadFeeSchedule extends FrontierFeeSchedule { @@ -267,7 +279,7 @@ object FeeSchedule { class AghartaFeeSchedule extends ByzantiumFeeSchedule - class PhoenixFeeSchedule extends AghartaFeeSchedule{ + class PhoenixFeeSchedule extends AghartaFeeSchedule { override val G_sload: BigInt = 800 override val G_balance: BigInt = 700 override val G_txdatanonzero = 16 From c0e24890c5da72293fe255ba80df93d5acc9a08b Mon Sep 17 00:00:00 2001 From: Michal Mrozek Date: Fri, 16 Oct 2020 14:43:10 +0200 Subject: [PATCH 3/3] [ECTM-212] Resolve fork in more deterministic manner --- .../ethereum/vm/BlockchainConfigForEvm.scala | 47 ++++++++++--------- .../scala/io/iohk/ethereum/vm/EvmConfig.scala | 35 +++++++------- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala b/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala index 7a45b988a2..de35fee457 100644 --- a/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala +++ b/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala @@ -3,33 +3,39 @@ package io.iohk.ethereum.vm import io.iohk.ethereum.domain.UInt256 import io.iohk.ethereum.utils.BlockchainConfig import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.{Agharta, Atlantis, BeforeAtlantis, EtcFork, Phoenix} -import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.{BeforeByzantium, Byzantium, Constantinople, Istanbul, Petersburg} +import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.{ + BeforeByzantium, + Byzantium, + Constantinople, + Istanbul, + Petersburg +} /** * A subset of [[io.iohk.ethereum.utils.BlockchainConfig]] that is required for instantiating an [[EvmConfig]] * Note that `accountStartNonce` is required for a [[WorldStateProxy]] implementation that is used * by a given VM */ -// FIXME manage etc/eth forks in a more sophisticated way +// FIXME manage etc/eth forks in a more sophisticated way [ETCM-249] case class BlockchainConfigForEvm( - // ETH forks - frontierBlockNumber: BigInt, - homesteadBlockNumber: BigInt, - eip150BlockNumber: BigInt, - eip160BlockNumber: BigInt, - eip161BlockNumber: BigInt, - byzantiumBlockNumber: BigInt, - constantinopleBlockNumber: BigInt, - istanbulBlockNumber: BigInt, - maxCodeSize: Option[BigInt], - accountStartNonce: UInt256, - // ETC forks - atlantisBlockNumber: BigInt, - aghartaBlockNumber: BigInt, - petersburgBlockNumber: BigInt, - phoenixBlockNumber: BigInt, - chainId: Byte -){ + // ETH forks + frontierBlockNumber: BigInt, + homesteadBlockNumber: BigInt, + eip150BlockNumber: BigInt, + eip160BlockNumber: BigInt, + eip161BlockNumber: BigInt, + byzantiumBlockNumber: BigInt, + constantinopleBlockNumber: BigInt, + istanbulBlockNumber: BigInt, + maxCodeSize: Option[BigInt], + accountStartNonce: UInt256, + // ETC forks + atlantisBlockNumber: BigInt, + aghartaBlockNumber: BigInt, + petersburgBlockNumber: BigInt, + phoenixBlockNumber: BigInt, + chainId: Byte +) { def etcForkForBlockNumber(blockNumber: BigInt): EtcFork = blockNumber match { case _ if blockNumber < atlantisBlockNumber => BeforeAtlantis case _ if blockNumber < aghartaBlockNumber => Atlantis @@ -58,7 +64,6 @@ object BlockchainConfigForEvm { val BeforeByzantium, Byzantium, Constantinople, Petersburg, Istanbul = Value } - def apply(blockchainConfig: BlockchainConfig): BlockchainConfigForEvm = { import blockchainConfig._ BlockchainConfigForEvm( diff --git a/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala b/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala index 575d582c0c..bfd0c67bfb 100644 --- a/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala +++ b/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala @@ -28,27 +28,28 @@ object EvmConfig { * returns the evm config that should be used for given block */ def forBlock(blockNumber: BigInt, blockchainConfig: BlockchainConfigForEvm): EvmConfig = { - val transitionBlockToConfigWithPriorityMapping: Map[BigInt, (Int, EvmConfigBuilder)] = Map( - blockchainConfig.frontierBlockNumber -> (1, FrontierConfigBuilder), - blockchainConfig.homesteadBlockNumber -> (2, HomesteadConfigBuilder), - blockchainConfig.eip150BlockNumber -> (3, PostEIP150ConfigBuilder), - blockchainConfig.eip160BlockNumber -> (4, PostEIP160ConfigBuilder), - blockchainConfig.eip161BlockNumber -> (5, PostEIP161ConfigBuilder), - blockchainConfig.byzantiumBlockNumber -> (6, ByzantiumConfigBuilder), - blockchainConfig.atlantisBlockNumber -> (6, AtlantisConfigBuilder), - blockchainConfig.constantinopleBlockNumber -> (7, ConstantinopleConfigBuilder), - blockchainConfig.aghartaBlockNumber -> (7, AghartaConfigBuilder), - blockchainConfig.petersburgBlockNumber -> (8, PetersburgConfigBuilder), - blockchainConfig.istanbulBlockNumber -> (9, IstanbulConfigBuilder), - blockchainConfig.phoenixBlockNumber -> (9, PhoenixConfigBuilder) + // FIXME manage etc/eth forks in a more sophisticated way [ETCM-249] + val transitionBlockToConfigWithPriorityMapping: List[(BigInt, Int, EvmConfigBuilder)] = List( + (blockchainConfig.frontierBlockNumber, 1, FrontierConfigBuilder), + (blockchainConfig.homesteadBlockNumber, 2, HomesteadConfigBuilder), + (blockchainConfig.eip150BlockNumber, 3, PostEIP150ConfigBuilder), + (blockchainConfig.eip160BlockNumber, 4, PostEIP160ConfigBuilder), + (blockchainConfig.eip161BlockNumber, 5, PostEIP161ConfigBuilder), + (blockchainConfig.byzantiumBlockNumber, 6, ByzantiumConfigBuilder), + (blockchainConfig.atlantisBlockNumber, 6, AtlantisConfigBuilder), + (blockchainConfig.constantinopleBlockNumber, 7, ConstantinopleConfigBuilder), + (blockchainConfig.aghartaBlockNumber, 7, AghartaConfigBuilder), + (blockchainConfig.petersburgBlockNumber, 8, PetersburgConfigBuilder), + (blockchainConfig.istanbulBlockNumber, 9, IstanbulConfigBuilder), + (blockchainConfig.phoenixBlockNumber, 9, PhoenixConfigBuilder) ) // highest transition block that is less/equal to `blockNumber` val evmConfigBuilder = transitionBlockToConfigWithPriorityMapping - .filterKeys(_ <= blockNumber) - .maxBy { case (number, (priority, _)) => (number, priority) } - ._2 - ._2 + .filterNot { case (number, _, _) => number > blockNumber } + .maxBy { case (number, priority, _) => (number, priority) } + ._3 + evmConfigBuilder(blockchainConfig) }