Allow IBFT validators to be forked at a given block (#173)

This change allows a user to specify a list of address which are
to become validators in an IBFT2 network at a specific block number.

This has required extending the VoteTallyCache, and also added a new
"CustomForks" section to the genesis file.

At the moment only validators are able to be changed, however the
framework now exists for future modifications to be defined that
affect the behaviour of the system "outside" of traditional Ethereum
milestones.

Signed-off-by: Trent Mohay <trent.mohay@consensys.net>
pull/182/head
Trent Mohay 5 years ago committed by GitHub
parent bc5a37ec19
commit dd46332530
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 49
      besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java
  2. 65
      config/src/main/java/org/hyperledger/besu/config/CustomForksConfigOptions.java
  3. 4
      config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java
  4. 2
      config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java
  5. 73
      config/src/main/java/org/hyperledger/besu/config/IbftFork.java
  6. 44
      config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java
  7. 5
      config/src/test-support/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java
  8. 120
      config/src/test/java/org/hyperledger/besu/config/JsonGenesisConfigOptionsTest.java
  9. 31
      config/src/test/resources/valid_config_with_custom_forks.json
  10. 46
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForkingVoteTallyCache.java
  11. 35
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/IbftValidatorOverrides.java
  12. 2
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/VoteTallyCache.java
  13. 109
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForkingVoteTallyCacheTest.java
  14. 49
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/VoteTallyCacheTest.java
  15. 74
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/VoteTallyCacheTestBase.java
  16. 2
      consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java
  17. 10
      consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/blockcreation/ProposerSelector.java
  18. 26
      consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/ProposerSelectorTest.java

@ -14,9 +14,13 @@
*/
package org.hyperledger.besu.controller;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.IbftConfigOptions;
import org.hyperledger.besu.config.IbftFork;
import org.hyperledger.besu.consensus.common.BlockInterface;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.ForkingVoteTallyCache;
import org.hyperledger.besu.consensus.common.IbftValidatorOverrides;
import org.hyperledger.besu.consensus.common.VoteProposer;
import org.hyperledger.besu.consensus.common.VoteTallyCache;
import org.hyperledger.besu.consensus.common.VoteTallyUpdater;
@ -53,6 +57,7 @@ import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.MinedBlockObserver;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.Util;
@ -65,10 +70,16 @@ import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.util.Subscribers;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class IbftBesuControllerBuilder extends BesuControllerBuilder<IbftContext> {
private static final Logger LOG = LogManager.getLogger();
private IbftEventQueue ibftEventQueue;
private IbftConfigOptions ibftConfig;
@ -115,12 +126,13 @@ public class IbftBesuControllerBuilder extends BesuControllerBuilder<IbftContext
miningParameters,
Util.publicKeyToAddress(nodeKeys.getPublicKey()));
final ProposerSelector proposerSelector =
new ProposerSelector(blockchain, blockInterface, true);
// NOTE: peers should not be used for accessing the network as it does not enforce the
// "only send once" filter applied by the UniqueMessageMulticaster.
final VoteTallyCache voteTallyCache = protocolContext.getConsensusState().getVoteTallyCache();
final ProposerSelector proposerSelector =
new ProposerSelector(blockchain, blockInterface, true, voteTallyCache);
peers = new ValidatorPeers(voteTallyCache);
final UniqueMessageMulticaster uniqueMessageMulticaster =
@ -215,16 +227,39 @@ public class IbftBesuControllerBuilder extends BesuControllerBuilder<IbftContext
@Override
protected IbftContext createConsensusContext(
final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
final IbftConfigOptions ibftConfig =
genesisConfig.getConfigOptions(genesisConfigOverrides).getIbft2ConfigOptions();
final GenesisConfigOptions configOptions =
genesisConfig.getConfigOptions(genesisConfigOverrides);
final IbftConfigOptions ibftConfig = configOptions.getIbft2ConfigOptions();
final EpochManager epochManager = new EpochManager(ibftConfig.getEpochLength());
final Map<Long, List<Address>> ibftValidatorForkMap =
convertIbftForks(configOptions.getCustomForks().getIbftForks());
return new IbftContext(
new VoteTallyCache(
new ForkingVoteTallyCache(
blockchain,
new VoteTallyUpdater(epochManager, new IbftBlockInterface()),
epochManager,
new IbftBlockInterface()),
new IbftBlockInterface(),
new IbftValidatorOverrides(ibftValidatorForkMap)),
new VoteProposer(),
blockInterface);
}
private Map<Long, List<Address>> convertIbftForks(final List<IbftFork> ibftForks) {
final Map<Long, List<Address>> result = new HashMap<>();
for (final IbftFork fork : ibftForks) {
fork.getValidators()
.map(
validators ->
result.put(
fork.getForkBlock(),
validators.stream()
.map(Address::fromHexString)
.collect(Collectors.toList())));
}
return result;
}
}

