Skip to content

Commit

Permalink
Added configurable block reward and recipient for IBFT2
Browse files Browse the repository at this point in the history
Signed-off-by: Trent Mohay <trent.mohay@consensys.net>
  • Loading branch information
Trent Mohay committed Jun 24, 2020
1 parent 89f37e4 commit 5770d7a
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.tests.acceptance.ibft2;

import java.io.IOException;
import java.math.BigInteger;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount;
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
import org.junit.Test;

public class Ibft2BlockRewardPayment extends AcceptanceTestBase {

@Test
public void validatorsArePaidBlockReward() throws IOException {
final String[] validators = {"validator1"};
final BesuNode validator1 = besu.createIbft2NodeWithValidators("validator1", validators);
final BesuNode validator2 = besu.createIbft2NodeWithValidators("validator2", validators);
cluster.start(validator1, validator2);
final Account validator1Account = Account.create(ethTransactions, validator1.getAddress());

final int blockRewardEth = 5;
final int blockToCheck = 2;

cluster.verify(validator1Account.balanceAtBlockEquals(Amount.ether(0), BigInteger.ZERO));
cluster.verify(validator1Account
.balanceAtBlockEquals(Amount.ether(blockRewardEth * blockToCheck),
BigInteger.valueOf(blockToCheck)));
}

}
3 changes: 2 additions & 1 deletion acceptance-tests/tests/src/test/resources/ibft/ibft.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"ibft2": {
"blockperiodseconds": 1,
"epochlength": 30000,
"requesttimeoutseconds": 5
"requesttimeoutseconds": 5,
"blockrewardeth": 5
}
},
"nonce": "0x0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.hyperledger.besu.config;

import java.util.Map;
import java.util.Optional;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap;
Expand All @@ -33,7 +34,8 @@ public class IbftConfigOptions {
private static final int DEFAULT_MESSAGE_QUEUE_LIMIT = 1000;
private static final int DEFAULT_DUPLICATE_MESSAGE_LIMIT = 100;
private static final int DEFAULT_FUTURE_MESSAGES_LIMIT = 1000;
private static final int DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE = 10;
private static final int DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE = 100;
private static final long DEFAULT_IBFT_BLOCK_REWARD_ETH = 0L;

private final ObjectNode ibftConfigRoot;

Expand Down Expand Up @@ -75,6 +77,14 @@ public int getFutureMessagesMaxDistance() {
ibftConfigRoot, "futuremessagesmaxdistance", DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE);
}

public Optional<String> getMiningBeneficiary() {
return JsonUtil.getString(ibftConfigRoot, "miningbeneficiary");
}

public long getBlockRewardEth() {
return JsonUtil.getLong(ibftConfigRoot, "blockrewardeth", DEFAULT_IBFT_BLOCK_REWARD_ETH);
}

Map<String, Object> asMap() {
final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
if (ibftConfigRoot.has("epochlength")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import static org.assertj.core.api.Assertions.assertThat;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

Expand Down Expand Up @@ -116,4 +118,40 @@ public void configWithAnIbftWithNoValidatorsListedIsValid() {
assertThat(configOptions.getTransitions().getIbftForks().get(1).getValidators().get().size())
.isEqualTo(1);
}

@Test
public void configWithValidIbftBlockRewardIsParsable() {
final ObjectNode configNode = loadConfigWithNoTransitions();

final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);
assertThat(configOptions.getIbft2ConfigOptions().getMiningBeneficiary()).isNotEmpty();
assertThat(configOptions.getIbft2ConfigOptions().getMiningBeneficiary().get())
.isEqualTo("0x1234567890123456789012345678901234567890");
assertThat(configOptions.getIbft2ConfigOptions().getBlockRewardEth()).isEqualTo(4);
}

@Test
public void ibftConfigWithoutMiningBeneficiaryDefaultsToEmpty() {
final ObjectNode configNode = loadConfigWithNoTransitions();
final ObjectNode ibftNode = (ObjectNode)configNode.get("ibft2");
ibftNode.remove("miningbeneficiary");

final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);

assertThat(configOptions.getIbft2ConfigOptions().getMiningBeneficiary()).isEmpty();
}

