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