@ -0,0 +1,65 @@
/*
* 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 static java.util.Collections.emptyList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
public class CustomForksConfigOptions {
public static final String IBFT2_FORKS = "ibft2";
public static final CustomForksConfigOptions DEFAULT =
new CustomForksConfigOptions(JsonUtil.createEmptyObjectNode());
private final ObjectNode customForkConfigRoot;
@JsonCreator
public CustomForksConfigOptions(final ObjectNode customForkConfigRoot) {
this.customForkConfigRoot = customForkConfigRoot;
}
public List<IbftFork> getIbftForks() {
final Optional<ArrayNode> ibftForksNode =
JsonUtil.getArrayNode(customForkConfigRoot, IBFT2_FORKS);
if (ibftForksNode.isEmpty()) {
return emptyList();
}
final List<IbftFork> ibftForks = Lists.newArrayList();
ibftForksNode
.get()
.elements()
.forEachRemaining(
node -> {
if (!node.isObject()) {
throw new IllegalArgumentException("Ibft2 fork is illegally formatted.");
}
ibftForks.add(new IbftFork((ObjectNode) node));
});
return Collections.unmodifiableList(ibftForks);
}
}

@ -83,9 +83,9 @@ public class GenesisConfigFile {
}
public GenesisConfigOptions getConfigOptions(final Map<String, String> overrides) {
ObjectNode config =
final ObjectNode config =
JsonUtil.getObjectNode(configRoot, "config").orElse(JsonUtil.createEmptyObjectNode());
return new JsonGenesisConfigOptions(config, overrides);
return JsonGenesisConfigOptions.fromJsonObjectWithOverrides(config, overrides);
}
public Stream<GenesisAllocation> streamAllocations() {

@ -71,4 +71,6 @@ public interface GenesisConfigOptions {
OptionalInt getEvmStackSize();
Map<String, Object> asMap();
CustomForksConfigOptions getCustomForks();
}

@ -0,0 +1,73 @@
/*
* 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.List;
import java.util.Optional;
import java.util.OptionalInt;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
public class IbftFork {
private static final String FORK_BLOCK_KEY = "block";
private static final String VALIDATORS_KEY = "validators";
private static final String BLOCK_PERIOD_SECONDS_KEY = "blockperiodseconds";
private final ObjectNode forkConfigRoot;
@JsonCreator
public IbftFork(final ObjectNode forkConfigRoot) {
this.forkConfigRoot = forkConfigRoot;
}
public long getForkBlock() {
return JsonUtil.getLong(forkConfigRoot, FORK_BLOCK_KEY)
.orElseThrow(
() ->
new IllegalArgumentException(
"Fork block not specified for IBFT2 fork in custom forks"));
}
public OptionalInt getBlockPeriodSeconds() {
return JsonUtil.getInt(forkConfigRoot, BLOCK_PERIOD_SECONDS_KEY);
}
public Optional<List<String>> getValidators() throws IllegalArgumentException {
final Optional<ArrayNode> validatorNode = JsonUtil.getArrayNode(forkConfigRoot, VALIDATORS_KEY);
if (validatorNode.isEmpty()) {
return Optional.empty();
}
List<String> validators = Lists.newArrayList();
validatorNode
.get()
.elements()
.forEachRemaining(
value -> {
if (!value.isTextual()) {
throw new IllegalArgumentException(
"Ibft Validator fork does not contain a string " + value.toString());
}
validators.add(value.asText());
});
return Optional.of(validators);
}
}

@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.config;
import static java.util.Collections.emptyMap;
import static java.util.Objects.isNull;
import java.math.BigInteger;
@ -24,6 +25,7 @@ import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.TreeMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap;
@ -33,23 +35,52 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
private static final String IBFT_LEGACY_CONFIG_KEY = "ibft";
private static final String IBFT2_CONFIG_KEY = "ibft2";
private static final String CLIQUE_CONFIG_KEY = "clique";
private static final String CUSTOM_FORKS_CONFIG_KEY = "customforks";
private final ObjectNode configRoot;
private final Map<String, String> configOverrides = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final CustomForksConfigOptions customForks;
public static JsonGenesisConfigOptions fromJsonObject(final ObjectNode configRoot) {
return new JsonGenesisConfigOptions(configRoot);
return fromJsonObjectWithOverrides(configRoot, emptyMap());
}
private JsonGenesisConfigOptions(final ObjectNode maybeConfig) {
this(maybeConfig, Collections.emptyMap());
static JsonGenesisConfigOptions fromJsonObjectWithOverrides(
final ObjectNode configRoot, final Map<String, String> configOverrides) {
final CustomForksConfigOptions customForksConfigOptions;
try {
customForksConfigOptions = loadCustomForksFrom(configRoot);
} catch (final JsonProcessingException e) {
throw new RuntimeException("CustomForks section of genesis file failed to decode.", e);
}
return new JsonGenesisConfigOptions(configRoot, configOverrides, customForksConfigOptions);
}
private static CustomForksConfigOptions loadCustomForksFrom(final ObjectNode parentNode)
throws JsonProcessingException {
final Optional<ObjectNode> customForksNode =
JsonUtil.getObjectNode(parentNode, CUSTOM_FORKS_CONFIG_KEY);
if (customForksNode.isEmpty()) {
return new CustomForksConfigOptions(JsonUtil.createEmptyObjectNode());
}
return new CustomForksConfigOptions(customForksNode.get());
}
private JsonGenesisConfigOptions(
final ObjectNode maybeConfig, final CustomForksConfigOptions customForksConfig) {
this(maybeConfig, Collections.emptyMap(), customForksConfig);
}
JsonGenesisConfigOptions(
final ObjectNode maybeConfig, final Map<String, String> configOverrides) {
final ObjectNode maybeConfig,
final Map<String, String> configOverrides,
final CustomForksConfigOptions customForksConfig) {
this.configRoot = isNull(maybeConfig) ? JsonUtil.createEmptyObjectNode() : maybeConfig;
if (configOverrides != null) {
this.configOverrides.putAll(configOverrides);
}
this.customForks = customForksConfig;
}
@Override
@ -115,6 +146,11 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
.orElse(EthashConfigOptions.DEFAULT);
}
@Override
public CustomForksConfigOptions getCustomForks() {
return customForks;
}
@Override
public OptionalLong getHomesteadBlockNumber() {
return getOptionalLong("homesteadblock");

@ -181,6 +181,11 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
return builder.build();
}
@Override
public CustomForksConfigOptions getCustomForks() {
return CustomForksConfigOptions.DEFAULT;
}
public StubGenesisConfigOptions homesteadBlock(final long blockNumber) {
homesteadBlockNumber = OptionalLong.of(blockNumber);
return this;

@ -0,0 +1,120 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.io.Resources;
import org.junit.Test;
public class JsonGenesisConfigOptionsTest {
private ObjectNode loadCompleteDataSet() {
try {
final String configText =
Resources.toString(
Resources.getResource("valid_config_with_custom_forks.json"), StandardCharsets.UTF_8);
return JsonUtil.objectNodeFromString(configText);
} catch (final IOException e) {
throw new RuntimeException("Failed to load resource", e);
}
}
private ObjectNode loadConfigWithNoCustomForks() {
final ObjectNode configNode = loadCompleteDataSet();
configNode.remove("customforks");
return configNode;
}
private ObjectNode loadConfigWithNoIbft2Forks() {
final ObjectNode configNode = loadCompleteDataSet();
final ObjectNode customForksNode = JsonUtil.getObjectNode(configNode, "customforks").get();
customForksNode.remove("ibft2");
return configNode;
}
private ObjectNode loadConfigWithAnIbft2ForkWithMissingValidators() {
final ObjectNode configNode = loadCompleteDataSet();
final ObjectNode customForksNode = JsonUtil.getObjectNode(configNode, "customforks").get();
final ArrayNode ibftNode = JsonUtil.getArrayNode(customForksNode, "ibft2").get();
((ObjectNode) ibftNode.get(0)).remove("validators");
return configNode;
}
@Test
public void customForksDecodesCorrectlyFromFile() {
final ObjectNode configNode = loadCompleteDataSet();
final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);
assertThat(configOptions.getCustomForks()).isNotNull();
assertThat(configOptions.getCustomForks().getIbftForks().size()).isEqualTo(2);
assertThat(configOptions.getCustomForks().getIbftForks().get(0).getForkBlock()).isEqualTo(20);
assertThat(configOptions.getCustomForks().getIbftForks().get(0).getValidators()).isNotEmpty();
assertThat(configOptions.getCustomForks().getIbftForks().get(0).getValidators().get())
.containsExactly(
"0x12345678901234567890123456789012345678900x1234567890123456789012345678901234567890",
"0x98765432109876543210987654321098765432100x9876543210987654321098765432109876543210");
assertThat(configOptions.getCustomForks().getIbftForks().get(1).getForkBlock()).isEqualTo(25);
assertThat(configOptions.getCustomForks().getIbftForks().get(1).getValidators()).isNotEmpty();
assertThat(configOptions.getCustomForks().getIbftForks().get(1).getValidators().get())
.containsExactly(
"0x12345678901234567890123456789012345678900x1234567890123456789012345678901234567890");
}
@Test
public void configWithMissingCustomForksIsValid() {
final ObjectNode configNode = loadConfigWithNoCustomForks();
final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);
assertThat(configOptions.getCustomForks()).isNotNull();
assertThat(configOptions.getCustomForks().getIbftForks().size()).isZero();
}
@Test
public void configWithNoIbft2ForksIsValid() {
final ObjectNode configNode = loadConfigWithNoIbft2Forks();
final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);
assertThat(configOptions.getCustomForks()).isNotNull();
assertThat(configOptions.getCustomForks().getIbftForks().size()).isZero();
}
@Test
public void configWithAnIbftWithNoValidatorsListedIsValid() {
final ObjectNode configNode = loadConfigWithAnIbft2ForkWithMissingValidators();
final JsonGenesisConfigOptions configOptions =
JsonGenesisConfigOptions.fromJsonObject(configNode);
assertThat(configOptions.getCustomForks().getIbftForks().get(0).getValidators().isPresent())
.isFalse();
assertThat(configOptions.getCustomForks().getIbftForks().get(1).getValidators().get().size())
.isEqualTo(1);
}
}

@ -0,0 +1,31 @@
{
"chainId": 4,
"homesteadBlock": 1,
"eip150Block": 2,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 3,
"eip158Block": 3,
"byzantiumBlock": 1035301,
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"customforks": {
"ibft2": [
{
"block": 20,
"validators": [
"0x12345678901234567890123456789012345678900x1234567890123456789012345678901234567890",
"0x98765432109876543210987654321098765432100x9876543210987654321098765432109876543210"
]
},
{
"block": 25,
"validators": [
"0x12345678901234567890123456789012345678900x1234567890123456789012345678901234567890"
]
}
]
}
}

@ -0,0 +1,46 @@
/*
* 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.common;
import static com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
public class ForkingVoteTallyCache extends VoteTallyCache {
private final IbftValidatorOverrides validatorOverrides;
public ForkingVoteTallyCache(
final Blockchain blockchain,
final VoteTallyUpdater voteTallyUpdater,
final EpochManager epochManager,
final BlockInterface blockInterface,
final IbftValidatorOverrides validatorOverrides) {
super(blockchain, voteTallyUpdater, epochManager, blockInterface);
checkNotNull(validatorOverrides);
this.validatorOverrides = validatorOverrides;
}
@Override
protected VoteTally getValidatorsAfter(final BlockHeader header) {
final long nextBlockNumber = header.getNumber() + 1L;
return validatorOverrides
.getForBlock(nextBlockNumber)
.map(VoteTally::new)
.orElse(super.getValidatorsAfter(header));
}
}

@ -0,0 +1,35 @@
/*
* 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.common;
import org.hyperledger.besu.ethereum.core.Address;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class IbftValidatorOverrides {
private final Map<Long, List<Address>> overriddenValidators;
public IbftValidatorOverrides(final Map<Long, List<Address>> overriddenValidators) {
this.overriddenValidators = overriddenValidators;
}
public Optional<Collection<Address>> getForBlock(final long blockNumber) {
return Optional.ofNullable(overriddenValidators.get(blockNumber));
}
}

@ -100,7 +100,7 @@ public class VoteTallyCache {
return constructMissingCacheEntries(intermediateBlocks, voteTally);
}
private VoteTally getValidatorsAfter(final BlockHeader header) {
protected VoteTally getValidatorsAfter(final BlockHeader header) {
if (epochManager.isEpochBlock(header.getNumber())) {
return new VoteTally(blockInterface.validatorsInBlock(header));
}

@ -0,0 +1,109 @@
/*
* 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.common;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import org.hyperledger.besu.ethereum.core.Address;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import org.junit.Test;
public class ForkingVoteTallyCacheTest extends VoteTallyCacheTestBase {
@Test
public void validatorFromForkAreReturnedRatherThanPriorBlock() {
final List<Address> forkedValidators =
Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6"));
final Map<Long, List<Address>> forkingValidatorMap = new HashMap<>();
forkingValidatorMap.put(3L, forkedValidators);
final VoteTallyUpdater tallyUpdater = mock(VoteTallyUpdater.class);
final ForkingVoteTallyCache cache =
new ForkingVoteTallyCache(
blockChain,
tallyUpdater,
new EpochManager(30_000),
blockInterface,
new IbftValidatorOverrides(forkingValidatorMap));
final VoteTally result = cache.getVoteTallyAfterBlock(block_2.getHeader());
assertThat(result.getValidators()).containsExactlyElementsOf(forkedValidators);
}
@Test
public void emptyForkingValidatorMapResultsInValidatorsBeingReadFromPreviousHeader() {
final VoteTallyUpdater tallyUpdater = mock(VoteTallyUpdater.class);
final ForkingVoteTallyCache cache =
new ForkingVoteTallyCache(
blockChain,
tallyUpdater,
new EpochManager(30_000),
blockInterface,
new IbftValidatorOverrides(new HashMap<>()));
final VoteTally result = cache.getVoteTallyAfterBlock(block_2.getHeader());
assertThat(result.getValidators()).containsExactlyElementsOf(validators);
}
@Test
public void validatorsInForkUsedIfForkDirectlyFollowsEpoch() {
final List<Address> forkedValidators =
Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6"));
final Map<Long, List<Address>> forkingValidatorMap = new HashMap<>();
forkingValidatorMap.put(3L, forkedValidators);
final VoteTallyUpdater tallyUpdater = mock(VoteTallyUpdater.class);
final ForkingVoteTallyCache cache =
new ForkingVoteTallyCache(
blockChain,
tallyUpdater,
new EpochManager(2L),
blockInterface,
new IbftValidatorOverrides(forkingValidatorMap));
final VoteTally result = cache.getVoteTallyAfterBlock(block_2.getHeader());
assertThat(result.getValidators()).containsExactlyElementsOf(forkedValidators);
}
@Test
public void atHeadApiOperatesIdenticallyToUnderlyingApi() {
final List<Address> forkedValidators =
Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6"));
final Map<Long, List<Address>> forkingValidatorMap = new HashMap<>();
forkingValidatorMap.put(3L, forkedValidators);
final VoteTallyUpdater tallyUpdater = mock(VoteTallyUpdater.class);
final ForkingVoteTallyCache cache =
new ForkingVoteTallyCache(
blockChain,
tallyUpdater,
new EpochManager(30_000L),
blockInterface,
new IbftValidatorOverrides(forkingValidatorMap));
final VoteTally result = cache.getVoteTallyAtHead();
assertThat(result.getValidators()).containsExactlyElementsOf(forkedValidators);
}
}

@ -16,7 +16,6 @@ package org.hyperledger.besu.consensus.common;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@ -25,64 +24,18 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
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 org.hyperledger.besu.util.bytes.BytesValue;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.assertj.core.util.Lists;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
public class VoteTallyCacheTest {
private final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
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(Lists.emptyList(), Lists.emptyList()));
}
private MutableBlockchain blockChain;
private Block genesisBlock;
private Block block_1;
private Block block_2;
private final List<Address> validators = Lists.newArrayList();
private final BlockInterface blockInterface = mock(BlockInterface.class);
@Before
public void constructThreeBlockChain() {
for (int i = 0; i < 3; i++) {
validators.add(AddressHelpers.ofValue(i));
}
headerBuilder.extraData(BytesValue.wrap(new byte[32]));
genesisBlock = createEmptyBlock(0, Hash.ZERO);
blockChain = createInMemoryBlockchain(genesisBlock);
block_1 = createEmptyBlock(1, genesisBlock.getHeader().getHash());
block_2 = createEmptyBlock(1, block_1.getHeader().getHash());
blockChain.appendBlock(block_1, Lists.emptyList());
blockChain.appendBlock(block_2, Lists.emptyList());
when(blockInterface.validatorsInBlock(any())).thenReturn(validators);
}
public class VoteTallyCacheTest extends VoteTallyCacheTestBase {
@Test
public void parentBlockVoteTallysAreCachedWhenChildVoteTallyRequested() {

@ -0,0 +1,74 @@
/*
* 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.common;
import static org.hyperledger.besu.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
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.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.util.bytes.BytesValue;
import java.util.List;
import org.assertj.core.util.Lists;
import org.junit.Before;
public class VoteTallyCacheTestBase {
protected final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
protected Block createEmptyBlock(final long blockNumber, final Hash parentHash) {
headerBuilder.number(blockNumber).parentHash(parentHash).coinbase(AddressHelpers.ofValue(0));
return new Block(
headerBuilder.buildHeader(), new BlockBody(Lists.emptyList(), Lists.emptyList()));
}
protected MutableBlockchain blockChain;
protected Block genesisBlock;
protected Block block_1;
protected Block block_2;
protected final List<Address> validators = Lists.newArrayList();
protected final BlockInterface blockInterface = mock(BlockInterface.class);
@Before
public void constructThreeBlockChain() {
for (int i = 0; i < 3; i++) {
validators.add(AddressHelpers.ofValue(i));
}
headerBuilder.extraData(BytesValue.wrap(new byte[32]));
genesisBlock = createEmptyBlock(0, Hash.ZERO);
blockChain = createInMemoryBlockchain(genesisBlock);
block_1 = createEmptyBlock(1, genesisBlock.getHeader().getHash());
block_2 = createEmptyBlock(2, block_1.getHeader().getHash());
blockChain.appendBlock(block_1, Lists.emptyList());
blockChain.appendBlock(block_2, Lists.emptyList());
when(blockInterface.validatorsInBlock(any())).thenReturn(validators);
}
}

@ -316,7 +316,7 @@ public class TestContextBuilder {
Util.publicKeyToAddress(nodeKeys.getPublicKey()));
final ProposerSelector proposerSelector =
new ProposerSelector(blockChain, blockInterface, true);
new ProposerSelector(blockChain, blockInterface, true, voteTallyCache);
final IbftExecutors ibftExecutors = IbftExecutors.create(new NoOpMetricsSystem());
final IbftFinalState finalState =

@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import org.hyperledger.besu.consensus.common.BlockInterface;
import org.hyperledger.besu.consensus.common.ValidatorProvider;
import org.hyperledger.besu.consensus.common.VoteTallyCache;
import org.hyperledger.besu.consensus.ibft.ConsensusRoundIdentifier;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
@ -55,15 +56,19 @@ public class ProposerSelector {
*/
private final Boolean changeEachBlock;
private final VoteTallyCache voteTallyCache;
private final BlockInterface blockInterface;
public ProposerSelector(
final Blockchain blockchain,
final BlockInterface blockInterface,
final boolean changeEachBlock) {
final boolean changeEachBlock,
final VoteTallyCache voteTallyCache) {
this.blockchain = blockchain;
this.blockInterface = blockInterface;
this.changeEachBlock = changeEachBlock;
this.voteTallyCache = voteTallyCache;
}
/**
@ -85,7 +90,8 @@ public class ProposerSelector {
final BlockHeader blockHeader = maybeParentHeader.get();
final Address prevBlockProposer = blockInterface.getProposerOfBlock(blockHeader);
final Collection<Address> validatorsForRound = blockInterface.validatorsInBlock(blockHeader);
final Collection<Address> validatorsForRound =
voteTallyCache.getVoteTallyAfterBlock(blockHeader).getValidators();
if (!validatorsForRound.contains(prevBlockProposer)) {
return handleMissingProposer(prevBlockProposer, validatorsForRound, roundIdentifier);

@ -21,6 +21,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.consensus.common.BlockInterface;
import org.hyperledger.besu.consensus.common.VoteTally;
import org.hyperledger.besu.consensus.common.VoteTallyCache;
import org.hyperledger.besu.consensus.ibft.ConsensusRoundIdentifier;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
@ -39,12 +41,13 @@ import org.junit.Test;
public class ProposerSelectorTest {
private final BlockInterface blockInterface = mock(BlockInterface.class);
private final VoteTallyCache voteTallyCache = mock(VoteTallyCache.class);
private Blockchain createMockedBlockChainWithHeadOf(
final long blockNumber, final Address proposer, final Collection<Address> validators) {
when(blockInterface.getProposerOfBlock(any())).thenReturn(proposer);
when(blockInterface.validatorsInBlock(any())).thenReturn(validators);
when(voteTallyCache.getVoteTallyAfterBlock(any())).thenReturn(new VoteTally(validators));
final BlockHeaderTestFixture headerBuilderFixture = new BlockHeaderTestFixture();
headerBuilderFixture.number(blockNumber);
@ -94,7 +97,8 @@ public class ProposerSelectorTest {
final Blockchain blockchain =
createMockedBlockChainWithHeadOf(PREV_BLOCK_NUMBER, localAddr, validatorList);
final ProposerSelector uut = new ProposerSelector(blockchain, blockInterface, true);
final ProposerSelector uut =
new ProposerSelector(blockchain, blockInterface, true, voteTallyCache);
final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(PREV_BLOCK_NUMBER + 1, 0);
@ -112,7 +116,8 @@ public class ProposerSelectorTest {
final Blockchain blockchain =
createMockedBlockChainWithHeadOf(PREV_BLOCK_NUMBER, localAddr, validatorList);
final ProposerSelector uut = new ProposerSelector(blockchain, blockInterface, true);
final ProposerSelector uut =
new ProposerSelector(blockchain, blockInterface, true, voteTallyCache);
final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(PREV_BLOCK_NUMBER + 1, 0);
@ -131,7 +136,8 @@ public class ProposerSelectorTest {
final Blockchain blockchain =
createMockedBlockChainWithHeadOf(PREV_BLOCK_NUMBER, localAddr, validatorList);
final ProposerSelector uut = new ProposerSelector(blockchain, blockInterface, false);
final ProposerSelector uut =
new ProposerSelector(blockchain, blockInterface, false, voteTallyCache);
final Address nextProposer = uut.selectProposerForRound(roundId);
assertThat(nextProposer).isEqualTo(localAddr);
@ -148,7 +154,8 @@ public class ProposerSelectorTest {
final Blockchain blockchain =
createMockedBlockChainWithHeadOf(PREV_BLOCK_NUMBER, localAddr, validatorList);
final ProposerSelector uut = new ProposerSelector(blockchain, blockInterface, false);
final ProposerSelector uut =
new ProposerSelector(blockchain, blockInterface, false, voteTallyCache);
assertThat(uut.selectProposerForRound(roundId)).isEqualTo(localAddr);
roundId = new ConsensusRoundIdentifier(PREV_BLOCK_NUMBER + 1, 1);
@ -174,7 +181,8 @@ public class ProposerSelectorTest {
final Blockchain blockchain =
createMockedBlockChainWithHeadOf(PREV_BLOCK_NUMBER, localAddr, validatorList);
final ProposerSelector uut = new ProposerSelector(blockchain, blockInterface, false);
final ProposerSelector uut =
new ProposerSelector(blockchain, blockInterface, false, voteTallyCache);
assertThat(uut.selectProposerForRound(roundId)).isEqualTo(validatorList.get(2));
}
@ -194,7 +202,8 @@ public class ProposerSelectorTest {
final Blockchain blockchain =
createMockedBlockChainWithHeadOf(PREV_BLOCK_NUMBER, localAddr, validatorList);
final ProposerSelector uut = new ProposerSelector(blockchain, blockInterface, true);
final ProposerSelector uut =
new ProposerSelector(blockchain, blockInterface, true, voteTallyCache);
assertThat(uut.selectProposerForRound(roundId)).isEqualTo(validatorList.get(2));
}
@ -215,7 +224,8 @@ public class ProposerSelectorTest {
final Blockchain blockchain =
createMockedBlockChainWithHeadOf(PREV_BLOCK_NUMBER, localAddr, validatorList);
final ProposerSelector uut = new ProposerSelector(blockchain, blockInterface, false);
final ProposerSelector uut =
new ProposerSelector(blockchain, blockInterface, false, voteTallyCache);
assertThat(uut.selectProposerForRound(roundId)).isEqualTo(validatorList.get(0));
}

Loading…
Cancel
Save