@Test
public void ibftConfigWithoutBlockRewardsDefaultsToZero() {
final ObjectNode configNode = loadConfigWithNoTransitions();
final ObjectNode ibftNode = (ObjectNode)configNode.get("ibft2");
ibftNode.remove("blockrewardeth");

final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);

assertThat(configOptions.getIbft2ConfigOptions().getBlockRewardEth()).isEqualTo(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
"requesttimeoutseconds": 10,
"blockrewardeth": 4,
"miningbeneficiary": "0x1234567890123456789012345678901234567890"
},
"transitions": {
"ibft2": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

import static org.hyperledger.besu.consensus.ibft.IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator;

import java.util.IllegalFormatException;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.IbftConfigOptions;
import org.hyperledger.besu.ethereum.MainnetBlockValidator;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockBodyValidator;
Expand All @@ -29,7 +31,9 @@

import java.math.BigInteger;

/** Defines the protocol behaviours for a blockchain using IBFT. */
/**
* Defines the protocol behaviours for a blockchain using IBFT.
*/
public class IbftProtocolSchedule {

private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE;
Expand All @@ -38,15 +42,13 @@ public static ProtocolSchedule create(
final GenesisConfigOptions config,
final PrivacyParameters privacyParameters,
final boolean isRevertReasonEnabled) {
final IbftConfigOptions ibftConfig = config.getIbftLegacyConfigOptions();
final long blockPeriod = ibftConfig.getBlockPeriodSeconds();

return new ProtocolScheduleBuilder(
config,
DEFAULT_CHAIN_ID,
builder -> applyIbftChanges(blockPeriod, builder),
privacyParameters,
isRevertReasonEnabled)
config,
DEFAULT_CHAIN_ID,
builder -> applyIbftChanges(config.getIbft2ConfigOptions(), builder),
privacyParameters,
isRevertReasonEnabled)
.createProtocolSchedule();
}

Expand All @@ -60,16 +62,35 @@ public static ProtocolSchedule create(final GenesisConfigOptions config) {
}

private static ProtocolSpecBuilder applyIbftChanges(
final long secondsBetweenBlocks, final ProtocolSpecBuilder builder) {
return builder
.blockHeaderValidatorBuilder(ibftBlockHeaderValidator(secondsBetweenBlocks))
.ommerHeaderValidatorBuilder(ibftBlockHeaderValidator(secondsBetweenBlocks))
final IbftConfigOptions ibftConfig, final ProtocolSpecBuilder builder) {

if(ibftConfig.getBlockRewardEth() < 0) {
throw new IllegalArgumentException("Ibft2 Block reward in config cannot be negative");
}

builder
.blockHeaderValidatorBuilder(ibftBlockHeaderValidator(ibftConfig.getBlockPeriodSeconds()))
.ommerHeaderValidatorBuilder(ibftBlockHeaderValidator(ibftConfig.getBlockPeriodSeconds()))
.blockBodyValidatorBuilder(MainnetBlockBodyValidator::new)
.blockValidatorBuilder(MainnetBlockValidator::new)
.blockImporterBuilder(MainnetBlockImporter::new)
.difficultyCalculator((time, parent, protocolContext) -> BigInteger.ONE)
.blockReward(Wei.ZERO)
.blockReward(Wei.fromEth(ibftConfig.getBlockRewardEth()))
.skipZeroBlockRewards(true)
.blockHeaderFunctions(IbftBlockHeaderFunctions.forOnChainBlock());

if (ibftConfig.getMiningBeneficiary().isPresent()) {
final Address miningBeneficiary;
try {
// Precalculate beneficiary to ensure string is valid now, rather than on lambda execution.
miningBeneficiary = Address.fromHexString(ibftConfig.getMiningBeneficiary().get());
} catch (final IllegalArgumentException e) {
throw new IllegalArgumentException(
"Mining beneficiary in config is not a valid ethereum address", e);
}
builder.miningBeneficiaryCalculator(header -> miningBeneficiary);
}

return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.hyperledger.besu.consensus.ibft;/*
* 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
*/

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Optional;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.IbftConfigOptions;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.junit.Test;

public class IbftProtocolScheduleTest {

GenesisConfigOptions genesisConfig = mock(GenesisConfigOptions.class);

@Test
public void ensureBlockRewardAndMiningBeneficiaryInProtocolSpecMatchConfig() {
final long arbitraryBlockRewardEth = 5;
final String miningBeneficiary = Address.fromHexString("0x1").toString();
final IbftConfigOptions ibftConfig = mock(IbftConfigOptions.class);
when(ibftConfig.getMiningBeneficiary()).thenReturn(Optional.of(miningBeneficiary));
when(ibftConfig.getBlockRewardEth()).thenReturn(arbitraryBlockRewardEth);

when(genesisConfig.getIbft2ConfigOptions()).thenReturn(ibftConfig);

final ProtocolSchedule schedule = IbftProtocolSchedule.create(genesisConfig);
final ProtocolSpec spec = schedule.getByBlockNumber(1);

spec.getBlockReward();

assertThat(spec.getBlockReward()).isEqualTo(Wei.fromEth(arbitraryBlockRewardEth));
assertThat(spec.getMiningBeneficiaryCalculator().calculateBeneficiary(null))
.isEqualTo(Address.fromHexString(miningBeneficiary));
}

@Test
public void illegalMiningBeneficiaryStringThrowsException() {
final String miningBeneficiary = "notHexStringOfTwentyBytes";
final IbftConfigOptions ibftConfig = mock(IbftConfigOptions.class);
when(ibftConfig.getMiningBeneficiary()).thenReturn(Optional.of(miningBeneficiary));
when(genesisConfig.getIbft2ConfigOptions()).thenReturn(ibftConfig);

assertThatThrownBy(() -> IbftProtocolSchedule.create(genesisConfig))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Mining beneficiary in config is not a valid ethereum address");
}

@Test
public void missingMiningBeneficiaryInConfigWillPayCoinbaseInHeader() {
final long arbitraryBlockRewardEth = 5;
final IbftConfigOptions ibftConfig = mock(IbftConfigOptions.class);
when(ibftConfig.getMiningBeneficiary()).thenReturn(Optional.empty());
when(ibftConfig.getBlockRewardEth()).thenReturn(arbitraryBlockRewardEth);
when(genesisConfig.getIbft2ConfigOptions()).thenReturn(ibftConfig);

final ProtocolSchedule schedule = IbftProtocolSchedule.create(genesisConfig);
final ProtocolSpec spec = schedule.getByBlockNumber(1);

final Address headerCoinbase = Address.fromHexString("0x123");
final BlockHeader header = mock(BlockHeader.class);
when(header.getCoinbase()).thenReturn(headerCoinbase);

assertThat(spec.getBlockReward()).isEqualTo(Wei.fromEth(arbitraryBlockRewardEth));
assertThat(spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header))
.isEqualTo(headerCoinbase);
}

@Test
public void negativeBlockRewardThrowsException() {
final long arbitraryBlockRewardEth = -5;
final IbftConfigOptions ibftConfig = mock(IbftConfigOptions.class);
when(ibftConfig.getMiningBeneficiary()).thenReturn(Optional.empty());
when(ibftConfig.getBlockRewardEth()).thenReturn(arbitraryBlockRewardEth);
when(genesisConfig.getIbft2ConfigOptions()).thenReturn(ibftConfig);

assertThatThrownBy(() -> IbftProtocolSchedule.create(genesisConfig))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Ibft2 Block reward in config cannot be negative");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
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;
Expand Down Expand Up @@ -162,9 +161,13 @@ public AbstractBlockProcessor.Result processBlock(
return AbstractBlockProcessor.Result.successful(receipts);
}

protected MiningBeneficiaryCalculator getMiningBeneficiaryCalculator() {
return miningBeneficiaryCalculator;
}

abstract boolean rewardCoinbase(
final MutableWorldState worldState,
final ProcessableBlockHeader header,
final BlockHeader header,
final List<BlockHeader> ommers,
final boolean skipZeroBlockRewards);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
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 org.hyperledger.besu.ethereum.core.fees.TransactionGasBudgetCalculator;
Expand Down Expand Up @@ -52,7 +51,7 @@ public ClassicBlockProcessor(
@Override
boolean rewardCoinbase(
final MutableWorldState worldState,
final ProcessableBlockHeader header,
final BlockHeader header,
final List<BlockHeader> ommers,
final boolean skipZeroBlockRewards) {
if (skipZeroBlockRewards && blockReward.isZero()) {
Expand Down
Loading

0 comments on commit 5770d7a

Please sign in to comment.