Spilt Ibft MessageValidator into components (#752)

Moving to IBFT2.1 requires that validation be conducted
on the signeddata aspects of a message separately from the
'piggybacked' block.

Move Validators to using Messages


Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
tmohay 6 years ago committed by GitHub
parent 562251638e
commit 4a7b9823ad
  1. 2
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java
  2. 13
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundFactory.java
  3. 10
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundState.java
  4. 202
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java
  5. 17
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java
  6. 2
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java
  7. 12
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidator.java
  8. 212
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidator.java
  9. 4
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
  10. 2
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java
  11. 17
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java
  12. 15
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java
  13. 253
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java
  14. 14
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java
  15. 4
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java
  16. 266
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java

@ -98,7 +98,7 @@ public class IbftBlockHeightManager implements BlockHeightManager {
new RoundState( new RoundState(
roundIdentifier, roundIdentifier,
finalState.getQuorum(), finalState.getQuorum(),
messageValidatorFactory.createMessageValidator(roundIdentifier, parentHeader)); messageValidatorFactory.createMessageValidator(roundIdentifier));
} }
@Override @Override

@ -17,6 +17,7 @@ import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator; import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator;
import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreatorFactory; import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreatorFactory;
import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidator; import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidator;
import tech.pegasys.pantheon.consensus.ibft.validation.SignedDataValidator;
import tech.pegasys.pantheon.ethereum.BlockValidator; import tech.pegasys.pantheon.ethereum.BlockValidator;
import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver;
@ -56,12 +57,12 @@ public class IbftRoundFactory {
roundIdentifier, roundIdentifier,
finalState.getQuorum(), finalState.getQuorum(),
new MessageValidator( new MessageValidator(
finalState.getValidators(), new SignedDataValidator(
finalState.getProposerForRound(roundIdentifier), finalState.getValidators(),
roundIdentifier, finalState.getProposerForRound(roundIdentifier),
blockValidator, roundIdentifier,
protocolContext, blockValidator,
parentHeader)); protocolContext)));
return createNewRoundWithState(parentHeader, roundState); return createNewRoundWithState(parentHeader, roundState);
} }

