Skip to content

Commit

Permalink
ETC Gotham Fork (#178)
Browse files Browse the repository at this point in the history
This PR introduces support for the Gotham fork on the Ethereum Classic Network including:

ECIP-1017: Monetary Policy and Final Modification to the Ethereum Classic Emission Schedule
ECIP 1039: Monetary policy rounding specification
ECIP 1041: Remove Difficulty Bomb

Signed-off-by: edwardmack <ed@edwardmack.com>
  • Loading branch information
edwardmack authored and shemnon committed Nov 13, 2019
1 parent a6da172 commit e60aa3f
Show file tree
Hide file tree
Showing 10 changed files with 422 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,30 @@ public interface GenesisConfigOptions {
*/
OptionalLong getDieHardBlockNumber();

/**
* Block number for Gotham fork on Classic network, the Gotham form includes changes to meet
* specification for ECIP-1017 and ECIP-1039 both regarding Monetary Policy (rewards).
*
* @see <a
* href="https://ecips.ethereumclassic.org/ECIPs/ecip-1017">https://ecips.ethereumclassic.org/ECIPs/ecip-1017</a>
* ECIP-1017: Monetary Policy and Final Modification to the Ethereum Classic Emission Schedule
* @see <a
* href="https://ecips.ethereumclassic.org/ECIPs/ecip-1039">https://ecips.ethereumclassic.org/ECIPs/ecip-1039</a>
* ECIP-1039: Monetary policy rounding specification
* @return block to activate Classic Gotham fork
*/
OptionalLong getGothamBlockNumber();

/**
* Block number to remove difficulty bomb, to meet specification for ECIP-1041.
*
* @see <a
* href="https://ecips.ethereumclassic.org/ECIPs/ecip-1041">https://ecips.ethereumclassic.org/ECIPs/ecip-1041</a>
* ECIP-1041: Remove Difficulty Bomb
* @return block number to remove difficulty bomb on classic network
*/
OptionalLong getDefuseDifficultyBombBlockNumber();

Optional<BigInteger> getChainId();

OptionalInt getContractSizeLimit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,16 @@ public OptionalLong getDieHardBlockNumber() {
return getOptionalLong("diehardblock");
}

@Override
public OptionalLong getGothamBlockNumber() {
return getOptionalLong("gothamblock");
}

@Override
public OptionalLong getDefuseDifficultyBombBlockNumber() {
return getOptionalLong("ecip1041block");
}

@Override
public Optional<BigInteger> getChainId() {
return getOptionalBigInteger("chainid");
Expand Down
2 changes: 2 additions & 0 deletions config/src/main/resources/classic.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"classicForkBlock": 1920000,
"ecip1015Block": 2500000,
"diehardBlock": 3000000,
"gothamBlock": 5000001,
"ecip1041Block": 5900000,
"ethash": {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
private OptionalLong classicForkBlock = OptionalLong.empty();
private OptionalLong ecip1015BlockNumber = OptionalLong.empty();
private OptionalLong diehardBlockNumber = OptionalLong.empty();
private OptionalLong gothamBlockNumber = OptionalLong.empty();
private OptionalLong defuseDifficultyBombBlockNumber = OptionalLong.empty();
private Optional<BigInteger> chainId = Optional.empty();
private OptionalInt contractSizeLimit = OptionalInt.empty();
private OptionalInt stackSizeLimit = OptionalInt.empty();
Expand Down Expand Up @@ -139,6 +141,16 @@ public OptionalLong getDieHardBlockNumber() {
return diehardBlockNumber;
}

@Override
public OptionalLong getGothamBlockNumber() {
return gothamBlockNumber;
}

@Override
public OptionalLong getDefuseDifficultyBombBlockNumber() {
return defuseDifficultyBombBlockNumber;
}

@Override
public OptionalInt getContractSizeLimit() {
return contractSizeLimit;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.mainnet;

import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.core.WorldState;
import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup;

import java.util.ArrayList;
import java.util.List;

import com.google.common.collect.ImmutableList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public abstract class AbstractBlockProcessor implements BlockProcessor {
@FunctionalInterface
public interface TransactionReceiptFactory {

TransactionReceipt create(
TransactionProcessor.Result result, WorldState worldState, long gasUsed);
}

private static final Logger LOG = LogManager.getLogger();

static final int MAX_GENERATION = 6;

public static class Result implements BlockProcessor.Result {

private static final AbstractBlockProcessor.Result FAILED =
new AbstractBlockProcessor.Result(false, null);

private final boolean successful;

private final List<TransactionReceipt> receipts;

public static AbstractBlockProcessor.Result successful(
final List<TransactionReceipt> receipts) {
return new AbstractBlockProcessor.Result(true, ImmutableList.copyOf(receipts));
}

public static AbstractBlockProcessor.Result failed() {
return FAILED;
}

Result(final boolean successful, final List<TransactionReceipt> receipts) {
this.successful = successful;
this.receipts = receipts;
}

@Override
public List<TransactionReceipt> getReceipts() {
return receipts;
}

@Override
public boolean isSuccessful() {
return successful;
}
}

private final TransactionProcessor transactionProcessor;

private final MainnetBlockProcessor.TransactionReceiptFactory transactionReceiptFactory;

final Wei blockReward;

private final boolean skipZeroBlockRewards;

private final MiningBeneficiaryCalculator miningBeneficiaryCalculator;

public AbstractBlockProcessor(
final TransactionProcessor transactionProcessor,
final MainnetBlockProcessor.TransactionReceiptFactory transactionReceiptFactory,
final Wei blockReward,
final MiningBeneficiaryCalculator miningBeneficiaryCalculator,
final boolean skipZeroBlockRewards) {
this.transactionProcessor = transactionProcessor;
this.transactionReceiptFactory = transactionReceiptFactory;
this.blockReward = blockReward;
this.miningBeneficiaryCalculator = miningBeneficiaryCalculator;
this.skipZeroBlockRewards = skipZeroBlockRewards;
}

@Override
public AbstractBlockProcessor.Result processBlock(
final Blockchain blockchain,
final MutableWorldState worldState,
final BlockHeader blockHeader,
final List<Transaction> transactions,
final List<BlockHeader> ommers) {

long gasUsed = 0;
final List<TransactionReceipt> receipts = new ArrayList<>();

for (final Transaction transaction : transactions) {
final long remainingGasBudget = blockHeader.getGasLimit() - gasUsed;
if (Long.compareUnsigned(transaction.getGasLimit(), remainingGasBudget) > 0) {
LOG.warn(
"Transaction processing error: transaction gas limit {} exceeds available block budget remaining {}",
transaction.getGasLimit(),
remainingGasBudget);
return AbstractBlockProcessor.Result.failed();
}

final WorldUpdater worldStateUpdater = worldState.updater();
final BlockHashLookup blockHashLookup = new BlockHashLookup(blockHeader, blockchain);
final Address miningBeneficiary =
miningBeneficiaryCalculator.calculateBeneficiary(blockHeader);

final TransactionProcessor.Result result =
transactionProcessor.processTransaction(
blockchain,
worldStateUpdater,
blockHeader,
transaction,
miningBeneficiary,
blockHashLookup,
true,
TransactionValidationParams.processingBlock());
if (result.isInvalid()) {
return AbstractBlockProcessor.Result.failed();
}

worldStateUpdater.commit();
gasUsed = transaction.getGasLimit() - result.getGasRemaining() + gasUsed;
final TransactionReceipt transactionReceipt =
transactionReceiptFactory.create(result, worldState, gasUsed);
receipts.add(transactionReceipt);
}

if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) {
return AbstractBlockProcessor.Result.failed();
}

worldState.persist();
return AbstractBlockProcessor.Result.successful(receipts);
}

abstract boolean rewardCoinbase(
final MutableWorldState worldState,
final ProcessableBlockHeader header,
final List<BlockHeader> ommers,
final boolean skipZeroBlockRewards);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.mainnet;

import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableAccount;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.core.WorldUpdater;

import java.math.BigInteger;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ClassicBlockProcessor extends AbstractBlockProcessor {

private static final Logger LOG = LogManager.getLogger();

private static final long ERA_LENGTH = 5_000_000L;

public ClassicBlockProcessor(
final TransactionProcessor transactionProcessor,
final TransactionReceiptFactory transactionReceiptFactory,
final Wei blockReward,
final MiningBeneficiaryCalculator miningBeneficiaryCalculator,
final boolean skipZeroBlockRewards) {
super(
transactionProcessor,
transactionReceiptFactory,
blockReward,
miningBeneficiaryCalculator,
skipZeroBlockRewards);
}

@Override
boolean rewardCoinbase(
final MutableWorldState worldState,
final ProcessableBlockHeader header,
final List<BlockHeader> ommers,
final boolean skipZeroBlockRewards) {
if (skipZeroBlockRewards && blockReward.isZero()) {
return true;
}
final int blockEra = getBlockEra(header.getNumber(), ERA_LENGTH);
final Wei winnerReward = getBlockWinnerRewardByEra(blockEra);
final Wei uncleInclusionReward = winnerReward.dividedBy(32);
final Wei coinbaseReward = winnerReward.plus(uncleInclusionReward.times(ommers.size()));
final WorldUpdater updater = worldState.updater();
final MutableAccount coinbase = updater.getOrCreate(header.getCoinbase()).getMutable();

coinbase.incrementBalance(coinbaseReward);
for (final BlockHeader ommerHeader : ommers) {
if (ommerHeader.getNumber() - header.getNumber() > MAX_GENERATION) {
LOG.warn(
"Block processing error: ommer block number {} more than {} generations current block number {}",
ommerHeader.getNumber(),
MAX_GENERATION,
header.getNumber());
return false;
}

final MutableAccount ommerCoinbase =
updater.getOrCreate(ommerHeader.getCoinbase()).getMutable();
ommerCoinbase.incrementBalance(uncleInclusionReward);
}

updater.commit();
return true;
}

// GetBlockEra gets which "Era" a given block is within, given an era length (ecip-1017 has
// era=5,000,000 blocks)
// Returns a zero-index era number, so "Era 1": 0, "Era 2": 1, "Era 3": 2 ...
private int getBlockEra(final long blockNumber, final long eraLength) {
// if genesis block or impossible nagative-numbered block, return zero
if (blockNumber < 0) return 0;
long remainder = (blockNumber - 1) % eraLength;
long base = blockNumber - remainder;
long d = base / eraLength;
return Math.toIntExact(d);
}

// getRewardByEra gets a block reward at disinflation rate.
// Constants MaxBlockReward, DisinflationRateQuotient, and DisinflationRateDivisor assumed.
private Wei getBlockWinnerRewardByEra(final int era) {
if (era == 0) {
return this.blockReward;
}

// MaxBlockReward _r_ * (4/5)**era == MaxBlockReward * (4**era) / (5**era)
// since (q/d)**n == q**n / d**n
// qed

BigInteger disinflationRateQuotient = BigInteger.valueOf(4);
BigInteger q;
q = disinflationRateQuotient.pow(era);

BigInteger disinflationRateDivisor = BigInteger.valueOf(5);
BigInteger d;
d = disinflationRateDivisor.pow(era);

BigInteger maximumBlockReward = BigInteger.valueOf(this.blockReward.toLong());
BigInteger r;
r = maximumBlockReward.multiply(q);

r = r.divide(d);
return Wei.of(r);
}
}
Loading

0 comments on commit e60aa3f

Please sign in to comment.