Allow QBFT to switch from the existing block header validator selection mechanism to using a smart contract (#2655)

Signed-off-by: Jason Frame <jasonwframe@gmail.com>
pull/2746/head
Jason Frame 3 years ago committed by GitHub
parent 62c4115591
commit a2fd2147cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 70
      besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java
  3. 12
      config/src/main/java/org/hyperledger/besu/config/BftFork.java
  4. 7
      config/src/main/java/org/hyperledger/besu/config/QbftConfigOptions.java
  5. 51
      config/src/main/java/org/hyperledger/besu/config/QbftFork.java
  6. 16
      config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java
  7. 14
      config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java
  8. 2
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java
  9. 7
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/BlockValidatorProvider.java
  10. 1
      consensus/qbft/build.gradle
  11. 78
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java
  12. 36
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestTransitions.java
  13. 84
      consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java
  14. 53
      consensus/qbft/src/integration-test/resources/genesis_migrating_validator_contract.json
  15. 12
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java
  16. 171
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolSchedule.java
  17. 16
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java
  18. 116
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProvider.java
  19. 19
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java
  20. 83
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorConfig.java
  21. 44
      consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorForksSchedule.java
  22. 22
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java
  23. 20
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java
  24. 181
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java
  25. 19
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java
  26. 50
      consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorForksScheduleTest.java

@ -19,6 +19,7 @@
- Implement EIP-3607 Reject transactions from senders with deployed code. [#2676](https://github.com/hyperledger/besu/pull/2676)
- Ignore all unknown fields when supplied to eth_estimateGas or eth_call. [\#2690](https://github.com/hyperledger/besu/pull/2690)
- \[EXPERIMENTAL\] Added support for QBFT with PKI-backed Block Creation. [#2647](https://github.com/hyperledger/besu/issues/2647)
- Added support for QBFT to use retrieve validators from a smart contract [#2574](https://github.com/hyperledger/besu/pull/2574)
### Bug Fixes
- Consider effective price and effective priority fee in transaction replacement rules [\#2529](https://github.com/hyperledger/besu/issues/2529)

@ -17,6 +17,7 @@ package org.hyperledger.besu.controller;
import org.hyperledger.besu.config.BftFork;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.QbftConfigOptions;
import org.hyperledger.besu.config.QbftFork;
import org.hyperledger.besu.consensus.common.BftValidatorOverrides;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.bft.BftContext;
@ -24,7 +25,6 @@ import org.hyperledger.besu.consensus.common.bft.BftEventQueue;
import org.hyperledger.besu.consensus.common.bft.BftExecutors;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.common.bft.BftProcessor;
import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule;
import org.hyperledger.besu.consensus.common.bft.BlockTimer;
import org.hyperledger.besu.consensus.common.bft.EthSynchronizerUpdater;
import org.hyperledger.besu.consensus.common.bft.EventMultiplexer;
@ -45,6 +45,7 @@ import org.hyperledger.besu.consensus.qbft.QbftBlockHeaderValidationRulesetFacto
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.QbftGossip;
import org.hyperledger.besu.consensus.qbft.QbftProtocolSchedule;
import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory;
import org.hyperledger.besu.consensus.qbft.jsonrpc.QbftJsonRpcMethods;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
@ -54,8 +55,11 @@ import org.hyperledger.besu.consensus.qbft.statemachine.QbftBlockHeightManagerFa
import org.hyperledger.besu.consensus.qbft.statemachine.QbftController;
import org.hyperledger.besu.consensus.qbft.statemachine.QbftRoundFactory;
import org.hyperledger.besu.consensus.qbft.validation.MessageValidatorFactory;
import org.hyperledger.besu.consensus.qbft.validator.ForkingValidatorProvider;
import org.hyperledger.besu.consensus.qbft.validator.TransactionValidatorProvider;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorContractController;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorSelectorConfig;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorSelectorForksSchedule;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.methods.JsonRpcMethods;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
@ -79,6 +83,7 @@ import org.hyperledger.besu.util.Subscribers;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -91,6 +96,7 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
private static final Logger LOG = LogManager.getLogger();
private BftEventQueue bftEventQueue;
private QbftConfigOptions qbftConfig;
private ValidatorSelectorForksSchedule qbftForksSchedule;
private ValidatorPeers peers;
@Override
@ -109,6 +115,7 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
protected void prepForBuild() {
qbftConfig = genesisConfig.getConfigOptions(genesisConfigOverrides).getQbftConfigOptions();
bftEventQueue = new BftEventQueue(qbftConfig.getMessageQueueLimit());
qbftForksSchedule = createQbftForksSchedule(genesisConfig.getConfigOptions());
}
@Override
@ -143,8 +150,6 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
final BftExecutors bftExecutors = BftExecutors.create(metricsSystem);
final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
final boolean createExtraDataWithRoundInformationOnly =
qbftConfig.getValidatorContractAddress().isPresent();
final BftBlockCreatorFactory blockCreatorFactory =
new QbftBlockCreatorFactory(
transactionPool.getPendingTransactions(),
@ -154,7 +159,7 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
localAddress,
qbftConfig.getMiningBeneficiary().map(Address::fromHexString).orElse(localAddress),
bftExtraDataCodec().get(),
createExtraDataWithRoundInformationOnly);
qbftForksSchedule);
final ValidatorProvider validatorProvider =
protocolContext.getConsensusState(BftContext.class).getValidatorProvider();
@ -251,10 +256,9 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
@Override
protected ProtocolSchedule createProtocolSchedule() {
final QbftBlockHeaderValidationRulesetFactory qbftBlockHeaderValidationRulesetFactory =
new QbftBlockHeaderValidationRulesetFactory(
qbftConfig.getValidatorContractAddress().isPresent());
new QbftBlockHeaderValidationRulesetFactory();
return BftProtocolSchedule.create(
return QbftProtocolSchedule.create(
genesisConfig.getConfigOptions(genesisConfigOverrides),
privacyParameters,
isRevertReasonEnabled,
@ -276,34 +280,38 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final ProtocolSchedule protocolSchedule) {
final GenesisConfigOptions configOptions =
genesisConfig.getConfigOptions(genesisConfigOverrides);
final QbftConfigOptions qbftConfig = configOptions.getQbftConfigOptions();
final EpochManager epochManager = new EpochManager(qbftConfig.getEpochLength());
final BftValidatorOverrides validatorOverrides =
convertBftForks(configOptions.getTransitions().getQbftForks());
final ValidatorProvider validatorProvider;
if (qbftConfig.getValidatorContractAddress().isEmpty()) {
validatorProvider =
BlockValidatorProvider.forkingValidatorProvider(
blockchain, epochManager, bftBlockInterface().get(), validatorOverrides);
} else {
final Address contractAddress =
Address.fromHexString(qbftConfig.getValidatorContractAddress().get());
final TransactionSimulator transactionSimulator =
new TransactionSimulator(blockchain, worldStateArchive, protocolSchedule);
final ValidatorContractController validatorContractController =
new ValidatorContractController(contractAddress, transactionSimulator);
validatorProvider = new TransactionValidatorProvider(blockchain, validatorContractController);
}
convertBftForks(genesisConfig.getConfigOptions().getTransitions().getQbftForks());
final TransactionSimulator transactionSimulator =
new TransactionSimulator(blockchain, worldStateArchive, protocolSchedule);
final BlockValidatorProvider blockValidatorProvider =
BlockValidatorProvider.forkingValidatorProvider(
blockchain, epochManager, bftBlockInterface().get(), validatorOverrides);
final TransactionValidatorProvider transactionValidatorProvider =
new TransactionValidatorProvider(
blockchain, new ValidatorContractController(transactionSimulator, qbftForksSchedule));
final ValidatorProvider validatorProvider =
new ForkingValidatorProvider(
blockchain, qbftForksSchedule, blockValidatorProvider, transactionValidatorProvider);
return new QbftContext(
validatorProvider, epochManager, bftBlockInterface().get(), pkiBlockCreationConfiguration);
}
private BftValidatorOverrides convertBftForks(final List<BftFork> bftForks) {
private ValidatorSelectorForksSchedule createQbftForksSchedule(
final GenesisConfigOptions configOptions) {
final ValidatorSelectorConfig initialFork =
ValidatorSelectorConfig.fromQbftConfig(configOptions.getQbftConfigOptions());
final List<ValidatorSelectorConfig> validatorSelectionForks =
convertToValidatorSelectionConfig(
genesisConfig.getConfigOptions().getTransitions().getQbftForks());
return new ValidatorSelectorForksSchedule(initialFork, validatorSelectionForks);
}
private BftValidatorOverrides convertBftForks(final List<QbftFork> bftForks) {
final Map<Long, List<Address>> result = new HashMap<>();
for (final BftFork fork : bftForks) {
@ -320,6 +328,14 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
return new BftValidatorOverrides(result);
}
private List<ValidatorSelectorConfig> convertToValidatorSelectionConfig(
final List<QbftFork> qbftForks) {
return qbftForks.stream()
.map(ValidatorSelectorConfig::fromQbftFork)
.flatMap(Optional::stream)
.collect(Collectors.toList());
}
private static MinedBlockObserver blockLogger(
final TransactionPool transactionPool, final Address localAddress) {
return block ->

@ -27,12 +27,12 @@ import org.apache.tuweni.bytes.Bytes;
public class BftFork {
private static final String FORK_BLOCK_KEY = "block";
private static final String VALIDATORS_KEY = "validators";
private static final String BLOCK_PERIOD_SECONDS_KEY = "blockperiodseconds";
private static final String BLOCK_REWARD_KEY = "blockreward";
public static final String FORK_BLOCK_KEY = "block";
public static final String VALIDATORS_KEY = "validators";
public static final String BLOCK_PERIOD_SECONDS_KEY = "blockperiodseconds";
public static final String BLOCK_REWARD_KEY = "blockreward";
private final ObjectNode forkConfigRoot;
protected final ObjectNode forkConfigRoot;
@JsonCreator
public BftFork(final ObjectNode forkConfigRoot) {
@ -79,7 +79,7 @@ public class BftFork {
value -> {
if (!value.isTextual()) {
throw new IllegalArgumentException(
"Bft Validator fork does not contain a string " + value.toString());
"Bft Validator fork does not contain a string " + value);
}
validators.add(value.asText());

@ -23,13 +23,14 @@ import com.google.common.collect.ImmutableMap;
public class QbftConfigOptions extends BftConfigOptions {
public static final QbftConfigOptions DEFAULT =
new QbftConfigOptions(JsonUtil.createEmptyObjectNode());
public static final String VALIDATOR_CONTRACT_ADDRESS = "validatorcontractaddress";
QbftConfigOptions(final ObjectNode bftConfigRoot) {
public QbftConfigOptions(final ObjectNode bftConfigRoot) {
super(bftConfigRoot);
}
public Optional<String> getValidatorContractAddress() {
return JsonUtil.getString(bftConfigRoot, "validatorcontractaddress");
return JsonUtil.getString(bftConfigRoot, VALIDATOR_CONTRACT_ADDRESS);
}
@Override
@ -37,7 +38,7 @@ public class QbftConfigOptions extends BftConfigOptions {
final Map<String, Object> map = super.asMap();
final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
builder.putAll(map);
builder.put("validatorcontractaddress", getValidatorContractAddress());
builder.put(VALIDATOR_CONTRACT_ADDRESS, getValidatorContractAddress());
return builder.build();
}
}

@ -0,0 +1,51 @@
/*
* 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.config;
import java.util.Arrays;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class QbftFork extends BftFork {
public enum VALIDATOR_SELECTION_MODE {
BLOCKHEADER,
CONTRACT
}
public static final String VALIDATOR_SELECTION_MODE_KEY = "validatorselectionmode";
public static final String VALIDATOR_CONTRACT_ADDRESS_KEY = "validatorcontractaddress";
@JsonCreator
public QbftFork(final ObjectNode forkConfigRoot) {
super(forkConfigRoot);
}
public Optional<VALIDATOR_SELECTION_MODE> getValidatorSelectionMode() {
final Optional<String> mode = JsonUtil.getString(forkConfigRoot, VALIDATOR_SELECTION_MODE_KEY);
return mode.flatMap(
m ->
Arrays.stream(VALIDATOR_SELECTION_MODE.values())
.filter(v -> v.name().equalsIgnoreCase(m))
.findFirst());
}
public Optional<String> getValidatorContractAddress() {
return JsonUtil.getString(forkConfigRoot, VALIDATOR_CONTRACT_ADDRESS_KEY);
}
}

@ -56,6 +56,8 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
private OptionalInt stackSizeLimit = OptionalInt.empty();
private final OptionalLong ecip1017EraRounds = OptionalLong.empty();
private Optional<String> ecCurve = Optional.empty();
private QbftConfigOptions qbftConfigOptions = QbftConfigOptions.DEFAULT;
private TransitionsConfigOptions transitions = TransitionsConfigOptions.DEFAULT;
@Override
public String getConsensusEngine() {
@ -109,7 +111,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
@Override
public QbftConfigOptions getQbftConfigOptions() {
return QbftConfigOptions.DEFAULT;
return qbftConfigOptions;
}
@Override
@ -328,7 +330,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
@Override
public TransitionsConfigOptions getTransitions() {
return TransitionsConfigOptions.DEFAULT;
return transitions;
}
@Override
@ -487,4 +489,14 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
this.ecCurve = ecCurve;
return this;
}
public StubGenesisConfigOptions qbftConfigOptions(final QbftConfigOptions qbftConfigOptions) {
this.qbftConfigOptions = qbftConfigOptions;
return this;
}
public StubGenesisConfigOptions transitions(final TransitionsConfigOptions transitions) {
this.transitions = transitions;
return this;
}
}

@ -19,6 +19,7 @@ import static java.util.Collections.emptyList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.node.ArrayNode;
@ -38,21 +39,22 @@ public class TransitionsConfigOptions {
}
public List<BftFork> getIbftForks() {
return getBftForks("ibft2");
return getBftForks("ibft2", BftFork::new);
}
public List<BftFork> getQbftForks() {
return getBftForks("qbft");
public List<QbftFork> getQbftForks() {
return getBftForks("qbft", QbftFork::new);
}
private List<BftFork> getBftForks(final String fieldKey) {
private <T> List<T> getBftForks(
final String fieldKey, final Function<ObjectNode, T> forkConstructor) {
final Optional<ArrayNode> bftForksNode = JsonUtil.getArrayNode(customForkConfigRoot, fieldKey);
if (bftForksNode.isEmpty()) {
return emptyList();
}
final List<BftFork> bftForks = Lists.newArrayList();
final List<T> bftForks = Lists.newArrayList();
bftForksNode
.get()
@ -62,7 +64,7 @@ public class TransitionsConfigOptions {
if (!node.isObject()) {
throw new IllegalArgumentException("Bft fork is illegally formatted.");
}
bftForks.add(new BftFork((ObjectNode) node));
bftForks.add(forkConstructor.apply((ObjectNode) node));
});
return Collections.unmodifiableList(bftForks);

@ -60,7 +60,7 @@ public class BftProtocolSchedule {
bftExtraDataCodec,
config.getBftConfigOptions().getBlockRewardWei()));
final Supplier<List<BftFork>> forks;
final Supplier<List<? extends BftFork>> forks;
if (config.isIbft2()) {
forks = () -> config.getTransitions().getIbftForks();
} else {

@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;
public class BlockValidatorProvider implements ValidatorProvider {
@ -32,7 +33,7 @@ public class BlockValidatorProvider implements ValidatorProvider {
private final VoteProvider voteProvider;
private final BlockInterface blockInterface;
public static ValidatorProvider forkingValidatorProvider(
public static BlockValidatorProvider forkingValidatorProvider(
final Blockchain blockchain,
final EpochManager epochManager,
final BlockInterface blockInterface,
@ -41,7 +42,7 @@ public class BlockValidatorProvider implements ValidatorProvider {
blockchain, epochManager, blockInterface, Optional.of(bftValidatorOverrides));
}
public static ValidatorProvider nonForkingValidatorProvider(
public static BlockValidatorProvider nonForkingValidatorProvider(
final Blockchain blockchain,
final EpochManager epochManager,
final BlockInterface blockInterface) {
@ -80,7 +81,7 @@ public class BlockValidatorProvider implements ValidatorProvider {
@Override
public Collection<Address> getValidatorsForBlock(final BlockHeader header) {
return blockInterface.validatorsInBlock(header);
return blockInterface.validatorsInBlock(header).stream().sorted().collect(Collectors.toList());
}
@Override

@ -89,6 +89,7 @@ dependencies {
testImplementation project(':metrics:core')
testImplementation project(':testutil')
testImplementation project(':crypto')
testImplementation project(':ethereum:core')
integrationTestImplementation project(path: ':crypto', configuration: 'testSupportArtifacts')
integrationTestImplementation project(path: ':consensus:common', configuration: 'testSupportArtifacts')

@ -19,7 +19,11 @@ import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider
import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive;
import static org.mockito.Mockito.mock;
import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.config.QbftConfigOptions;
import org.hyperledger.besu.config.QbftFork;
import org.hyperledger.besu.config.StubGenesisConfigOptions;
import org.hyperledger.besu.consensus.common.BftValidatorOverrides;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions;
import org.hyperledger.besu.consensus.common.bft.BftBlockInterface;
@ -29,7 +33,6 @@ import org.hyperledger.besu.consensus.common.bft.BftExecutors;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.common.bft.BftHelpers;
import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule;
import org.hyperledger.besu.consensus.common.bft.BlockTimer;
import org.hyperledger.besu.consensus.common.bft.EventMultiplexer;
import org.hyperledger.besu.consensus.common.bft.Gossiper;
@ -53,14 +56,18 @@ import org.hyperledger.besu.consensus.qbft.QbftBlockHeaderValidationRulesetFacto
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.QbftGossip;
import org.hyperledger.besu.consensus.qbft.QbftProtocolSchedule;
import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
import org.hyperledger.besu.consensus.qbft.statemachine.QbftBlockHeightManagerFactory;
import org.hyperledger.besu.consensus.qbft.statemachine.QbftController;
import org.hyperledger.besu.consensus.qbft.statemachine.QbftRoundFactory;
import org.hyperledger.besu.consensus.qbft.validation.MessageValidatorFactory;
import org.hyperledger.besu.consensus.qbft.validator.ForkingValidatorProvider;
import org.hyperledger.besu.consensus.qbft.validator.TransactionValidatorProvider;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorContractController;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorSelectorConfig;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorSelectorForksSchedule;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.GenesisState;
@ -181,6 +188,7 @@ public class TestContextBuilder {
private boolean useGossip = false;
private Optional<String> genesisFile = Optional.empty();
private List<NodeParams> nodeParams = Collections.emptyList();
private List<QbftFork> qbftForks = Collections.emptyList();
public TestContextBuilder clock(final Clock clock) {
this.clock = clock;
@ -223,6 +231,11 @@ public class TestContextBuilder {
return this;
}
public TestContextBuilder qbftForks(final List<QbftFork> qbftForks) {
this.qbftForks = qbftForks;
return this;
}
public TestContext build() {
final NetworkLayout networkNodes;
if (nodeParams.isEmpty()) {
@ -281,7 +294,8 @@ public class TestContextBuilder {
bftEventQueue,
gossiper,
synchronizerUpdater,
useValidatorContract);
useValidatorContract,
qbftForks);
// Add each networkNode to the Multicaster (such that each can receive msgs from local node).
// NOTE: the remotePeers needs to be ordered based on Address (as this is used to determine
@ -358,7 +372,8 @@ public class TestContextBuilder {
final BftEventQueue bftEventQueue,
final Gossiper gossiper,
final SynchronizerUpdater synchronizerUpdater,
final boolean useValidatorContract) {
final boolean useValidatorContract,
final List<QbftFork> qbftForks) {
final MiningParameters miningParams =
new MiningParameters.Builder()
@ -369,11 +384,22 @@ public class TestContextBuilder {
.build();
final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions();
final Map<String, Object> qbftConfigValues =
useValidatorContract
? Map.of(
QbftConfigOptions.VALIDATOR_CONTRACT_ADDRESS,
VALIDATOR_CONTRACT_ADDRESS.toHexString())
: Collections.emptyMap();
genesisConfigOptions.byzantiumBlock(0);
genesisConfigOptions.qbftConfigOptions(
new QbftConfigOptions(JsonUtil.objectNodeFromMap(qbftConfigValues)));
genesisConfigOptions.transitions(new TestTransitions(qbftForks));
final QbftBlockHeaderValidationRulesetFactory qbftBlockHeaderValidationRulesetFactory =
new QbftBlockHeaderValidationRulesetFactory(useValidatorContract);
new QbftBlockHeaderValidationRulesetFactory();
final ProtocolSchedule protocolSchedule =
BftProtocolSchedule.create(
QbftProtocolSchedule.create(
genesisConfigOptions,
qbftBlockHeaderValidationRulesetFactory::blockHeaderValidator,
BFT_EXTRA_DATA_ENCODER);
@ -384,18 +410,28 @@ public class TestContextBuilder {
final BftBlockInterface blockInterface = new BftBlockInterface(BFT_EXTRA_DATA_ENCODER);
final ValidatorProvider validatorProvider;
if (useValidatorContract) {
final TransactionSimulator transactionSimulator =
new TransactionSimulator(blockChain, worldStateArchive, protocolSchedule);
final ValidatorContractController validatorContractController =
new ValidatorContractController(VALIDATOR_CONTRACT_ADDRESS, transactionSimulator);
validatorProvider = new TransactionValidatorProvider(blockChain, validatorContractController);
} else {
validatorProvider =
BlockValidatorProvider.nonForkingValidatorProvider(
blockChain, epochManager, blockInterface);
}
final BftValidatorOverrides validatorOverrides =
new BftValidatorOverrides(Collections.emptyMap());
final TransactionSimulator transactionSimulator =
new TransactionSimulator(blockChain, worldStateArchive, protocolSchedule);
final ValidatorSelectorConfig genesisFork = createGenesisFork(useValidatorContract);
final List<ValidatorSelectorConfig> validatorSelectorForks =
qbftForks.stream()
.map(ValidatorSelectorConfig::fromQbftFork)
.flatMap(Optional::stream)
.collect(Collectors.toList());
final ValidatorSelectorForksSchedule forksSchedule =
new ValidatorSelectorForksSchedule(genesisFork, validatorSelectorForks);
final BlockValidatorProvider blockValidatorProvider =
BlockValidatorProvider.forkingValidatorProvider(
blockChain, epochManager, blockInterface, validatorOverrides);
final TransactionValidatorProvider transactionValidatorProvider =
new TransactionValidatorProvider(
blockChain, new ValidatorContractController(transactionSimulator, forksSchedule));
final ValidatorProvider validatorProvider =
new ForkingValidatorProvider(
blockChain, forksSchedule, blockValidatorProvider, transactionValidatorProvider);
final ProtocolContext protocolContext =
new ProtocolContext(
@ -423,7 +459,7 @@ public class TestContextBuilder {
localAddress,
localAddress,
BFT_EXTRA_DATA_ENCODER,
useValidatorContract);
forksSchedule);
final ProposerSelector proposerSelector =
new ProposerSelector(blockChain, blockInterface, true, validatorProvider);
@ -489,4 +525,10 @@ public class TestContextBuilder {
messageFactory,
validatorProvider);
}
private static ValidatorSelectorConfig createGenesisFork(final boolean useValidatorContract) {
return useValidatorContract
? ValidatorSelectorConfig.createContractConfig(0L, VALIDATOR_CONTRACT_ADDRESS.toHexString())
: ValidatorSelectorConfig.createBlockConfig(0L);
}
}

@ -0,0 +1,36 @@
/*
* 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.consensus.qbft.support;
import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.config.QbftFork;
import org.hyperledger.besu.config.TransitionsConfigOptions;
import java.util.List;
public class TestTransitions extends TransitionsConfigOptions {
private final List<QbftFork> forks;
public TestTransitions(final List<QbftFork> forks) {
super(JsonUtil.createEmptyObjectNode());
this.forks = forks;
}
@Override
public List<QbftFork> getQbftForks() {
return forks;
}
}

@ -16,10 +16,15 @@ package org.hyperledger.besu.consensus.qbft.test;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.config.BftFork;
import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.config.QbftFork;
import org.hyperledger.besu.config.QbftFork.VALIDATOR_SELECTION_MODE;
import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.events.BlockTimerExpiry;
import org.hyperledger.besu.consensus.common.bft.inttest.NodeParams;
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.support.TestContext;
import org.hyperledger.besu.consensus.qbft.support.TestContextBuilder;
import org.hyperledger.besu.crypto.NodeKeyUtils;
@ -30,6 +35,9 @@ import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.io.Resources;
import org.apache.tuweni.bytes.Bytes32;
@ -46,22 +54,20 @@ public class ValidatorContractTest {
private final Clock fixedClock =
Clock.fixed(Instant.ofEpochSecond(blockTimeStamp), ZoneId.systemDefault());
// Configuration ensures unit under test will be responsible for sending first proposal
@SuppressWarnings("UnstableApiUsage")
private final TestContext context =
new TestContextBuilder()
.indexOfFirstLocallyProposedBlock(0)
.nodeParams(
List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY))))
.clock(fixedClock)
.genesisFile(Resources.getResource("genesis_validator_contract.json").getFile())
.useValidatorContract(true)
.buildAndStart();
private final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(1, 0);
@Test
public void retrievesValidatorsFromValidatorContract() {
final TestContext context =
new TestContextBuilder()
.indexOfFirstLocallyProposedBlock(0)
.nodeParams(
List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY))))
.clock(fixedClock)
.genesisFile(Resources.getResource("genesis_validator_contract.json").getFile())
.useValidatorContract(true)
.buildAndStart();
// create new block
context.getController().handleBlockTimerExpiry(new BlockTimerExpiry(roundId));
assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1);
@ -72,4 +78,58 @@ public class ValidatorContractTest {
assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).containsExactly(NODE_ADDRESS);
assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS);
}
@Test
public void migratesFromBlockToValidatorContract() {
final QbftExtraDataCodec extraDataCodec = new QbftExtraDataCodec();
final List<QbftFork> qbftForks =
List.of(createContractFork(1, TestContextBuilder.VALIDATOR_CONTRACT_ADDRESS));
final TestContext context =
new TestContextBuilder()
.indexOfFirstLocallyProposedBlock(0)
.nodeParams(
List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY))))
.clock(fixedClock)
.genesisFile(
Resources.getResource("genesis_migrating_validator_contract.json").getFile())
.useValidatorContract(false)
.qbftForks(qbftForks)
.buildAndStart();
// block 0 uses block header voting with 1 validator
// block 1 onwards uses validator contract with 2 validators
final List<Address> block0Addresses = List.of(NODE_ADDRESS);
final List<Address> block1Addresses =
Stream.of(NODE_ADDRESS, Address.fromHexString("ca1c5ff73ed8370397114006dd1258e16433f41b"))
.sorted()
.collect(Collectors.toList());
// create new block
context.getController().handleBlockTimerExpiry(new BlockTimerExpiry(roundId));
assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1);
final ValidatorProvider validatorProvider = context.getValidatorProvider();
final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get();
final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get();
assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).isEqualTo(block0Addresses);
assertThat(extraDataCodec.decode(genesisBlock).getValidators()).containsExactly(NODE_ADDRESS);
// contract block extra data cannot contain validators or vote
assertThat(validatorProvider.getValidatorsForBlock(block1)).isEqualTo(block1Addresses);
assertThat(extraDataCodec.decode(block1).getValidators()).isEmpty();
assertThat(extraDataCodec.decode(block1).getVote()).isEmpty();
}
private QbftFork createContractFork(final long block, final Address contractAddress) {
return new QbftFork(
JsonUtil.objectNodeFromMap(
Map.of(
BftFork.FORK_BLOCK_KEY,
block,
QbftFork.VALIDATOR_SELECTION_MODE_KEY,
VALIDATOR_SELECTION_MODE.CONTRACT,
QbftFork.VALIDATOR_CONTRACT_ADDRESS_KEY,
contractAddress.toHexString())));
}
}

@ -0,0 +1,53 @@
{
"config": {
"chainid": 2017,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0
},
"transitions": {
"qbft": [
{
"block": 1,
"validatorSelectionMode": "contract",
"validatorContractAddress": "0x0000000000000000000000000000000000008888"
}
]
},
"nonce": "0x0",
"timestamp": "0x0",
"extraData": "0xf83aa00000000000000000000000000000000000000000000000000000000000000000d594eac51e3fe1afc9894f0dfeab8ceb471899b932dfc080c0",
"gasLimit": "0x29b92700",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"64d9be4177f418bcf4e56adad85f33e3a64efe22": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"9f66f8a0f0a6537e4a36aa1799673ea7ae97a166": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"a7f25969fb6f3d5ac09a88862c90b5ff664557a7": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"f4bbfd32c11c9d63e9b4c77bb225810f840342df": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"0x0000000000000000000000000000000000008888": {
"comment": "validator smart contract. This is compiled from validator_contract.sol using solc --evm-version byzantium --bin-runtime validator_contract.sol",
"balance": "0",
"code": "608060405234801561001057600080fd5b5060043610610048576000357c010000000000000000000000000000000000000000000000000000000090048063b7ab4db51461004d575b600080fd5b61005561006b565b604051610062919061017e565b60405180910390f35b606060008054806020026020016040519081016040528092919081815260200182805480156100ef57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116100a5575b5050505050905090565b60006101058383610111565b60208301905092915050565b61011a816101d9565b82525050565b600061012b826101b0565b61013581856101c8565b9350610140836101a0565b8060005b8381101561017157815161015888826100f9565b9750610163836101bb565b925050600181019050610144565b5085935050505092915050565b600060208201905081810360008301526101988184610120565b905092915050565b6000819050602082019050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b60006101e4826101eb565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff8216905091905056fea26469706673582212206d880cf012c1677c691bf6f2f0a0e4eadf57866ffe5cd2d9833d3cfdf27b15f664736f6c63430008060033",
"storage": {
"0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000002",
"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "000000000000000000000000ca1c5ff73ed8370397114006dd1258e16433f41b",
"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df"
}
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

@ -33,21 +33,17 @@ import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampMore
import org.apache.tuweni.units.bigints.UInt256;
public class QbftBlockHeaderValidationRulesetFactory {
private final boolean extraDataContractBasedValidatorRule;
public QbftBlockHeaderValidationRulesetFactory(
final boolean extraDataContractBasedValidatorRule) {
this.extraDataContractBasedValidatorRule = extraDataContractBasedValidatorRule;
}
/**
* Produces a BlockHeaderValidator configured for assessing bft block headers which are to form
* part of the BlockChain (i.e. not proposed blocks, which do not contain commit seals)
*
* @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks.
* @param useValidatorContract whether validator selection is using a validator contract
* @return BlockHeaderValidator configured for assessing bft block headers
*/
public BlockHeaderValidator.Builder blockHeaderValidator(final long secondsBetweenBlocks) {
public BlockHeaderValidator.Builder blockHeaderValidator(
final long secondsBetweenBlocks, final boolean useValidatorContract) {
return new BlockHeaderValidator.Builder()
.addRule(new AncestryValidationRule())
.addRule(new GasUsageValidationRule())
@ -61,7 +57,7 @@ public class QbftBlockHeaderValidationRulesetFactory {
.addRule(
new ConstantFieldValidationRule<>(
"Difficulty", BlockHeader::getDifficulty, UInt256.ONE))
.addRule(new QbftValidatorsValidationRule(extraDataContractBasedValidatorRule))
.addRule(new QbftValidatorsValidationRule(useValidatorContract))
.addRule(new BftCoinbaseValidationRule())
.addRule(new BftCommitSealsValidationRule());
}

@ -0,0 +1,171 @@
/*
* 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.consensus.qbft;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.QbftFork;
import org.hyperledger.besu.config.QbftFork.VALIDATOR_SELECTION_MODE;
import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
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.BlockHeaderValidator;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockBodyValidator;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockImporter;
import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/** Defines the protocol behaviours for a blockchain using a QBFT consensus mechanism. */
// TODO-jf remove duplication with the BftProtocolSchedule
public class QbftProtocolSchedule {
private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE;
public static ProtocolSchedule create(
final GenesisConfigOptions config,
final PrivacyParameters privacyParameters,
final boolean isRevertReasonEnabled,
final BiFunction<Integer, Boolean, BlockHeaderValidator.Builder> blockHeaderRuleset,
final BftExtraDataCodec bftExtraDataCodec) {
final Map<Long, Function<ProtocolSpecBuilder, ProtocolSpecBuilder>> specMap = new HashMap<>();
specMap.put(
0L,
builder ->
applyBftChanges(
config.getBftConfigOptions(),
builder,
config.isQuorum(),
blockHeaderRuleset,
bftExtraDataCodec,
Optional.of(config.getBftConfigOptions().getBlockRewardWei()),
config.getQbftConfigOptions().getValidatorContractAddress().isPresent()));
final Supplier<List<QbftFork>> forks = () -> config.getTransitions().getQbftForks();
forks
.get()
.forEach(
fork ->
specMap.put(
fork.getForkBlock(),
builder ->
applyBftChanges(
config.getBftConfigOptions(),
builder,
config.isQuorum(),
blockHeaderRuleset,
bftExtraDataCodec,
fork.getBlockRewardWei(),
fork.getValidatorSelectionMode()
.filter(m -> m == VALIDATOR_SELECTION_MODE.CONTRACT)
.isPresent())));
final ProtocolSpecAdapters specAdapters = new ProtocolSpecAdapters(specMap);
return new ProtocolScheduleBuilder(
config,
DEFAULT_CHAIN_ID,
specAdapters,
privacyParameters,
isRevertReasonEnabled,
config.isQuorum())
.createProtocolSchedule();
}
public static ProtocolSchedule create(
final GenesisConfigOptions config,
final boolean isRevertReasonEnabled,
final BiFunction<Integer, Boolean, BlockHeaderValidator.Builder> blockHeaderRuleset,
final BftExtraDataCodec bftExtraDataCodec) {
return create(
config,
PrivacyParameters.DEFAULT,
isRevertReasonEnabled,
blockHeaderRuleset,
bftExtraDataCodec);
}
public static ProtocolSchedule create(
final GenesisConfigOptions config,
final BiFunction<Integer, Boolean, BlockHeaderValidator.Builder> blockHeaderRuleset,
final BftExtraDataCodec bftExtraDataCodec) {
return create(config, PrivacyParameters.DEFAULT, false, blockHeaderRuleset, bftExtraDataCodec);
}
private static ProtocolSpecBuilder applyBftChanges(
final BftConfigOptions configOptions,
final ProtocolSpecBuilder builder,
final boolean goQuorumMode,
final BiFunction<Integer, Boolean, BlockHeaderValidator.Builder> blockHeaderRuleset,
final BftExtraDataCodec bftExtraDataCodec,
final Optional<BigInteger> blockReward,
final boolean useValidatorContract) {
if (configOptions.getEpochLength() <= 0) {
throw new IllegalArgumentException("Epoch length in config must be greater than zero");
}
if (blockReward.isPresent() && blockReward.get().signum() < 0) {
throw new IllegalArgumentException("Bft Block reward in config cannot be negative");
}
builder
.blockHeaderValidatorBuilder(
feeMarket ->
blockHeaderRuleset.apply(
configOptions.getBlockPeriodSeconds(), useValidatorContract))
.ommerHeaderValidatorBuilder(
feeMarket ->
blockHeaderRuleset.apply(
configOptions.getBlockPeriodSeconds(), useValidatorContract))
.blockBodyValidatorBuilder(MainnetBlockBodyValidator::new)
.blockValidatorBuilder(MainnetProtocolSpecs.blockValidatorBuilder(goQuorumMode))
.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;
}
}

@ -14,11 +14,14 @@
*/
package org.hyperledger.besu.consensus.qbft.blockcreation;
import org.hyperledger.besu.config.QbftFork.VALIDATOR_SELECTION_MODE;
import org.hyperledger.besu.consensus.common.ConsensusHelpers;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec;
import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreatorFactory;
import org.hyperledger.besu.consensus.qbft.QbftContext;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorSelectorConfig;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorSelectorForksSchedule;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator;
import org.hyperledger.besu.ethereum.core.Address;
@ -34,7 +37,7 @@ import org.apache.tuweni.bytes.Bytes;
/** Supports contract based voters and validators in extra data */
public class QbftBlockCreatorFactory extends BftBlockCreatorFactory {
private final boolean extraDataWithRoundInformationOnly;
private final ValidatorSelectorForksSchedule validatorSelectorForksSchedule;
public QbftBlockCreatorFactory(
final AbstractPendingTransactionsSorter pendingTransactions,
@ -44,7 +47,7 @@ public class QbftBlockCreatorFactory extends BftBlockCreatorFactory {
final Address localAddress,
final Address miningBeneficiary,
final BftExtraDataCodec bftExtraDataCodec,
final boolean extraDataWithRoundInformationOnly) {
final ValidatorSelectorForksSchedule validatorSelectorForksSchedule) {
super(
pendingTransactions,
protocolContext,
@ -53,7 +56,7 @@ public class QbftBlockCreatorFactory extends BftBlockCreatorFactory {
localAddress,
miningBeneficiary,
bftExtraDataCodec);
this.extraDataWithRoundInformationOnly = extraDataWithRoundInformationOnly;
this.validatorSelectorForksSchedule = validatorSelectorForksSchedule;
}
@Override
@ -70,7 +73,12 @@ public class QbftBlockCreatorFactory extends BftBlockCreatorFactory {
@Override
public Bytes createExtraData(final int round, final BlockHeader parentHeader) {
if (extraDataWithRoundInformationOnly) {
final Optional<VALIDATOR_SELECTION_MODE> validatorSelectionMode =
validatorSelectorForksSchedule
.getFork(parentHeader.getNumber() + 1L)
.map(ValidatorSelectorConfig::getValidatorSelectionMode);
if (validatorSelectionMode.isPresent()
&& validatorSelectionMode.get().equals(VALIDATOR_SELECTION_MODE.CONTRACT)) {
// vote and validators will come from contract instead of block
final BftExtraData extraData =
new BftExtraData(

@ -0,0 +1,116 @@
/*
* 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.consensus.qbft.validator;
import static org.hyperledger.besu.config.QbftFork.VALIDATOR_SELECTION_MODE.BLOCKHEADER;
import static org.hyperledger.besu.config.QbftFork.VALIDATOR_SELECTION_MODE.CONTRACT;
import org.hyperledger.besu.config.QbftFork.VALIDATOR_SELECTION_MODE;
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.consensus.common.validator.VoteProvider;
import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
public class ForkingValidatorProvider implements ValidatorProvider {
private final Blockchain blockchain;
private final ValidatorSelectorForksSchedule forksSchedule;
private final BlockValidatorProvider blockValidatorProvider;
private final TransactionValidatorProvider transactionValidatorProvider;
public ForkingValidatorProvider(
final Blockchain blockchain,
final ValidatorSelectorForksSchedule forksSchedule,
final BlockValidatorProvider blockValidatorProvider,
final TransactionValidatorProvider transactionValidatorProvider) {
this.blockchain = blockchain;
this.forksSchedule = forksSchedule;
this.blockValidatorProvider = blockValidatorProvider;
this.transactionValidatorProvider = transactionValidatorProvider;
}
@Override
public Collection<Address> getValidatorsAtHead() {
final BlockHeader header = blockchain.getChainHeadHeader();
return getValidators(header.getNumber(), ValidatorProvider::getValidatorsAtHead);
}
@Override
public Collection<Address> getValidatorsAfterBlock(final BlockHeader header) {
return getValidators(header.getNumber(), p -> p.getValidatorsAfterBlock(header));
}
@Override
public Collection<Address> getValidatorsForBlock(final BlockHeader header) {
return getValidators(header.getNumber(), p -> p.getValidatorsForBlock(header));
}
@Override
public Optional<VoteProvider> getVoteProvider() {
return resolveValidatorProvider(blockchain.getChainHeadHeader().getNumber()).getVoteProvider();
}
private Collection<Address> getValidators(
final long block, final Function<ValidatorProvider, Collection<Address>> getValidators) {
final Optional<ValidatorSelectorConfig> fork = forksSchedule.getFork(block);
final ValidatorProvider validatorProvider = resolveValidatorProvider(block);
if (fork.isPresent()) {
final VALIDATOR_SELECTION_MODE validatorSelectionMode =
fork.get().getValidatorSelectionMode();
// when moving to a block validator the first block needs to be initialised or created with
// the previous block state otherwise we would have no validators
if (validatorSelectionMode.equals(BLOCKHEADER)) {
if (block > 0 && block == fork.get().getBlock()) {
final long prevBlockNumber = block - 1L;
final Optional<BlockHeader> prevBlockHeader = blockchain.getBlockHeader(prevBlockNumber);
if (prevBlockHeader.isPresent()) {
return resolveValidatorProvider(prevBlockNumber)
.getValidatorsForBlock(prevBlockHeader.get());
}
}
return getValidators.apply(validatorProvider);
}
}
return getValidators.apply(validatorProvider);
}
private ValidatorProvider resolveValidatorProvider(final long block) {
final Optional<ValidatorSelectorConfig> fork = forksSchedule.getFork(block);
if (fork.isPresent()) {
final VALIDATOR_SELECTION_MODE validatorSelectionMode =
fork.get().getValidatorSelectionMode();
if (validatorSelectionMode.equals(BLOCKHEADER)) {
return blockValidatorProvider;
}
if (validatorSelectionMode.equals(CONTRACT) && fork.get().getContractAddress().isPresent()) {
return transactionValidatorProvider;
} else if (block > 0) { // if no contract address then resolve using previous block
return resolveValidatorProvider(block - 1L);
}
}
throw new IllegalStateException("Unknown qbft validator selection mode");
}
}

@ -35,14 +35,15 @@ import org.web3j.abi.datatypes.Type;
public class ValidatorContractController {
public static final String GET_VALIDATORS = "getValidators";
public static final String CONTRACT_ERROR_MSG = "Failed validator smart contract call";
private final Address contractAddress;
private final TransactionSimulator transactionSimulator;
private final ValidatorSelectorForksSchedule forksSchedule;
private final Function getValidatorsFunction;
public ValidatorContractController(
final Address contractAddress, final TransactionSimulator transactionSimulator) {
this.contractAddress = contractAddress;
final TransactionSimulator transactionSimulator,
final ValidatorSelectorForksSchedule forksSchedule) {
this.transactionSimulator = transactionSimulator;
this.forksSchedule = forksSchedule;
try {
this.getValidatorsFunction =
@ -74,11 +75,23 @@ public class ValidatorContractController {
private Optional<TransactionSimulatorResult> callFunction(
final long blockNumber, final Function function) {
final Bytes payload = Bytes.fromHexString(FunctionEncoder.encode(function));
final Address contractAddress = resolveContractAddress(blockNumber);
final CallParameter callParams =
new CallParameter(null, contractAddress, -1, null, null, payload);
return transactionSimulator.process(callParams, blockNumber);
}
private Address resolveContractAddress(final long blockNumber) {
return forksSchedule
.getFork(blockNumber)
.flatMap(ValidatorSelectorConfig::getContractAddress)
.map(Address::fromHexString)
.orElseThrow(
() ->
new RuntimeException(
"Error resolving smart contract address unable to make validator contract call"));
}
@SuppressWarnings("rawtypes")
private List<Type> decodeResult(
final TransactionSimulatorResult result, final Function function) {

@ -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.consensus.qbft.validator;
import org.hyperledger.besu.config.QbftConfigOptions;
import org.hyperledger.besu.config.QbftFork;
import org.hyperledger.besu.config.QbftFork.VALIDATOR_SELECTION_MODE;
import java.util.Optional;
public class ValidatorSelectorConfig {
private final long block;
private final VALIDATOR_SELECTION_MODE validatorSelectionMode;
private final Optional<String> contractAddress;
private ValidatorSelectorConfig(
final long block,
final VALIDATOR_SELECTION_MODE validatorSelectionMode,
final Optional<String> contractAddress) {
this.block = block;
this.validatorSelectionMode = validatorSelectionMode;
this.contractAddress = contractAddress;
}
public static ValidatorSelectorConfig createBlockConfig(final long block) {
return new ValidatorSelectorConfig(
block, VALIDATOR_SELECTION_MODE.BLOCKHEADER, Optional.empty());
}
public static ValidatorSelectorConfig createContractConfig(
final long block, final String contractAddress) {
return new ValidatorSelectorConfig(
block, VALIDATOR_SELECTION_MODE.CONTRACT, Optional.of(contractAddress));
}
public static Optional<ValidatorSelectorConfig> fromQbftFork(final QbftFork qbftFork) {
if (qbftFork.getValidatorSelectionMode().isPresent()) {
final VALIDATOR_SELECTION_MODE mode = qbftFork.getValidatorSelectionMode().get();
if (mode == VALIDATOR_SELECTION_MODE.BLOCKHEADER) {
return Optional.of(createBlockConfig(qbftFork.getForkBlock()));
} else if (mode == VALIDATOR_SELECTION_MODE.CONTRACT
&& qbftFork.getValidatorContractAddress().isPresent()) {
return Optional.of(
createContractConfig(
qbftFork.getForkBlock(), qbftFork.getValidatorContractAddress().get()));
}
}
return Optional.empty();
}
public static ValidatorSelectorConfig fromQbftConfig(final QbftConfigOptions qbftConfigOptions) {
if (qbftConfigOptions.getValidatorContractAddress().isPresent()) {
return createContractConfig(0, qbftConfigOptions.getValidatorContractAddress().get());
} else {
return createBlockConfig(0);
}
}
public long getBlock() {
return block;
}
public VALIDATOR_SELECTION_MODE getValidatorSelectionMode() {
return validatorSelectionMode;
}
public Optional<String> getContractAddress() {
return contractAddress;
}
}

@ -0,0 +1,44 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.qbft.validator;
import java.util.Comparator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.TreeSet;
public class ValidatorSelectorForksSchedule {
private final NavigableSet<ValidatorSelectorConfig> forks =
new TreeSet<>(Comparator.comparing(ValidatorSelectorConfig::getBlock).reversed());
public ValidatorSelectorForksSchedule(
final ValidatorSelectorConfig initialFork, final List<ValidatorSelectorConfig> forks) {
this.forks.add(initialFork);
this.forks.addAll(forks);
}
public Optional<ValidatorSelectorConfig> getFork(final long blockNumber) {
for (final ValidatorSelectorConfig f : forks) {
if (blockNumber >= f.getBlock()) {
return Optional.of(f);
}
}
return Optional.empty();
}
}

@ -66,7 +66,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
getPresetHeaderBuilder(2, proposerNodeKey, validators, parentHeader).buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -87,7 +87,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
getPresetHeaderBuilder(2, proposerNodeKey, emptyList(), parentHeader).buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -113,7 +113,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
.buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -136,7 +136,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
.buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -159,7 +159,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
.buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -186,7 +186,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
.buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -213,7 +213,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
.buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -236,7 +236,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
.buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -257,7 +257,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
getPresetHeaderBuilder(2, proposerNodeKey, validators, null).buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -281,7 +281,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
.buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(
@ -304,7 +304,7 @@ public class QbftBlockHeaderValidationRulesetFactoryTest {
.buildHeader();
final BlockHeaderValidator validator =
new QbftBlockHeaderValidationRulesetFactory(false).blockHeaderValidator(5).build();
new QbftBlockHeaderValidationRulesetFactory().blockHeaderValidator(5, false).build();
assertThat(
validator.validateHeader(

@ -21,12 +21,17 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.common.bft.BftExtraData;
import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorSelectorConfig;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorSelectorForksSchedule;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
@ -40,6 +45,12 @@ public class QbftBlockCreatorFactoryTest {
final MiningParameters miningParams = mock(MiningParameters.class);
when(miningParams.getExtraData()).thenReturn(Bytes.wrap("Qbft tests".getBytes(UTF_8)));
final ValidatorSelectorConfig genesisFork = ValidatorSelectorConfig.createBlockConfig(0);
final ValidatorSelectorConfig contractFork =
ValidatorSelectorConfig.createContractConfig(2, "");
final ValidatorSelectorForksSchedule qbftForksSchedule =
new ValidatorSelectorForksSchedule(genesisFork, List.of(contractFork));
qbftBlockCreatorFactory =
new QbftBlockCreatorFactory(
mock(AbstractPendingTransactionsSorter.class),
@ -49,12 +60,15 @@ public class QbftBlockCreatorFactoryTest {
mock(Address.class),
mock(Address.class),
extraDataCodec,
true); // extraDataWithRoundInformationOnly
qbftForksSchedule);
}
@Test
public void extraDataWithoutValidatorsAndVoteIsCreated() {
final Bytes encodedExtraData = qbftBlockCreatorFactory.createExtraData(3, null);
public void contractValidatorModeCreatesExtraDataWithoutValidatorsAndVote() {
final BlockHeader parentHeader = mock(BlockHeader.class);
when(parentHeader.getNumber()).thenReturn(1L);
final Bytes encodedExtraData = qbftBlockCreatorFactory.createExtraData(3, parentHeader);
final BftExtraData bftExtraData = extraDataCodec.decodeRaw(encodedExtraData);
assertThat(bftExtraData.getValidators()).isEmpty();

@ -0,0 +1,181 @@
/*
* 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.consensus.qbft.validator;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.common.validator.VoteProvider;
import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.AddressHelpers;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Hash;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ForkingValidatorProviderTest {
private static final Address CONTRACT_ADDRESS_1 = Address.fromHexString("0x888");
private static final Address CONTRACT_ADDRESS_2 = Address.fromHexString("0x999");
private static final List<Address> BLOCK_ADDRESSES =
List.of(Address.fromHexString("1"), Address.fromHexString("2"));
private static final List<Address> CONTRACT_ADDRESSES_1 =
List.of(Address.fromHexString("3"), Address.fromHexString("4"));
private static final List<Address> CONTRACT_ADDRESSES_2 =
List.of(Address.fromHexString("5"), Address.fromHexString("6"), Address.fromHexString("7"));
@Mock private BlockValidatorProvider blockValidatorProvider;
@Mock private TransactionValidatorProvider contractValidatorProvider;
private MutableBlockchain blockChain;
private BlockHeader genesisHeader;
private BlockHeader header1;
private BlockHeader header2;
private final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
@Before
public void setup() {
headerBuilder.extraData(Bytes.wrap(new byte[32]));
Block genesisBlock = createEmptyBlock(0, Hash.ZERO);
Block block_1 = createEmptyBlock(1, genesisBlock.getHeader().getHash());
Block block_2 = createEmptyBlock(2, block_1.getHeader().getHash());
genesisHeader = genesisBlock.getHeader();
header1 = block_1.getHeader();
header2 = block_2.getHeader();
blockChain = createInMemoryBlockchain(genesisBlock);
blockChain.appendBlock(block_1, emptyList());
blockChain.appendBlock(block_2, emptyList());
when(blockValidatorProvider.getValidatorsForBlock(any())).thenReturn(BLOCK_ADDRESSES);
when(contractValidatorProvider.getValidatorsForBlock(header1)).thenReturn(CONTRACT_ADDRESSES_1);
when(contractValidatorProvider.getValidatorsForBlock(header2)).thenReturn(CONTRACT_ADDRESSES_2);
}
private Block createEmptyBlock(final long blockNumber, final Hash parentHash) {
headerBuilder.number(blockNumber).parentHash(parentHash).coinbase(AddressHelpers.ofValue(0));
return new Block(headerBuilder.buildHeader(), new BlockBody(emptyList(), emptyList()));
}
@Test
public void usesInitialValidatorProviderWhenNoForks() {
final ValidatorSelectorForksSchedule forksSchedule =
new ValidatorSelectorForksSchedule(createBlockFork(0), Collections.emptyList());
final ForkingValidatorProvider validatorProvider =
new ForkingValidatorProvider(
blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider);
when(blockValidatorProvider.getValidatorsAtHead()).thenReturn(BLOCK_ADDRESSES);
when(blockValidatorProvider.getValidatorsAfterBlock(header1)).thenReturn(BLOCK_ADDRESSES);
assertThat(validatorProvider.getValidatorsAtHead()).isEqualTo(BLOCK_ADDRESSES);
assertThat(validatorProvider.getValidatorsForBlock(header1)).isEqualTo(BLOCK_ADDRESSES);
assertThat(validatorProvider.getValidatorsAfterBlock(header1)).isEqualTo(BLOCK_ADDRESSES);
}
@Test
public void migratesFromBlockToContractValidatorProvider() {
final ValidatorSelectorForksSchedule forksSchedule =
new ValidatorSelectorForksSchedule(
createBlockFork(0), List.of(createContractFork(1L, CONTRACT_ADDRESS_1)));
final ForkingValidatorProvider validatorProvider =
new ForkingValidatorProvider(
blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider);
assertThat(validatorProvider.getValidatorsForBlock(genesisHeader)).isEqualTo(BLOCK_ADDRESSES);
assertThat(validatorProvider.getValidatorsForBlock(header1)).isEqualTo(CONTRACT_ADDRESSES_1);
}
@Test
public void migratesFromContractToBlockValidatorProvider() {
final ValidatorSelectorForksSchedule forksSchedule =
new ValidatorSelectorForksSchedule(
createContractFork(0, CONTRACT_ADDRESS_1), List.of(createBlockFork(1L)));
final ForkingValidatorProvider validatorProvider =
new ForkingValidatorProvider(
blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider);
when(contractValidatorProvider.getValidatorsForBlock(genesisHeader))
.thenReturn(CONTRACT_ADDRESSES_1);
assertThat(validatorProvider.getValidatorsForBlock(genesisHeader))
.isEqualTo(CONTRACT_ADDRESSES_1);
assertThat(validatorProvider.getValidatorsForBlock(header1)).isEqualTo(CONTRACT_ADDRESSES_1);
assertThat(validatorProvider.getValidatorsForBlock(header2)).isEqualTo(BLOCK_ADDRESSES);
}
@Test
public void migratesFromContractToContractValidatorProvider() {
final ValidatorSelectorForksSchedule forksSchedule =
new ValidatorSelectorForksSchedule(
createBlockFork(0),
List.of(
createContractFork(1L, CONTRACT_ADDRESS_1),
createContractFork(2L, CONTRACT_ADDRESS_2)));
final ForkingValidatorProvider validatorProvider =
new ForkingValidatorProvider(
blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider);
assertThat(validatorProvider.getValidatorsForBlock(genesisHeader)).isEqualTo(BLOCK_ADDRESSES);
assertThat(validatorProvider.getValidatorsForBlock(header1)).isEqualTo(CONTRACT_ADDRESSES_1);
assertThat(validatorProvider.getValidatorsForBlock(header2)).isEqualTo(CONTRACT_ADDRESSES_2);
}
@Test
public void voteProviderIsDelegatesToHeadFork() {
final ValidatorSelectorForksSchedule forksSchedule =
new ValidatorSelectorForksSchedule(
createBlockFork(0),
List.of(createBlockFork(1), createContractFork(2, CONTRACT_ADDRESS_1)));
final ForkingValidatorProvider validatorProvider =
new ForkingValidatorProvider(
blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider);
final VoteProvider voteProvider = Mockito.mock(VoteProvider.class);
when(contractValidatorProvider.getVoteProvider()).thenReturn(Optional.of(voteProvider));
assertThat(validatorProvider.getVoteProvider()).contains(voteProvider);
}
private ValidatorSelectorConfig createContractFork(
final long block, final Address contractAddress) {
return ValidatorSelectorConfig.createContractConfig(block, contractAddress.toHexString());
}
private ValidatorSelectorConfig createBlockFork(final long block) {
return ValidatorSelectorConfig.createBlockConfig(block);
}
}

@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -42,16 +43,18 @@ import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.Function;
public class ValidatorContractControllerTest {
public static final String GET_VALIDATORS_FUNCTION_RESULT =
private static final String GET_VALIDATORS_FUNCTION_RESULT =
"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df";
public static final Address VALIDATOR_ADDRESS =
private static final Address VALIDATOR_ADDRESS =
Address.fromHexString("0xeac51e3fe1afc9894f0dfeab8ceb471899b932df");
final Address CONTRACT_ADDRESS = Address.fromHexString("1");
private static final Address CONTRACT_ADDRESS = Address.fromHexString("1");
private final TransactionSimulator transactionSimulator =
Mockito.mock(TransactionSimulator.class);
private final Transaction transaction = Mockito.mock(Transaction.class);
private CallParameter callParameter;
private ValidatorSelectorConfig genesisFork;
private ValidatorSelectorForksSchedule qbftForksSchedule;
@Before
public void setup() {
@ -62,6 +65,8 @@ public class ValidatorContractControllerTest {
List.of(new TypeReference<DynamicArray<org.web3j.abi.datatypes.Address>>() {}));
final Bytes payload = Bytes.fromHexString(FunctionEncoder.encode(getValidatorsFunction));
callParameter = new CallParameter(null, CONTRACT_ADDRESS, -1, null, null, payload);
genesisFork = ValidatorSelectorConfig.createContractConfig(0, CONTRACT_ADDRESS.toHexString());
qbftForksSchedule = new ValidatorSelectorForksSchedule(genesisFork, Collections.emptyList());
}
@Test
@ -79,7 +84,7 @@ public class ValidatorContractControllerTest {
when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result));
final ValidatorContractController validatorContractController =
new ValidatorContractController(CONTRACT_ADDRESS, transactionSimulator);
new ValidatorContractController(transactionSimulator, qbftForksSchedule);
final Collection<Address> validators = validatorContractController.getValidators(1);
assertThat(validators).containsExactly(VALIDATOR_ADDRESS);
}
@ -95,7 +100,7 @@ public class ValidatorContractControllerTest {
when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result));
final ValidatorContractController validatorContractController =
new ValidatorContractController(CONTRACT_ADDRESS, transactionSimulator);
new ValidatorContractController(transactionSimulator, qbftForksSchedule);
Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1))
.hasMessage("Failed validator smart contract call");
}
@ -114,7 +119,7 @@ public class ValidatorContractControllerTest {
when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result));
final ValidatorContractController validatorContractController =
new ValidatorContractController(CONTRACT_ADDRESS, transactionSimulator);
new ValidatorContractController(transactionSimulator, qbftForksSchedule);
Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1))
.hasMessage("Failed validator smart contract call");
}
@ -124,7 +129,7 @@ public class ValidatorContractControllerTest {
when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.empty());
final ValidatorContractController validatorContractController =
new ValidatorContractController(CONTRACT_ADDRESS, transactionSimulator);
new ValidatorContractController(transactionSimulator, qbftForksSchedule);
Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1))
.hasMessage("Failed validator smart contract call");
}

@ -0,0 +1,50 @@
/*
* 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.consensus.qbft.validator;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
public class ValidatorSelectorForksScheduleTest {
@Test
public void retrievesGenesisFork() {
final ValidatorSelectorConfig genesisFork = ValidatorSelectorConfig.createBlockConfig(0);
final ValidatorSelectorForksSchedule schedule =
new ValidatorSelectorForksSchedule(genesisFork, Collections.emptyList());
assertThat(schedule.getFork(0)).contains(genesisFork);
assertThat(schedule.getFork(1)).contains(genesisFork);
}
@Test
public void retrievesLatestFork() {
final ValidatorSelectorConfig genesisFork = ValidatorSelectorConfig.createBlockConfig(0);
final ValidatorSelectorConfig contractFork =
ValidatorSelectorConfig.createContractConfig(1, "0x1");
final ValidatorSelectorForksSchedule schedule =
new ValidatorSelectorForksSchedule(genesisFork, List.of(contractFork));
assertThat(schedule.getFork(0)).contains(genesisFork);
assertThat(schedule.getFork(1)).contains(contractFork);
assertThat(schedule.getFork(2)).contains(contractFork);
assertThat(schedule.getFork(3)).contains(contractFork);
}
}
Loading…
Cancel
Save