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

Created message validators for NewRound and RoundChange #760

Merged
merged 2 commits into from
Feb 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public void handleNewRoundPayload(final NewRound newRound) {
}
LOG.info("Received NewRound Payload for {}", newRound.getRoundIdentifier());

if (newRoundMessageValidator.validateNewRoundMessage(newRound.getSignedPayload())) {
if (newRoundMessageValidator.validateNewRoundMessage(newRound)) {
if (messageAge == FUTURE_ROUND) {
startNewRound(newRound.getRoundIdentifier().getRoundNumber());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Optional<Collection<RoundChange>> appendRoundChangeMessage(final RoundCha
}

private boolean isMessageValid(final RoundChange msg) {
return roundChangeMessageValidator.validateMessage(msg.getSignedPayload());
return roundChangeMessageValidator.validateRoundChange(msg);
}

private RoundChangeStatus storeRoundChangeMessage(final RoundChange msg) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,23 @@ public RoundChangeMessageValidator createRoundChangeMessageValidator(
final Collection<Address> validators =
protocolContext.getConsensusState().getVoteTally().getValidators();
return new RoundChangeMessageValidator(
this::createSignedDataValidator,
validators,
prepareMessageCountForQuorum(
IbftHelpers.calculateRequiredValidatorQuorum(validators.size())),
parentHeader.getNumber() + 1);
new RoundChangePayloadValidator(
this::createSignedDataValidator,
validators,
prepareMessageCountForQuorum(
IbftHelpers.calculateRequiredValidatorQuorum(validators.size())),
parentHeader.getNumber() + 1));
}

public NewRoundMessageValidator createNewRoundValidator(final BlockHeader parentHeader) {
final Collection<Address> validators =
protocolContext.getConsensusState().getVoteTally().getValidators();
return new NewRoundMessageValidator(
validators,
proposerSelector,
this::createSignedDataValidator,
IbftHelpers.calculateRequiredValidatorQuorum(validators.size()),
parentHeader.getNumber() + 1);
new NewRoundPayloadValidator(
validators,
proposerSelector,
this::createSignedDataValidator,
IbftHelpers.calculateRequiredValidatorQuorum(validators.size()),
parentHeader.getNumber() + 1));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018 ConsenSys AG.
* 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
Expand All @@ -12,165 +12,17 @@
*/
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.blockcreation.ProposerSelector;
import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparedCertificate;
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.RoundChangeMessageValidator.MessageValidatorForHeightFactory;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;

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

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;

public class NewRoundMessageValidator {

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;

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

public boolean validateNewRoundMessage(final SignedData<NewRoundPayload> msg) {
private final NewRoundPayloadValidator payloadValidator;

final NewRoundPayload payload = msg.getPayload();
final ConsensusRoundIdentifier rootRoundIdentifier = payload.getRoundIdentifier();
final Address expectedProposer = proposerSelector.selectProposerForRound(rootRoundIdentifier);
final RoundChangeCertificate roundChangeCert = payload.getRoundChangeCertificate();

if (!expectedProposer.equals(msg.getAuthor())) {
LOG.info("Invalid NewRound message, did not originate from expected proposer.");
return false;
}

if (msg.getPayload().getRoundIdentifier().getSequenceNumber() != chainHeight) {
LOG.info("Invalid NewRound message, not valid for local chain height.");
return false;
}

if (msg.getPayload().getRoundIdentifier().getRoundNumber() == 0) {
LOG.info("Invalid NewRound message, illegally targets a new round of 0.");
return false;
}

final SignedData<ProposalPayload> proposalPayload = payload.getProposalPayload();
final SignedDataValidator proposalValidator =
messageValidatorFactory.createAt(rootRoundIdentifier);
if (!proposalValidator.addSignedProposalPayload(proposalPayload)) {
LOG.info("Invalid NewRound message, embedded proposal failed validation");
return false;
}

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

return validateProposalMessageMatchesLatestPrepareCertificate(payload);
public NewRoundMessageValidator(final NewRoundPayloadValidator payloadValidator) {
this.payloadValidator = payloadValidator;
}

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 RoundChangeMessageValidator roundChangeValidator =
new RoundChangeMessageValidator(
messageValidatorFactory,
validators,
prepareMessageCountForQuorum(quorum),
chainHeight);

if (!roundChangeValidator.validateMessage(roundChangeMsg)) {
LOG.info("Invalid NewRound message, embedded RoundChange message failed validation.");
return false;
}
}
return true;
}

private boolean validateProposalMessageMatchesLatestPrepareCertificate(
final NewRoundPayload payload) {

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

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

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

// Get the hash of the block in latest prepareCert, not including the Round field.
final Hash roundAgnosticBlockHashPreparedCert =
IbftBlockHashing.calculateHashOfIbftBlockOnChain(
latestPreparedCertificate
.get()
.getProposalPayload()
.getPayload()
.getBlock()
.getHeader());

final Hash roundAgnosticBlockHashProposal =
IbftBlockHashing.calculateHashOfIbftBlockOnChain(
payload.getProposalPayload().getPayload().getBlock().getHeader());

if (!roundAgnosticBlockHashPreparedCert.equals(roundAgnosticBlockHashProposal)) {
LOG.info(
"Invalid NewRound message, block in latest RoundChange does not match proposed block.");
return false;
}

return true;
public boolean validateNewRoundMessage(final NewRound msg) {
return payloadValidator.validateNewRoundMessage(msg.getSignedPayload());
}
}
Loading