Skip to content

Commit

Permalink
Added configurable block reward and recipient for IBFT2 (#1132)
Browse files Browse the repository at this point in the history
The IBFT2 configuration section of a genesis file has been updated
to allow the specification of a block reward (defaults to 0) and also an explicit
recipient (defaults to the block proposer).

The block reward can be specified as a hex string (with 0x prefix) or a decimal
string (no prefix), and is defined in Wei.

These values are not modifiable for the duration of the network.

Signed-off-by: Trent Mohay <[email protected]>
  • Loading branch information
rain-on authored Jun 25, 2020
1 parent f53bfd6 commit 79ed3f6
Show file tree
Hide file tree
Showing 17 changed files with 319 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ public Condition balanceEquals(final Amount expectedBalance) {
}

public Condition balanceAtBlockEquals(final Amount expectedBalance, final BigInteger block) {
return new ExpectAccountBalanceAtBlock(eth, this, block, expectedBalance.getValue(), Unit.WEI);
return new ExpectAccountBalanceAtBlock(
eth, this, block, expectedBalance.getValue(), expectedBalance.getUnit());
}

public Condition balanceDoesNotChange(final int startingBalance) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 static java.util.Collections.singletonList;

import org.hyperledger.besu.ethereum.core.Address;
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 java.io.IOException;
import java.math.BigInteger;
import java.util.Optional;

import org.junit.Test;

public class Ibft2BlockRewardPaymentAcceptanceTest extends AcceptanceTestBase {

@Test
public void validatorsArePaidBlockReward() throws IOException {
final String[] validators = {"validator"};
final BesuNode validator = besu.createIbft2NodeWithValidators("validator", validators);
final BesuNode nonValidator = besu.createIbft2NodeWithValidators("nonValidator", validators);
cluster.start(validator, nonValidator);
final Account validator1Account = Account.create(ethTransactions, validator.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)));
}

@Test
public void payBlockRewardToConfiguredNode() throws IOException {
final String[] validators = {"validator1"};
final BesuNode validator1 = besu.createIbft2NodeWithValidators("validator1", validators);
final Optional<String> initialConfig =
validator1.getGenesisConfigProvider().create(singletonList(validator1));
if (initialConfig.isEmpty()) {
throw new RuntimeException("Unable to generate genesis config.");
}
final String miningBeneficiaryAddress = "0x1234567890123456789012345678901234567890";

final String configWithMiningBeneficiary =
initialConfig
.get()
.replace(
"\"ibft2\": {",
"\"ibft2\": { \"miningbeneficiary\": \"" + miningBeneficiaryAddress + "\",");

validator1.setGenesisConfig(configWithMiningBeneficiary);

final Account miningBeneficiaryAccount =
Account.create(ethTransactions, Address.fromHexString(miningBeneficiaryAddress));

// This starts a node, without executing its configGenerator
cluster.runNodeStart(validator1);
final int blockRewardEth = 5;
final int blockToCheck = 2;

cluster.verify(miningBeneficiaryAccount.balanceAtBlockEquals(Amount.ether(0), BigInteger.ZERO));
cluster.verify(
miningBeneficiaryAccount.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,
"blockreward": "5000000000000000000"
}
},
"nonce": "0x0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,16 @@ protected MiningCoordinator createMiningCoordinator(
final MutableBlockchain blockchain = protocolContext.getBlockchain();
final IbftExecutors ibftExecutors = IbftExecutors.create(metricsSystem);

final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
final IbftBlockCreatorFactory blockCreatorFactory =
new IbftBlockCreatorFactory(
gasLimitCalculator,
transactionPool.getPendingTransactions(),
protocolContext,
protocolSchedule,
miningParameters,
Util.publicKeyToAddress(nodeKey.getPublicKey()));
localAddress,
ibftConfig.getMiningBeneficiary().map(Address::fromHexString).orElse(localAddress));

// NOTE: peers should not be used for accessing the network as it does not enforce the
// "only send once" filter applied by the UniqueMessageMulticaster.
Expand Down
1 change: 1 addition & 0 deletions config/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies {
implementation 'com.google.guava:guava'
implementation 'org.apache.logging.log4j:log4j-api'
implementation 'info.picocli:picocli'
implementation 'org.apache.tuweni:tuweni-bytes'
runtimeOnly 'org.apache.logging.log4j:log4j-core'

testImplementation project(':testutil')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
*/
package org.hyperledger.besu.config;

import java.math.BigInteger;
import java.util.Map;
import java.util.Optional;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap;
import org.apache.tuweni.bytes.Bytes;

public class IbftConfigOptions {

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

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

public BigInteger getBlockRewardWei() {
final Optional<String> configFileContent = JsonUtil.getString(ibftConfigRoot, "blockreward");

if (configFileContent.isEmpty()) {
return BigInteger.ZERO;
}
final String weiStr = configFileContent.get();
if (weiStr.startsWith("0x")) {
return new BigInteger(1, Bytes.fromHexStringLenient(weiStr).toArrayUnsafe());
}
return new BigInteger(weiStr);
}

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 @@ -116,4 +116,52 @@ 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().getBlockRewardWei()).isEqualTo(21);
}