@ -66,10 +66,10 @@ public class RoundState {
public boolean setProposedBlock(final Proposal msg) { public boolean setProposedBlock(final Proposal msg) {
if (!proposalMessage.isPresent()) { if (!proposalMessage.isPresent()) {
if (validator.addSignedProposalPayload(msg.getSignedPayload())) { if (validator.addSignedProposalPayload(msg)) {
proposalMessage = Optional.of(msg); proposalMessage = Optional.of(msg);
preparePayloads.removeIf(p -> !validator.validatePrepareMessage(p.getSignedPayload())); preparePayloads.removeIf(p -> !validator.validatePrepareMessage(p));
commitPayloads.removeIf(p -> !validator.validateCommmitMessage(p.getSignedPayload())); commitPayloads.removeIf(p -> !validator.validateCommitMessage(p));
updateState(); updateState();
return true; return true;
} }
@ -79,7 +79,7 @@ public class RoundState {
} }
public void addPrepareMessage(final Prepare msg) { public void addPrepareMessage(final Prepare msg) {
if (!proposalMessage.isPresent() || validator.validatePrepareMessage(msg.getSignedPayload())) { if (!proposalMessage.isPresent() || validator.validatePrepareMessage(msg)) {
preparePayloads.add(msg); preparePayloads.add(msg);
LOG.debug("Round state added prepare message prepare={}", msg); LOG.debug("Round state added prepare message prepare={}", msg);
} }
@ -87,7 +87,7 @@ public class RoundState {
} }
public void addCommitMessage(final Commit msg) { public void addCommitMessage(final Commit msg) {
if (!proposalMessage.isPresent() || validator.validateCommmitMessage(msg.getSignedPayload())) { if (!proposalMessage.isPresent() || validator.validateCommitMessage(msg)) {
commitPayloads.add(msg); commitPayloads.add(msg);
LOG.debug("Round state added commit message commit={}", msg); LOG.debug("Round state added commit message commit={}", msg);
} }

@ -12,205 +12,27 @@
*/ */
package tech.pegasys.pantheon.consensus.ibft.validation; package tech.pegasys.pantheon.consensus.ibft.validation;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.IbftContext; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.payload.CommitPayload;
import tech.pegasys.pantheon.consensus.ibft.payload.Payload;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparePayload;
import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload;
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.Address;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode;
import java.util.Collection;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MessageValidator { public class MessageValidator {
private static final Logger LOG = LogManager.getLogger(); private final SignedDataValidator signedDataValidator;
private final Collection<Address> validators;
private final Address expectedProposer;
private final ConsensusRoundIdentifier roundIdentifier;
private final BlockValidator<IbftContext> blockValidator;
private final ProtocolContext<IbftContext> protocolContext;
private final BlockHeader parentHeader;
private Optional<SignedData<ProposalPayload>> proposal = Optional.empty();
public MessageValidator(
final Collection<Address> validators,
final Address expectedProposer,
final ConsensusRoundIdentifier roundIdentifier,
final BlockValidator<IbftContext> blockValidator,
final ProtocolContext<IbftContext> protocolContext,
final BlockHeader parentHeader) {
this.validators = validators;
this.expectedProposer = expectedProposer;
this.roundIdentifier = roundIdentifier;
this.blockValidator = blockValidator;
this.protocolContext = protocolContext;
this.parentHeader = parentHeader;
}
public boolean addSignedProposalPayload(final SignedData<ProposalPayload> msg) {
if (proposal.isPresent()) {
return handleSubsequentProposal(proposal.get(), msg);
}
if (!validateSignedProposalPayload(msg)) {
return false;
}
if (!validateBlockMatchesProposalRound(msg.getPayload())) {
return false;
}
proposal = Optional.of(msg);
return true;
}
private boolean validateSignedProposalPayload(final SignedData<ProposalPayload> msg) {
if (!msg.getPayload().getRoundIdentifier().equals(roundIdentifier)) {
LOG.info("Invalid Proposal message, does not match current round.");
return false;
}
if (!msg.getAuthor().equals(expectedProposer)) {
LOG.info(
"Invalid Proposal message, was not created by the proposer expected for the "
+ "associated round.");
return false;
}
final Block proposedBlock = msg.getPayload().getBlock();
final Optional<BlockProcessingOutputs> validationResult =
blockValidator.validateAndProcessBlock(
protocolContext, proposedBlock, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL);
if (!validationResult.isPresent()) {
LOG.info("Invalid Proposal message, block did not pass validation.");
return false;
}
return true;
}
private boolean handleSubsequentProposal(
final SignedData<ProposalPayload> existingMsg, final SignedData<ProposalPayload> newMsg) {
if (!existingMsg.getAuthor().equals(newMsg.getAuthor())) {
LOG.debug("Received subsequent invalid Proposal message; sender differs from original.");
return false;
}
final ProposalPayload existingData = existingMsg.getPayload();
final ProposalPayload newData = newMsg.getPayload();
if (!proposalMessagesAreIdentical(existingData, newData)) {
LOG.debug("Received subsequent invalid Proposal message; content differs from original.");
return false;
}
return true;
}
public boolean validatePrepareMessage(final SignedData<PreparePayload> msg) {
final String msgType = "Prepare";
if (!isMessageForCurrentRoundFromValidatorAndProposalAvailable(msg, msgType)) {
return false;
}
if (msg.getAuthor().equals(expectedProposer)) {
LOG.info("Illegal Prepare message; was sent by the round's proposer.");
return false;
}
return validateDigestMatchesProposal(msg.getPayload().getDigest(), msgType);
}
public boolean validateCommmitMessage(final SignedData<CommitPayload> msg) {
final String msgType = "Commit";
if (!isMessageForCurrentRoundFromValidatorAndProposalAvailable(msg, msgType)) {
return false;
}
final Block proposedBlock = proposal.get().getPayload().getBlock();
final Address commitSealCreator =
Util.signatureToAddress(msg.getPayload().getCommitSeal(), proposedBlock.getHash());
if (!commitSealCreator.equals(msg.getAuthor())) {
LOG.info("Invalid Commit message. Seal was not created by the message transmitter.");
return false;
}
return validateDigestMatchesProposal(msg.getPayload().getDigest(), msgType);
}
private boolean isMessageForCurrentRoundFromValidatorAndProposalAvailable(
final SignedData<? extends Payload> msg, final String msgType) {
if (!msg.getPayload().getRoundIdentifier().equals(roundIdentifier)) {
LOG.info("Invalid {} message, does not match current round.", msgType);
return false;
}
if (!validators.contains(msg.getAuthor())) {
LOG.info(
"Invalid {} message, was not transmitted by a validator for the " + "associated round.",
msgType);
return false;
}
if (!proposal.isPresent()) { public MessageValidator(final SignedDataValidator signedDataValidator) {
LOG.info( this.signedDataValidator = signedDataValidator;
"Unable to validate {} message. No Proposal exists against which to validate "
+ "block digest.",
msgType);
return false;
}
return true;
} }
private boolean validateDigestMatchesProposal(final Hash digest, final String msgType) { public boolean addSignedProposalPayload(final Proposal msg) {
final Block proposedBlock = proposal.get().getPayload().getBlock(); return signedDataValidator.addSignedProposalPayload(msg.getSignedPayload());
if (!digest.equals(proposedBlock.getHash())) {
LOG.info(
"Illegal {} message, digest does not match the block in the Prepare Message.", msgType);
return false;
}
return true;
} }
private boolean proposalMessagesAreIdentical( public boolean validatePrepareMessage(final Prepare msg) {
final ProposalPayload right, final ProposalPayload left) { return signedDataValidator.validatePrepareMessage(msg.getSignedPayload());
return right.getBlock().getHash().equals(left.getBlock().getHash())
&& right.getRoundIdentifier().equals(left.getRoundIdentifier());
} }
private boolean validateBlockMatchesProposalRound(final ProposalPayload payload) { public boolean validateCommitMessage(final Commit msg) {
final ConsensusRoundIdentifier msgRound = payload.getRoundIdentifier(); return signedDataValidator.validateCommmitMessage(msg.getSignedPayload());
final IbftExtraData extraData =
IbftExtraData.decode(payload.getBlock().getHeader().getExtraData());
if (extraData.getRound() != msgRound.getRoundNumber()) {
LOG.info("Invalid Proposal message, round number in block does not match that in message.");
return false;
}
return true;
} }
} }

@ -41,18 +41,21 @@ public class MessageValidatorFactory {
this.protocolContext = protocolContext; this.protocolContext = protocolContext;
} }
public MessageValidator createMessageValidator( private SignedDataValidator createSignedDataValidator(
final ConsensusRoundIdentifier roundIdentifier, final BlockHeader parentHeader) { final ConsensusRoundIdentifier roundIdentifier) {
final BlockValidator<IbftContext> blockValidator = final BlockValidator<IbftContext> blockValidator =
protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockValidator(); protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockValidator();
return new MessageValidator( return new SignedDataValidator(
protocolContext.getConsensusState().getVoteTally().getValidators(), protocolContext.getConsensusState().getVoteTally().getValidators(),
proposerSelector.selectProposerForRound(roundIdentifier), proposerSelector.selectProposerForRound(roundIdentifier),
roundIdentifier, roundIdentifier,
blockValidator, blockValidator,
protocolContext, protocolContext);
parentHeader); }
public MessageValidator createMessageValidator(final ConsensusRoundIdentifier roundIdentifier) {
return new MessageValidator(createSignedDataValidator(roundIdentifier));
} }
public RoundChangeMessageValidator createRoundChangeMessageValidator( public RoundChangeMessageValidator createRoundChangeMessageValidator(
@ -60,7 +63,7 @@ public class MessageValidatorFactory {
final Collection<Address> validators = final Collection<Address> validators =
protocolContext.getConsensusState().getVoteTally().getValidators(); protocolContext.getConsensusState().getVoteTally().getValidators();
return new RoundChangeMessageValidator( return new RoundChangeMessageValidator(
roundIdentifier -> createMessageValidator(roundIdentifier, parentHeader), this::createSignedDataValidator,
validators, validators,
prepareMessageCountForQuorum( prepareMessageCountForQuorum(
IbftHelpers.calculateRequiredValidatorQuorum(validators.size())), IbftHelpers.calculateRequiredValidatorQuorum(validators.size())),
@ -73,7 +76,7 @@ public class MessageValidatorFactory {
return new NewRoundMessageValidator( return new NewRoundMessageValidator(
validators, validators,
proposerSelector, proposerSelector,
roundIdentifier -> createMessageValidator(roundIdentifier, parentHeader), this::createSignedDataValidator,
IbftHelpers.calculateRequiredValidatorQuorum(validators.size()), IbftHelpers.calculateRequiredValidatorQuorum(validators.size()),
parentHeader.getNumber() + 1); parentHeader.getNumber() + 1);
} }

@ -80,7 +80,7 @@ public class NewRoundMessageValidator {
} }
final SignedData<ProposalPayload> proposalPayload = payload.getProposalPayload(); final SignedData<ProposalPayload> proposalPayload = payload.getProposalPayload();
final MessageValidator proposalValidator = final SignedDataValidator proposalValidator =
messageValidatorFactory.createAt(rootRoundIdentifier); messageValidatorFactory.createAt(rootRoundIdentifier);
if (!proposalValidator.addSignedProposalPayload(proposalPayload)) { if (!proposalValidator.addSignedProposalPayload(proposalPayload)) {
LOG.info("Invalid NewRound message, embedded proposal failed validation"); LOG.info("Invalid NewRound message, embedded proposal failed validation");

@ -81,15 +81,15 @@ public class RoundChangeMessageValidator {
return false; return false;
} }
final MessageValidator messageValidator = final SignedDataValidator signedDataValidator =
messageValidatorFactory.createAt(proposalRoundIdentifier); messageValidatorFactory.createAt(proposalRoundIdentifier);
return validateConsistencyOfPrepareCertificateMessages(certificate, messageValidator); return validateConsistencyOfPrepareCertificateMessages(certificate, signedDataValidator);
} }
private boolean validateConsistencyOfPrepareCertificateMessages( private boolean validateConsistencyOfPrepareCertificateMessages(
final PreparedCertificate certificate, final MessageValidator messageValidator) { final PreparedCertificate certificate, final SignedDataValidator signedDataValidator) {
if (!messageValidator.addSignedProposalPayload(certificate.getProposalPayload())) { if (!signedDataValidator.addSignedProposalPayload(certificate.getProposalPayload())) {
LOG.info("Invalid RoundChange message, embedded Proposal message failed validation."); LOG.info("Invalid RoundChange message, embedded Proposal message failed validation.");
return false; return false;
} }
@ -102,7 +102,7 @@ public class RoundChangeMessageValidator {
} }
for (final SignedData<PreparePayload> prepareMsg : certificate.getPreparePayloads()) { for (final SignedData<PreparePayload> prepareMsg : certificate.getPreparePayloads()) {
if (!messageValidator.validatePrepareMessage(prepareMsg)) { if (!signedDataValidator.validatePrepareMessage(prepareMsg)) {
LOG.info("Invalid RoundChange message, embedded Prepare message failed validation."); LOG.info("Invalid RoundChange message, embedded Prepare message failed validation.");
return false; return false;
} }
@ -130,6 +130,6 @@ public class RoundChangeMessageValidator {
@FunctionalInterface @FunctionalInterface
public interface MessageValidatorForHeightFactory { public interface MessageValidatorForHeightFactory {
MessageValidator createAt(final ConsensusRoundIdentifier roundIdentifier); SignedDataValidator createAt(final ConsensusRoundIdentifier roundIdentifier);
} }
} }

@ -0,0 +1,212 @@
/*
* 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.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.payload.CommitPayload;
import tech.pegasys.pantheon.consensus.ibft.payload.Payload;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparePayload;
import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload;
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.Address;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode;
import java.util.Collection;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class SignedDataValidator {
private static final Logger LOG = LogManager.getLogger();
private final Collection<Address> validators;
private final Address expectedProposer;
private final ConsensusRoundIdentifier roundIdentifier;
private final BlockValidator<IbftContext> blockValidator;
private final ProtocolContext<IbftContext> protocolContext;
private Optional<SignedData<ProposalPayload>> proposal = Optional.empty();
public SignedDataValidator(
final Collection<Address> validators,
final Address expectedProposer,
final ConsensusRoundIdentifier roundIdentifier,
final BlockValidator<IbftContext> blockValidator,
final ProtocolContext<IbftContext> protocolContext) {
this.validators = validators;
this.expectedProposer = expectedProposer;
this.roundIdentifier = roundIdentifier;
this.blockValidator = blockValidator;
this.protocolContext = protocolContext;
}
public boolean addSignedProposalPayload(final SignedData<ProposalPayload> msg) {
if (proposal.isPresent()) {
return handleSubsequentProposal(proposal.get(), msg);
}
if (!validateSignedProposalPayload(msg)) {
return false;
}
if (!validateBlockMatchesProposalRound(msg.getPayload())) {
return false;
}
proposal = Optional.of(msg);
return true;
}
private boolean validateSignedProposalPayload(final SignedData<ProposalPayload> msg) {
if (!msg.getPayload().getRoundIdentifier().equals(roundIdentifier)) {
LOG.info("Invalid Proposal message, does not match current round.");
return false;
}
if (!msg.getAuthor().equals(expectedProposer)) {
LOG.info(
"Invalid Proposal message, was not created by the proposer expected for the "
+ "associated round.");
return false;
}
final Block proposedBlock = msg.getPayload().getBlock();
final Optional<BlockProcessingOutputs> validationResult =
blockValidator.validateAndProcessBlock(
protocolContext, proposedBlock, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL);
if (!validationResult.isPresent()) {
LOG.info("Invalid Proposal message, block did not pass validation.");
return false;
}
return true;
}
private boolean handleSubsequentProposal(
final SignedData<ProposalPayload> existingMsg, final SignedData<ProposalPayload> newMsg) {
if (!existingMsg.getAuthor().equals(newMsg.getAuthor())) {
LOG.debug("Received subsequent invalid Proposal message; sender differs from original.");
return false;
}
final ProposalPayload existingData = existingMsg.getPayload();
final ProposalPayload newData = newMsg.getPayload();
if (!proposalMessagesAreIdentical(existingData, newData)) {
LOG.debug("Received subsequent invalid Proposal message; content differs from original.");
return false;
}
return true;
}
public boolean validatePrepareMessage(final SignedData<PreparePayload> msg) {
final String msgType = "Prepare";
if (!isMessageForCurrentRoundFromValidatorAndProposalAvailable(msg, msgType)) {
return false;
}
if (msg.getAuthor().equals(expectedProposer)) {
LOG.info("Illegal Prepare message; was sent by the round's proposer.");
return false;
}
return validateDigestMatchesProposal(msg.getPayload().getDigest(), msgType);
}
public boolean validateCommmitMessage(final SignedData<CommitPayload> msg) {
final String msgType = "Commit";
if (!isMessageForCurrentRoundFromValidatorAndProposalAvailable(msg, msgType)) {
return false;
}
final Block proposedBlock = proposal.get().getPayload().getBlock();
final Address commitSealCreator =
Util.signatureToAddress(msg.getPayload().getCommitSeal(), proposedBlock.getHash());
if (!commitSealCreator.equals(msg.getAuthor())) {
LOG.info("Invalid Commit message. Seal was not created by the message transmitter.");
return false;
}
return validateDigestMatchesProposal(msg.getPayload().getDigest(), msgType);
}
private boolean isMessageForCurrentRoundFromValidatorAndProposalAvailable(
final SignedData<? extends Payload> msg, final String msgType) {
if (!msg.getPayload().getRoundIdentifier().equals(roundIdentifier)) {
LOG.info("Invalid {} message, does not match current round.", msgType);
return false;
}
if (!validators.contains(msg.getAuthor())) {
LOG.info(
"Invalid {} message, was not transmitted by a validator for the " + "associated round.",
msgType);
return false;
}
if (!proposal.isPresent()) {
LOG.info(
"Unable to validate {} message. No Proposal exists against which to validate "
+ "block digest.",
msgType);
return false;
}
return true;
}
private boolean validateDigestMatchesProposal(final Hash digest, final String msgType) {
final Block proposedBlock = proposal.get().getPayload().getBlock();
if (!digest.equals(proposedBlock.getHash())) {
LOG.info(
"Illegal {} message, digest does not match the block in the Prepare Message.", msgType);
return false;
}
return true;
}
private boolean proposalMessagesAreIdentical(
final ProposalPayload right, final ProposalPayload left) {
return right.getBlock().getHash().equals(left.getBlock().getHash())
&& right.getRoundIdentifier().equals(left.getRoundIdentifier());
}
private boolean validateBlockMatchesProposalRound(final ProposalPayload payload) {
final ConsensusRoundIdentifier msgRound = payload.getRoundIdentifier();
final IbftExtraData extraData =
IbftExtraData.decode(payload.getBlock().getHeader().getExtraData());
if (extraData.getRound() != msgRound.getRoundNumber()) {
LOG.info("Invalid Proposal message, round number in block does not match that in message.");
return false;
}
return true;
}
}

@ -127,7 +127,7 @@ public class IbftBlockHeightManagerTest {
final MessageValidator messageValidator = mock(MessageValidator.class); final MessageValidator messageValidator = mock(MessageValidator.class);
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true); when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
when(messageValidator.validateCommmitMessage(any())).thenReturn(true); when(messageValidator.validateCommitMessage(any())).thenReturn(true);
when(messageValidator.validatePrepareMessage(any())).thenReturn(true); when(messageValidator.validatePrepareMessage(any())).thenReturn(true);
when(finalState.getTransmitter()).thenReturn(messageTransmitter); when(finalState.getTransmitter()).thenReturn(messageTransmitter);
when(finalState.getBlockTimer()).thenReturn(blockTimer); when(finalState.getBlockTimer()).thenReturn(blockTimer);
@ -138,7 +138,7 @@ public class IbftBlockHeightManagerTest {
when(newRoundMessageValidator.validateNewRoundMessage(any())).thenReturn(true); when(newRoundMessageValidator.validateNewRoundMessage(any())).thenReturn(true);
when(messageValidatorFactory.createNewRoundValidator(any())) when(messageValidatorFactory.createNewRoundValidator(any()))
.thenReturn(newRoundMessageValidator); .thenReturn(newRoundMessageValidator);
when(messageValidatorFactory.createMessageValidator(any(), any())).thenReturn(messageValidator); when(messageValidatorFactory.createMessageValidator(any())).thenReturn(messageValidator);
protocolContext = protocolContext =
new ProtocolContext<>(null, null, new IbftContext(new VoteTally(validators), null)); new ProtocolContext<>(null, null, new IbftContext(new VoteTally(validators), null));

@ -98,7 +98,7 @@ public class IbftRoundTest {
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true); when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
when(messageValidator.validatePrepareMessage(any())).thenReturn(true); when(messageValidator.validatePrepareMessage(any())).thenReturn(true);
when(messageValidator.validateCommmitMessage(any())).thenReturn(true); when(messageValidator.validateCommitMessage(any())).thenReturn(true);
proposedExtraData = proposedExtraData =
new IbftExtraData( new IbftExtraData(

@ -27,8 +27,8 @@ import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparePayload; import tech.pegasys.pantheon.consensus.ibft.payload.PreparePayload;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparedCertificate; import tech.pegasys.pantheon.consensus.ibft.payload.PreparedCertificate;
import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; import tech.pegasys.pantheon.consensus.ibft.payload.SignedData;
import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidator;
import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangeMessageValidator; import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangeMessageValidator;
import tech.pegasys.pantheon.consensus.ibft.validation.SignedDataValidator;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.BlockValidator; import tech.pegasys.pantheon.ethereum.BlockValidator;
import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs; import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs;
@ -88,33 +88,30 @@ public class RoundChangeManagerTest {
when(messageValidatorFactory.createAt(ri1)) when(messageValidatorFactory.createAt(ri1))
.thenAnswer( .thenAnswer(
invocation -> invocation ->
new MessageValidator( new SignedDataValidator(
validators, validators,
Util.publicKeyToAddress(proposerKey.getPublicKey()), Util.publicKeyToAddress(proposerKey.getPublicKey()),
ri1, ri1,
blockValidator, blockValidator,
protocolContext, protocolContext));
parentHeader));
when(messageValidatorFactory.createAt(ri2)) when(messageValidatorFactory.createAt(ri2))
.thenAnswer( .thenAnswer(
invocation -> invocation ->
new MessageValidator( new SignedDataValidator(
validators, validators,
Util.publicKeyToAddress(validator1Key.getPublicKey()), Util.publicKeyToAddress(validator1Key.getPublicKey()),
ri2, ri2,
blockValidator, blockValidator,
protocolContext, protocolContext));
parentHeader));
when(messageValidatorFactory.createAt(ri3)) when(messageValidatorFactory.createAt(ri3))
.thenAnswer( .thenAnswer(
invocation -> invocation ->
new MessageValidator( new SignedDataValidator(
validators, validators,
Util.publicKeyToAddress(validator2Key.getPublicKey()), Util.publicKeyToAddress(validator2Key.getPublicKey()),
ri3, ri3,
blockValidator, blockValidator,
protocolContext, protocolContext));
parentHeader));
final RoundChangeMessageValidator roundChangeMessageValidator = final RoundChangeMessageValidator roundChangeMessageValidator =
new RoundChangeMessageValidator( new RoundChangeMessageValidator(

@ -107,7 +107,7 @@ public class RoundStateTest {
@Test @Test
public void singleValidatorRequiresCommitMessageToBeCommitted() { public void singleValidatorRequiresCommitMessageToBeCommitted() {
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true); when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
when(messageValidator.validateCommmitMessage(any())).thenReturn(true); when(messageValidator.validateCommitMessage(any())).thenReturn(true);
final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator); final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator);
@ -184,9 +184,8 @@ public class RoundStateTest {
final RoundState roundState = new RoundState(roundIdentifier, 3, messageValidator); final RoundState roundState = new RoundState(roundIdentifier, 3, messageValidator);
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true); when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
when(messageValidator.validatePrepareMessage(firstPrepare.getSignedPayload())).thenReturn(true); when(messageValidator.validatePrepareMessage(firstPrepare)).thenReturn(true);
when(messageValidator.validatePrepareMessage(secondPrepare.getSignedPayload())) when(messageValidator.validatePrepareMessage(secondPrepare)).thenReturn(false);
.thenReturn(false);
roundState.addPrepareMessage(firstPrepare); roundState.addPrepareMessage(firstPrepare);
roundState.addPrepareMessage(secondPrepare); roundState.addPrepareMessage(secondPrepare);
@ -219,10 +218,8 @@ public class RoundStateTest {
validatorMessageFactories.get(0).createSignedProposalPayload(roundIdentifier, block); validatorMessageFactories.get(0).createSignedProposalPayload(roundIdentifier, block);
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true); when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
when(messageValidator.validatePrepareMessage(firstPrepare.getSignedPayload())) when(messageValidator.validatePrepareMessage(firstPrepare)).thenReturn(false);
.thenReturn(false); when(messageValidator.validatePrepareMessage(secondPrepare)).thenReturn(true);
when(messageValidator.validatePrepareMessage(secondPrepare.getSignedPayload()))
.thenReturn(true);
roundState.setProposedBlock(proposal); roundState.setProposedBlock(proposal);
assertThat(roundState.isPrepared()).isFalse(); assertThat(roundState.isPrepared()).isFalse();
@ -237,7 +234,7 @@ public class RoundStateTest {
@Test @Test
public void commitSealsAreExtractedFromReceivedMessages() { public void commitSealsAreExtractedFromReceivedMessages() {
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true); when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
when(messageValidator.validateCommmitMessage(any())).thenReturn(true); when(messageValidator.validateCommitMessage(any())).thenReturn(true);
final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator);

@ -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 * 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 * the License. You may obtain a copy of the License at
@ -12,256 +12,75 @@
*/ */
package tech.pegasys.pantheon.consensus.ibft.validation; package tech.pegasys.pantheon.consensus.ibft.validation;
import static java.util.Optional.empty;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftContext; import tech.pegasys.pantheon.consensus.ibft.TestHelpers;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.BlockValidator;
import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.AddressHelpers;
import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import com.google.common.collect.Lists; import org.assertj.core.util.Lists;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class MessageValidatorTest { public class MessageValidatorTest {
private final KeyPair proposerKey = KeyPair.generate(); private KeyPair keyPair = KeyPair.generate();
private final KeyPair validatorKey = KeyPair.generate(); private MessageFactory messageFactory = new MessageFactory(keyPair);
private final KeyPair nonValidatorKey = KeyPair.generate(); private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1);
private final MessageFactory proposerMessageFactory = new MessageFactory(proposerKey);
private final MessageFactory validatorMessageFactory = new MessageFactory(validatorKey);
private final MessageFactory nonValidatorMessageFactory = new MessageFactory(nonValidatorKey);
private final List<Address> validators = Lists.newArrayList(); private SignedDataValidator signedDataValidator = mock(SignedDataValidator.class);
@Mock private BlockValidator<IbftContext> blockValidator; private MessageValidator messageValidator = new MessageValidator(signedDataValidator);
private final BlockHeader parentHeader = mock(BlockHeader.class);
private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(2, 0);
private MessageValidator validator;
private final Block block = mock(Block.class); private final List<Address> validators =
Lists.newArrayList(
AddressHelpers.ofValue(0),
AddressHelpers.ofValue(1),
AddressHelpers.ofValue(2),
AddressHelpers.ofValue(3));
private final Block block =
TestHelpers.createProposalBlock(validators, roundIdentifier.getRoundNumber());
@Before @Before
public void setup() { public void setup() {
validators.add(Util.publicKeyToAddress(proposerKey.getPublicKey())); when(signedDataValidator.addSignedProposalPayload(any())).thenReturn(true);
validators.add(Util.publicKeyToAddress(validatorKey.getPublicKey())); when(signedDataValidator.validatePrepareMessage(any())).thenReturn(true);
when(signedDataValidator.validateCommmitMessage(any())).thenReturn(true);
final ProtocolContext<IbftContext> protocolContext =
new ProtocolContext<>(
mock(MutableBlockchain.class), mock(WorldStateArchive.class), mock(IbftContext.class));
validator =
new MessageValidator(
validators,
Util.publicKeyToAddress(proposerKey.getPublicKey()),
roundIdentifier,
blockValidator,
protocolContext,
parentHeader);
when(block.getHash()).thenReturn(Hash.fromHexStringLenient("1"));
when(blockValidator.validateAndProcessBlock(any(), any(), any(), any()))
.thenReturn(Optional.of(new BlockProcessingOutputs(null, null)));
insertRoundToBlockHeader(0);
}
private void insertRoundToBlockHeader(final int round) {
final IbftExtraData extraData =
new IbftExtraData(
BytesValue.wrap(new byte[32]), Collections.emptyList(), empty(), round, validators);
final BlockHeader header = mock(BlockHeader.class);
when(header.getExtraData()).thenReturn(extraData.encode());
when(block.getHeader()).thenReturn(header);
}
@Test
public void receivingAPrepareMessageBeforeProposalFails() {
final Prepare prepareMsg =
proposerMessageFactory.createSignedPreparePayload(roundIdentifier, Hash.ZERO);
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingACommitMessageBeforeProposalFails() {
final Commit commitMsg =
proposerMessageFactory.createSignedCommitPayload(
roundIdentifier, Hash.ZERO, SECP256K1.sign(Hash.ZERO, proposerKey));
assertThat(validator.validateCommmitMessage(commitMsg.getSignedPayload())).isFalse();
} }
@Test @Test
public void receivingProposalMessageFromNonProposerFails() { public void messageValidatorDefersToUnderlyingSignedDataValidator() {
final Proposal proposalMsg = final Proposal proposal = messageFactory.createSignedProposalPayload(roundIdentifier, block);
validatorMessageFactory.createSignedProposalPayload(roundIdentifier, mock(Block.class));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isFalse();
}
@Test final Prepare prepare =
public void receivingProposalMessageWithIllegalBlockFails() { messageFactory.createSignedPreparePayload(roundIdentifier, block.getHash());
when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())).thenReturn(empty());
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, mock(Block.class));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingPrepareFromProposerFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Prepare prepareMsg =
proposerMessageFactory.createSignedPreparePayload(roundIdentifier, block.getHash());
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingPrepareFromNonValidatorFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Prepare prepareMsg =
nonValidatorMessageFactory.createSignedPreparePayload(roundIdentifier, block.getHash());
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingMessagesWithDifferentRoundIdFromProposalFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final ConsensusRoundIdentifier invalidRoundIdentifier = final Commit commit =
new ConsensusRoundIdentifier( messageFactory.createSignedCommitPayload(
roundIdentifier.getSequenceNumber(), roundIdentifier.getRoundNumber() + 1); roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), keyPair));
final Prepare prepareMsg =
validatorMessageFactory.createSignedPreparePayload(invalidRoundIdentifier, block.getHash());
final Commit commitMsg =
validatorMessageFactory.createSignedCommitPayload(
invalidRoundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), proposerKey));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue(); messageValidator.addSignedProposalPayload(proposal);
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isFalse(); verify(signedDataValidator, times(1)).addSignedProposalPayload(proposal.getSignedPayload());
assertThat(validator.validateCommmitMessage(commitMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingPrepareNonProposerValidatorWithCorrectRoundIsSuccessful() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Prepare prepareMsg =
validatorMessageFactory.createSignedPreparePayload(roundIdentifier, block.getHash());
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isTrue();
}
@Test
public void receivingACommitMessageWithAnInvalidCommitSealFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Commit commitMsg =
proposerMessageFactory.createSignedCommitPayload(
roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), nonValidatorKey));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validateCommmitMessage(commitMsg.getSignedPayload())).isFalse();
}
@Test
public void commitMessageContainingValidSealFromValidatorIsSuccessful() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Commit proposerCommitMsg =
proposerMessageFactory.createSignedCommitPayload(
roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), proposerKey));
final Commit validatorCommitMsg =
validatorMessageFactory.createSignedCommitPayload(
roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), validatorKey));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validateCommmitMessage(proposerCommitMsg.getSignedPayload())).isTrue();
assertThat(validator.validateCommmitMessage(validatorCommitMsg.getSignedPayload())).isTrue();
}
@Test
public void subsequentProposalHasDifferentSenderFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
final Proposal secondProposalMsg =
validatorMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(secondProposalMsg.getSignedPayload())).isFalse();
}
@Test
public void subsequentProposalHasDifferentContentFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
final ConsensusRoundIdentifier newRoundIdentifier = new ConsensusRoundIdentifier(3, 0);
final Proposal secondProposalMsg =
proposerMessageFactory.createSignedProposalPayload(newRoundIdentifier, block);
assertThat(validator.addSignedProposalPayload(secondProposalMsg.getSignedPayload())).isFalse();
}
@Test
public void subsequentProposalHasIdenticalSenderAndContentIsSuccessful() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
final Proposal secondProposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(secondProposalMsg.getSignedPayload())).isTrue();
}
@Test
public void blockRoundMisMatchWithMessageRoundFails() {
insertRoundToBlockHeader(roundIdentifier.getRoundNumber() + 1);
final Proposal proposalMsg = messageValidator.validatePrepareMessage(prepare);
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block); verify(signedDataValidator, times(1)).validatePrepareMessage(prepare.getSignedPayload());
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isFalse(); messageValidator.validateCommitMessage(commit);
verify(signedDataValidator, times(1)).validateCommmitMessage(commit.getSignedPayload());
} }
} }

