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 9fc85f65340..e5aecbe5d53 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -333,7 +333,7 @@ protected BftContext createConsensusContext( new TransactionSimulator(blockchain, worldStateArchive, protocolSchedule); transactionValidatorProvider = new TransactionValidatorProvider( - blockchain, new ValidatorContractController(transactionSimulator, qbftForksSchedule)); + blockchain, new ValidatorContractController(transactionSimulator), qbftForksSchedule); final ValidatorProvider validatorProvider = new ForkingValidatorProvider( 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 5586dea301f..7593ca32344 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 @@ -32,6 +32,7 @@ public class BlockValidatorProvider implements ValidatorProvider { private final VoteTallyCache voteTallyCache; private final VoteProvider voteProvider; private final BlockInterface blockInterface; + private final Optional bftValidatorOverrides; public static BlockValidatorProvider forkingValidatorProvider( final Blockchain blockchain, @@ -67,6 +68,7 @@ private BlockValidatorProvider( : new VoteTallyCache(blockchain, voteTallyUpdater, epochManager, blockInterface); this.voteProvider = new BlockVoteProvider(voteTallyCache, voteProposer); this.blockInterface = blockInterface; + this.bftValidatorOverrides = bftValidatorOverrides; } @Override @@ -88,4 +90,8 @@ public Collection
getValidatorsForBlock(final BlockHeader header) { public Optional getVoteProviderAtHead() { return Optional.of(voteProvider); } + + public boolean hasValidatorOverridesForBlockNumber(final long blockNumber) { + return bftValidatorOverrides.map(bvo -> bvo.getForBlock(blockNumber).isPresent()).orElse(false); + } } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/BlockValidatorProviderTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/BlockValidatorProviderTest.java new file mode 100644 index 00000000000..c8c1a3873be --- /dev/null +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/BlockValidatorProviderTest.java @@ -0,0 +1,86 @@ +/* + * Copyright Hyperledger Besu contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.common.validator.blockbased; + +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.consensus.common.BftValidatorOverrides; +import org.hyperledger.besu.consensus.common.BlockInterface; +import org.hyperledger.besu.consensus.common.EpochManager; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.chain.Blockchain; + +import java.util.List; +import java.util.Map; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class BlockValidatorProviderTest { + + @Mock private BlockInterface blockInterface; + @Mock private Blockchain blockchain; + @Mock private EpochManager epochManager; + + @Test + public void nonForkingValidatorProviderHasNoOverrides() { + final BlockValidatorProvider blockValidatorProvider = + BlockValidatorProvider.nonForkingValidatorProvider( + blockchain, epochManager, blockInterface); + + assertThat(blockValidatorProvider.hasValidatorOverridesForBlockNumber(0)).isFalse(); + } + + @Test + public void forkingValidatorProviderHasNoOverrides() { + final BlockValidatorProvider blockValidatorProvider = + BlockValidatorProvider.forkingValidatorProvider( + blockchain, epochManager, blockInterface, new BftValidatorOverrides(emptyMap())); + + assertThat(blockValidatorProvider.hasValidatorOverridesForBlockNumber(0)).isFalse(); + } + + @Test + public void forkingValidatorProviderHasOverridesForBlock1() { + final Map> overriddenValidators = + Map.of(1L, List.of(Address.fromHexString("0"))); + final BftValidatorOverrides bftValidatorOverrides = + new BftValidatorOverrides(overriddenValidators); + final BlockValidatorProvider blockValidatorProvider = + BlockValidatorProvider.forkingValidatorProvider( + blockchain, epochManager, blockInterface, bftValidatorOverrides); + + SoftAssertions.assertSoftly( + (softly) -> { + softly + .assertThat(blockValidatorProvider.hasValidatorOverridesForBlockNumber(0)) + .as("Block 0 should have no overridden validators") + .isFalse(); + softly + .assertThat(blockValidatorProvider.hasValidatorOverridesForBlockNumber(1)) + .as("Block 1 should have some overridden validators") + .isTrue(); + softly + .assertThat(blockValidatorProvider.hasValidatorOverridesForBlockNumber(2)) + .as("Block 2 should have no overridden validators") + .isFalse(); + }); + } +} 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 0ffd05c866f..8f8ec87ef6c 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,6 +19,7 @@ import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; import static org.mockito.Mockito.mock; +import org.hyperledger.besu.config.BftFork; import org.hyperledger.besu.config.JsonQbftConfigOptions; import org.hyperledger.besu.config.JsonUtil; import org.hyperledger.besu.config.QbftConfigOptions; @@ -108,6 +109,7 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -413,8 +415,7 @@ private static ControllerAndState createControllerAndFinalState( QbftProtocolSchedule.create( genesisConfigOptions, forksSchedule, BFT_EXTRA_DATA_ENCODER, EvmConfiguration.DEFAULT); - final BftValidatorOverrides validatorOverrides = - new BftValidatorOverrides(Collections.emptyMap()); + final BftValidatorOverrides validatorOverrides = convertBftForks(qbftForks); final TransactionSimulator transactionSimulator = new TransactionSimulator(blockChain, worldStateArchive, protocolSchedule); @@ -423,7 +424,7 @@ private static ControllerAndState createControllerAndFinalState( blockChain, epochManager, blockInterface, validatorOverrides); final TransactionValidatorProvider transactionValidatorProvider = new TransactionValidatorProvider( - blockChain, new ValidatorContractController(transactionSimulator, forksSchedule)); + blockChain, new ValidatorContractController(transactionSimulator), forksSchedule); final ValidatorProvider validatorProvider = new ForkingValidatorProvider( blockChain, forksSchedule, blockValidatorProvider, transactionValidatorProvider); @@ -532,4 +533,21 @@ private static QbftConfigOptions createGenesisConfig(final boolean useValidatorC } return qbftConfigOptions; } + + private static BftValidatorOverrides convertBftForks(final List bftForks) { + final Map> result = new HashMap<>(); + + for (final BftFork fork : bftForks) { + fork.getValidators() + .ifPresent( + validators -> + result.put( + fork.getForkBlock(), + validators.stream() + .map(Address::fromHexString) + .collect(Collectors.toList()))); + } + + return new BftValidatorOverrides(result); + } } 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 6c0a65216ec..344c37f4425 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 @@ -43,6 +43,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.io.Resources; import org.apache.tuweni.bytes.Bytes32; import org.junit.Before; @@ -60,6 +61,9 @@ public class ValidatorContractTest { private static final Bytes32 NODE_2_PRIVATE_KEY = Bytes32.fromHexString("0xa3bdf521b0f286a80918c4b67000dfd2a2bdef97e94d268016ef9ec86648eac4"); + final Address NEW_VALIDATOR_CONTRACT_ADDRESS = + Address.fromHexString("0x0000000000000000000000000000000000009999"); + private TestClock clock; private final QbftExtraDataCodec extraDataCodec = new QbftExtraDataCodec(); @@ -96,9 +100,11 @@ public void transitionsFromBlockHeaderModeToValidatorContractMode() { List.of(createContractFork(1, TestContextBuilder.VALIDATOR_CONTRACT_ADDRESS)); final TestContext context = new TestContextBuilder() - .indexOfFirstLocallyProposedBlock(0) + .indexOfFirstLocallyProposedBlock(1) .nodeParams( - List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)))) + List.of( + new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)), + new NodeParams(NODE_2_ADDRESS, NodeKeyUtils.createFrom(NODE_2_PRIVATE_KEY)))) .clock(TestClock.fixed()) .genesisFile( Resources.getResource("genesis_migrating_validator_contract.json").getFile()) @@ -112,7 +118,7 @@ public void transitionsFromBlockHeaderModeToValidatorContractMode() { final List
block1Addresses = Stream.of(NODE_ADDRESS, NODE_2_ADDRESS).sorted().collect(Collectors.toList()); - createNewBlockAsProposer(context, 1); + remotePeerProposesNewBlock(context, 1L); final ValidatorProvider validatorProvider = context.getValidatorProvider(); final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); @@ -127,6 +133,46 @@ public void transitionsFromBlockHeaderModeToValidatorContractMode() { assertThat(extraDataCodec.decode(block1).getVote()).isEmpty(); } + @Test + public void transitionsFromValidatorContractModeToValidatorContractMode() { + final List qbftForks = List.of(createContractFork(1, NEW_VALIDATOR_CONTRACT_ADDRESS)); + + final TestContext context = + new TestContextBuilder() + .indexOfFirstLocallyProposedBlock(1) + .nodeParams( + List.of( + new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)), + new NodeParams(NODE_2_ADDRESS, NodeKeyUtils.createFrom(NODE_2_PRIVATE_KEY)))) + .clock(clock) + .genesisFile(Resources.getResource("genesis_validator_contract.json").getFile()) + .useValidatorContract(true) + .qbftForks(qbftForks) + .buildAndStart(); + + // block 0 uses validator contract with 1 validator + // block 1 uses validator contract with 2 validators + final List
block0Addresses = List.of(NODE_ADDRESS); + final List
block1Addresses = + Stream.of(NODE_ADDRESS, NODE_2_ADDRESS).sorted().collect(Collectors.toList()); + + remotePeerProposesNewBlock(context, 1L); + + final ValidatorProvider validatorProvider = context.getValidatorProvider(); + final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); + final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get(); + + // contract block extra data cannot contain validators or vote + assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).isEqualTo(block0Addresses); + assertThat(extraDataCodec.decode(genesisBlock).getValidators()).isEmpty(); + assertThat(extraDataCodec.decode(genesisBlock).getVote()).isEmpty(); + + // 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(); + } + @Test public void transitionsFromValidatorContractModeToBlockHeaderMode() { final List qbftForks = List.of(createBlockHeaderFork(1)); @@ -136,15 +182,14 @@ public void transitionsFromValidatorContractModeToBlockHeaderMode() { .nodeParams( List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)))) .clock(clock) - .genesisFile( - Resources.getResource("genesis_migrating_validator_blockheader.json").getFile()) + .genesisFile(Resources.getResource("genesis_validator_contract.json").getFile()) .useValidatorContract(true) .qbftForks(qbftForks) .buildAndStart(); // block 0 uses validator contract with 1 validator - // block 1 onwards uses block header voting (which reuses previous block's validators upon - // switching) + // block 1 onwards uses block header voting (this transitioning block reuses the previous + // block's validators) final List
block0Addresses = Stream.of(NODE_ADDRESS).collect(Collectors.toList()); createNewBlockAsProposer(context, 1); @@ -191,8 +236,9 @@ public void transitionsFromBlockHeaderModeToValidatorContractModeThenBack() { final List
block1Addresses = Stream.of(NODE_ADDRESS, NODE_2_ADDRESS).sorted().collect(Collectors.toList()); - createNewBlockAsProposer(context, 1L); - remotePeerProposesNewBlock(context, 2L); + remotePeerProposesNewBlock(context, 1L); + clock.step(TestContextBuilder.BLOCK_TIMER_SEC, SECONDS); + createNewBlockAsProposer(context, 2L); final ValidatorProvider validatorProvider = context.getValidatorProvider(); final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); @@ -213,11 +259,119 @@ public void transitionsFromBlockHeaderModeToValidatorContractModeThenBack() { .containsExactly(NODE_2_ADDRESS, NODE_ADDRESS); } + @Test + public void transitionsFromValidatorContractModeToBlockHeaderModeThenBackToNewContract() { + final List qbftForks = + List.of(createBlockHeaderFork(1), createContractFork(2, NEW_VALIDATOR_CONTRACT_ADDRESS)); + + final TestContext context = + new TestContextBuilder() + .indexOfFirstLocallyProposedBlock(1) + .nodeParams( + List.of( + new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)), + new NodeParams(NODE_2_ADDRESS, NodeKeyUtils.createFrom(NODE_2_PRIVATE_KEY)))) + .clock(clock) + .genesisFile(Resources.getResource("genesis_validator_contract.json").getFile()) + .useValidatorContract(true) + .qbftForks(qbftForks) + .buildAndStart(); + + // block 0 uses validator contract with 1 validator + // block 1 uses block header voting with block 0's validators + // block 2 uses validator contract with 2 validators + final List
block0Addresses = List.of(NODE_ADDRESS); + final List
block2Addresses = + Stream.of(NODE_ADDRESS, NODE_2_ADDRESS).sorted().collect(Collectors.toList()); + + createNewBlockAsProposer(context, 1L); + clock.step(TestContextBuilder.BLOCK_TIMER_SEC, SECONDS); + remotePeerProposesNewBlock(context, 2L); + + final ValidatorProvider validatorProvider = context.getValidatorProvider(); + final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); + final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get(); + final BlockHeader block2 = context.getBlockchain().getBlockHeader(2).get(); + + // contract block extra data cannot contain validators or vote + assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).isEqualTo(block0Addresses); + assertThat(extraDataCodec.decode(genesisBlock).getValidators()).isEmpty(); + assertThat(extraDataCodec.decode(genesisBlock).getVote()).isEmpty(); + + assertThat(validatorProvider.getValidatorsForBlock(block1)).isEqualTo(block0Addresses); + assertThat(extraDataCodec.decode(block1).getValidators()).containsExactly(NODE_ADDRESS); + + // contract block extra data cannot contain validators or vote + assertThat(validatorProvider.getValidatorsForBlock(block2)).isEqualTo(block2Addresses); + assertThat(extraDataCodec.decode(block2).getValidators()).isEmpty(); + assertThat(extraDataCodec.decode(block2).getVote()).isEmpty(); + } + + @Test + public void + transitionsFromValidatorContractModeToBlockHeaderModeWithOverriddenValidatorsThenBackToNewContract() { + final List qbftForks = + List.of( + createBlockHeaderForkWithValidators(1, List.of(NODE_2_ADDRESS)), + createContractFork(2, NEW_VALIDATOR_CONTRACT_ADDRESS)); + + final TestContext context = + new TestContextBuilder() + .indexOfFirstLocallyProposedBlock(0) + .nodeParams( + List.of( + new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)), + new NodeParams(NODE_2_ADDRESS, NodeKeyUtils.createFrom(NODE_2_PRIVATE_KEY)))) + .clock(clock) + .genesisFile(Resources.getResource("genesis_validator_contract.json").getFile()) + .useValidatorContract(true) + .qbftForks(qbftForks) + .buildAndStart(); + + // block 0 uses validator contract with 1 validator + // block 1 uses block header voting with overridden validator + // block 2 uses validator contract with 2 validators + final List
block0Addresses = List.of(NODE_ADDRESS); + final List
block1Addresses = List.of(NODE_2_ADDRESS); + final List
block2Addresses = + Stream.of(NODE_ADDRESS, NODE_2_ADDRESS).sorted().collect(Collectors.toList()); + + createNewBlockAsProposer(context, 1L); + clock.step(TestContextBuilder.BLOCK_TIMER_SEC, SECONDS); + remotePeerProposesNewBlock(context, 2L); + + final ValidatorProvider validatorProvider = context.getValidatorProvider(); + final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); + final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get(); + final BlockHeader block2 = context.getBlockchain().getBlockHeader(2).get(); + + // contract block extra data cannot contain validators or vote + assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).isEqualTo(block0Addresses); + assertThat(extraDataCodec.decode(genesisBlock).getValidators()).isEmpty(); + assertThat(extraDataCodec.decode(genesisBlock).getVote()).isEmpty(); + + assertThat(validatorProvider.getValidatorsForBlock(block1)).isEqualTo(block1Addresses); + assertThat(extraDataCodec.decode(block1).getValidators()).containsExactly(NODE_2_ADDRESS); + + // contract block extra data cannot contain validators or vote + assertThat(validatorProvider.getValidatorsForBlock(block2)).isEqualTo(block2Addresses); + assertThat(extraDataCodec.decode(block2).getValidators()).isEmpty(); + assertThat(extraDataCodec.decode(block2).getVote()).isEmpty(); + } + private void createNewBlockAsProposer(final TestContext context, final long blockNumber) { ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(blockNumber, 0); + // trigger proposal context.getController().handleBlockTimerExpiry(new BlockTimerExpiry(roundId)); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(blockNumber); + + // peers commit proposed block + Block proposedBlock = + context.createBlockForProposalFromChainHead(clock.instant().getEpochSecond()); + RoundSpecificPeers peers = context.roundSpecificPeers(roundId); + peers.commitForNonProposing(roundId, proposedBlock); + + assertThat(context.getCurrentChainHeight()).isEqualTo(blockNumber); context .getController() .handleNewBlockEvent(new NewChainHead(context.getBlockchain().getChainHeadHeader())); @@ -228,15 +382,12 @@ private void remotePeerProposesNewBlock(final TestContext context, final long bl RoundSpecificPeers peers = context.roundSpecificPeers(roundId); ValidatorPeer remoteProposer = peers.getProposer(); - clock.step( - TestContextBuilder.BLOCK_TIMER_SEC, - SECONDS); // avoid failing the TimestampMoreRecentThanParent validation rule final Block blockToPropose = context.createBlockForProposalFromChainHead( clock.instant().getEpochSecond(), remoteProposer.getNodeAddress()); remoteProposer.injectProposal(roundId, blockToPropose); remoteProposer.injectCommit(roundId, blockToPropose); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(blockNumber); + assertThat(context.getCurrentChainHeight()).isEqualTo(blockNumber); context .getController() @@ -264,4 +415,23 @@ private QbftFork createBlockHeaderFork(final long block) { QbftFork.VALIDATOR_SELECTION_MODE_KEY, VALIDATOR_SELECTION_MODE.BLOCKHEADER))); } + + private QbftFork createBlockHeaderForkWithValidators( + final long block, final List
validators) { + + final List jsonValidators = + validators.stream() + .map(v -> TextNode.valueOf(v.toHexString())) + .collect(Collectors.toList()); + + return new QbftFork( + JsonUtil.objectNodeFromMap( + Map.of( + BftFork.FORK_BLOCK_KEY, + block, + QbftFork.VALIDATOR_SELECTION_MODE_KEY, + VALIDATOR_SELECTION_MODE.BLOCKHEADER, + BftFork.VALIDATORS_KEY, + JsonUtil.getObjectMapper().createArrayNode().addAll(jsonValidators)))); + } } diff --git a/consensus/qbft/src/integration-test/resources/genesis_migrating_validator_blockheader.json b/consensus/qbft/src/integration-test/resources/genesis_migrating_validator_blockheader.json deleted file mode 100644 index 44820ec94c2..00000000000 --- a/consensus/qbft/src/integration-test/resources/genesis_migrating_validator_blockheader.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "nonce": "0x0", - "timestamp": "0x0", - "extraData": "0xe5a00000000000000000000000000000000000000000000000000000000000000000c0c080c0", - "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": "0000000000000000000000000000000000000000000000000000000000000001", - "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df" - } - } - }, - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} diff --git a/consensus/qbft/src/integration-test/resources/genesis_validator_contract.json b/consensus/qbft/src/integration-test/resources/genesis_validator_contract.json index 9d90f0b100d..a7efa67fa17 100644 --- a/consensus/qbft/src/integration-test/resources/genesis_validator_contract.json +++ b/consensus/qbft/src/integration-test/resources/genesis_validator_contract.json @@ -1,12 +1,4 @@ { - "config": { - "chainid": 2017, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0 - }, "nonce": "0x0", "timestamp": "0x0", "extraData": "0xe5a00000000000000000000000000000000000000000000000000000000000000000c0c080c0", @@ -35,6 +27,16 @@ "0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000001", "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df" } + }, + "0x0000000000000000000000000000000000009999": { + "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": "000000000000000000000000e98d92560fac3069ccff53ef348ded26a51d4b68", + "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df" + } } }, "number": "0x0", diff --git a/consensus/qbft/src/integration-test/resources/log4j2.xml b/consensus/qbft/src/integration-test/resources/log4j2.xml index c99354fba2f..17267895af5 100644 --- a/consensus/qbft/src/integration-test/resources/log4j2.xml +++ b/consensus/qbft/src/integration-test/resources/log4j2.xml @@ -12,5 +12,7 @@ + + 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 index 354927d47c8..c3674a39cd6 100644 --- 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 @@ -49,18 +49,23 @@ public ForkingValidatorProvider( @Override public Collection
getValidatorsAtHead() { - final BlockHeader header = blockchain.getChainHeadHeader(); - return getValidators(header.getNumber(), ValidatorProvider::getValidatorsAtHead); + final BlockHeader chainHead = blockchain.getChainHeadHeader(); + return getValidatorsAfterBlock(chainHead); } @Override - public Collection
getValidatorsAfterBlock(final BlockHeader header) { - return getValidators(header.getNumber(), p -> p.getValidatorsAfterBlock(header)); + public Collection
getValidatorsAfterBlock(final BlockHeader parentHeader) { + final long nextBlock = parentHeader.getNumber() + 1; + final ValidatorProvider validatorProvider = resolveValidatorProvider(nextBlock); + return getValidators( + validatorProvider, nextBlock, p -> p.getValidatorsAfterBlock(parentHeader)); } @Override public Collection
getValidatorsForBlock(final BlockHeader header) { - return getValidators(header.getNumber(), p -> p.getValidatorsForBlock(header)); + final ValidatorProvider validatorProvider = resolveValidatorProvider(header.getNumber()); + return getValidators( + validatorProvider, header.getNumber(), p -> p.getValidatorsForBlock(header)); } @Override @@ -75,15 +80,19 @@ public Optional getVoteProviderAfterBlock(final BlockHeader header } private Collection
getValidators( - final long block, final Function> getValidators) { - final BftForkSpec forkSpec = forksSchedule.getFork(block); - final ValidatorProvider validatorProvider = resolveValidatorProvider(block); + final ValidatorProvider validatorProvider, + final long blockNumber, + final Function> getValidators) { + + final BftForkSpec forkSpec = forksSchedule.getFork(blockNumber); // 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 (!forkSpec.getConfigOptions().isValidatorContractMode()) { - if (block > 0 && block == forkSpec.getBlock()) { - final long prevBlockNumber = block - 1L; + // unless the validators are being explicitly overridden + if (forkSpec.getConfigOptions().isValidatorBlockHeaderMode() + && !blockValidatorProvider.hasValidatorOverridesForBlockNumber(blockNumber)) { + if (forkSpec.getBlock() > 0 && blockNumber == forkSpec.getBlock()) { + final long prevBlockNumber = blockNumber - 1L; final Optional prevBlockHeader = blockchain.getBlockHeader(prevBlockNumber); if (prevBlockHeader.isPresent()) { return resolveValidatorProvider(prevBlockNumber) diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProvider.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProvider.java index 327cf2706c7..607c0ab4092 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProvider.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProvider.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.consensus.qbft.validator; +import org.hyperledger.besu.config.QbftConfigOptions; +import org.hyperledger.besu.consensus.common.bft.BftForksSchedule; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.consensus.common.validator.VoteProvider; import org.hyperledger.besu.datatypes.Address; @@ -32,34 +34,51 @@ public class TransactionValidatorProvider implements ValidatorProvider { private final Blockchain blockchain; private final ValidatorContractController validatorContractController; - private final Cache> validatorCache = + private final BftForksSchedule forksSchedule; + private final Cache> afterBlockValidatorCache = + CacheBuilder.newBuilder().maximumSize(100).build(); + private final Cache> forBlockValidatorCache = CacheBuilder.newBuilder().maximumSize(100).build(); public TransactionValidatorProvider( - final Blockchain blockchain, final ValidatorContractController validatorContractController) { + final Blockchain blockchain, + final ValidatorContractController validatorContractController, + final BftForksSchedule forksSchedule) { this.blockchain = blockchain; this.validatorContractController = validatorContractController; + this.forksSchedule = forksSchedule; } @Override public Collection
getValidatorsAtHead() { - return getValidatorsForBlock(blockchain.getChainHeadHeader()); + return getValidatorsAfterBlock(blockchain.getChainHeadHeader()); } @Override - public Collection
getValidatorsAfterBlock(final BlockHeader header) { - // For the validator contract we determine the vote from the previous block - return getValidatorsForBlock(header); + public Collection
getValidatorsAfterBlock(final BlockHeader parentHeader) { + // For the validator contract we determine the validators from the previous block but the + // address from block about to be created + final long nextBlock = parentHeader.getNumber() + 1; + final Address contractAddress = resolveContractAddress(nextBlock); + return getValidatorsFromContract(afterBlockValidatorCache, parentHeader, contractAddress); } @Override public Collection
getValidatorsForBlock(final BlockHeader header) { + final Address contractAddress = resolveContractAddress(header.getNumber()); + return getValidatorsFromContract(forBlockValidatorCache, header, contractAddress); + } + + private Collection
getValidatorsFromContract( + final Cache> validatorCache, + final BlockHeader header, + final Address contractAddress) { final long blockNumber = header.getNumber(); try { return validatorCache.get( blockNumber, () -> - validatorContractController.getValidators(blockNumber).stream() + validatorContractController.getValidators(blockNumber, contractAddress).stream() .sorted() .collect(Collectors.toList())); } catch (final ExecutionException e) { @@ -71,4 +90,16 @@ public Collection
getValidatorsForBlock(final BlockHeader header) { public Optional getVoteProviderAtHead() { return Optional.empty(); } + + private Address resolveContractAddress(final long blockNumber) { + return forksSchedule + .getFork(blockNumber) + .getConfigOptions() + .getValidatorContractAddress() + .map(Address::fromHexString) + .orElseThrow( + () -> + new RuntimeException( + "Error resolving smart contract address unable to make validator contract call")); + } } 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 87ec1276d0b..a4fe8bb204a 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 @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.consensus.qbft.validator; -import org.hyperledger.besu.config.QbftConfigOptions; -import org.hyperledger.besu.consensus.common.bft.BftForksSchedule; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; @@ -38,14 +36,10 @@ public class ValidatorContractController { public static final String GET_VALIDATORS = "getValidators"; public static final String CONTRACT_ERROR_MSG = "Failed validator smart contract call"; private final TransactionSimulator transactionSimulator; - private final BftForksSchedule forksSchedule; private final Function getValidatorsFunction; - public ValidatorContractController( - final TransactionSimulator transactionSimulator, - final BftForksSchedule forksSchedule) { + public ValidatorContractController(final TransactionSimulator transactionSimulator) { this.transactionSimulator = transactionSimulator; - this.forksSchedule = forksSchedule; try { this.getValidatorsFunction = @@ -58,8 +52,8 @@ public ValidatorContractController( } } - public Collection
getValidators(final long blockNumber) { - return callFunction(blockNumber, getValidatorsFunction) + public Collection
getValidators(final long blockNumber, final Address contractAddress) { + return callFunction(blockNumber, getValidatorsFunction, contractAddress) .map(this::parseGetValidatorsResult) .orElseThrow(() -> new IllegalStateException(CONTRACT_ERROR_MSG)); } @@ -75,26 +69,13 @@ private Collection
parseGetValidatorsResult(final TransactionSimulatorR } private Optional callFunction( - final long blockNumber, final Function function) { + final long blockNumber, final Function function, final Address contractAddress) { 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) - .getConfigOptions() - .getValidatorContractAddress() - .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/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java index f3959dde940..5aea1172983 100644 --- 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 @@ -17,19 +17,18 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.consensus.qbft.validator.ValidatorTestUtils.createBlockForkSpec; +import static org.hyperledger.besu.consensus.qbft.validator.ValidatorTestUtils.createContractForkSpec; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import org.hyperledger.besu.config.JsonQbftConfigOptions; import org.hyperledger.besu.config.QbftConfigOptions; -import org.hyperledger.besu.consensus.common.bft.BftForkSpec; import org.hyperledger.besu.consensus.common.bft.BftForksSchedule; import org.hyperledger.besu.consensus.common.validator.VoteProvider; import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider; -import org.hyperledger.besu.consensus.qbft.MutableQbftConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -99,13 +98,13 @@ private Block createEmptyBlock(final long blockNumber, final Hash parentHash) { @Test public void usesInitialValidatorProviderWhenNoForks() { final BftForksSchedule forksSchedule = - new BftForksSchedule<>(createBlockFork(0), List.of()); + new BftForksSchedule<>(createBlockForkSpec(0), List.of()); final ForkingValidatorProvider validatorProvider = new ForkingValidatorProvider( blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider); - when(blockValidatorProvider.getValidatorsAtHead()).thenReturn(BLOCK_ADDRESSES); + when(blockValidatorProvider.getValidatorsAfterBlock(header2)).thenReturn(BLOCK_ADDRESSES); when(blockValidatorProvider.getValidatorsAfterBlock(header1)).thenReturn(BLOCK_ADDRESSES); assertThat(validatorProvider.getValidatorsAtHead()).isEqualTo(BLOCK_ADDRESSES); @@ -117,7 +116,7 @@ public void usesInitialValidatorProviderWhenNoForks() { public void migratesFromBlockToContractValidatorProvider() { final BftForksSchedule forksSchedule = new BftForksSchedule<>( - createBlockFork(0), List.of(createContractFork(1L, CONTRACT_ADDRESS_1))); + createBlockForkSpec(0), List.of(createContractForkSpec(1L, CONTRACT_ADDRESS_1))); final ForkingValidatorProvider validatorProvider = new ForkingValidatorProvider( blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider); @@ -130,7 +129,7 @@ public void migratesFromBlockToContractValidatorProvider() { public void migratesFromContractToBlockValidatorProvider() { final BftForksSchedule forksSchedule = new BftForksSchedule<>( - createContractFork(0, CONTRACT_ADDRESS_1), List.of(createBlockFork(1))); + createContractForkSpec(0, CONTRACT_ADDRESS_1), List.of(createBlockForkSpec(1))); final ForkingValidatorProvider validatorProvider = new ForkingValidatorProvider( blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider); @@ -144,14 +143,34 @@ public void migratesFromContractToBlockValidatorProvider() { assertThat(validatorProvider.getValidatorsForBlock(header2)).isEqualTo(BLOCK_ADDRESSES); } + @Test + public void migratesFromContractToBlockValidatorProviderWithValidatorOverrides() { + final BftForksSchedule forksSchedule = + new BftForksSchedule<>( + createContractForkSpec(0, CONTRACT_ADDRESS_1), + List.of(ValidatorTestUtils.createBlockForkSpec(1))); + final ForkingValidatorProvider validatorProvider = + new ForkingValidatorProvider( + blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider); + + when(contractValidatorProvider.getValidatorsForBlock(genesisHeader)) + .thenReturn(CONTRACT_ADDRESSES_1); + when(blockValidatorProvider.hasValidatorOverridesForBlockNumber(1)).thenReturn(true); + + assertThat(validatorProvider.getValidatorsForBlock(genesisHeader)) + .isEqualTo(CONTRACT_ADDRESSES_1); + assertThat(validatorProvider.getValidatorsForBlock(header1)).isEqualTo(BLOCK_ADDRESSES); + assertThat(validatorProvider.getValidatorsForBlock(header2)).isEqualTo(BLOCK_ADDRESSES); + } + @Test public void migratesFromContractToContractValidatorProvider() { final BftForksSchedule forksSchedule = new BftForksSchedule<>( - createBlockFork(0), + createBlockForkSpec(0), List.of( - createContractFork(1L, CONTRACT_ADDRESS_1), - createContractFork(2L, CONTRACT_ADDRESS_2))); + createContractForkSpec(1L, CONTRACT_ADDRESS_1), + createContractForkSpec(2L, CONTRACT_ADDRESS_2))); final ForkingValidatorProvider validatorProvider = new ForkingValidatorProvider( @@ -166,8 +185,8 @@ public void migratesFromContractToContractValidatorProvider() { public void voteProviderIsDelegatesToHeadFork_whenHeadIsContractFork() { final BftForksSchedule forksSchedule = new BftForksSchedule<>( - createBlockFork(0), - List.of(createBlockFork(1), createContractFork(2, CONTRACT_ADDRESS_1))); + createBlockForkSpec(0), + List.of(createBlockForkSpec(1), createContractForkSpec(2, CONTRACT_ADDRESS_1))); final ForkingValidatorProvider validatorProvider = new ForkingValidatorProvider( @@ -182,7 +201,7 @@ public void voteProviderIsDelegatesToHeadFork_whenHeadIsContractFork() { @Test public void voteProviderIsDelegatesToHeadFork_whenHeadIsBlockFork() { final BftForksSchedule forksSchedule = - new BftForksSchedule<>(createBlockFork(0), emptyList()); + new BftForksSchedule<>(createBlockForkSpec(0), emptyList()); final ForkingValidatorProvider validatorProvider = new ForkingValidatorProvider( @@ -198,8 +217,8 @@ public void voteProviderIsDelegatesToHeadFork_whenHeadIsBlockFork() { public void getVoteProviderAfterBlock_correctVoteProviderIsResolved() { final BftForksSchedule forksSchedule = new BftForksSchedule<>( - createBlockFork(0), - List.of(createBlockFork(1), createContractFork(2, CONTRACT_ADDRESS_1))); + createBlockForkSpec(0), + List.of(createBlockForkSpec(1), createContractForkSpec(2, CONTRACT_ADDRESS_1))); final ForkingValidatorProvider validatorProvider = new ForkingValidatorProvider( blockChain, forksSchedule, blockValidatorProvider, contractValidatorProvider); @@ -218,17 +237,4 @@ public void getVoteProviderAfterBlock_correctVoteProviderIsResolved() { softly.assertThat(validatorProvider.getVoteProviderAfterBlock(header2)).isEmpty(); }); } - - private BftForkSpec createContractFork( - final long block, final Address contractAddress) { - final MutableQbftConfigOptions qbftConfigOptions = - new MutableQbftConfigOptions(JsonQbftConfigOptions.DEFAULT); - qbftConfigOptions.setValidatorContractAddress(Optional.of(contractAddress.toHexString())); - return new BftForkSpec<>(block, qbftConfigOptions); - } - - private BftForkSpec createBlockFork(final long block) { - final QbftConfigOptions qbftConfigOptions = JsonQbftConfigOptions.DEFAULT; - return new BftForkSpec<>(block, qbftConfigOptions); - } } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProviderTest.java index 80be8c2bb6f..108a6ea5bfb 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProviderTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/TransactionValidatorProviderTest.java @@ -16,12 +16,15 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.consensus.qbft.validator.ValidatorTestUtils.createContractForkSpec; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.QbftConfigOptions; +import org.hyperledger.besu.consensus.common.bft.BftForksSchedule; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -38,21 +41,28 @@ 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.junit.MockitoJUnitRunner; +@RunWith(MockitoJUnitRunner.class) public class TransactionValidatorProviderTest { - private final ValidatorContractController validatorContractController = - mock(ValidatorContractController.class); + @Mock private ValidatorContractController validatorContractController; protected MutableBlockchain blockChain; protected Block genesisBlock; protected Block block_1; protected Block block_2; private Block block_3; + private BftForksSchedule forksSchedule; private final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("1"); @Before public void setup() { + forksSchedule = + new BftForksSchedule<>(createContractForkSpec(0L, CONTRACT_ADDRESS), emptyList()); genesisBlock = createEmptyBlock(0, Hash.ZERO); blockChain = createInMemoryBlockchain(genesisBlock); headerBuilder.extraData(Bytes.wrap(new byte[32])); @@ -78,11 +88,11 @@ public void validatorsAfterBlockAreRetrievedUsingContractController() { final List
validatorsAt3 = Lists.newArrayList( Address.fromHexString("5"), Address.fromHexString("6"), Address.fromHexString("7")); - when(validatorContractController.getValidators(2)).thenReturn(validatorsAt2); - when(validatorContractController.getValidators(3)).thenReturn(validatorsAt3); + when(validatorContractController.getValidators(2, CONTRACT_ADDRESS)).thenReturn(validatorsAt2); + when(validatorContractController.getValidators(3, CONTRACT_ADDRESS)).thenReturn(validatorsAt3); final TransactionValidatorProvider validatorProvider = - new TransactionValidatorProvider(blockChain, validatorContractController); + new TransactionValidatorProvider(blockChain, validatorContractController, forksSchedule); assertThat(validatorProvider.getValidatorsAfterBlock(block_2.getHeader())) .containsExactlyElementsOf(validatorsAt2); @@ -97,11 +107,11 @@ public void validatorsForBlockAreRetrievedUsingContractController() { final List
validatorsAt3 = Lists.newArrayList( Address.fromHexString("5"), Address.fromHexString("6"), Address.fromHexString("7")); - when(validatorContractController.getValidators(2)).thenReturn(validatorsAt2); - when(validatorContractController.getValidators(3)).thenReturn(validatorsAt3); + when(validatorContractController.getValidators(2, CONTRACT_ADDRESS)).thenReturn(validatorsAt2); + when(validatorContractController.getValidators(3, CONTRACT_ADDRESS)).thenReturn(validatorsAt3); final TransactionValidatorProvider validatorProvider = - new TransactionValidatorProvider(blockChain, validatorContractController); + new TransactionValidatorProvider(blockChain, validatorContractController, forksSchedule); assertThat(validatorProvider.getValidatorsForBlock(block_2.getHeader())) .containsExactlyElementsOf(validatorsAt2); @@ -113,10 +123,10 @@ public void validatorsForBlockAreRetrievedUsingContractController() { public void validatorsAtHeadAreRetrievedUsingContractController() { final List
validators = Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6")); - when(validatorContractController.getValidators(3)).thenReturn(validators); + when(validatorContractController.getValidators(3, CONTRACT_ADDRESS)).thenReturn(validators); final TransactionValidatorProvider validatorProvider = - new TransactionValidatorProvider(blockChain, validatorContractController); + new TransactionValidatorProvider(blockChain, validatorContractController, forksSchedule); assertThat(validatorProvider.getValidatorsAtHead()).containsExactlyElementsOf(validators); } @@ -125,13 +135,13 @@ public void validatorsAtHeadAreRetrievedUsingContractController() { public void validatorsAtHeadContractCallIsCached() { final List
validators = Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6")); - when(validatorContractController.getValidators(3)).thenReturn(validators); + when(validatorContractController.getValidators(3, CONTRACT_ADDRESS)).thenReturn(validators); final TransactionValidatorProvider validatorProvider = - new TransactionValidatorProvider(blockChain, validatorContractController); + new TransactionValidatorProvider(blockChain, validatorContractController, forksSchedule); assertThat(validatorProvider.getValidatorsAtHead()).containsExactlyElementsOf(validators); - verify(validatorContractController).getValidators(3); + verify(validatorContractController).getValidators(3, CONTRACT_ADDRESS); assertThat(validatorProvider.getValidatorsAtHead()).containsExactlyElementsOf(validators); verifyNoMoreInteractions(validatorContractController); @@ -141,15 +151,15 @@ public void validatorsAtHeadContractCallIsCached() { public void validatorsAfterBlockContractCallIsCached() { final List
validators = Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6")); - when(validatorContractController.getValidators(2)).thenReturn(validators); + when(validatorContractController.getValidators(2, CONTRACT_ADDRESS)).thenReturn(validators); final TransactionValidatorProvider validatorProvider = - new TransactionValidatorProvider(blockChain, validatorContractController); + new TransactionValidatorProvider(blockChain, validatorContractController, forksSchedule); final Collection
result = validatorProvider.getValidatorsAfterBlock(block_2.getHeader()); assertThat(result).containsExactlyElementsOf(validators); - verify(validatorContractController).getValidators(2); + verify(validatorContractController).getValidators(2, CONTRACT_ADDRESS); final Collection
resultCached = validatorProvider.getValidatorsAfterBlock(block_2.getHeader()); @@ -157,15 +167,37 @@ public void validatorsAfterBlockContractCallIsCached() { verifyNoMoreInteractions(validatorContractController); } + @Test + public void getValidatorsAfterBlock_and_getValidatorsForBlock_useDifferentCaches() { + final List
validators = + Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6")); + when(validatorContractController.getValidators(2, CONTRACT_ADDRESS)).thenReturn(validators); + + final TransactionValidatorProvider validatorProvider = + new TransactionValidatorProvider(blockChain, validatorContractController, forksSchedule); + + validatorProvider.getValidatorsAfterBlock(block_2.getHeader()); // cache miss + verify(validatorContractController, times(1)).getValidators(2, CONTRACT_ADDRESS); + + validatorProvider.getValidatorsAfterBlock(block_2.getHeader()); // cache hit + verifyNoMoreInteractions(validatorContractController); + + validatorProvider.getValidatorsForBlock(block_2.getHeader()); // cache miss + verify(validatorContractController, times(2)).getValidators(2, CONTRACT_ADDRESS); + + validatorProvider.getValidatorsAfterBlock(block_2.getHeader()); // cache hit + verifyNoMoreInteractions(validatorContractController); + } + @Test public void validatorsMustBeSorted() { final List
validators = Lists.newArrayList( Address.fromHexString("9"), Address.fromHexString("8"), Address.fromHexString("7")); - when(validatorContractController.getValidators(3)).thenReturn(validators); + when(validatorContractController.getValidators(3, CONTRACT_ADDRESS)).thenReturn(validators); final TransactionValidatorProvider validatorProvider = - new TransactionValidatorProvider(blockChain, validatorContractController); + new TransactionValidatorProvider(blockChain, validatorContractController, forksSchedule); final Collection
result = validatorProvider.getValidatorsAtHead(); final List
expectedValidators = @@ -176,7 +208,7 @@ public void validatorsMustBeSorted() { @Test public void voteProviderIsEmpty() { TransactionValidatorProvider transactionValidatorProvider = - new TransactionValidatorProvider(blockChain, validatorContractController); + new TransactionValidatorProvider(blockChain, validatorContractController, forksSchedule); assertThat(transactionValidatorProvider.getVoteProviderAtHead()).isEmpty(); } 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 eab4c6bd4df..4dc848172f1 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 @@ -16,13 +16,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.consensus.qbft.validator.ValidatorContractController.GET_VALIDATORS; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; import org.hyperledger.besu.config.JsonQbftConfigOptions; -import org.hyperledger.besu.config.QbftConfigOptions; -import org.hyperledger.besu.consensus.common.bft.BftForkSpec; -import org.hyperledger.besu.consensus.common.bft.BftForksSchedule; import org.hyperledger.besu.consensus.qbft.MutableQbftConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.Transaction; @@ -59,10 +55,6 @@ public class ValidatorContractControllerTest { private final Transaction transaction = Mockito.mock(Transaction.class); private CallParameter callParameter; - @SuppressWarnings("unchecked") - private final BftForksSchedule qbftForksSchedule = - Mockito.mock(BftForksSchedule.class); - @Before public void setup() { final Function getValidatorsFunction = @@ -75,7 +67,6 @@ public void setup() { final MutableQbftConfigOptions qbftConfigOptions = new MutableQbftConfigOptions(JsonQbftConfigOptions.DEFAULT); qbftConfigOptions.setValidatorContractAddress(Optional.of(CONTRACT_ADDRESS.toHexString())); - when(qbftForksSchedule.getFork(anyLong())).thenReturn(new BftForkSpec<>(0, qbftConfigOptions)); } @Test @@ -93,8 +84,9 @@ public void decodesGetValidatorsResultFromContractCall() { when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result)); final ValidatorContractController validatorContractController = - new ValidatorContractController(transactionSimulator, qbftForksSchedule); - final Collection
validators = validatorContractController.getValidators(1); + new ValidatorContractController(transactionSimulator); + final Collection
validators = + validatorContractController.getValidators(1, CONTRACT_ADDRESS); assertThat(validators).containsExactly(VALIDATOR_ADDRESS); } @@ -109,8 +101,9 @@ public void throwErrorIfInvalidSimulationResult() { when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result)); final ValidatorContractController validatorContractController = - new ValidatorContractController(transactionSimulator, qbftForksSchedule); - Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1)) + new ValidatorContractController(transactionSimulator); + Assertions.assertThatThrownBy( + () -> validatorContractController.getValidators(1, CONTRACT_ADDRESS)) .hasMessage("Failed validator smart contract call"); } @@ -128,8 +121,9 @@ public void throwErrorIfFailedSimulationResult() { when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result)); final ValidatorContractController validatorContractController = - new ValidatorContractController(transactionSimulator, qbftForksSchedule); - Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1)) + new ValidatorContractController(transactionSimulator); + Assertions.assertThatThrownBy( + () -> validatorContractController.getValidators(1, CONTRACT_ADDRESS)) .hasMessage("Failed validator smart contract call"); } @@ -144,9 +138,10 @@ public void throwErrorIfUnexpectedSuccessfulEmptySimulationResult() { when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result)); final ValidatorContractController validatorContractController = - new ValidatorContractController(transactionSimulator, qbftForksSchedule); + new ValidatorContractController(transactionSimulator); - Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1)) + Assertions.assertThatThrownBy( + () -> validatorContractController.getValidators(1, CONTRACT_ADDRESS)) .hasMessage("Unexpected empty result from validator smart contract call"); } @@ -155,8 +150,9 @@ public void throwErrorIfEmptySimulationResult() { when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.empty()); final ValidatorContractController validatorContractController = - new ValidatorContractController(transactionSimulator, qbftForksSchedule); - Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1)) + new ValidatorContractController(transactionSimulator); + Assertions.assertThatThrownBy( + () -> validatorContractController.getValidators(1, CONTRACT_ADDRESS)) .hasMessage("Failed validator smart contract call"); } } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorTestUtils.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorTestUtils.java new file mode 100644 index 00000000000..31eb4b8526c --- /dev/null +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorTestUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright Hyperledger Besu contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbft.validator; + +import org.hyperledger.besu.config.JsonQbftConfigOptions; +import org.hyperledger.besu.config.QbftConfigOptions; +import org.hyperledger.besu.consensus.common.bft.BftForkSpec; +import org.hyperledger.besu.consensus.qbft.MutableQbftConfigOptions; +import org.hyperledger.besu.datatypes.Address; + +import java.util.Optional; + +class ValidatorTestUtils { + + static BftForkSpec createContractForkSpec( + final long block, final Address contractAddress) { + final MutableQbftConfigOptions qbftConfigOptions = + new MutableQbftConfigOptions(JsonQbftConfigOptions.DEFAULT); + qbftConfigOptions.setValidatorContractAddress(Optional.of(contractAddress.toHexString())); + return new BftForkSpec<>(block, qbftConfigOptions); + } + + static BftForkSpec createBlockForkSpec(final long block) { + final MutableQbftConfigOptions qbftConfigOptions = + new MutableQbftConfigOptions(JsonQbftConfigOptions.DEFAULT); + return new BftForkSpec<>(block, qbftConfigOptions); + } +}