diff --git a/CHANGELOG.md b/CHANGELOG.md index 585c12f943..8f228fc329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java index bda705542c..3a0ed9674b 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -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 bftForks) { + private ValidatorSelectorForksSchedule createQbftForksSchedule( + final GenesisConfigOptions configOptions) { + final ValidatorSelectorConfig initialFork = + ValidatorSelectorConfig.fromQbftConfig(configOptions.getQbftConfigOptions()); + final List validatorSelectionForks = + convertToValidatorSelectionConfig( + genesisConfig.getConfigOptions().getTransitions().getQbftForks()); + return new ValidatorSelectorForksSchedule(initialFork, validatorSelectionForks); + } + + private BftValidatorOverrides convertBftForks(final List bftForks) { final Map> result = new HashMap<>(); for (final BftFork fork : bftForks) { @@ -320,6 +328,14 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder { return new BftValidatorOverrides(result); } + private List convertToValidatorSelectionConfig( + final List 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 -> diff --git a/config/src/main/java/org/hyperledger/besu/config/BftFork.java b/config/src/main/java/org/hyperledger/besu/config/BftFork.java index 8d4a2a3701..cb250a3dc1 100644 --- a/config/src/main/java/org/hyperledger/besu/config/BftFork.java +++ b/config/src/main/java/org/hyperledger/besu/config/BftFork.java @@ -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()); diff --git a/config/src/main/java/org/hyperledger/besu/config/QbftConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/QbftConfigOptions.java index 6ed2975bf8..df8d672c0d 100644 --- a/config/src/main/java/org/hyperledger/besu/config/QbftConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/QbftConfigOptions.java @@ -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 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 map = super.asMap(); final ImmutableMap.Builder builder = ImmutableMap.builder(); builder.putAll(map); - builder.put("validatorcontractaddress", getValidatorContractAddress()); + builder.put(VALIDATOR_CONTRACT_ADDRESS, getValidatorContractAddress()); return builder.build(); } } diff --git a/config/src/main/java/org/hyperledger/besu/config/QbftFork.java b/config/src/main/java/org/hyperledger/besu/config/QbftFork.java new file mode 100644 index 0000000000..c6c4c1a1e0 --- /dev/null +++ b/config/src/main/java/org/hyperledger/besu/config/QbftFork.java @@ -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 getValidatorSelectionMode() { + final Optional 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 getValidatorContractAddress() { + return JsonUtil.getString(forkConfigRoot, VALIDATOR_CONTRACT_ADDRESS_KEY); + } +} diff --git a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java index 3178112156..66dbb5bf47 100644 --- a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java @@ -56,6 +56,8 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions { private OptionalInt stackSizeLimit = OptionalInt.empty(); private final OptionalLong ecip1017EraRounds = OptionalLong.empty(); private Optional 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; + } } diff --git a/config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java index 2d0bb9a059..f67bf34fa1 100644 --- a/config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java @@ -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 getIbftForks() { - return getBftForks("ibft2"); + return getBftForks("ibft2", BftFork::new); } - public List getQbftForks() { - return getBftForks("qbft"); + public List getQbftForks() { + return getBftForks("qbft", QbftFork::new); } - private List getBftForks(final String fieldKey) { + private List getBftForks( + final String fieldKey, final Function forkConstructor) { final Optional bftForksNode = JsonUtil.getArrayNode(customForkConfigRoot, fieldKey); if (bftForksNode.isEmpty()) { return emptyList(); } - final List bftForks = Lists.newArrayList(); + final List 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); diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java index df44fc8cb1..9348eb926e 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java @@ -60,7 +60,7 @@ public class BftProtocolSchedule { bftExtraDataCodec, config.getBftConfigOptions().getBlockRewardWei())); - final Supplier> forks; + final Supplier> forks; if (config.isIbft2()) { forks = () -> config.getTransitions().getIbftForks(); } else { diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/BlockValidatorProvider.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/BlockValidatorProvider.java index 7af97f0b24..de484eb407 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/BlockValidatorProvider.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/BlockValidatorProvider.java @@ -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
getValidatorsForBlock(final BlockHeader header) { - return blockInterface.validatorsInBlock(header); + return blockInterface.validatorsInBlock(header).stream().sorted().collect(Collectors.toList()); } @Override diff --git a/consensus/qbft/build.gradle b/consensus/qbft/build.gradle index 7364409180..e19f390b2c 100644 --- a/consensus/qbft/build.gradle +++ b/consensus/qbft/build.gradle @@ -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') diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index bbd46779ae..4d2b17b244 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -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 genesisFile = Optional.empty(); private List nodeParams = Collections.emptyList(); + private List 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 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 qbftForks) { final MiningParameters miningParams = new MiningParameters.Builder() @@ -369,11 +384,22 @@ public class TestContextBuilder { .build(); final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions(); + final Map 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 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); + } } diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestTransitions.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestTransitions.java new file mode 100644 index 0000000000..c1581f737b --- /dev/null +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestTransitions.java @@ -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 forks; + + public TestTransitions(final List forks) { + super(JsonUtil.createEmptyObjectNode()); + this.forks = forks; + } + + @Override + public List getQbftForks() { + return forks; + } +} diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java index 41d83b5197..39be31d6fc 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java @@ -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 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
block0Addresses = List.of(NODE_ADDRESS); + final List
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()))); + } } diff --git a/consensus/qbft/src/integration-test/resources/genesis_migrating_validator_contract.json b/consensus/qbft/src/integration-test/resources/genesis_migrating_validator_contract.json new file mode 100644 index 0000000000..046049c805 --- /dev/null +++ b/consensus/qbft/src/integration-test/resources/genesis_migrating_validator_contract.json @@ -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" +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java index a47c68da8f..466c019fb3 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java @@ -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()); } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolSchedule.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolSchedule.java new file mode 100644 index 0000000000..e516fd91b3 --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolSchedule.java @@ -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 blockHeaderRuleset, + final BftExtraDataCodec bftExtraDataCodec) { + final Map> 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> 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 blockHeaderRuleset, + final BftExtraDataCodec bftExtraDataCodec) { + return create( + config, + PrivacyParameters.DEFAULT, + isRevertReasonEnabled, + blockHeaderRuleset, + bftExtraDataCodec); + } + + public static ProtocolSchedule create( + final GenesisConfigOptions config, + final BiFunction 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 blockHeaderRuleset, + final BftExtraDataCodec bftExtraDataCodec, + final Optional 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; + } +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java index 429a15ac04..9e799e2048 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java @@ -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 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( diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProvider.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProvider.java new file mode 100644 index 0000000000..12661de8b1 --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProvider.java @@ -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
getValidatorsAtHead() { + final BlockHeader header = blockchain.getChainHeadHeader(); + return getValidators(header.getNumber(), ValidatorProvider::getValidatorsAtHead); + } + + @Override + public Collection
getValidatorsAfterBlock(final BlockHeader header) { + return getValidators(header.getNumber(), p -> p.getValidatorsAfterBlock(header)); + } + + @Override + public Collection
getValidatorsForBlock(final BlockHeader header) { + return getValidators(header.getNumber(), p -> p.getValidatorsForBlock(header)); + } + + @Override + public Optional getVoteProvider() { + return resolveValidatorProvider(blockchain.getChainHeadHeader().getNumber()).getVoteProvider(); + } + + private Collection
getValidators( + final long block, final Function> getValidators) { + final Optional 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 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 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"); + } +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java index 5e6f845701..97c2d91de8 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java @@ -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 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 decodeResult( final TransactionSimulatorResult result, final Function function) { diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorConfig.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorConfig.java new file mode 100644 index 0000000000..9f1062ad43 --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorConfig.java @@ -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 contractAddress; + + private ValidatorSelectorConfig( + final long block, + final VALIDATOR_SELECTION_MODE validatorSelectionMode, + final Optional 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 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 getContractAddress() { + return contractAddress; + } +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorForksSchedule.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorForksSchedule.java new file mode 100644 index 0000000000..15f751f6f9 --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorForksSchedule.java @@ -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 forks = + new TreeSet<>(Comparator.comparing(ValidatorSelectorConfig::getBlock).reversed()); + + public ValidatorSelectorForksSchedule( + final ValidatorSelectorConfig initialFork, final List forks) { + this.forks.add(initialFork); + this.forks.addAll(forks); + } + + public Optional getFork(final long blockNumber) { + for (final ValidatorSelectorConfig f : forks) { + if (blockNumber >= f.getBlock()) { + return Optional.of(f); + } + } + + return Optional.empty(); + } +} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java index 362473ad70..68003ca21d 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java @@ -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( diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java index b1129d18ff..bb3a5f97d1 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java @@ -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(); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java new file mode 100644 index 0000000000..6ca14a3a5e --- /dev/null +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java @@ -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
BLOCK_ADDRESSES = + List.of(Address.fromHexString("1"), Address.fromHexString("2")); + private static final List
CONTRACT_ADDRESSES_1 = + List.of(Address.fromHexString("3"), Address.fromHexString("4")); + private static final List
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); + } +} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java index 5f2a85f75c..82b096e1ab 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractControllerTest.java @@ -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>() {})); 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
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"); } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorForksScheduleTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorForksScheduleTest.java new file mode 100644 index 0000000000..08560edf77 --- /dev/null +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorSelectorForksScheduleTest.java @@ -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); + } +}