@ -41,7 +41,7 @@ import com.google.common.collect.Lists;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class NewRoundMessageValidatorTest { public class NewRoundSignedDataValidatorTest {
private final KeyPair proposerKey = KeyPair.generate(); private final KeyPair proposerKey = KeyPair.generate();
private final KeyPair validatorKey = KeyPair.generate(); private final KeyPair validatorKey = KeyPair.generate();
@ -58,7 +58,7 @@ public class NewRoundMessageValidatorTest {
private final ProposerSelector proposerSelector = mock(ProposerSelector.class); private final ProposerSelector proposerSelector = mock(ProposerSelector.class);
private final MessageValidatorForHeightFactory validatorFactory = private final MessageValidatorForHeightFactory validatorFactory =
mock(MessageValidatorForHeightFactory.class); mock(MessageValidatorForHeightFactory.class);
private final MessageValidator messageValidator = mock(MessageValidator.class); private final SignedDataValidator signedDataValidator = mock(SignedDataValidator.class);
private Block proposedBlock; private Block proposedBlock;
private NewRound validMsg; private NewRound validMsg;
@ -78,9 +78,9 @@ public class NewRoundMessageValidatorTest {
when(proposerSelector.selectProposerForRound(any())).thenReturn(proposerAddress); when(proposerSelector.selectProposerForRound(any())).thenReturn(proposerAddress);
when(validatorFactory.createAt(any())).thenReturn(messageValidator); when(validatorFactory.createAt(any())).thenReturn(signedDataValidator);
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true); when(signedDataValidator.addSignedProposalPayload(any())).thenReturn(true);
when(messageValidator.validatePrepareMessage(any())).thenReturn(true); when(signedDataValidator.validatePrepareMessage(any())).thenReturn(true);
validator = validator =
new NewRoundMessageValidator( new NewRoundMessageValidator(
@ -226,7 +226,7 @@ public class NewRoundMessageValidatorTest {
msgBuilder.setRoundChangeCertificate(roundChangeBuilder.buildCertificate()); msgBuilder.setRoundChangeCertificate(roundChangeBuilder.buildCertificate());
// The prepare Message in the RoundChange Cert will be deemed illegal. // The prepare Message in the RoundChange Cert will be deemed illegal.
when(messageValidator.validatePrepareMessage(any())).thenReturn(false); when(signedDataValidator.validatePrepareMessage(any())).thenReturn(false);
final NewRound msg = signPayload(msgBuilder.build(), proposerKey); final NewRound msg = signPayload(msgBuilder.build(), proposerKey);
@ -293,7 +293,7 @@ public class NewRoundMessageValidatorTest {
@Test @Test
public void embeddedProposalFailsValidation() { public void embeddedProposalFailsValidation() {
when(messageValidator.addSignedProposalPayload(any())).thenReturn(false, true); when(signedDataValidator.addSignedProposalPayload(any())).thenReturn(false, true);
final Proposal proposal = final Proposal proposal =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock); proposerMessageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock);

@ -40,7 +40,7 @@ import com.google.common.collect.Lists;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class RoundChangeMessageValidatorTest { public class RoundChangeSignedDataValidatorTest {
private final KeyPair proposerKey = KeyPair.generate(); private final KeyPair proposerKey = KeyPair.generate();
private final KeyPair validatorKey = KeyPair.generate(); private final KeyPair validatorKey = KeyPair.generate();
@ -56,7 +56,7 @@ public class RoundChangeMessageValidatorTest {
private final Block block = mock(Block.class); private final Block block = mock(Block.class);
private final MessageValidator basicValidator = mock(MessageValidator.class); private final SignedDataValidator basicValidator = mock(SignedDataValidator.class);
private final List<Address> validators = Lists.newArrayList(); private final List<Address> validators = Lists.newArrayList();
private final MessageValidatorForHeightFactory validatorFactory = private final MessageValidatorForHeightFactory validatorFactory =

@ -0,0 +1,266 @@
/*
* 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 static java.util.Optional.empty;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.BlockValidator;
import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class SignedDataValidatorTest {
private final KeyPair proposerKey = KeyPair.generate();
private final KeyPair validatorKey = KeyPair.generate();
private final KeyPair nonValidatorKey = KeyPair.generate();
private final MessageFactory proposerMessageFactory = new MessageFactory(proposerKey);
private final MessageFactory validatorMessageFactory = new MessageFactory(validatorKey);
private final MessageFactory nonValidatorMessageFactory = new MessageFactory(nonValidatorKey);
private final List<Address> validators = Lists.newArrayList();
@Mock private BlockValidator<IbftContext> blockValidator;
private final BlockHeader parentHeader = mock(BlockHeader.class);
private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(2, 0);
private SignedDataValidator validator;
private final Block block = mock(Block.class);
@Before
public void setup() {
validators.add(Util.publicKeyToAddress(proposerKey.getPublicKey()));
validators.add(Util.publicKeyToAddress(validatorKey.getPublicKey()));
final ProtocolContext<IbftContext> protocolContext =
new ProtocolContext<>(
mock(MutableBlockchain.class), mock(WorldStateArchive.class), mock(IbftContext.class));
validator =
new SignedDataValidator(
validators,
Util.publicKeyToAddress(proposerKey.getPublicKey()),
roundIdentifier,
blockValidator,
protocolContext);
when(block.getHash()).thenReturn(Hash.fromHexStringLenient("1"));
when(blockValidator.validateAndProcessBlock(any(), any(), any(), any()))
.thenReturn(Optional.of(new BlockProcessingOutputs(null, null)));
insertRoundToBlockHeader(0);
}
private void insertRoundToBlockHeader(final int round) {
final IbftExtraData extraData =
new IbftExtraData(
BytesValue.wrap(new byte[32]), Collections.emptyList(), empty(), round, validators);
final BlockHeader header = mock(BlockHeader.class);
when(header.getExtraData()).thenReturn(extraData.encode());
when(block.getHeader()).thenReturn(header);
}
@Test
public void receivingAPrepareMessageBeforeProposalFails() {
final Prepare prepareMsg =
proposerMessageFactory.createSignedPreparePayload(roundIdentifier, Hash.ZERO);
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingACommitMessageBeforeProposalFails() {
final Commit commitMsg =
proposerMessageFactory.createSignedCommitPayload(
roundIdentifier, Hash.ZERO, SECP256K1.sign(block.getHash(), proposerKey));
assertThat(validator.validateCommmitMessage(commitMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingProposalMessageFromNonProposerFails() {
final Proposal proposalMsg =
validatorMessageFactory.createSignedProposalPayload(roundIdentifier, mock(Block.class));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingProposalMessageWithIllegalBlockFails() {
when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())).thenReturn(empty());
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, mock(Block.class));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingPrepareFromProposerFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Prepare prepareMsg =
proposerMessageFactory.createSignedPreparePayload(roundIdentifier, block.getHash());
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingPrepareFromNonValidatorFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Prepare prepareMsg =
nonValidatorMessageFactory.createSignedPreparePayload(roundIdentifier, block.getHash());
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingMessagesWithDifferentRoundIdFromProposalFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final ConsensusRoundIdentifier invalidRoundIdentifier =
new ConsensusRoundIdentifier(
roundIdentifier.getSequenceNumber(), roundIdentifier.getRoundNumber() + 1);
final Prepare prepareMsg =
validatorMessageFactory.createSignedPreparePayload(invalidRoundIdentifier, block.getHash());
final Commit commitMsg =
validatorMessageFactory.createSignedCommitPayload(
invalidRoundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), proposerKey));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isFalse();
assertThat(validator.validateCommmitMessage(commitMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingPrepareNonProposerValidatorWithCorrectRoundIsSuccessful() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Prepare prepareMsg =
validatorMessageFactory.createSignedPreparePayload(roundIdentifier, block.getHash());
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validatePrepareMessage(prepareMsg.getSignedPayload())).isTrue();
}
@Test
public void receivingACommitMessageWithAnInvalidCommitSealFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Commit commitMsg =
proposerMessageFactory.createSignedCommitPayload(
roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), nonValidatorKey));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validateCommmitMessage(commitMsg.getSignedPayload())).isFalse();
}
@Test
public void commitMessageContainingValidSealFromValidatorIsSuccessful() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
final Commit proposerCommitMsg =
proposerMessageFactory.createSignedCommitPayload(
roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), proposerKey));
final Commit validatorCommitMsg =
validatorMessageFactory.createSignedCommitPayload(
roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), validatorKey));
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
assertThat(validator.validateCommmitMessage(proposerCommitMsg.getSignedPayload())).isTrue();
assertThat(validator.validateCommmitMessage(validatorCommitMsg.getSignedPayload())).isTrue();
}
@Test
public void subsequentProposalHasDifferentSenderFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
final Proposal secondProposalMsg =
validatorMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(secondProposalMsg.getSignedPayload())).isFalse();
}
@Test
public void subsequentProposalHasDifferentContentFails() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
final ConsensusRoundIdentifier newRoundIdentifier = new ConsensusRoundIdentifier(3, 0);
final Proposal secondProposalMsg =
proposerMessageFactory.createSignedProposalPayload(newRoundIdentifier, block);
assertThat(validator.addSignedProposalPayload(secondProposalMsg.getSignedPayload())).isFalse();
}
@Test
public void subsequentProposalHasIdenticalSenderAndContentIsSuccessful() {
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isTrue();
final Proposal secondProposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(secondProposalMsg.getSignedPayload())).isTrue();
}
@Test
public void blockRoundMisMatchWithMessageRoundFails() {
insertRoundToBlockHeader(roundIdentifier.getRoundNumber() + 1);
final Proposal proposalMsg =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isFalse();
}
}
Loading…
Cancel
Save