@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("blockreward");

final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);

assertThat(configOptions.getIbft2ConfigOptions().getBlockRewardWei()).isEqualTo(0);
}

@Test
public void ibftBlockRewardAsDecimalNumberCorrectlyDecodes() {
final ObjectNode configNode = loadConfigWithNoTransitions();
final ObjectNode ibftNode = (ObjectNode) configNode.get("ibft2");
ibftNode.put("blockreward", "12");

final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);

assertThat(configOptions.getIbft2ConfigOptions().getBlockRewardWei()).isEqualTo(12);
}
}
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,
"blockreward": "0x15",
"miningbeneficiary": "0x1234567890123456789012345678901234567890"
},
"transitions": {
"ibft2": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,14 +306,16 @@ private static ControllerAndState createControllerAndFinalState(
Optional.empty(),
TransactionPoolConfiguration.DEFAULT_PRICE_BUMP);

final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
final IbftBlockCreatorFactory blockCreatorFactory =
new IbftBlockCreatorFactory(
(gasLimit) -> gasLimit,
pendingTransactions, // changed from IbftBesuController
protocolContext,
protocolSchedule,
miningParams,
Util.publicKeyToAddress(nodeKey.getPublicKey()));
localAddress,
localAddress);

final ProposerSelector proposerSelector =
new ProposerSelector(blockChain, blockInterface, true, voteTallyCache);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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 @@ -38,13 +39,11 @@ 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),
builder -> applyIbftChanges(config.getIbft2ConfigOptions(), builder),
privacyParameters,
isRevertReasonEnabled)
.createProtocolSchedule();
Expand All @@ -60,16 +59,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.getBlockRewardWei().signum() < 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.of(ibftConfig.getBlockRewardWei()))
.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
Expand Up @@ -41,7 +41,8 @@ public IbftBlockCreator(
final Function<Long, Long> gasLimitCalculator,
final Wei minTransactionGasPrice,
final Double minBlockOccupancyRatio,
final BlockHeader parentHeader) {
final BlockHeader parentHeader,
final Address miningBeneficiary) {
super(
localAddress,
extraDataCalculator,
Expand All @@ -50,7 +51,7 @@ public IbftBlockCreator(
protocolSchedule,
gasLimitCalculator,
minTransactionGasPrice,
localAddress,
miningBeneficiary,
minBlockOccupancyRatio,
parentHeader);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class IbftBlockCreatorFactory {
protected final ProtocolContext protocolContext;
protected final ProtocolSchedule protocolSchedule;
private final Address localAddress;
final Address miningBeneficiary;

private volatile Bytes vanityData;
private volatile Wei minTransactionGasPrice;
Expand All @@ -54,7 +55,8 @@ public IbftBlockCreatorFactory(
final ProtocolContext protocolContext,
final ProtocolSchedule protocolSchedule,
final MiningParameters miningParams,
final Address localAddress) {
final Address localAddress,
final Address miningBeneficiary) {
this.gasLimitCalculator = gasLimitCalculator;
this.pendingTransactions = pendingTransactions;
this.protocolContext = protocolContext;
Expand All @@ -63,6 +65,7 @@ public IbftBlockCreatorFactory(
this.minTransactionGasPrice = miningParams.getMinTransactionGasPrice();
this.minBlockOccupancyRatio = miningParams.getMinBlockOccupancyRatio();
this.vanityData = miningParams.getExtraData();
this.miningBeneficiary = miningBeneficiary;
}

public IbftBlockCreator create(final BlockHeader parentHeader, final int round) {
Expand All @@ -75,7 +78,8 @@ public IbftBlockCreator create(final BlockHeader parentHeader, final int round)
gasLimitCalculator,
minTransactionGasPrice,
minBlockOccupancyRatio,
parentHeader);
parentHeader,
miningBeneficiary);
}

public void setExtraData(final Bytes extraData) {
Expand Down
Loading

0 comments on commit 79ed3f6

Please sign in to comment.