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

Commit

Permalink
Break out RoundChangeCertificate validation (#864)
Browse files Browse the repository at this point in the history
With upcoming changes to remove the NewRound message, and place
the RoundChangeCertificate in the Proposal, it was decided to break
out the RoundChangeCertiifcate validation into a separate file
to minimise changes during message restructuring.
  • Loading branch information
rain-on authored Feb 15, 2019
1 parent ac63edc commit ce56ea8
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,19 @@ public NewRoundMessageValidator createNewRoundValidator(final long chainHeight)
final BlockValidator<IbftContext> blockValidator =
protocolSchedule.getByBlockNumber(chainHeight).getBlockValidator();

final RoundChangeCertificateValidator roundChangeCertificateValidator =
new RoundChangeCertificateValidator(
validators, this::createSignedDataValidator, chainHeight);

return new NewRoundMessageValidator(
new NewRoundPayloadValidator(
validators,
proposerSelector,
this::createSignedDataValidator,
IbftHelpers.calculateRequiredValidatorQuorum(validators.size()),
chainHeight),
chainHeight,
roundChangeCertificateValidator),
new ProposalBlockConsistencyValidator(),
blockValidator,
protocolContext);
protocolContext,
roundChangeCertificateValidator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,14 @@
*/
package tech.pegasys.pantheon.consensus.ibft.validation;

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

import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockInterface;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparedCertificate;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangePayload;
import tech.pegasys.pantheon.consensus.ibft.payload.SignedData;
import tech.pegasys.pantheon.ethereum.BlockValidator;
import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode;

import java.util.Collection;
import java.util.Optional;

