Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Commit

Permalink
Ibft split extradata validation rule into components (#581)
Browse files Browse the repository at this point in the history
In order to validate a block separate to its commit seals, the commit
seals must be validated separately to the validator content of the
block.

At this stage, the commit rule is  _not_ light, however this change
is to be conducted in subsequent PRs.
  • Loading branch information
rain-on authored Jan 16, 2019
1 parent f1a3a0c commit d75f6a3
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
package tech.pegasys.pantheon.consensus.ibft;

import tech.pegasys.pantheon.consensus.ibft.headervalidationrules.IbftCoinbaseValidationRule;
import tech.pegasys.pantheon.consensus.ibft.headervalidationrules.IbftExtraDataValidationRule;
import tech.pegasys.pantheon.consensus.ibft.headervalidationrules.IbftCommitSealsValidationRule;
import tech.pegasys.pantheon.consensus.ibft.headervalidationrules.IbftValidatorsValidationRule;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator;
Expand Down Expand Up @@ -53,7 +54,9 @@ public static BlockHeaderValidator<IbftContext> ibftProposedBlockValidator(

private static BlockHeaderValidator<IbftContext> createValidator(
final long secondsBetweenBlocks, final boolean validateCommitSeals) {
return new BlockHeaderValidator.Builder<IbftContext>()

final BlockHeaderValidator.Builder<IbftContext> builder = new BlockHeaderValidator.Builder<>();
builder
.addRule(new AncestryValidationRule())
.addRule(new GasUsageValidationRule())
.addRule(new GasLimitRangeAndDeltaValidationRule(5000, 0x7fffffffffffffffL))
Expand All @@ -69,8 +72,11 @@ private static BlockHeaderValidator<IbftContext> createValidator(
new ConstantFieldValidationRule<>(
"Difficulty", BlockHeader::getDifficulty, UInt256.ONE))
.addRule(new ConstantFieldValidationRule<>("Nonce", BlockHeader::getNonce, 0L))
.addRule(new IbftExtraDataValidationRule(validateCommitSeals))
.addRule(new IbftCoinbaseValidationRule())
.build();
.addRule(new IbftValidatorsValidationRule())
.addRule(new IbftCoinbaseValidationRule());
if (validateCommitSeals) {
builder.addRule(new IbftCommitSealsValidationRule());
}
return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2019 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.
*/
package tech.pegasys.pantheon.consensus.ibft.headervalidationrules;

import static tech.pegasys.pantheon.consensus.ibft.IbftHelpers.calculateRequiredValidatorQuorum;

import tech.pegasys.pantheon.consensus.common.ValidatorProvider;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.mainnet.AttachedBlockHeaderValidationRule;

import java.util.Collection;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* Ensures the commit seals in the block header were created by known validators (as determined by
* tracking votes and validator state on the blockchain).
*
* <p>This also ensures sufficient commit seals exist in the block to make it valid.
*/
public class IbftCommitSealsValidationRule
implements AttachedBlockHeaderValidationRule<IbftContext> {

private static final Logger LOGGER = LogManager.getLogger();

@Override
public boolean validate(
final BlockHeader header,
final BlockHeader parent,
final ProtocolContext<IbftContext> protocolContext) {
final ValidatorProvider validatorProvider = protocolContext.getConsensusState().getVoteTally();
final IbftExtraData ibftExtraData = IbftExtraData.decode(header.getExtraData());

final List<Address> committers =
IbftBlockHashing.recoverCommitterAddresses(header, ibftExtraData);

return validateCommitters(committers, validatorProvider.getValidators());
}

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

final int minimumSealsRequired = calculateRequiredValidatorQuorum(storedValidators.size());
if (committers.size() < minimumSealsRequired) {
LOGGER.trace(
"Insufficient committers to seal block. (Required {}, received {})",
minimumSealsRequired,
committers.size());
return false;
}

if (!storedValidators.containsAll(committers)) {
LOGGER.trace("Not all committers are in the locally maintained validator list.");
return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
*/
package tech.pegasys.pantheon.consensus.ibft.headervalidationrules;

import static tech.pegasys.pantheon.consensus.ibft.IbftHelpers.calculateRequiredValidatorQuorum;

import tech.pegasys.pantheon.consensus.common.ValidatorProvider;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
Expand All @@ -25,26 +22,19 @@
import tech.pegasys.pantheon.ethereum.rlp.RLPException;

import java.util.Collection;
import java.util.List;

import com.google.common.collect.Iterables;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* Ensures the byte content of the extraData field can be deserialised into an appropriate
* structure, and that the structure created contains data matching expectations from preceding
* blocks.
* Ensures the Validators listed in the block header match that tracked in memory (which was in-turn
* created by tracking votes included on the block chain).
*/
public class IbftExtraDataValidationRule implements AttachedBlockHeaderValidationRule<IbftContext> {

private static final Logger LOGGER = LogManager.getLogger(IbftExtraDataValidationRule.class);
public class IbftValidatorsValidationRule
implements AttachedBlockHeaderValidationRule<IbftContext> {

private final boolean validateCommitSeals;

public IbftExtraDataValidationRule(final boolean validateCommitSeals) {
this.validateCommitSeals = validateCommitSeals;
}
private static final Logger LOGGER = LogManager.getLogger();

@Override
public boolean validate(
Expand All @@ -59,8 +49,7 @@ public boolean validate(
*
* <ul>
* <li>Bytes in the extra data field can be decoded as per IBFT specification
* <li>Proposer (derived from the proposerSeal) is a member of the validators
* <li>Committers (derived from committerSeals) are all members of the validators
* <li>Validators in block matches that tracked in memory.
* </ul>
*
* @param header the block header containing the extraData to be validated.
Expand All @@ -75,14 +64,6 @@ private boolean validateExtraData(

final Collection<Address> storedValidators = validatorProvider.getValidators();

if (validateCommitSeals) {
final List<Address> committers =
IbftBlockHashing.recoverCommitterAddresses(header, ibftExtraData);
if (!validateCommitters(committers, storedValidators)) {
return false;
}
}

if (!Iterables.elementsEqual(ibftExtraData.getValidators(), storedValidators)) {
LOGGER.trace(
"Incorrect validators. Expected {} but got {}.",
Expand All @@ -101,24 +82,4 @@ private boolean validateExtraData(

return true;
}

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

final int minimumSealsRequired = calculateRequiredValidatorQuorum(storedValidators.size());
if (committers.size() < minimumSealsRequired) {
LOGGER.trace(
"Insufficient committers to seal block. (Required {}, received {})",
minimumSealsRequired,
committers.size());
return false;
}

if (!storedValidators.containsAll(committers)) {
LOGGER.trace("Not all committers are in the locally maintained validator list.");
return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2019 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.
*/
package tech.pegasys.pantheon.consensus.ibft.headervalidationrules;

import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraDataFixture;
import tech.pegasys.pantheon.consensus.ibft.Vote;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture;
import tech.pegasys.pantheon.util.bytes.BytesValue;

import java.util.List;
import java.util.Optional;

public class HeaderValidationTestHelpers {

public static BlockHeader createProposedBlockHeader(
final List<Address> validators,
final List<KeyPair> committerKeyPairs,
final boolean useDifferentRoundNumbersForCommittedSeals) {
final int BASE_ROUND_NUMBER = 5;
final BlockHeaderTestFixture builder = new BlockHeaderTestFixture();
builder.number(1); // must NOT be block 0, as that should not contain seals at all

final BlockHeader header = builder.buildHeader();

final IbftExtraData ibftExtraData =
IbftExtraDataFixture.createExtraData(
header,
BytesValue.wrap(new byte[IbftExtraData.EXTRA_VANITY_LENGTH]),
Optional.of(Vote.authVote(Address.fromHexString("1"))),
validators,
committerKeyPairs,
BASE_ROUND_NUMBER,
useDifferentRoundNumbersForCommittedSeals);

builder.extraData(ibftExtraData.encode());
return builder.buildHeader();
}
}
Loading

0 comments on commit d75f6a3

Please sign in to comment.