Skip to content

Commit

Permalink
Add 2/3n Commit count for IBFT blocks (hyperledger#1906)
Browse files Browse the repository at this point in the history
This allows Besu to interop with an Quorum IBFT-1 network which has been updated to use a 2/3 validator threshold, rather than 2F+1.

Signed-off-by: Trent Mohay <trent.mohay@consensys.net>
  • Loading branch information
Trent Mohay authored Feb 28, 2021
1 parent a4ffec2 commit e2963d6
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
package org.hyperledger.besu.controller;

import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.IbftLegacyConfigOptions;
import org.hyperledger.besu.consensus.common.BlockInterface;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.VoteProposer;
Expand Down Expand Up @@ -83,7 +83,7 @@ protected ProtocolSchedule createProtocolSchedule() {
@Override
protected BftContext createConsensusContext(
final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
final BftConfigOptions ibftConfig =
final IbftLegacyConfigOptions ibftConfig =
genesisConfig.getConfigOptions(genesisConfigOverrides).getIbftLegacyConfigOptions();
final EpochManager epochManager = new EpochManager(ibftConfig.getEpochLength());
final VoteTallyCache voteTallyCache =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,52 +38,51 @@ public class BftConfigOptions {
private static final int DEFAULT_FUTURE_MESSAGES_LIMIT = 1000;
private static final int DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE = 10;

private final ObjectNode ibftConfigRoot;
private final ObjectNode bftConfigRoot;

BftConfigOptions(final ObjectNode ibftConfigRoot) {
this.ibftConfigRoot = ibftConfigRoot;
BftConfigOptions(final ObjectNode bftConfigRoot) {
this.bftConfigRoot = bftConfigRoot;
}

public long getEpochLength() {
return JsonUtil.getLong(ibftConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH);
return JsonUtil.getLong(bftConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH);
}

public int getBlockPeriodSeconds() {
return JsonUtil.getInt(ibftConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
return JsonUtil.getInt(bftConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
}

public int getRequestTimeoutSeconds() {
return JsonUtil.getInt(ibftConfigRoot, "requesttimeoutseconds", DEFAULT_ROUND_EXPIRY_SECONDS);
return JsonUtil.getInt(bftConfigRoot, "requesttimeoutseconds", DEFAULT_ROUND_EXPIRY_SECONDS);
}

public int getGossipedHistoryLimit() {
return JsonUtil.getInt(ibftConfigRoot, "gossipedhistorylimit", DEFAULT_GOSSIPED_HISTORY_LIMIT);
return JsonUtil.getInt(bftConfigRoot, "gossipedhistorylimit", DEFAULT_GOSSIPED_HISTORY_LIMIT);
}

public int getMessageQueueLimit() {
return JsonUtil.getInt(ibftConfigRoot, "messagequeuelimit", DEFAULT_MESSAGE_QUEUE_LIMIT);
return JsonUtil.getInt(bftConfigRoot, "messagequeuelimit", DEFAULT_MESSAGE_QUEUE_LIMIT);
}

public int getDuplicateMessageLimit() {
return JsonUtil.getInt(
ibftConfigRoot, "duplicatemessagelimit", DEFAULT_DUPLICATE_MESSAGE_LIMIT);
return JsonUtil.getInt(bftConfigRoot, "duplicatemessagelimit", DEFAULT_DUPLICATE_MESSAGE_LIMIT);
}

public int getFutureMessagesLimit() {
return JsonUtil.getInt(ibftConfigRoot, "futuremessageslimit", DEFAULT_FUTURE_MESSAGES_LIMIT);
return JsonUtil.getInt(bftConfigRoot, "futuremessageslimit", DEFAULT_FUTURE_MESSAGES_LIMIT);
}

public int getFutureMessagesMaxDistance() {
return JsonUtil.getInt(
ibftConfigRoot, "futuremessagesmaxdistance", DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE);
bftConfigRoot, "futuremessagesmaxdistance", DEFAULT_FUTURE_MESSAGES_MAX_DISTANCE);
}

public Optional<String> getMiningBeneficiary() {
return JsonUtil.getString(ibftConfigRoot, "miningbeneficiary");
return JsonUtil.getString(bftConfigRoot, "miningbeneficiary");
}

public BigInteger getBlockRewardWei() {
final Optional<String> configFileContent = JsonUtil.getString(ibftConfigRoot, "blockreward");
final Optional<String> configFileContent = JsonUtil.getString(bftConfigRoot, "blockreward");

if (configFileContent.isEmpty()) {
return BigInteger.ZERO;
Expand All @@ -97,28 +96,28 @@ public BigInteger getBlockRewardWei() {

Map<String, Object> asMap() {
final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
if (ibftConfigRoot.has("epochlength")) {
if (bftConfigRoot.has("epochlength")) {
builder.put("epochLength", getEpochLength());
}
if (ibftConfigRoot.has("blockperiodseconds")) {
if (bftConfigRoot.has("blockperiodseconds")) {
builder.put("blockPeriodSeconds", getBlockPeriodSeconds());
}
if (ibftConfigRoot.has("requesttimeoutseconds")) {
if (bftConfigRoot.has("requesttimeoutseconds")) {
builder.put("requestTimeoutSeconds", getRequestTimeoutSeconds());
}
if (ibftConfigRoot.has("gossipedhistorylimit")) {
if (bftConfigRoot.has("gossipedhistorylimit")) {
builder.put("gossipedHistoryLimit", getGossipedHistoryLimit());
}
if (ibftConfigRoot.has("messagequeuelimit")) {
if (bftConfigRoot.has("messagequeuelimit")) {
builder.put("messageQueueLimit", getMessageQueueLimit());
}
if (ibftConfigRoot.has("duplicatemessagelimit")) {
if (bftConfigRoot.has("duplicatemessagelimit")) {
builder.put("duplicateMessageLimit", getDuplicateMessageLimit());
}
if (ibftConfigRoot.has("futuremessageslimit")) {
if (bftConfigRoot.has("futuremessageslimit")) {
builder.put("futureMessagesLimit", getFutureMessagesLimit());
}
if (ibftConfigRoot.has("futuremessagesmaxdistance")) {
if (bftConfigRoot.has("futuremessagesmaxdistance")) {
builder.put("futureMessagesMaxDistance", getFutureMessagesMaxDistance());
}
return builder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public interface GenesisConfigOptions {

String getConsensusEngine();

BftConfigOptions getIbftLegacyConfigOptions();
IbftLegacyConfigOptions getIbftLegacyConfigOptions();

CliqueConfigOptions getCliqueConfigOptions();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.Map;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap;

public class IbftLegacyConfigOptions {

public static final IbftLegacyConfigOptions DEFAULT =
new IbftLegacyConfigOptions(JsonUtil.createEmptyObjectNode());

private static final long DEFAULT_EPOCH_LENGTH = 30_000;
private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 1;
private static final int DEFAULT_ROUND_EXPIRY_SECONDS = 1;
private static final long DEFAULT_CEIL_2N_BY_3_BLOCK = 0L;

private final ObjectNode ibftConfigRoot;

IbftLegacyConfigOptions(final ObjectNode ibftConfigRoot) {
this.ibftConfigRoot = ibftConfigRoot;
}

public long getEpochLength() {
return JsonUtil.getLong(ibftConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH);
}

public int getBlockPeriodSeconds() {
return JsonUtil.getInt(ibftConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
}

public int getRequestTimeoutSeconds() {
return JsonUtil.getInt(ibftConfigRoot, "requesttimeoutseconds", DEFAULT_ROUND_EXPIRY_SECONDS);
}

public long getCeil2Nby3Block() {
return JsonUtil.getLong(ibftConfigRoot, "ceil2nby3block", DEFAULT_CEIL_2N_BY_3_BLOCK);
}

Map<String, Object> asMap() {
final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
if (ibftConfigRoot.has("epochlength")) {
builder.put("epochLength", getEpochLength());
}
if (ibftConfigRoot.has("blockperiodseconds")) {
builder.put("blockPeriodSeconds", getBlockPeriodSeconds());
}
if (ibftConfigRoot.has("requesttimeoutseconds")) {
builder.put("requestTimeoutSeconds", getRequestTimeoutSeconds());
}
if (ibftConfigRoot.has("ceil2nby3block")) {
builder.put("ceil2nby3block", getCeil2Nby3Block());
}

return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ public boolean isQbft() {
}

@Override
public BftConfigOptions getIbftLegacyConfigOptions() {
public IbftLegacyConfigOptions getIbftLegacyConfigOptions() {
return JsonUtil.getObjectNode(configRoot, IBFT_LEGACY_CONFIG_KEY)
.map(BftConfigOptions::new)
.orElse(BftConfigOptions.DEFAULT);
.map(IbftLegacyConfigOptions::new)
.orElse(IbftLegacyConfigOptions.DEFAULT);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ public boolean isQbft() {
}

@Override
public BftConfigOptions getIbftLegacyConfigOptions() {
return BftConfigOptions.DEFAULT;
public IbftLegacyConfigOptions getIbftLegacyConfigOptions() {
return IbftLegacyConfigOptions.DEFAULT;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ private BftConfigOptions fromConfigOptions(final Map<String, Object> ibftConfigO
final ObjectNode rootNode = JsonUtil.createEmptyObjectNode();
final ObjectNode configNode = JsonUtil.createEmptyObjectNode();
final ObjectNode options = JsonUtil.objectNodeFromMap(ibftConfigOptions);
configNode.set("ibft", options);
configNode.set("ibft2", options);
rootNode.set("config", configNode);
return GenesisConfigFile.fromConfig(rootNode).getConfigOptions().getIbftLegacyConfigOptions();
return GenesisConfigFile.fromConfig(rootNode).getConfigOptions().getBftConfigOptions();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void shouldUseIbftLegacyWhenIbftInConfig() {
public void shouldNotUseIbftLegacyIfIbftNotPresent() {
final GenesisConfigOptions config = fromConfigOptions(emptyMap());
assertThat(config.isIbftLegacy()).isFalse();
assertThat(config.getIbftLegacyConfigOptions()).isSameAs(BftConfigOptions.DEFAULT);
assertThat(config.getIbftLegacyConfigOptions()).isSameAs(IbftLegacyConfigOptions.DEFAULT);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ public class IbftBlockHeaderValidationRulesetFactory {
* part of the BlockChain (i.e. not proposed blocks, which do not contain commit seals)
*
* @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks.
* @param ceil2nBy3Block the block after which 2/3n commit seals must exist, rather than 2F+1
* @return BlockHeaderValidator configured for assessing ibft block headers
*/
public static BlockHeaderValidator.Builder ibftBlockHeaderValidator(
final long secondsBetweenBlocks) {
return createValidator(secondsBetweenBlocks, true);
final long secondsBetweenBlocks, final long ceil2nBy3Block) {
return createValidator(secondsBetweenBlocks, true, ceil2nBy3Block);
}

/**
Expand All @@ -51,11 +52,13 @@ public static BlockHeaderValidator.Builder ibftBlockHeaderValidator(
*/
public static BlockHeaderValidator.Builder ibftProposedBlockValidator(
final long secondsBetweenBlocks) {
return createValidator(secondsBetweenBlocks, false);
return createValidator(secondsBetweenBlocks, false, 0);
}

private static BlockHeaderValidator.Builder createValidator(
final long secondsBetweenBlocks, final boolean validateCommitSeals) {
final long secondsBetweenBlocks,
final boolean validateCommitSeals,
final long ceil2nBy3Block) {
return new BlockHeaderValidator.Builder()
.addRule(new AncestryValidationRule())
.addRule(new GasUsageValidationRule())
Expand All @@ -72,6 +75,6 @@ private static BlockHeaderValidator.Builder createValidator(
new ConstantFieldValidationRule<>(
"Difficulty", BlockHeader::getDifficulty, UInt256.ONE))
.addRule(new VoteValidationRule())
.addRule(new IbftExtraDataValidationRule(validateCommitSeals));
.addRule(new IbftExtraDataValidationRule(validateCommitSeals, ceil2nBy3Block));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import static org.hyperledger.besu.consensus.ibftlegacy.IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator;

import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.IbftLegacyConfigOptions;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockBodyValidator;
Expand All @@ -38,13 +38,15 @@ public static ProtocolSchedule create(
final GenesisConfigOptions config,
final PrivacyParameters privacyParameters,
final boolean isRevertReasonEnabled) {
final BftConfigOptions ibftConfig = config.getIbftLegacyConfigOptions();
final IbftLegacyConfigOptions ibftConfig = config.getIbftLegacyConfigOptions();
final long blockPeriod = ibftConfig.getBlockPeriodSeconds();

return new ProtocolScheduleBuilder(
config,
DEFAULT_CHAIN_ID,
builder -> applyIbftChanges(blockPeriod, builder, config.isQuorum()),
builder ->
applyIbftChanges(
blockPeriod, builder, config.isQuorum(), ibftConfig.getCeil2Nby3Block()),
privacyParameters,
isRevertReasonEnabled,
config.isQuorum())
Expand All @@ -59,10 +61,11 @@ public static ProtocolSchedule create(
private static ProtocolSpecBuilder applyIbftChanges(
final long secondsBetweenBlocks,
final ProtocolSpecBuilder builder,
final boolean goQuorumMode) {
final boolean goQuorumMode,
final long ceil2nBy3Block) {
return builder
.blockHeaderValidatorBuilder(ibftBlockHeaderValidator(secondsBetweenBlocks))
.ommerHeaderValidatorBuilder(ibftBlockHeaderValidator(secondsBetweenBlocks))
.blockHeaderValidatorBuilder(ibftBlockHeaderValidator(secondsBetweenBlocks, ceil2nBy3Block))
.ommerHeaderValidatorBuilder(ibftBlockHeaderValidator(secondsBetweenBlocks, ceil2nBy3Block))
.blockBodyValidatorBuilder(MainnetBlockBodyValidator::new)
.blockValidatorBuilder(MainnetProtocolSpecs.blockValidatorBuilder(goQuorumMode))
.blockImporterBuilder(MainnetBlockImporter::new)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.hyperledger.besu.consensus.common.ValidatorProvider;
import org.hyperledger.besu.consensus.common.bft.BftContext;
import org.hyperledger.besu.consensus.common.bft.BftHelpers;
import org.hyperledger.besu.consensus.ibftlegacy.IbftBlockHashing;
import org.hyperledger.besu.consensus.ibftlegacy.IbftExtraData;
import org.hyperledger.besu.consensus.ibftlegacy.IbftHelpers;
Expand Down Expand Up @@ -44,9 +45,11 @@ public class IbftExtraDataValidationRule implements AttachedBlockHeaderValidatio
private static final Logger LOG = LogManager.getLogger();

private final boolean validateCommitSeals;
private final long ceil2nBy3Block;

public IbftExtraDataValidationRule(final boolean validateCommitSeals) {
public IbftExtraDataValidationRule(final boolean validateCommitSeals, final long ceil2nBy3Block) {
this.validateCommitSeals = validateCommitSeals;
this.ceil2nBy3Block = ceil2nBy3Block;
}

@Override
Expand All @@ -72,7 +75,13 @@ public boolean validate(
if (validateCommitSeals) {
final List<Address> committers =
IbftBlockHashing.recoverCommitterAddresses(header, ibftExtraData);
if (!validateCommitters(committers, storedValidators)) {

final int minimumSealsRequired =
header.getNumber() < ceil2nBy3Block
? IbftHelpers.calculateRequiredValidatorQuorum(storedValidators.size())
: BftHelpers.calculateRequiredValidatorQuorum(storedValidators.size());

if (!validateCommitters(committers, storedValidators, minimumSealsRequired)) {
return false;
}
}
Expand Down Expand Up @@ -113,10 +122,9 @@ public boolean validate(
}

private boolean validateCommitters(
final Collection<Address> committers, final Collection<Address> storedValidators) {

final int minimumSealsRequired =
IbftHelpers.calculateRequiredValidatorQuorum(storedValidators.size());
final Collection<Address> committers,
final Collection<Address> storedValidators,
final int minimumSealsRequired) {
if (committers.size() < minimumSealsRequired) {
LOG.info(
"Invalid block header: Insufficient committers to seal block. (Required {}, received {})",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void ibftValidateHeaderPasses() {
final BlockHeader blockHeader = buildBlockHeader(2, proposerKeyPair, validators, parentHeader);

final BlockHeaderValidator validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5).build();
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5, 0).build();

assertThat(
validator.validateHeader(
Expand All @@ -91,7 +91,7 @@ public void ibftValidateHeaderFails() {
final BlockHeader blockHeader = buildBlockHeader(2, proposerKeyPair, validators, null);

final BlockHeaderValidator validator =
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5).build();
IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator(5, 0).build();

assertThat(
validator.validateHeader(
Expand Down
Loading

0 comments on commit e2963d6

Please sign in to comment.