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

Commit

Permalink
Round change validation (#315)
Browse files Browse the repository at this point in the history
RoundChangeValidator has been added which is responsible for
validating the content of a RoundChange message by using the
underlying MessageValidator capabilities.
  • Loading branch information
rain-on authored Nov 29, 2018
1 parent e60b784 commit 24b8d73
Show file tree
Hide file tree
Showing 5 changed files with 386 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput;

import java.util.Objects;

import com.google.common.base.MoreObjects;

/**
Expand Down Expand Up @@ -89,4 +91,21 @@ public String toString() {
.add("Round", round)
.toString();
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConsensusRoundIdentifier that = (ConsensusRoundIdentifier) o;
return sequence == that.sequence && round == that.round;
}

@Override
public int hashCode() {
return Objects.hash(sequence, round);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,6 @@ private boolean validateDigestMatchesPreprepareBlock(final Hash digest, final St
private boolean preprepareMessagesAreIdentical(
final IbftUnsignedPrePrepareMessageData right, final IbftUnsignedPrePrepareMessageData left) {
return right.getBlock().getHash().equals(left.getBlock().getHash())
&& (right.getRoundIdentifier().compareTo(left.getRoundIdentifier()) == 0);
&& right.getRoundIdentifier().equals(left.getRoundIdentifier());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 2018 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 tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftPreparedCertificate;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftSignedMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedPrePrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedPrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedRoundChangeMessageData;
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 RoundChangeMessageValidator {

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

private final MessageValidatorFactory messageValidatorFactory;
private final Collection<Address> validators;
private final long minimumPrepareMessages;
private final ConsensusRoundIdentifier currentRound;

public RoundChangeMessageValidator(
final MessageValidatorFactory messageValidatorFactory,
final Collection<Address> validators,
final long minimumPrepareMessages,
final ConsensusRoundIdentifier currentRound) {
this.messageValidatorFactory = messageValidatorFactory;
this.validators = validators;
this.minimumPrepareMessages = minimumPrepareMessages;
this.currentRound = currentRound;
}

public boolean validateMessage(
final IbftSignedMessageData<IbftUnsignedRoundChangeMessageData> msg) {

if (!validators.contains(msg.getSender())) {
LOG.info(
"Invalid RoundChange message, was not transmitted by a validator for the associated"
+ " round.");
return false;
}

final ConsensusRoundIdentifier roundChangeTarget =
msg.getUnsignedMessageData().getRoundChangeIdentifier();

if (roundChangeTarget.getSequenceNumber() != currentRound.getSequenceNumber()) {
LOG.info("Invalid RoundChange message, not valid for local chain height.");
return false;
}

if (msg.getUnsignedMessageData().getPreparedCertificate().isPresent()) {
final IbftPreparedCertificate certificate =
msg.getUnsignedMessageData().getPreparedCertificate().get();

return validatePrepareCertificate(certificate, roundChangeTarget);
}

return true;
}

private boolean validatePrepareCertificate(
final IbftPreparedCertificate certificate, final ConsensusRoundIdentifier roundChangeTarget) {
final IbftSignedMessageData<IbftUnsignedPrePrepareMessageData> preprepareMessage =
certificate.getIbftPrePrepareMessage();

final ConsensusRoundIdentifier prepareCertRound =
preprepareMessage.getUnsignedMessageData().getRoundIdentifier();

if (!validatePreprepareCertificateRound(prepareCertRound, roundChangeTarget)) {
return false;
}

final MessageValidator messageValidator = messageValidatorFactory.createAt(prepareCertRound);
return validateConsistencyOfPrepareCertificateMessages(certificate, messageValidator);
}

private boolean validateConsistencyOfPrepareCertificateMessages(
final IbftPreparedCertificate certificate, final MessageValidator messageValidator) {

if (!messageValidator.addPreprepareMessage(certificate.getIbftPrePrepareMessage())) {
LOG.info("Invalid RoundChange message, embedded Preprepare message failed validation.");
return false;
}

if (certificate.getIbftPrepareMessages().size() < minimumPrepareMessages) {
LOG.info(
"Invalid RoundChange message, insufficient prepare messages exist to justify "
+ "prepare certificate.");
return false;
}

for (final IbftSignedMessageData<IbftUnsignedPrepareMessageData> prepareMsg :
certificate.getIbftPrepareMessages()) {
if (!messageValidator.validatePrepareMessage(prepareMsg)) {
LOG.info("Invalid RoundChange message, embedded Prepare message failed validation.");
return false;
}
}

return true;
}

private boolean validatePreprepareCertificateRound(
final ConsensusRoundIdentifier prepareCertRound,
final ConsensusRoundIdentifier roundChangeTarget) {

if (prepareCertRound.getSequenceNumber() != roundChangeTarget.getSequenceNumber()) {
LOG.info("Invalid RoundChange message, PreprepareCertificate is not for local chain height.");
return false;
}

if (prepareCertRound.getRoundNumber() >= roundChangeTarget.getRoundNumber()) {
LOG.info(
"Invalid RoundChange message, PreprepareCertificate is newer than RoundChange target.");
return false;
}
return true;
}

@FunctionalInterface
public interface MessageValidatorFactory {

MessageValidator createAt(final ConsensusRoundIdentifier roundIdentifier);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ public class MessageValidatorTest {
private final IbftMessageFactory nonValidatorMessageFactory =
new IbftMessageFactory(nonValidatorKey);

private List<Address> validators = Lists.newArrayList();
private final List<Address> validators = Lists.newArrayList();

@Mock private BlockHeaderValidator<IbftContext> headerValidator;
private BlockHeader parentHeader = mock(BlockHeader.class);
private final BlockHeader parentHeader = mock(BlockHeader.class);
private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(2, 0);
private MessageValidator validator;

Expand Down
Loading

0 comments on commit 24b8d73

Please sign in to comment.