import org.apache.logging.log4j.LogManager;
Expand All @@ -43,16 +33,19 @@ public class NewRoundMessageValidator {
private final ProposalBlockConsistencyValidator proposalConsistencyValidator;
private final BlockValidator<IbftContext> blockValidator;
private final ProtocolContext<IbftContext> protocolContext;
private final RoundChangeCertificateValidator roundChangeCertificateValidator;

public NewRoundMessageValidator(
final NewRoundPayloadValidator payloadValidator,
final ProposalBlockConsistencyValidator proposalConsistencyValidator,
final BlockValidator<IbftContext> blockValidator,
final ProtocolContext<IbftContext> protocolContext) {
final ProtocolContext<IbftContext> protocolContext,
final RoundChangeCertificateValidator roundChangeCertificateValidator) {
this.payloadValidator = payloadValidator;
this.proposalConsistencyValidator = proposalConsistencyValidator;
this.blockValidator = blockValidator;
this.protocolContext = protocolContext;
this.roundChangeCertificateValidator = roundChangeCertificateValidator;
}

public boolean validateNewRoundMessage(final NewRound msg) {
Expand All @@ -61,8 +54,8 @@ public boolean validateNewRoundMessage(final NewRound msg) {
return false;
}

if (!validateProposalMessageMatchesLatestPrepareCertificate(
msg.getSignedPayload().getPayload(), msg.getBlock())) {
if (!roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate(
msg.getRoundChangeCertificate(), msg.getBlock())) {
LOG.debug(
"Illegal NewRound message, piggybacked block does not match latest PrepareCertificate");
return false;
Expand All @@ -88,44 +81,4 @@ private boolean validateBlock(final Block block) {

return true;
}

private boolean validateProposalMessageMatchesLatestPrepareCertificate(
final NewRoundPayload payload, final Block proposedBlock) {

final RoundChangeCertificate roundChangeCert = payload.getRoundChangeCertificate();
final Collection<SignedData<RoundChangePayload>> roundChangePayloads =
roundChangeCert.getRoundChangePayloads();

final Optional<PreparedCertificate> latestPreparedCertificate =
findLatestPreparedCertificate(roundChangePayloads);

if (!latestPreparedCertificate.isPresent()) {
LOG.trace(
"No round change messages have a preparedCertificate, any valid block may be proposed.");
return true;
}

// Need to check that if we substitute the LatestPrepareCert round number into the supplied
// block that we get the SAME hash as PreparedCert.
final Block currentBlockWithOldRound =
IbftBlockInterface.replaceRoundInBlock(
proposedBlock,
latestPreparedCertificate
.get()
.getProposalPayload()
.getPayload()
.getRoundIdentifier()
.getRoundNumber(),
IbftBlockHashing::calculateDataHashForCommittedSeal);

if (!currentBlockWithOldRound
.getHash()
.equals(latestPreparedCertificate.get().getProposalPayload().getPayload().getDigest())) {
LOG.info(
"Invalid NewRound message, block in latest RoundChange does not match proposed block.");
return false;
}

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

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

import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector;
import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload;
import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangePayload;
import tech.pegasys.pantheon.consensus.ibft.payload.SignedData;
import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangePayloadValidator.MessageValidatorForHeightFactory;
import tech.pegasys.pantheon.ethereum.core.Address;

import java.util.Collection;

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

public class NewRoundPayloadValidator {

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

private final Collection<Address> validators;
private final ProposerSelector proposerSelector;
private final MessageValidatorForHeightFactory messageValidatorFactory;
private final long quorum;
private final long chainHeight;
private final RoundChangeCertificateValidator roundChangeCertificateValidator;

public NewRoundPayloadValidator(
final Collection<Address> validators,
final ProposerSelector proposerSelector,
final MessageValidatorForHeightFactory messageValidatorFactory,
final long quorum,
final long chainHeight) {
this.validators = validators;
final long chainHeight,
final RoundChangeCertificateValidator roundChangeCertificateValidator) {
this.proposerSelector = proposerSelector;
this.messageValidatorFactory = messageValidatorFactory;
this.quorum = quorum;
this.chainHeight = chainHeight;
this.roundChangeCertificateValidator = roundChangeCertificateValidator;
}

public boolean validateNewRoundMessage(final SignedData<NewRoundPayload> msg) {
Expand Down Expand Up @@ -82,49 +74,11 @@ public boolean validateNewRoundMessage(final SignedData<NewRoundPayload> msg) {
return false;
}

if (!validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
if (!roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
rootRoundIdentifier, roundChangeCert)) {
return false;
}

return true;
}

private boolean validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
final ConsensusRoundIdentifier expectedRound, final RoundChangeCertificate roundChangeCert) {

final Collection<SignedData<RoundChangePayload>> roundChangeMsgs =
roundChangeCert.getRoundChangePayloads();

if (roundChangeMsgs.size() < quorum) {
LOG.info(
"Invalid NewRound message, RoundChange certificate has insufficient "
+ "RoundChange messages.");
return false;
}

if (!roundChangeCert.getRoundChangePayloads().stream()
.allMatch(p -> p.getPayload().getRoundIdentifier().equals(expectedRound))) {
LOG.info(
"Invalid NewRound message, not all embedded RoundChange messages have a "
+ "matching target round.");
return false;
}

for (final SignedData<RoundChangePayload> roundChangeMsg :
roundChangeCert.getRoundChangePayloads()) {
final RoundChangePayloadValidator roundChangeValidator =
new RoundChangePayloadValidator(
messageValidatorFactory,
validators,
prepareMessageCountForQuorum(quorum),
chainHeight);

if (!roundChangeValidator.validateRoundChange(roundChangeMsg)) {
LOG.info("Invalid NewRound message, embedded RoundChange message failed validation.");
return false;
}
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* 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.validation;

import static tech.pegasys.pantheon.consensus.ibft.IbftHelpers.findLatestPreparedCertificate;
import static tech.pegasys.pantheon.consensus.ibft.IbftHelpers.prepareMessageCountForQuorum;

import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockInterface;
import tech.pegasys.pantheon.consensus.ibft.IbftHelpers;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparedCertificate;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangePayload;
import tech.pegasys.pantheon.consensus.ibft.payload.SignedData;
import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangePayloadValidator.MessageValidatorForHeightFactory;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Block;

import java.util.Collection;
import java.util.Optional;

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

public class RoundChangeCertificateValidator {

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

private final Collection<Address> validators;
private final MessageValidatorForHeightFactory messageValidatorFactory;
private final long quorum;
private final long chainHeight;

public RoundChangeCertificateValidator(
final Collection<Address> validators,
final MessageValidatorForHeightFactory messageValidatorFactory,
final long chainHeight) {
this.validators = validators;
this.messageValidatorFactory = messageValidatorFactory;
this.quorum = IbftHelpers.calculateRequiredValidatorQuorum(validators.size());
this.chainHeight = chainHeight;
}

public boolean validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
final ConsensusRoundIdentifier expectedRound, final RoundChangeCertificate roundChangeCert) {

final Collection<SignedData<RoundChangePayload>> roundChangeMsgs =
roundChangeCert.getRoundChangePayloads();

if (roundChangeMsgs.size() < quorum) {
LOG.info("Invalid RoundChangeCertificate, insufficient RoundChange messages.");
return false;
}

if (!roundChangeCert.getRoundChangePayloads().stream()
.allMatch(p -> p.getPayload().getRoundIdentifier().equals(expectedRound))) {
LOG.info(
"Invalid RoundChangeCertificate, not all embedded RoundChange messages have a "
+ "matching target round.");
return false;
}

final RoundChangePayloadValidator roundChangeValidator =
new RoundChangePayloadValidator(
messageValidatorFactory, validators, prepareMessageCountForQuorum(quorum), chainHeight);

if (!roundChangeCert.getRoundChangePayloads().stream()
.allMatch(roundChangeValidator::validateRoundChange)) {
LOG.info("Invalid NewRound message, embedded RoundChange message failed validation.");
return false;
}

return true;
}

public boolean validateProposalMessageMatchesLatestPrepareCertificate(
final RoundChangeCertificate roundChangeCert, final Block proposedBlock) {

final Collection<SignedData<RoundChangePayload>> roundChangePayloads =
roundChangeCert.getRoundChangePayloads();

final Optional<PreparedCertificate> latestPreparedCertificate =
findLatestPreparedCertificate(roundChangePayloads);

if (!latestPreparedCertificate.isPresent()) {
LOG.trace(
"No round change messages have a preparedCertificate, any valid block may be proposed.");
return true;
}

// Need to check that if we substitute the LatestPrepareCert round number into the supplied
// block that we get the SAME hash as PreparedCert.
final Block currentBlockWithOldRound =
IbftBlockInterface.replaceRoundInBlock(
proposedBlock,
latestPreparedCertificate
.get()
.getProposalPayload()
.getPayload()
.getRoundIdentifier()
.getRoundNumber(),
IbftBlockHashing::calculateDataHashForCommittedSeal);

if (!currentBlockWithOldRound
.getHash()
.equals(latestPreparedCertificate.get().getProposalPayload().getPayload().getDigest())) {
LOG.info(
"Invalid NewRound message, block in latest RoundChange does not match proposed block.");
return false;
}

return true;
}
}
Loading

0 comments on commit ce56ea8

Please sign in to comment.