mirror of https://github.com/hyperledger/besu
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
parent
62c4115591
commit
a2fd2147cd
@ -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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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" |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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"); |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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…
Reference in new issue