[Issue 3115] Support mining beneficiary transitions (#3505)

Signed-off-by: Meredith Baxter <meredith.baxter@palm.io>
pull/3433/head
mbaxter 3 years ago committed by GitHub
parent 6947a6c34d
commit 6efdd94a69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 29
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/blockchain/Amount.java
  3. 5
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/account/ExpectAccountBalanceAtBlock.java
  4. 46
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java
  5. 189
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftBlockRewardPaymentAcceptanceTest.java
  6. 6
      besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java
  7. 7
      besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java
  8. 4
      config/src/main/java/org/hyperledger/besu/config/BftConfigOptions.java
  9. 19
      config/src/main/java/org/hyperledger/besu/config/BftFork.java
  10. 16
      config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java
  11. 11
      config/src/main/java/org/hyperledger/besu/config/JsonUtil.java
  12. 130
      config/src/test/java/org/hyperledger/besu/config/BFTForkTest.java
  13. 31
      config/src/test/java/org/hyperledger/besu/config/JsonBftConfigOptionsTest.java
  14. 7
      config/src/test/java/org/hyperledger/besu/config/JsonGenesisConfigOptionsTest.java
  15. 32
      config/src/test/java/org/hyperledger/besu/config/JsonUtilTest.java
  16. 2
      consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java
  17. 40
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolSchedule.java
  18. 10
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java
  19. 12
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java
  20. 13
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java
  21. 4
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java
  22. 6
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BftFinalState.java
  23. 23
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleTest.java
  24. 136
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleTest.java
  25. 182
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseForksSchedulesFactoryTest.java
  26. 2
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinatorTest.java
  27. 6
      consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java
  28. 5
      consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactory.java
  29. 2
      consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundFactory.java
  30. 51
      consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactoryTest.java
  31. 2
      consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java
  32. 2
      consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/blockcreation/IbftBlockCreator.java
  33. 2
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java
  34. 7
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java
  35. 5
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java
  36. 12
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java
  37. 2
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundFactory.java
  38. 5
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java
  39. 44
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java
  40. 21
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java
  41. 2
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java
  42. 4
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java

@ -7,6 +7,7 @@
- Execution specific RPC endpoint [[#3378](https://github.com/hyperledger/besu/issues/3378)
- Adds JWT authentication to Engine APIs
- Tracing APIs: trace_rawTransaction, trace_get, trace_callMany
- Allow mining beneficiary to transition at specific blocks for ibft2 and qbft consensus mechanisms. [#3115](https://github.com/hyperledger/besu/issues/3115)
### Bug Fixes
- Reject locally-sourced transactions below the minimum gas price when not mining. [#3397](https://github.com/hyperledger/besu/pull/3397)

@ -49,20 +49,27 @@ public class Amount {
return unit;
}
public Amount add(final Amount other) {
final BigDecimal weiResult =
Convert.toWei(value, unit).add(Convert.toWei(other.value, other.unit));
final Unit denomination = getMostPreciseUnit(unit, other.unit);
final BigDecimal result = Convert.fromWei(weiResult, denomination);
return new Amount(result, denomination);
}
public Amount subtract(final Amount subtracting) {
final BigDecimal weiResult =
Convert.toWei(value, unit).subtract(Convert.toWei(subtracting.value, subtracting.unit));
final Unit denominator;
if (unit.getWeiFactor().compareTo(subtracting.unit.getWeiFactor()) < 0) {
denominator = unit;
} else {
denominator = subtracting.unit;
}
final Unit denomination = getMostPreciseUnit(unit, subtracting.unit);
final BigDecimal result = Convert.fromWei(weiResult, denomination);
final BigDecimal result =
Convert.fromWei(
Convert.toWei(value, unit).subtract(Convert.toWei(subtracting.value, subtracting.unit)),
denominator);
return new Amount(result, denomination);
}
return new Amount(result, denominator);
private Unit getMostPreciseUnit(final Unit a, final Unit b) {
return a.getWeiFactor().compareTo(b.getWeiFactor()) < 0 ? a : b;
}
}

@ -52,6 +52,11 @@ public class ExpectAccountBalanceAtBlock implements Condition {
WaitUtils.waitFor(
() ->
assertThat(node.execute(eth.getBalanceAtBlock(account, block)))
.withFailMessage(
() ->
String.format(
"Expected address %s at block %s to have balance %s",
account.getAddress(), block.toString(10), expectedBalance.toString(10)))
.isEqualTo(expectedBalance));
}
}

@ -20,6 +20,7 @@ import org.hyperledger.besu.Runner;
import org.hyperledger.besu.RunnerBuilder;
import org.hyperledger.besu.cli.config.EthNetworkConfig;
import org.hyperledger.besu.cli.config.NetworkName;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfigurationProvider;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.BesuControllerBuilder;
@ -161,27 +162,30 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
final int maxPeers = 25;
final BesuController besuController =
builder
.synchronizerConfiguration(new SynchronizerConfiguration.Builder().build())
.dataDirectory(node.homeDirectory())
.miningParameters(node.getMiningParameters())
.privacyParameters(node.getPrivacyParameters())
.nodeKey(new NodeKey(new KeyPairSecurityModule(KeyPairUtil.loadKeyPair(dataDir))))
.metricsSystem(metricsSystem)
.transactionPoolConfiguration(txPoolConfig)
.ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig())
.clock(Clock.systemUTC())
.isRevertReasonEnabled(node.isRevertReasonEnabled())
.storageProvider(storageProvider)
.gasLimitCalculator(GasLimitCalculator.constant())
.pkiBlockCreationConfiguration(
node.getPkiKeyStoreConfiguration()
.map(
(pkiConfig) -> new PkiBlockCreationConfigurationProvider().load(pkiConfig)))
.evmConfiguration(EvmConfiguration.DEFAULT)
.maxPeers(maxPeers)
.build();
builder
.synchronizerConfiguration(new SynchronizerConfiguration.Builder().build())
.dataDirectory(node.homeDirectory())
.miningParameters(node.getMiningParameters())
.privacyParameters(node.getPrivacyParameters())
.nodeKey(new NodeKey(new KeyPairSecurityModule(KeyPairUtil.loadKeyPair(dataDir))))
.metricsSystem(metricsSystem)
.transactionPoolConfiguration(txPoolConfig)
.ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig())
.clock(Clock.systemUTC())
.isRevertReasonEnabled(node.isRevertReasonEnabled())
.storageProvider(storageProvider)
.gasLimitCalculator(GasLimitCalculator.constant())
.pkiBlockCreationConfiguration(
node.getPkiKeyStoreConfiguration()
.map(pkiConfig -> new PkiBlockCreationConfigurationProvider().load(pkiConfig)))
.evmConfiguration(EvmConfiguration.DEFAULT)
.maxPeers(maxPeers);
node.getGenesisConfig()
.map(GenesisConfigFile::fromConfig)
.ifPresent(builder::genesisConfigFile);
final BesuController besuController = builder.build();
final RunnerBuilder runnerBuilder = new RunnerBuilder();
runnerBuilder.permissioningConfiguration(node.getPermissioningConfiguration());

@ -22,12 +22,22 @@ import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount;
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Optional;
import java.util.TreeMap;
import org.junit.Test;
public class BftBlockRewardPaymentAcceptanceTest extends ParameterizedBftTestBase {
private static final Amount BLOCK_REWARD = Amount.wei(new BigInteger("5000000000000000000", 10));
public BftBlockRewardPaymentAcceptanceTest(
final String testName, final BftAcceptanceTestParameterization nodeFactory) {
super(testName, nodeFactory);
@ -61,25 +71,15 @@ public class BftBlockRewardPaymentAcceptanceTest extends ParameterizedBftTestBas
if (initialConfig.isEmpty()) {
throw new RuntimeException("Unable to generate genesis config.");
}
final String miningBeneficiaryAddress = "0x1234567890123456789012345678901234567890";
final String configWithMiningBeneficiary =
initialConfig
.get()
.replace(
"\"" + bftType + "\": {",
"\""
+ bftType
+ "\": { \"miningbeneficiary\": \""
+ miningBeneficiaryAddress
+ "\",");
validator1.setGenesisConfig(configWithMiningBeneficiary);
final String miningBeneficiaryAddress = "0x1234567890123456789012345678901234567890";
final Account miningBeneficiaryAccount =
Account.create(ethTransactions, Address.fromHexString(miningBeneficiaryAddress));
// This starts a node, without executing its configGenerator
final String bftOptions = formatKeyValues("miningbeneficiary", miningBeneficiaryAddress);
final String configWithMiningBeneficiary = configureBftOptions(initialConfig.get(), bftOptions);
validator1.setGenesisConfig(configWithMiningBeneficiary);
cluster.start(validator1);
final int blockRewardEth = 5;
final int blockToCheck = 2;
@ -89,4 +89,163 @@ public class BftBlockRewardPaymentAcceptanceTest extends ParameterizedBftTestBas
miningBeneficiaryAccount.balanceAtBlockEquals(
Amount.ether(blockRewardEth * blockToCheck), BigInteger.valueOf(blockToCheck)));
}
@Test
public void payBlockRewardAccordingToTransitions_defaultInitialMiningBeneficiary()
throws Exception {
final List<Address> addresses = generateAddresses(2);
final Map<Long, Optional<Address>> transitions =
Map.of(
2L, Optional.of(addresses.get(0)),
3L, Optional.of(addresses.get(1)),
6L, Optional.empty(),
8L, Optional.of(addresses.get(0)));
testMiningBeneficiaryTransitions(Optional.empty(), transitions);
}
@Test
public void payBlockRewardAccordingToTransitions_customInitialMiningBeneficiary()
throws Exception {
final List<Address> addresses = generateAddresses(4);
final Map<Long, Optional<Address>> transitions =
Map.of(
1L, Optional.of(addresses.get(1)),
2L, Optional.of(addresses.get(2)),
3L, Optional.of(addresses.get(3)),
4L, Optional.empty());
testMiningBeneficiaryTransitions(Optional.of(addresses.get(0)), transitions);
}
private List<Address> generateAddresses(final int count) {
final List<Address> addresses = new ArrayList<>();
for (int i = 0; i < count; i++) {
addresses.add(Address.fromHexString(Integer.toString(i + 1, 16)));
}
return addresses;
}
private void testMiningBeneficiaryTransitions(
final Optional<Address> initialMiningBeneficiary,
final Map<Long, Optional<Address>> miningBeneficiaryTransitions)
throws Exception {
final String[] validators = {"validator1"};
final BesuNode validator1 =
nodeFactory.createNodeWithValidators(besu, "validator1", validators);
final Optional<String> initialConfig =
validator1.getGenesisConfigProvider().create(singletonList(validator1));
if (initialConfig.isEmpty()) {
throw new RuntimeException("Unable to generate genesis config.");
}
final String miningBeneficiary = initialMiningBeneficiary.map(Address::toHexString).orElse("");
final String bftOptions = formatKeyValues("miningbeneficiary", miningBeneficiary);
final String configWithMiningBeneficiary =
configureBftOptions(initialConfig.get(), bftOptions, miningBeneficiaryTransitions);
validator1.setGenesisConfig(configWithMiningBeneficiary);
cluster.start(validator1);
// Check that expected address receive block reward at each block
final NavigableMap<Long, Optional<Address>> orderedTransitions =
new TreeMap<>(miningBeneficiaryTransitions);
final long lastConfiguredBlock = orderedTransitions.lastKey();
final Map<Address, Amount> accountBalances = new HashMap<>();
for (long i = 1; i < lastConfiguredBlock + 2; i++) {
final Address beneficiaryAddress =
Optional.ofNullable(orderedTransitions.floorEntry(i))
.flatMap(Entry::getValue)
.orElse(validator1.getAddress());
final Account beneficiary = Account.create(ethTransactions, beneficiaryAddress);
final Amount currentBalance =
accountBalances.computeIfAbsent(beneficiaryAddress, __ -> Amount.wei(BigInteger.ZERO));
cluster.verify(beneficiary.balanceAtBlockEquals(currentBalance, BigInteger.valueOf(i - 1)));
final Amount newBalance = currentBalance.add(BLOCK_REWARD);
cluster.verify(beneficiary.balanceAtBlockEquals(newBalance, BigInteger.valueOf(i)));
accountBalances.put(beneficiaryAddress, newBalance);
}
}
private String formatKeyValues(final Object... keyOrValue) {
if (keyOrValue.length % 2 == 1) {
// An odd number of strings cannot form a set of key-value pairs
throw new IllegalArgumentException("Must supply key-value pairs");
}
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < keyOrValue.length; i += 2) {
if (i > 0) {
stringBuilder.append(", ");
}
final String key = keyOrValue[i].toString();
final Object value = keyOrValue[i + 1];
final String valueStr = value instanceof String ? quote(value) : value.toString();
stringBuilder.append(String.format("\n%s: %s", quote(key), valueStr));
}
return stringBuilder.toString();
}
private String quote(final Object value) {
return '"' + value.toString() + '"';
}
private String configureBftOptions(final String originalOptions, final String bftOptions) {
return configureBftOptions(originalOptions, bftOptions, Collections.emptyMap());
}
private String configureBftOptions(
final String originalOptions,
final String bftOptions,
final Map<Long, Optional<Address>> transitions) {
final StringBuilder stringBuilder =
new StringBuilder()
.append(formatBftTransitionsOptions(transitions))
.append(",\n")
.append(quote(bftType))
.append(": {")
.append(bftOptions)
.append(",");
return originalOptions.replace(quote(bftType) + ": {", stringBuilder.toString());
}
private String formatBftTransitionsOptions(final Map<Long, Optional<Address>> transitions) {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(quote("transitions"));
stringBuilder.append(": {\n");
stringBuilder.append(quote(bftType));
stringBuilder.append(": [");
boolean isFirst = true;
for (Long blockNumber : transitions.keySet()) {
if (!isFirst) {
stringBuilder.append(",");
}
isFirst = false;
final Optional<Address> miningBeneficiary = transitions.get(blockNumber);
stringBuilder.append("\n");
stringBuilder.append(formatTransition(blockNumber, miningBeneficiary));
}
stringBuilder.append("\n]");
stringBuilder.append("}\n");
return stringBuilder.toString();
}
private String formatTransition(
final long blockNumber, final Optional<Address> miningBeneficiary) {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("{");
stringBuilder.append(
formatKeyValues(
"block",
blockNumber,
"miningbeneficiary",
miningBeneficiary.map(Address::toHexString).orElse("")));
stringBuilder.append("}");
return stringBuilder.toString();
}
}

@ -141,14 +141,14 @@ public class IbftBesuControllerBuilder extends BftBesuControllerBuilder {
BftExecutors.create(metricsSystem, BftExecutors.ConsensusType.IBFT);
final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
final BftBlockCreatorFactory blockCreatorFactory =
new BftBlockCreatorFactory(
final BftBlockCreatorFactory<?> blockCreatorFactory =
new BftBlockCreatorFactory<>(
transactionPool.getPendingTransactions(),
protocolContext,
protocolSchedule,
forksSchedule,
miningParameters,
localAddress,
bftConfig.getMiningBeneficiary().map(Address::fromHexString).orElse(localAddress),
bftExtraDataCodec().get());
final ValidatorProvider validatorProvider =

@ -180,16 +180,15 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
BftExecutors.create(metricsSystem, BftExecutors.ConsensusType.QBFT);
final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
final BftBlockCreatorFactory blockCreatorFactory =
final BftBlockCreatorFactory<?> blockCreatorFactory =
new QbftBlockCreatorFactory(
transactionPool.getPendingTransactions(),
protocolContext,
protocolSchedule,
qbftForksSchedule,
miningParameters,
localAddress,
qbftConfig.getMiningBeneficiary().map(Address::fromHexString).orElse(localAddress),
bftExtraDataCodec().get(),
qbftForksSchedule);
bftExtraDataCodec().get());
final ValidatorProvider validatorProvider =
protocolContext.getConsensusContext(BftContext.class).getValidatorProvider();

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.config;
import org.hyperledger.besu.datatypes.Address;
import java.math.BigInteger;
import java.util.Map;
import java.util.Optional;
@ -36,7 +38,7 @@ public interface BftConfigOptions {
int getFutureMessagesMaxDistance();
Optional<String> getMiningBeneficiary();
Optional<Address> getMiningBeneficiary();
BigInteger getBlockRewardWei();

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.config;
import org.hyperledger.besu.datatypes.Address;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
@ -31,6 +33,7 @@ public class BftFork {
public static final String VALIDATORS_KEY = "validators";
public static final String BLOCK_PERIOD_SECONDS_KEY = "blockperiodseconds";
public static final String BLOCK_REWARD_KEY = "blockreward";
public static final String MINING_BENEFICIARY_KEY = "miningbeneficiary";
protected final ObjectNode forkConfigRoot;
@ -64,6 +67,22 @@ public class BftFork {
return Optional.of(new BigInteger(weiStr));
}
public Optional<Address> getMiningBeneficiary() {
try {
return JsonUtil.getString(forkConfigRoot, MINING_BENEFICIARY_KEY)
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Address::fromHexStringStrict);
} catch (final IllegalArgumentException e) {
throw new IllegalArgumentException(
"Mining beneficiary in transition config is not a valid ethereum address", e);
}
}
public boolean isMiningBeneficiaryConfigured() {
return JsonUtil.hasKey(forkConfigRoot, MINING_BENEFICIARY_KEY);
}
public Optional<List<String>> getValidators() throws IllegalArgumentException {
final Optional<ArrayNode> validatorNode = JsonUtil.getArrayNode(forkConfigRoot, VALIDATORS_KEY);

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.config;
import org.hyperledger.besu.datatypes.Address;
import java.math.BigInteger;
import java.util.Map;
import java.util.Optional;
@ -40,7 +42,7 @@ public class JsonBftConfigOptions implements BftConfigOptions {
protected final ObjectNode bftConfigRoot;
JsonBftConfigOptions(final ObjectNode bftConfigRoot) {
public JsonBftConfigOptions(final ObjectNode bftConfigRoot) {
this.bftConfigRoot = bftConfigRoot;
}
@ -87,8 +89,16 @@ public class JsonBftConfigOptions implements BftConfigOptions {
}
@Override
public Optional<String> getMiningBeneficiary() {
return JsonUtil.getString(bftConfigRoot, "miningbeneficiary");
public Optional<Address> getMiningBeneficiary() {
try {
return JsonUtil.getString(bftConfigRoot, "miningbeneficiary")
.map(String::trim)
.filter(s -> !s.isBlank())
.map(Address::fromHexStringStrict);
} catch (final IllegalArgumentException e) {
throw new IllegalArgumentException(
"Mining beneficiary in config is not a valid ethereum address", e);
}
}
@Override

@ -102,6 +102,17 @@ public class JsonUtil {
return getValueAsString(node, key).orElse(defaultValue);
}
/**
* Checks whether an {@code ObjectNode} contains the given key.
*
* @param node The {@code ObjectNode} to inspect.
* @param key The key to check.
* @return Returns true if the given key is set.
*/
public static boolean hasKey(final ObjectNode node, final String key) {
return node.has(key);
}
/**
* Returns textual (string) value at {@code key}. See {@link #getValueAsString} for retrieving
* non-textual values in string form.

@ -0,0 +1,130 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.datatypes.Address;
import java.util.Collections;
import java.util.Map;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test;
public class BFTForkTest {
private final String BENEFICIARY = "0x1111111111111111111111111111111111111111";
private final String TRUNCATED_BENEFICIARY = BENEFICIARY.substring(0, BENEFICIARY.length() - 2);
@Test
public void getMiningBeneficiary_fromEmptyConfig() {
ObjectNode config = JsonUtil.objectNodeFromMap(Collections.emptyMap());
final BftFork bftFork = new BftFork(config);
assertThat(bftFork.getMiningBeneficiary()).isEmpty();
}
@Test
public void getMiningBeneficiary_withValidAddress() {
ObjectNode config =
JsonUtil.objectNodeFromMap(Map.of(BftFork.MINING_BENEFICIARY_KEY, BENEFICIARY));
final BftFork bftFork = new BftFork(config);
assertThat(bftFork.getMiningBeneficiary().map(Address::toHexString)).contains(BENEFICIARY);
}
@Test
public void getMiningBeneficiary_withValidAddressMissingPrefix() {
ObjectNode config =
JsonUtil.objectNodeFromMap(
Map.of(BftFork.MINING_BENEFICIARY_KEY, BENEFICIARY.substring(2)));
final BftFork bftFork = new BftFork(config);
assertThat(bftFork.getMiningBeneficiary().map(Address::toHexString)).contains(BENEFICIARY);
}
@Test
public void getMiningBeneficiary_withValidAddressAndEmptySpace() {
ObjectNode config =
JsonUtil.objectNodeFromMap(
Map.of(BftFork.MINING_BENEFICIARY_KEY, "\t" + BENEFICIARY + " "));
final BftFork bftFork = new BftFork(config);
assertThat(bftFork.getMiningBeneficiary().map(Address::toHexString)).contains(BENEFICIARY);
}
@Test
public void getMiningBeneficiary_withInvalidValue() {
final String beneficiary = "random";
testGetMiningBeneficiaryWithInvalidAddress(beneficiary);
}
@Test
public void getMiningBeneficiary_withInvalidAddress() {
testGetMiningBeneficiaryWithInvalidAddress(TRUNCATED_BENEFICIARY);
}
@Test
public void getMiningBeneficiary_withInvalidAddressAndWhitespace() {
final String beneficiary = TRUNCATED_BENEFICIARY + " ";
testGetMiningBeneficiaryWithInvalidAddress(beneficiary);
}
private void testGetMiningBeneficiaryWithInvalidAddress(final String miningBeneficiary) {
ObjectNode config =
JsonUtil.objectNodeFromMap(Map.of(BftFork.MINING_BENEFICIARY_KEY, miningBeneficiary));
final BftFork bftFork = new BftFork(config);
assertThatThrownBy(bftFork::getMiningBeneficiary)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(
"Mining beneficiary in transition config is not a valid ethereum address");
}
@Test
public void getMiningBeneficiary_whenEmptyValueIsProvided() {
ObjectNode config = JsonUtil.objectNodeFromMap(Map.of(BftFork.MINING_BENEFICIARY_KEY, " "));
final BftFork bftFork = new BftFork(config);
assertThat(bftFork.getMiningBeneficiary()).isEmpty();
}
@Test
public void isMiningBeneficiaryConfigured_noKeySet() {
final String jsonStr = "{}";
final ObjectNode config = JsonUtil.objectNodeFromString(jsonStr);
final BftFork bftFork = new BftFork(config);
assertThat(bftFork.isMiningBeneficiaryConfigured()).isFalse();
}
@Test
public void isMiningBeneficiaryConfigured_whenEmptyValueIsProvided() {
ObjectNode config = JsonUtil.objectNodeFromMap(Map.of(BftFork.MINING_BENEFICIARY_KEY, ""));
final BftFork bftFork = new BftFork(config);
assertThat(bftFork.isMiningBeneficiaryConfigured()).isTrue();
}
@Test
public void isMiningBeneficiaryConfigured_whenNonEmptyValueIsProvided() {
ObjectNode config =
JsonUtil.objectNodeFromMap(Map.of(BftFork.MINING_BENEFICIARY_KEY, BENEFICIARY));
final BftFork bftFork = new BftFork(config);
assertThat(bftFork.isMiningBeneficiaryConfigured()).isTrue();
}
}

@ -19,6 +19,8 @@ import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.datatypes.Address;
import java.util.Map;
import com.fasterxml.jackson.databind.node.ObjectNode;
@ -189,6 +191,35 @@ public class JsonBftConfigOptionsTest {
.isEqualTo(EXPECTED_DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE);
}
@Test
public void shouldGetMiningBeneficiaryFromConfig() {
final Address miningBeneficiary =
Address.fromHexString("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73");
final BftConfigOptions config =
fromConfigOptions(singletonMap("miningbeneficiary", miningBeneficiary.toString()));
assertThat(config.getMiningBeneficiary()).contains(miningBeneficiary);
}
@Test
public void shouldGetEmptyMiningBeneficiaryFromConfig() {
final BftConfigOptions config = fromConfigOptions(singletonMap("miningbeneficiary", " "));
assertThat(config.getMiningBeneficiary()).isEmpty();
}
@Test
public void shouldFallbackToDefaultEmptyMiningBeneficiary() {
final BftConfigOptions config = fromConfigOptions(emptyMap());
assertThat(config.getMiningBeneficiary()).isEmpty();
}
@Test
public void shouldThrowOnInvalidMiningBeneficiary() {
final BftConfigOptions config = fromConfigOptions(singletonMap("miningbeneficiary", "bla"));
assertThatThrownBy(config::getMiningBeneficiary)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Mining beneficiary in config is not a valid ethereum address");
}
private BftConfigOptions fromConfigOptions(final Map<String, Object> ibftConfigOptions) {
final ObjectNode rootNode = JsonUtil.createEmptyObjectNode();
final ObjectNode configNode = JsonUtil.createEmptyObjectNode();

@ -16,6 +16,8 @@ package org.hyperledger.besu.config;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.datatypes.Address;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@ -156,9 +158,8 @@ public class JsonGenesisConfigOptionsTest {
final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);
assertThat(configOptions.getBftConfigOptions().getMiningBeneficiary()).isNotEmpty();
assertThat(configOptions.getBftConfigOptions().getMiningBeneficiary().get())
.isEqualTo("0x1234567890123456789012345678901234567890");
assertThat(configOptions.getBftConfigOptions().getMiningBeneficiary().map(Address::toHexString))
.contains("0x1234567890123456789012345678901234567890");
assertThat(configOptions.getBftConfigOptions().getBlockRewardWei()).isEqualTo(21);
}

@ -749,4 +749,36 @@ public class JsonUtilTest {
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Expected array value but got string");
}
@Test
public void hasKey_noMatchingKey() {
final String jsonStr = "{\"test\": \"bla\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThat(JsonUtil.hasKey(rootNode, "target")).isFalse();
}
@Test
public void hasKey_nullMatchingKey() {
final String jsonStr = "{\"target\": null }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThat(JsonUtil.hasKey(rootNode, "target")).isTrue();
}
@Test
public void hasKey_emptyStringMatchingKey() {
final String jsonStr = "{\"target\": \"\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThat(JsonUtil.hasKey(rootNode, "target")).isTrue();
}
@Test
public void hasKey_nonEmptyMatchingKey() {
final String jsonStr = "{\"target\": \"bla\" }";
final ObjectNode rootNode = JsonUtil.objectNodeFromString(jsonStr);
assertThat(JsonUtil.hasKey(rootNode, "target")).isTrue();
}
}

@ -59,13 +59,13 @@ public class CliqueBlockCreator extends AbstractBlockCreator {
final EpochManager epochManager) {
super(
coinbase,
__ -> Util.publicKeyToAddress(nodeKey.getPublicKey()),
targetGasLimitSupplier,
extraDataCalculator,
pendingTransactions,
protocolContext,
protocolSchedule,
minTransactionGasPrice,
Util.publicKeyToAddress(nodeKey.getPublicKey()),
minBlockOccupancyRatio,
parentHeader);
this.nodeKey = nodeKey;

@ -17,7 +17,6 @@ package org.hyperledger.besu.consensus.common.bft;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator;
@ -34,7 +33,6 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
/** Defines the protocol behaviours for a blockchain using a BFT consensus mechanism. */
@ -59,11 +57,7 @@ public abstract class BaseBftProtocolSchedule {
forkSpec.getBlock(),
builder ->
applyBftChanges(
forkSpec.getValue(),
builder,
config.isQuorum(),
bftExtraDataCodec,
Optional.of(forkSpec.getValue().getBlockRewardWei()))));
builder, forkSpec.getValue(), config.isQuorum(), bftExtraDataCodec)));
final ProtocolSpecAdapters specAdapters = new ProtocolSpecAdapters(specMap);
@ -82,21 +76,18 @@ public abstract class BaseBftProtocolSchedule {
final BftConfigOptions config, final FeeMarket feeMarket);
private ProtocolSpecBuilder applyBftChanges(
final BftConfigOptions configOptions,
final ProtocolSpecBuilder builder,
final BftConfigOptions configOptions,
final boolean goQuorumMode,
final BftExtraDataCodec bftExtraDataCodec,
final Optional<BigInteger> blockReward) {
final BftExtraDataCodec bftExtraDataCodec) {
if (configOptions.getEpochLength() <= 0) {
throw new IllegalArgumentException("Epoch length in config must be greater than zero");
}
if (blockReward.isPresent() && blockReward.get().signum() < 0) {
if (configOptions.getBlockRewardWei().signum() < 0) {
throw new IllegalArgumentException("Bft Block reward in config cannot be negative");
}
builder
return builder
.blockHeaderValidatorBuilder(
feeMarket -> createBlockHeaderRuleset(configOptions, feeMarket))
.ommerHeaderValidatorBuilder(
@ -106,22 +97,9 @@ public abstract class BaseBftProtocolSchedule {
.blockImporterBuilder(MainnetBlockImporter::new)
.difficultyCalculator((time, parent, protocolContext) -> BigInteger.ONE)
.skipZeroBlockRewards(true)
.blockHeaderFunctions(BftBlockHeaderFunctions.forOnchainBlock(bftExtraDataCodec));
blockReward.ifPresent(bigInteger -> builder.blockReward(Wei.of(bigInteger)));
if (configOptions.getMiningBeneficiary().isPresent()) {
final Address miningBeneficiary;
try {
// Precalculate beneficiary to ensure string is valid now, rather than on lambda execution.
miningBeneficiary = Address.fromHexString(configOptions.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;
.blockHeaderFunctions(BftBlockHeaderFunctions.forOnchainBlock(bftExtraDataCodec))
.blockReward(Wei.of(configOptions.getBlockRewardWei()))
.miningBeneficiaryCalculator(
header -> configOptions.getMiningBeneficiary().orElseGet(header::getCoinbase));
}
}

@ -14,8 +14,11 @@
*/
package org.hyperledger.besu.consensus.common.bft;
import static com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.datatypes.Address;
import java.math.BigInteger;
import java.util.Map;
@ -34,7 +37,7 @@ public class MutableBftConfigOptions implements BftConfigOptions {
private int duplicateMessageLimit;
private int futureMessagesLimit;
private int futureMessageMaxDistance;
private Optional<String> miningBeneficiary;
private Optional<Address> miningBeneficiary;
private BigInteger blockRewardWei;
public MutableBftConfigOptions(final BftConfigOptions bftConfigOptions) {
@ -91,7 +94,7 @@ public class MutableBftConfigOptions implements BftConfigOptions {
}
@Override
public Optional<String> getMiningBeneficiary() {
public Optional<Address> getMiningBeneficiary() {
return miningBeneficiary;
}
@ -137,7 +140,8 @@ public class MutableBftConfigOptions implements BftConfigOptions {
this.futureMessageMaxDistance = futureMessageMaxDistance;
}
public void setMiningBeneficiary(final Optional<String> miningBeneficiary) {
public void setMiningBeneficiary(final Optional<Address> miningBeneficiary) {
checkNotNull(miningBeneficiary);
this.miningBeneficiary = miningBeneficiary;
}

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.consensus.common.bft.blockcreation;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.common.bft.BftHelpers;
@ -37,6 +39,7 @@ public class BftBlockCreator extends AbstractBlockCreator {
private final BftExtraDataCodec bftExtraDataCodec;
public BftBlockCreator(
final ForksSchedule<? extends BftConfigOptions> forksSchedule,
final Address localAddress,
final Supplier<Optional<Long>> targetGasLimitSupplier,
final ExtraDataCalculator extraDataCalculator,
@ -46,22 +49,27 @@ public class BftBlockCreator extends AbstractBlockCreator {
final Wei minTransactionGasPrice,
final Double minBlockOccupancyRatio,
final BlockHeader parentHeader,
final Address miningBeneficiary,
final BftExtraDataCodec bftExtraDataCodec) {
super(
localAddress,
miningBeneficiaryCalculator(localAddress, forksSchedule),
targetGasLimitSupplier,
extraDataCalculator,
pendingTransactions,
protocolContext,
protocolSchedule,
minTransactionGasPrice,
miningBeneficiary,
minBlockOccupancyRatio,
parentHeader);
this.bftExtraDataCodec = bftExtraDataCodec;
}
private static MiningBeneficiaryCalculator miningBeneficiaryCalculator(
final Address localAddress, final ForksSchedule<? extends BftConfigOptions> forksSchedule) {
return blockNum ->
forksSchedule.getFork(blockNum).getValue().getMiningBeneficiary().orElse(localAddress);
}
@Override
protected BlockHeader createFinalBlockHeader(final SealableBlockHeader sealableBlockHeader) {
final BlockHeaderBuilder builder =

@ -16,7 +16,9 @@ package org.hyperledger.besu.consensus.common.bft.blockcreation;
import static com.google.common.base.Preconditions.checkState;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.consensus.common.ConsensusHelpers;
import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.consensus.common.bft.BftContext;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
@ -42,14 +44,13 @@ import java.util.concurrent.atomic.AtomicLong;
import org.apache.tuweni.bytes.Bytes;
public class BftBlockCreatorFactory {
public class BftBlockCreatorFactory<T extends BftConfigOptions> {
protected final ForksSchedule<T> forksSchedule;
private final AbstractPendingTransactionsSorter pendingTransactions;
protected final ProtocolContext protocolContext;
protected final ProtocolSchedule protocolSchedule;
protected final BftExtraDataCodec bftExtraDataCodec;
private final Address localAddress;
final Address miningBeneficiary;
protected volatile Bytes vanityData;
private volatile Wei minTransactionGasPrice;
@ -60,24 +61,25 @@ public class BftBlockCreatorFactory {
final AbstractPendingTransactionsSorter pendingTransactions,
final ProtocolContext protocolContext,
final ProtocolSchedule protocolSchedule,
final ForksSchedule<T> forksSchedule,
final MiningParameters miningParams,
final Address localAddress,
final Address miningBeneficiary,
final BftExtraDataCodec bftExtraDataCodec) {
this.pendingTransactions = pendingTransactions;
this.protocolContext = protocolContext;
this.protocolSchedule = protocolSchedule;
this.forksSchedule = forksSchedule;
this.localAddress = localAddress;
this.minTransactionGasPrice = miningParams.getMinTransactionGasPrice();
this.minBlockOccupancyRatio = miningParams.getMinBlockOccupancyRatio();
this.vanityData = miningParams.getExtraData();
this.miningBeneficiary = miningBeneficiary;
this.bftExtraDataCodec = bftExtraDataCodec;
this.targetGasLimit = miningParams.getTargetGasLimit();
}
public BlockCreator create(final BlockHeader parentHeader, final int round) {
return new BftBlockCreator(
forksSchedule,
localAddress,
() -> targetGasLimit.map(AtomicLong::longValue),
ph -> createExtraData(round, ph),
@ -87,7 +89,6 @@ public class BftBlockCreatorFactory {
minTransactionGasPrice,
minBlockOccupancyRatio,
parentHeader,
miningBeneficiary,
bftExtraDataCodec);
}

@ -49,7 +49,7 @@ public class BftMiningCoordinator implements MiningCoordinator, BlockAddedObserv
private final BftEventHandler eventHandler;
private final BftProcessor bftProcessor;
private final BftBlockCreatorFactory blockCreatorFactory;
private final BftBlockCreatorFactory<?> blockCreatorFactory;
protected final Blockchain blockchain;
private final BftEventQueue eventQueue;
private final BftExecutors bftExecutors;
@ -61,7 +61,7 @@ public class BftMiningCoordinator implements MiningCoordinator, BlockAddedObserv
final BftExecutors bftExecutors,
final BftEventHandler eventHandler,
final BftProcessor bftProcessor,
final BftBlockCreatorFactory blockCreatorFactory,
final BftBlockCreatorFactory<?> blockCreatorFactory,
final Blockchain blockchain,
final BftEventQueue eventQueue) {
this.bftExecutors = bftExecutors;

@ -36,7 +36,7 @@ public class BftFinalState {
private final ProposerSelector proposerSelector;
private final RoundTimer roundTimer;
private final BlockTimer blockTimer;
private final BftBlockCreatorFactory blockCreatorFactory;
private final BftBlockCreatorFactory<?> blockCreatorFactory;
private final Clock clock;
private final ValidatorMulticaster validatorMulticaster;
@ -48,7 +48,7 @@ public class BftFinalState {
final ValidatorMulticaster validatorMulticaster,
final RoundTimer roundTimer,
final BlockTimer blockTimer,
final BftBlockCreatorFactory blockCreatorFactory,
final BftBlockCreatorFactory<?> blockCreatorFactory,
final Clock clock) {
this.validatorProvider = validatorProvider;
this.nodeKey = nodeKey;
@ -93,7 +93,7 @@ public class BftFinalState {
return blockTimer;
}
public BftBlockCreatorFactory getBlockCreatorFactory() {
public BftBlockCreatorFactory<?> getBlockCreatorFactory() {
return blockCreatorFactory;
}

@ -19,8 +19,10 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.JsonBftConfigOptions;
import org.hyperledger.besu.consensus.common.bft.MutableBftConfigOptions;
import org.hyperledger.besu.datatypes.Address;
import java.util.List;
import java.util.Optional;
import org.junit.Test;
@ -44,14 +46,31 @@ public class ForksScheduleTest {
new ForkSpec<>(0, JsonBftConfigOptions.DEFAULT);
final ForkSpec<BftConfigOptions> forkSpec1 = createForkSpec(1, 10);
final ForkSpec<BftConfigOptions> forkSpec2 = createForkSpec(2, 20);
final Optional<Address> miningBeneficiary3 =
Optional.of(Address.fromHexString("0xdee0519f7c7cb0f9843fa1e93b99255c89507a9c"));
final ForkSpec<BftConfigOptions> forkSpec3 =
createForkSpecWithMiningBeneficiary(3, miningBeneficiary3);
final ForkSpec<BftConfigOptions> forkSpec4 =
createForkSpecWithMiningBeneficiary(4, Optional.empty());
final ForksSchedule<BftConfigOptions> schedule =
new ForksSchedule<>(List.of(genesisForkSpec, forkSpec1, forkSpec2));
new ForksSchedule<>(List.of(genesisForkSpec, forkSpec1, forkSpec2, forkSpec3, forkSpec4));
assertThat(schedule.getFork(0)).isEqualTo(genesisForkSpec);
assertThat(schedule.getFork(1)).isEqualTo(forkSpec1);
assertThat(schedule.getFork(2)).isEqualTo(forkSpec2);
assertThat(schedule.getFork(3)).isEqualTo(forkSpec2);
assertThat(schedule.getFork(3)).isEqualTo(forkSpec3);
assertThat(schedule.getFork(3).getValue().getMiningBeneficiary()).isEqualTo(miningBeneficiary3);
assertThat(schedule.getFork(4)).isEqualTo(forkSpec4);
assertThat(schedule.getFork(4).getValue().getMiningBeneficiary()).isEmpty();
}
private ForkSpec<BftConfigOptions> createForkSpecWithMiningBeneficiary(
final long block, final Optional<Address> beneficiary) {
final MutableBftConfigOptions bftConfigOptions =
new MutableBftConfigOptions(JsonBftConfigOptions.DEFAULT);
bftConfigOptions.setMiningBeneficiary(beneficiary);
return new ForkSpec<>(block, bftConfigOptions);
}
private ForkSpec<BftConfigOptions> createForkSpec(

@ -49,11 +49,9 @@ public class BaseBftProtocolScheduleTest {
@Test
public void ensureBlockRewardAndMiningBeneficiaryInProtocolSpecMatchConfig() {
final BigInteger arbitraryBlockReward = BigInteger.valueOf(5);
final String miningBeneficiary = Address.fromHexString("0x1").toString();
final BftConfigOptions configOptions = mock(JsonBftConfigOptions.class);
when(configOptions.getMiningBeneficiary()).thenReturn(Optional.of(miningBeneficiary));
when(configOptions.getBlockRewardWei()).thenReturn(arbitraryBlockReward);
when(configOptions.getEpochLength()).thenReturn(3000L);
final Address miningBeneficiary = Address.fromHexString("0x1");
final BftConfigOptions configOptions =
createBftConfig(arbitraryBlockReward, Optional.of(miningBeneficiary));
when(genesisConfig.getTransitions()).thenReturn(TransitionsConfigOptions.DEFAULT);
when(genesisConfig.getBftConfigOptions()).thenReturn(configOptions);
@ -63,31 +61,14 @@ public class BaseBftProtocolScheduleTest {
final ProtocolSpec spec = schedule.getByBlockNumber(1);
assertThat(spec.getBlockReward()).isEqualTo(Wei.of(arbitraryBlockReward));
assertThat(spec.getMiningBeneficiaryCalculator().calculateBeneficiary(null))
.isEqualTo(Address.fromHexString(miningBeneficiary));
}
@Test
public void illegalMiningBeneficiaryStringThrowsException() {
final String miningBeneficiary = "notHexStringOfTwentyBytes";
final BftConfigOptions configOptions = mock(JsonBftConfigOptions.class);
when(configOptions.getMiningBeneficiary()).thenReturn(Optional.of(miningBeneficiary));
when(genesisConfig.getBftConfigOptions()).thenReturn(configOptions);
when(configOptions.getEpochLength()).thenReturn(3000L);
when(configOptions.getBlockRewardWei()).thenReturn(BigInteger.ZERO);
when(genesisConfig.getTransitions()).thenReturn(TransitionsConfigOptions.DEFAULT);
assertThatThrownBy(() -> createProtocolSchedule(List.of(new ForkSpec<>(0, configOptions))))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Mining beneficiary in config is not a valid ethereum address");
assertThat(spec.getMiningBeneficiaryCalculator().calculateBeneficiary(mock(BlockHeader.class)))
.isEqualTo(miningBeneficiary);
}
@Test
public void missingMiningBeneficiaryInConfigWillPayCoinbaseInHeader() {
final BigInteger arbitraryBlockReward = BigInteger.valueOf(3);
final BftConfigOptions configOptions = mock(JsonBftConfigOptions.class);
when(configOptions.getMiningBeneficiary()).thenReturn(Optional.empty());
when(configOptions.getBlockRewardWei()).thenReturn(arbitraryBlockReward);
when(configOptions.getEpochLength()).thenReturn(3000L);
final BftConfigOptions configOptions = createBftConfig(arbitraryBlockReward);
when(genesisConfig.getBftConfigOptions()).thenReturn(configOptions);
when(genesisConfig.getTransitions()).thenReturn(TransitionsConfigOptions.DEFAULT);
@ -104,13 +85,80 @@ public class BaseBftProtocolScheduleTest {
.isEqualTo(headerCoinbase);
}
@Test
public void ensureForksAreRespected_beginWithNonEmptyMiningBeneficiary() {
ensureForksAreRespected(false);
}
@Test
public void ensureForksAreRespected_beginWithEmptyMiningBeneficiary() {
ensureForksAreRespected(true);
}
private void ensureForksAreRespected(final boolean initialBeneficiaryIsEmpty) {
final Address headerCoinbase = Address.fromHexString("0x123");
final Address beneficiary1 = Address.fromHexString("0x01");
final Address beneficiary2 = Address.fromHexString("0x02");
final Address beneficiary3 = Address.fromHexString("0x03");
final BlockHeader header = mock(BlockHeader.class);
when(header.getCoinbase()).thenReturn(headerCoinbase);
// Alternate empty and non-empty mining beneficiary config
final BftConfigOptions initialConfig =
createBftConfig(
BigInteger.valueOf(3),
Optional.of(beneficiary1).filter(__ -> !initialBeneficiaryIsEmpty));
final BftConfigOptions fork1 =
createBftConfig(
BigInteger.valueOf(2),
Optional.of(beneficiary2).filter(__ -> initialBeneficiaryIsEmpty));
final BftConfigOptions fork2 =
createBftConfig(
BigInteger.valueOf(1),
Optional.of(beneficiary3).filter(__ -> !initialBeneficiaryIsEmpty));
when(genesisConfig.getBftConfigOptions()).thenReturn(initialConfig);
when(genesisConfig.getTransitions()).thenReturn(TransitionsConfigOptions.DEFAULT);
final ProtocolSchedule schedule =
createProtocolSchedule(
List.of(
new ForkSpec<>(0, initialConfig),
new ForkSpec<>(2, fork1),
new ForkSpec<>(5, fork2)));
// Check initial config
for (int i = 0; i < 2; i++) {
final ProtocolSpec spec = schedule.getByBlockNumber(i);
final Address expectedBeneficiary = initialBeneficiaryIsEmpty ? headerCoinbase : beneficiary1;
assertThat(spec.getBlockReward()).isEqualTo(Wei.of(BigInteger.valueOf(3)));
assertThat(spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header))
.isEqualTo(expectedBeneficiary);
}
// Check fork1
for (int i = 2; i < 5; i++) {
final ProtocolSpec spec = schedule.getByBlockNumber(i);
final Address expectedBeneficiary = initialBeneficiaryIsEmpty ? beneficiary2 : headerCoinbase;
assertThat(spec.getBlockReward()).isEqualTo(Wei.of(BigInteger.valueOf(2)));
assertThat(spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header))
.isEqualTo(expectedBeneficiary);
}
// Check fork2
for (int i = 5; i < 8; i++) {
final ProtocolSpec spec = schedule.getByBlockNumber(i);
final Address expectedBeneficiary = initialBeneficiaryIsEmpty ? headerCoinbase : beneficiary3;
assertThat(spec.getBlockReward()).isEqualTo(Wei.of(BigInteger.valueOf(1)));
assertThat(spec.getMiningBeneficiaryCalculator().calculateBeneficiary(header))
.isEqualTo(expectedBeneficiary);
}
}
@Test
public void negativeBlockRewardThrowsException() {
final BigInteger arbitraryBlockReward = BigInteger.valueOf(-3);
final BftConfigOptions configOptions = mock(JsonBftConfigOptions.class);
when(configOptions.getMiningBeneficiary()).thenReturn(Optional.empty());
when(configOptions.getBlockRewardWei()).thenReturn(arbitraryBlockReward);
when(configOptions.getEpochLength()).thenReturn(3000L);
final BftConfigOptions configOptions = createBftConfig(arbitraryBlockReward);
when(genesisConfig.getBftConfigOptions()).thenReturn(configOptions);
when(genesisConfig.getTransitions()).thenReturn(TransitionsConfigOptions.DEFAULT);
@ -122,10 +170,8 @@ public class BaseBftProtocolScheduleTest {
@Test
public void zeroEpochLengthThrowsException() {
final BigInteger arbitraryBlockReward = BigInteger.valueOf(3);
final BftConfigOptions configOptions = mock(JsonBftConfigOptions.class);
when(configOptions.getMiningBeneficiary()).thenReturn(Optional.empty());
final BftConfigOptions configOptions = createBftConfig(arbitraryBlockReward);
when(configOptions.getEpochLength()).thenReturn(0L);
when(configOptions.getBlockRewardWei()).thenReturn(arbitraryBlockReward);
when(genesisConfig.getBftConfigOptions()).thenReturn(configOptions);
when(genesisConfig.getTransitions()).thenReturn(TransitionsConfigOptions.DEFAULT);
@ -137,10 +183,8 @@ public class BaseBftProtocolScheduleTest {
@Test
public void negativeEpochLengthThrowsException() {
final BigInteger arbitraryBlockReward = BigInteger.valueOf(3);
final BftConfigOptions configOptions = mock(JsonBftConfigOptions.class);
when(configOptions.getMiningBeneficiary()).thenReturn(Optional.empty());
final BftConfigOptions configOptions = createBftConfig(arbitraryBlockReward);
when(configOptions.getEpochLength()).thenReturn(-3000L);
when(configOptions.getBlockRewardWei()).thenReturn(arbitraryBlockReward);
when(genesisConfig.getBftConfigOptions()).thenReturn(configOptions);
when(genesisConfig.getTransitions()).thenReturn(TransitionsConfigOptions.DEFAULT);
@ -152,11 +196,9 @@ public class BaseBftProtocolScheduleTest {
@Test
public void blockRewardSpecifiedInTransitionCreatesNewMilestone() {
final BigInteger arbitraryBlockReward = BigInteger.valueOf(5);
final String miningBeneficiary = Address.fromHexString("0x1").toString();
final BftConfigOptions configOptions = mock(JsonBftConfigOptions.class);
when(configOptions.getMiningBeneficiary()).thenReturn(Optional.of(miningBeneficiary));
when(configOptions.getBlockRewardWei()).thenReturn(arbitraryBlockReward);
when(configOptions.getEpochLength()).thenReturn(3000L);
final Address miningBeneficiary = Address.fromHexString("0x1");
final BftConfigOptions configOptions =
createBftConfig(arbitraryBlockReward, Optional.of(miningBeneficiary));
when(genesisConfig.getBftConfigOptions()).thenReturn(configOptions);
when(genesisConfig.isIbft2()).thenReturn(true);
@ -196,4 +238,18 @@ public class BaseBftProtocolScheduleTest {
bftExtraDataCodec,
EvmConfiguration.DEFAULT);
}
private BftConfigOptions createBftConfig(final BigInteger blockReward) {
return createBftConfig(blockReward, Optional.empty());
}
private BftConfigOptions createBftConfig(
final BigInteger blockReward, final Optional<Address> miningBeneficiary) {
final BftConfigOptions bftConfig = mock(JsonBftConfigOptions.class);
when(bftConfig.getMiningBeneficiary()).thenReturn(miningBeneficiary);
when(bftConfig.getBlockRewardWei()).thenReturn(blockReward);
when(bftConfig.getEpochLength()).thenReturn(3000L);
return bftConfig;
}
}

@ -0,0 +1,182 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.consensus.common.bft;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.BftFork;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.consensus.common.ForkSpec;
import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.datatypes.Address;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test;
public abstract class BaseForksSchedulesFactoryTest<
C extends BftConfigOptions, M extends MutableBftConfigOptions> {
@Test
public void createsScheduleForJustGenesisConfig() {
final C configOptions = createBftOptions();
final ForkSpec<C> expectedForkSpec = new ForkSpec<>(0, createBftOptions());
final GenesisConfigOptions genesisConfigOptions = createGenesisConfig(configOptions);
final ForksSchedule<C> forksSchedule = createForkSchedule(genesisConfigOptions);
assertThat(forksSchedule.getFork(0)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
assertThat(forksSchedule.getFork(1)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
assertThat(forksSchedule.getFork(2)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
}
@Test
public void createsScheduleThatChangesMiningBeneficiary_beneficiaryInitiallyEmpty() {
final Address beneficiaryAddress =
Address.fromHexString("0x1111111111111111111111111111111111111111");
final C qbftConfigOptions = createBftOptions();
final ObjectNode forkWithBeneficiary =
JsonUtil.objectNodeFromMap(
Map.of(
BftFork.FORK_BLOCK_KEY,
1,
BftFork.MINING_BENEFICIARY_KEY,
beneficiaryAddress.toHexString()));
final ObjectNode forkWithNoBeneficiary =
JsonUtil.objectNodeFromMap(
Map.of(BftFork.FORK_BLOCK_KEY, 2, BftFork.MINING_BENEFICIARY_KEY, ""));
final GenesisConfigOptions genesisConfigOptions =
createGenesisConfig(qbftConfigOptions, forkWithBeneficiary, forkWithNoBeneficiary);
final ForksSchedule<C> forksSchedule = createForkSchedule(genesisConfigOptions);
assertThat(forksSchedule.getFork(0).getValue().getMiningBeneficiary()).isEmpty();
assertThat(forksSchedule.getFork(1).getValue().getMiningBeneficiary())
.contains(beneficiaryAddress);
assertThat(forksSchedule.getFork(2).getValue().getMiningBeneficiary()).isEmpty();
}
@Test
public void createsScheduleThatChangesMiningBeneficiary_beneficiaryInitiallyNonEmpty() {
final Address beneficiaryAddress =
Address.fromHexString("0x1111111111111111111111111111111111111111");
final Address beneficiaryAddress2 = Address.fromHexString("0x02");
final C qbftConfigOptions =
createBftOptions(o -> o.setMiningBeneficiary(Optional.of(beneficiaryAddress)));
final ObjectNode forkWithBeneficiary =
JsonUtil.objectNodeFromMap(
Map.of(BftFork.FORK_BLOCK_KEY, 1, BftFork.MINING_BENEFICIARY_KEY, ""));
final ObjectNode forkWithNoBeneficiary =
JsonUtil.objectNodeFromMap(
Map.of(
BftFork.FORK_BLOCK_KEY,
2,
BftFork.MINING_BENEFICIARY_KEY,
beneficiaryAddress2.toUnprefixedHexString()));
final GenesisConfigOptions genesisConfigOptions =
createGenesisConfig(qbftConfigOptions, forkWithBeneficiary, forkWithNoBeneficiary);
final ForksSchedule<C> forksSchedule = createForkSchedule(genesisConfigOptions);
assertThat(forksSchedule.getFork(0).getValue().getMiningBeneficiary())
.contains(beneficiaryAddress);
assertThat(forksSchedule.getFork(1).getValue().getMiningBeneficiary()).isEmpty();
assertThat(forksSchedule.getFork(2).getValue().getMiningBeneficiary())
.contains(beneficiaryAddress2);
}
@Test
public void
createsScheduleThatChangesMiningBeneficiary_shouldNotModifyBeneficiaryUnlessExplicitlyConfigured() {
final Address initialBeneficiaryAddress =
Address.fromHexString("0x1111111111111111111111111111111111111111");
final Address beneficiaryAddress2 = Address.fromHexString("0x02");
final C qbftConfigOptions =
createBftOptions(o -> o.setMiningBeneficiary(Optional.of(initialBeneficiaryAddress)));
ObjectNode[] forks = {
// No change to beneficiary
JsonUtil.objectNodeFromMap(
Map.of(BftFork.FORK_BLOCK_KEY, 1, BftFork.BLOCK_PERIOD_SECONDS_KEY, 2)),
// Clear beneficiary
JsonUtil.objectNodeFromMap(
Map.of(BftFork.FORK_BLOCK_KEY, 3, BftFork.MINING_BENEFICIARY_KEY, "")),
// No change to beneficiary
JsonUtil.objectNodeFromMap(
Map.of(BftFork.FORK_BLOCK_KEY, 5, BftFork.BLOCK_PERIOD_SECONDS_KEY, 4)),
// Set beneficiary
JsonUtil.objectNodeFromMap(
Map.of(
BftFork.FORK_BLOCK_KEY,
7,
BftFork.MINING_BENEFICIARY_KEY,
beneficiaryAddress2.toUnprefixedHexString()))
};
final GenesisConfigOptions genesisConfigOptions = createGenesisConfig(qbftConfigOptions, forks);
final ForksSchedule<C> forksSchedule = createForkSchedule(genesisConfigOptions);
assertThat(forksSchedule.getFork(0).getValue().getMiningBeneficiary())
.contains(initialBeneficiaryAddress);
assertThat(forksSchedule.getFork(1).getValue().getMiningBeneficiary())
.contains(initialBeneficiaryAddress);
assertThat(forksSchedule.getFork(2).getValue().getMiningBeneficiary())
.contains(initialBeneficiaryAddress);
assertThat(forksSchedule.getFork(3).getValue().getMiningBeneficiary()).isEmpty();
assertThat(forksSchedule.getFork(4).getValue().getMiningBeneficiary()).isEmpty();
assertThat(forksSchedule.getFork(5).getValue().getMiningBeneficiary()).isEmpty();
assertThat(forksSchedule.getFork(6).getValue().getMiningBeneficiary()).isEmpty();
assertThat(forksSchedule.getFork(7).getValue().getMiningBeneficiary())
.contains(beneficiaryAddress2);
assertThat(forksSchedule.getFork(8).getValue().getMiningBeneficiary())
.contains(beneficiaryAddress2);
}
@Test
public void createsScheduleWithInvalidMiningBeneficiary_shouldThrow() {
final C qbftConfigOptions = createBftOptions();
final ObjectNode invalidFork =
JsonUtil.objectNodeFromMap(
Map.of(BftFork.FORK_BLOCK_KEY, 1, BftFork.MINING_BENEFICIARY_KEY, "bla"));
final GenesisConfigOptions genesisConfigOptions =
createGenesisConfig(qbftConfigOptions, invalidFork);
assertThatThrownBy(() -> createForkSchedule(genesisConfigOptions))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(
"Mining beneficiary in transition config is not a valid ethereum address");
}
protected abstract C createBftOptions(final Consumer<M> optionModifier);
protected C createBftOptions() {
return createBftOptions(__ -> {});
}
protected abstract GenesisConfigOptions createGenesisConfig(
final C configOptions, final ObjectNode... forks);
protected abstract ForksSchedule<C> createForkSchedule(
final GenesisConfigOptions genesisConfigOptions);
}

@ -47,7 +47,7 @@ public class BftMiningCoordinatorTest {
@Mock private BftEventHandler controller;
@Mock private BftExecutors bftExecutors;
@Mock private BftProcessor bftProcessor;
@Mock private BftBlockCreatorFactory bftBlockCreatorFactory;
@Mock private BftBlockCreatorFactory<?> bftBlockCreatorFactory;
@Mock private Blockchain blockChain;
@Mock private Block block;
@Mock private BlockBody blockBody;

@ -341,14 +341,14 @@ public class TestContextBuilder {
TransactionPoolConfiguration.DEFAULT_PRICE_BUMP);
final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
final BftBlockCreatorFactory blockCreatorFactory =
new BftBlockCreatorFactory(
final BftBlockCreatorFactory<?> blockCreatorFactory =
new BftBlockCreatorFactory<>(
pendingTransactions, // changed from IbftBesuController
protocolContext,
protocolSchedule,
forksSchedule,
miningParams,
localAddress,
localAddress,
IBFT_EXTRA_DATA_ENCODER);
final ProposerSelector proposerSelector =

@ -39,6 +39,11 @@ public class IbftForksSchedulesFactory {
fork.getBlockPeriodSeconds().ifPresent(bftConfigOptions::setBlockPeriodSeconds);
fork.getBlockRewardWei().ifPresent(bftConfigOptions::setBlockRewardWei);
if (fork.isMiningBeneficiaryConfigured()) {
// Only override if mining beneficiary is explicitly configured
bftConfigOptions.setMiningBeneficiary(fork.getMiningBeneficiary());
}
return bftConfigOptions;
}
}

@ -31,7 +31,7 @@ import org.hyperledger.besu.util.Subscribers;
public class IbftRoundFactory {
private final BftFinalState finalState;
private final BftBlockCreatorFactory blockCreatorFactory;
private final BftBlockCreatorFactory<?> blockCreatorFactory;
private final ProtocolContext protocolContext;
private final ProtocolSchedule protocolSchedule;
private final Subscribers<MinedBlockObserver> minedBlockObservers;

@ -19,42 +19,30 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.BftFork;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.JsonQbftConfigOptions;
import org.hyperledger.besu.config.JsonBftConfigOptions;
import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.config.StubGenesisConfigOptions;
import org.hyperledger.besu.config.TransitionsConfigOptions;
import org.hyperledger.besu.consensus.common.ForkSpec;
import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.consensus.common.bft.BaseForksSchedulesFactoryTest;
import org.hyperledger.besu.consensus.common.bft.MutableBftConfigOptions;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test;
public class IbftForksSchedulesFactoryTest {
@Test
public void createsScheduleForJustGenesisConfig() {
final MutableBftConfigOptions bftConfigOptions =
new MutableBftConfigOptions(JsonQbftConfigOptions.DEFAULT);
final ForkSpec<BftConfigOptions> expectedForkSpec = new ForkSpec<>(0, bftConfigOptions);
final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions();
genesisConfigOptions.bftConfigOptions(bftConfigOptions);
final ForksSchedule<BftConfigOptions> forksSchedule =
IbftForksSchedulesFactory.create(genesisConfigOptions);
assertThat(forksSchedule.getFork(0)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
assertThat(forksSchedule.getFork(1)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
assertThat(forksSchedule.getFork(2)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
}
public class IbftForksSchedulesFactoryTest
extends BaseForksSchedulesFactoryTest<BftConfigOptions, MutableBftConfigOptions> {
@Test
public void createsScheduleWithForkThatOverridesGenesisValues() {
final MutableBftConfigOptions configOptions =
new MutableBftConfigOptions(JsonQbftConfigOptions.DEFAULT);
new MutableBftConfigOptions(JsonBftConfigOptions.DEFAULT);
final ObjectNode fork =
JsonUtil.objectNodeFromMap(
@ -78,19 +66,36 @@ public class IbftForksSchedulesFactoryTest {
final BftConfigOptions expectedForkConfig =
new MutableBftConfigOptions(
new JsonQbftConfigOptions(JsonUtil.objectNodeFromMap(forkOptions)));
new JsonBftConfigOptions(JsonUtil.objectNodeFromMap(forkOptions)));
final ForkSpec<BftConfigOptions> expectedFork = new ForkSpec<>(1, expectedForkConfig);
assertThat(forksSchedule.getFork(1)).usingRecursiveComparison().isEqualTo(expectedFork);
assertThat(forksSchedule.getFork(2)).usingRecursiveComparison().isEqualTo(expectedFork);
}
private GenesisConfigOptions createGenesisConfig(
final BftConfigOptions configOptions, final ObjectNode fork) {
@Override
protected GenesisConfigOptions createGenesisConfig(
final BftConfigOptions configOptions, final ObjectNode... fork) {
final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions();
genesisConfigOptions.bftConfigOptions(configOptions);
genesisConfigOptions.transitions(
new TransitionsConfigOptions(JsonUtil.objectNodeFromMap(Map.of("ibft2", List.of(fork)))));
new TransitionsConfigOptions(
JsonUtil.objectNodeFromMap(Map.of("ibft2", Arrays.asList(fork)))));
return genesisConfigOptions;
}
@Override
protected ForksSchedule<BftConfigOptions> createForkSchedule(
final GenesisConfigOptions genesisConfigOptions) {
return IbftForksSchedulesFactory.create(genesisConfigOptions);
}
@Override
protected BftConfigOptions createBftOptions(
final Consumer<MutableBftConfigOptions> optionModifier) {
final MutableBftConfigOptions options =
new MutableBftConfigOptions(JsonBftConfigOptions.DEFAULT);
optionModifier.accept(options);
return options;
}
}

@ -126,6 +126,7 @@ public class BftBlockCreatorTest {
final BftBlockCreator blockCreator =
new BftBlockCreator(
forksSchedule,
initialValidatorList.get(0),
() -> Optional.of(10_000_000L),
parent ->
@ -142,7 +143,6 @@ public class BftBlockCreatorTest {
Wei.ZERO,
0.8,
parentHeader,
initialValidatorList.get(0),
bftExtraDataEncoder);
final int secondsBetweenBlocks = 1;

@ -58,13 +58,13 @@ public class IbftBlockCreator extends AbstractBlockCreator {
final BlockHeader parentHeader) {
super(
coinbase,
__ -> Util.publicKeyToAddress(nodeKeys.getPublicKey()),
targetGasLimitSupplier,
extraDataCalculator,
pendingTransactions,
protocolContext,
protocolSchedule,
minTransactionGasPrice,
Util.publicKeyToAddress(nodeKeys.getPublicKey()),
minBlockOccupancyRatio,
parentHeader);
this.nodeKeys = nodeKeys;

@ -49,13 +49,13 @@ public class MergeBlockCreator extends AbstractBlockCreator {
final BlockHeader parentHeader) {
super(
miningBeneficiary,
__ -> miningBeneficiary,
targetGasLimitSupplier,
extraDataCalculator,
pendingTransactions,
protocolContext,
protocolSchedule,
minTransactionGasPrice,
miningBeneficiary,
minBlockOccupancyRatio,
parentHeader);
}

@ -446,16 +446,15 @@ public class TestContextBuilder {
TransactionPoolConfiguration.DEFAULT_PRICE_BUMP);
final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
final BftBlockCreatorFactory blockCreatorFactory =
final BftBlockCreatorFactory<?> blockCreatorFactory =
new QbftBlockCreatorFactory(
pendingTransactions, // changed from QbftBesuController
protocolContext,
protocolSchedule,
forksSchedule,
miningParams,
localAddress,
localAddress,
BFT_EXTRA_DATA_ENCODER,
forksSchedule);
BFT_EXTRA_DATA_ENCODER);
final ProposerSelector proposerSelector =
new ProposerSelector(blockChain, blockInterface, true, validatorProvider);

@ -42,6 +42,11 @@ public class QbftForksSchedulesFactory {
fork.getBlockPeriodSeconds().ifPresent(bftConfigOptions::setBlockPeriodSeconds);
fork.getBlockRewardWei().ifPresent(bftConfigOptions::setBlockRewardWei);
if (fork.isMiningBeneficiaryConfigured()) {
// Only override if mining beneficiary is explicitly configured
bftConfigOptions.setMiningBeneficiary(fork.getMiningBeneficiary());
}
if (fork.getValidatorSelectionMode().isPresent()) {
final VALIDATOR_SELECTION_MODE mode = fork.getValidatorSelectionMode().get();
if (mode == VALIDATOR_SELECTION_MODE.BLOCKHEADER) {

@ -35,27 +35,23 @@ import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
/** Supports contract based voters and validators in extra data */
public class QbftBlockCreatorFactory extends BftBlockCreatorFactory {
private final ForksSchedule<QbftConfigOptions> forksSchedule;
public class QbftBlockCreatorFactory extends BftBlockCreatorFactory<QbftConfigOptions> {
public QbftBlockCreatorFactory(
final AbstractPendingTransactionsSorter pendingTransactions,
final ProtocolContext protocolContext,
final ProtocolSchedule protocolSchedule,
final ForksSchedule<QbftConfigOptions> forksSchedule,
final MiningParameters miningParams,
final Address localAddress,
final Address miningBeneficiary,
final BftExtraDataCodec bftExtraDataCodec,
final ForksSchedule<QbftConfigOptions> forksSchedule) {
final BftExtraDataCodec bftExtraDataCodec) {
super(
pendingTransactions,
protocolContext,
protocolSchedule,
forksSchedule,
miningParams,
localAddress,
miningBeneficiary,
bftExtraDataCodec);
this.forksSchedule = forksSchedule;
}
@Override

@ -31,7 +31,7 @@ import org.hyperledger.besu.util.Subscribers;
public class QbftRoundFactory {
private final BftFinalState finalState;
private final BftBlockCreatorFactory blockCreatorFactory;
private final BftBlockCreatorFactory<?> blockCreatorFactory;
private final ProtocolContext protocolContext;
private final ProtocolSchedule protocolSchedule;
private final Subscribers<MinedBlockObserver> minedBlockObservers;

@ -62,11 +62,10 @@ public class QbftBlockCreatorFactoryTest {
mock(AbstractPendingTransactionsSorter.class),
mock(ProtocolContext.class),
mock(ProtocolSchedule.class),
forksSchedule,
miningParams,
mock(Address.class),
mock(Address.class),
extraDataCodec,
forksSchedule);
extraDataCodec);
}
@Test

@ -28,37 +28,26 @@ import org.hyperledger.besu.config.StubGenesisConfigOptions;
import org.hyperledger.besu.config.TransitionsConfigOptions;
import org.hyperledger.besu.consensus.common.ForkSpec;
import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.consensus.common.bft.BaseForksSchedulesFactoryTest;
import org.hyperledger.besu.consensus.qbft.MutableQbftConfigOptions;
import org.hyperledger.besu.consensus.qbft.QbftForksSchedulesFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.core.AddressHelpers;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.junit.Test;
public class QbftForksSchedulesFactoryTest {
@Test
public void createsScheduleForJustGenesisConfig() {
final MutableQbftConfigOptions qbftConfigOptions =
new MutableQbftConfigOptions(JsonQbftConfigOptions.DEFAULT);
final ForkSpec<QbftConfigOptions> expectedForkSpec = new ForkSpec<>(0, qbftConfigOptions);
final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions();
genesisConfigOptions.qbftConfigOptions(qbftConfigOptions);
final ForksSchedule<QbftConfigOptions> forksSchedule =
QbftForksSchedulesFactory.create(genesisConfigOptions);
assertThat(forksSchedule.getFork(0)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
assertThat(forksSchedule.getFork(1)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
assertThat(forksSchedule.getFork(2)).usingRecursiveComparison().isEqualTo(expectedForkSpec);
}
public class QbftForksSchedulesFactoryTest
extends BaseForksSchedulesFactoryTest<QbftConfigOptions, MutableQbftConfigOptions> {
@Test
public void createsScheduleWithForkThatOverridesGenesisValues() {
@ -189,12 +178,29 @@ public class QbftForksSchedulesFactoryTest {
"QBFT transition to blockheader mode requires a validators list containing at least one validator");
}
private GenesisConfigOptions createGenesisConfig(
final QbftConfigOptions configOptions, final ObjectNode fork) {
@Override
protected GenesisConfigOptions createGenesisConfig(
final QbftConfigOptions configOptions, final ObjectNode... forks) {
final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions();
genesisConfigOptions.qbftConfigOptions(configOptions);
genesisConfigOptions.transitions(
new TransitionsConfigOptions(JsonUtil.objectNodeFromMap(Map.of("qbft", List.of(fork)))));
new TransitionsConfigOptions(
JsonUtil.objectNodeFromMap(Map.of("qbft", Arrays.asList(forks)))));
return genesisConfigOptions;
}
@Override
protected ForksSchedule<QbftConfigOptions> createForkSchedule(
final GenesisConfigOptions genesisConfigOptions) {
return QbftForksSchedulesFactory.create(genesisConfigOptions);
}
@Override
protected QbftConfigOptions createBftOptions(
final Consumer<MutableQbftConfigOptions> optionModifier) {
final MutableQbftConfigOptions options =
new MutableQbftConfigOptions(JsonQbftConfigOptions.DEFAULT);
optionModifier.accept(options);
return options;
}
}

@ -67,6 +67,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
private static final Logger LOG = LoggerFactory.getLogger(AbstractBlockCreator.class);
protected final Address coinbase;
private final MiningBeneficiaryCalculator miningBeneficiaryCalculator;
protected final Supplier<Optional<Long>> targetGasLimitSupplier;
private final ExtraDataCalculator extraDataCalculator;
@ -76,7 +77,6 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
protected final BlockHeaderFunctions blockHeaderFunctions;
private final Wei minTransactionGasPrice;
private final Double minBlockOccupancyRatio;
private final Address miningBeneficiary;
protected final BlockHeader parentHeader;
protected final ProtocolSpec protocolSpec;
@ -84,16 +84,17 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
protected AbstractBlockCreator(
final Address coinbase,
final MiningBeneficiaryCalculator miningBeneficiaryCalculator,
final Supplier<Optional<Long>> targetGasLimitSupplier,
final ExtraDataCalculator extraDataCalculator,
final AbstractPendingTransactionsSorter pendingTransactions,
final ProtocolContext protocolContext,
final ProtocolSchedule protocolSchedule,
final Wei minTransactionGasPrice,
final Address miningBeneficiary,
final Double minBlockOccupancyRatio,
final BlockHeader parentHeader) {
this.coinbase = coinbase;
this.miningBeneficiaryCalculator = miningBeneficiaryCalculator;
this.targetGasLimitSupplier = targetGasLimitSupplier;
this.extraDataCalculator = extraDataCalculator;
this.pendingTransactions = pendingTransactions;
@ -101,7 +102,6 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
this.protocolSchedule = protocolSchedule;
this.minTransactionGasPrice = minTransactionGasPrice;
this.minBlockOccupancyRatio = minBlockOccupancyRatio;
this.miningBeneficiary = miningBeneficiary;
this.parentHeader = parentHeader;
this.protocolSpec = protocolSchedule.getByBlockNumber(parentHeader.getNumber() + 1);
blockHeaderFunctions = ScheduleBasedBlockHeaderFunctions.create(protocolSchedule);
@ -152,6 +152,8 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
try {
final ProcessableBlockHeader processableBlockHeader =
createPendingBlockHeader(timestamp, maybePrevRandao);
final Address miningBeneficiary =
miningBeneficiaryCalculator.getMiningBeneficiary(processableBlockHeader.getNumber());
throwIfStopped();
@ -164,7 +166,8 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
throwIfStopped();
final BlockTransactionSelector.TransactionSelectionResults transactionResults =
selectTransactions(processableBlockHeader, disposableWorldState, maybeTransactions);
selectTransactions(
processableBlockHeader, disposableWorldState, maybeTransactions, miningBeneficiary);
throwIfStopped();
@ -176,6 +179,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
disposableWorldState,
processableBlockHeader,
ommers,
miningBeneficiary,
newProtocolSpec.getBlockReward(),
newProtocolSpec.isSkipZeroBlockRewards())) {
LOG.trace("Failed to apply mining reward, exiting.");
@ -216,7 +220,8 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
private BlockTransactionSelector.TransactionSelectionResults selectTransactions(
final ProcessableBlockHeader processableBlockHeader,
final MutableWorldState disposableWorldState,
final Optional<List<Transaction>> transactions)
final Optional<List<Transaction>> transactions,
final Address miningBeneficiary)
throws RuntimeException {
final MainnetTransactionProcessor transactionProcessor = protocolSpec.getTransactionProcessor();
@ -329,6 +334,7 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
final MutableWorldState worldState,
final ProcessableBlockHeader header,
final List<BlockHeader> ommers,
final Address miningBeneficiary,
final Wei blockReward,
final boolean skipZeroBlockRewards) {
@ -371,4 +377,9 @@ public abstract class AbstractBlockCreator implements AsyncBlockCreator {
protected abstract BlockHeader createFinalBlockHeader(
final SealableBlockHeader sealableBlockHeader);
@FunctionalInterface
protected interface MiningBeneficiaryCalculator {
Address getMiningBeneficiary(long blockNumber);
}
}

@ -52,13 +52,13 @@ public class PoWBlockCreator extends AbstractBlockCreator {
final BlockHeader parentHeader) {
super(
coinbase,
__ -> coinbase,
targetGasLimitSupplier,
extraDataCalculator,
pendingTransactions,
protocolContext,
protocolSchedule,
minTransactionGasPrice,
coinbase,
minBlockOccupancyRatio,
parentHeader);

@ -249,7 +249,7 @@ public class PoWBlockCreatorTest {
.buildProcessableBlockHeader();
blockCreator.rewardBeneficiary(
mutableWorldState, header, Collections.emptyList(), Wei.ZERO, false);
mutableWorldState, header, Collections.emptyList(), BLOCK_1_COINBASE, Wei.ZERO, false);
assertThat(mutableWorldState.get(BLOCK_1_COINBASE)).isNotNull();
assertThat(mutableWorldState.get(BLOCK_1_COINBASE).getBalance()).isEqualTo(Wei.ZERO);
@ -322,7 +322,7 @@ public class PoWBlockCreatorTest {
.buildProcessableBlockHeader();
blockCreator.rewardBeneficiary(
mutableWorldState, header, Collections.emptyList(), Wei.ZERO, true);
mutableWorldState, header, Collections.emptyList(), BLOCK_1_COINBASE, Wei.ZERO, true);
assertThat(mutableWorldState.get(BLOCK_1_COINBASE)).isNull();
}

Loading…
Cancel
Save