diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java
index 280e8c5371..0693a15c66 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java
@@ -98,7 +98,7 @@ public class IbftBlockHeightManager implements BlockHeightManager {
new RoundState(
roundIdentifier,
finalState.getQuorum(),
- messageValidatorFactory.createMessageValidator(roundIdentifier, parentHeader));
+ messageValidatorFactory.createMessageValidator(roundIdentifier));
}
@Override
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundFactory.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundFactory.java
index 8e46a8db41..8431bc2c54 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundFactory.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundFactory.java
@@ -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.IbftBlockCreatorFactory;
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.ProtocolContext;
import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver;
@@ -56,12 +57,12 @@ public class IbftRoundFactory {
roundIdentifier,
finalState.getQuorum(),
new MessageValidator(
- finalState.getValidators(),
- finalState.getProposerForRound(roundIdentifier),
- roundIdentifier,
- blockValidator,
- protocolContext,
- parentHeader));
+ new SignedDataValidator(
+ finalState.getValidators(),
+ finalState.getProposerForRound(roundIdentifier),
+ roundIdentifier,
+ blockValidator,
+ protocolContext)));
return createNewRoundWithState(parentHeader, roundState);
}
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundState.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundState.java
index 655182a917..7846384b9d 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundState.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundState.java
@@ -66,10 +66,10 @@ public class RoundState {
public boolean setProposedBlock(final Proposal msg) {
if (!proposalMessage.isPresent()) {
- if (validator.addSignedProposalPayload(msg.getSignedPayload())) {
+ if (validator.addSignedProposalPayload(msg)) {
proposalMessage = Optional.of(msg);
- preparePayloads.removeIf(p -> !validator.validatePrepareMessage(p.getSignedPayload()));
- commitPayloads.removeIf(p -> !validator.validateCommmitMessage(p.getSignedPayload()));
+ preparePayloads.removeIf(p -> !validator.validatePrepareMessage(p));
+ commitPayloads.removeIf(p -> !validator.validateCommitMessage(p));
updateState();
return true;
}
@@ -79,7 +79,7 @@ public class RoundState {
}
public void addPrepareMessage(final Prepare msg) {
- if (!proposalMessage.isPresent() || validator.validatePrepareMessage(msg.getSignedPayload())) {
+ if (!proposalMessage.isPresent() || validator.validatePrepareMessage(msg)) {
preparePayloads.add(msg);
LOG.debug("Round state added prepare message prepare={}", msg);
}
@@ -87,7 +87,7 @@ public class RoundState {
}
public void addCommitMessage(final Commit msg) {
- if (!proposalMessage.isPresent() || validator.validateCommmitMessage(msg.getSignedPayload())) {
+ if (!proposalMessage.isPresent() || validator.validateCommitMessage(msg)) {
commitPayloads.add(msg);
LOG.debug("Round state added commit message commit={}", msg);
}
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java
index ba894df327..999cc8f4fd 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java
@@ -12,205 +12,27 @@
*/
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.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;
+import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
+import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
+import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
public class MessageValidator {
- private static final Logger LOG = LogManager.getLogger();
-
- private final Collection
validators;
- private final Address expectedProposer;
- private final ConsensusRoundIdentifier roundIdentifier;
- private final BlockValidator blockValidator;
- private final ProtocolContext protocolContext;
- private final BlockHeader parentHeader;
-
- private Optional> proposal = Optional.empty();
-
- public MessageValidator(
- final Collection validators,
- final Address expectedProposer,
- final ConsensusRoundIdentifier roundIdentifier,
- final BlockValidator blockValidator,
- final ProtocolContext 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 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 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 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 existingMsg, final SignedData 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 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 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;
- }
+ private final SignedDataValidator signedDataValidator;
- if (!proposal.isPresent()) {
- LOG.info(
- "Unable to validate {} message. No Proposal exists against which to validate "
- + "block digest.",
- msgType);
- return false;
- }
- return true;
+ public MessageValidator(final SignedDataValidator signedDataValidator) {
+ this.signedDataValidator = signedDataValidator;
}
- 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;
+ public boolean addSignedProposalPayload(final Proposal msg) {
+ return signedDataValidator.addSignedProposalPayload(msg.getSignedPayload());
}
- private boolean proposalMessagesAreIdentical(
- final ProposalPayload right, final ProposalPayload left) {
- return right.getBlock().getHash().equals(left.getBlock().getHash())
- && right.getRoundIdentifier().equals(left.getRoundIdentifier());
+ public boolean validatePrepareMessage(final Prepare msg) {
+ return signedDataValidator.validatePrepareMessage(msg.getSignedPayload());
}
- 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;
+ public boolean validateCommitMessage(final Commit msg) {
+ return signedDataValidator.validateCommmitMessage(msg.getSignedPayload());
}
}
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java
index 0b05f8e84c..1c557185b1 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java
@@ -41,18 +41,21 @@ public class MessageValidatorFactory {
this.protocolContext = protocolContext;
}
- public MessageValidator createMessageValidator(
- final ConsensusRoundIdentifier roundIdentifier, final BlockHeader parentHeader) {
+ private SignedDataValidator createSignedDataValidator(
+ final ConsensusRoundIdentifier roundIdentifier) {
final BlockValidator blockValidator =
protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockValidator();
- return new MessageValidator(
+ return new SignedDataValidator(
protocolContext.getConsensusState().getVoteTally().getValidators(),
proposerSelector.selectProposerForRound(roundIdentifier),
roundIdentifier,
blockValidator,
- protocolContext,
- parentHeader);
+ protocolContext);
+ }
+
+ public MessageValidator createMessageValidator(final ConsensusRoundIdentifier roundIdentifier) {
+ return new MessageValidator(createSignedDataValidator(roundIdentifier));
}
public RoundChangeMessageValidator createRoundChangeMessageValidator(
@@ -60,7 +63,7 @@ public class MessageValidatorFactory {
final Collection validators =
protocolContext.getConsensusState().getVoteTally().getValidators();
return new RoundChangeMessageValidator(
- roundIdentifier -> createMessageValidator(roundIdentifier, parentHeader),
+ this::createSignedDataValidator,
validators,
prepareMessageCountForQuorum(
IbftHelpers.calculateRequiredValidatorQuorum(validators.size())),
@@ -73,7 +76,7 @@ public class MessageValidatorFactory {
return new NewRoundMessageValidator(
validators,
proposerSelector,
- roundIdentifier -> createMessageValidator(roundIdentifier, parentHeader),
+ this::createSignedDataValidator,
IbftHelpers.calculateRequiredValidatorQuorum(validators.size()),
parentHeader.getNumber() + 1);
}
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java
index d15c4880b8..1e9b88ebac 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java
@@ -80,7 +80,7 @@ public class NewRoundMessageValidator {
}
final SignedData proposalPayload = payload.getProposalPayload();
- final MessageValidator proposalValidator =
+ final SignedDataValidator proposalValidator =
messageValidatorFactory.createAt(rootRoundIdentifier);
if (!proposalValidator.addSignedProposalPayload(proposalPayload)) {
LOG.info("Invalid NewRound message, embedded proposal failed validation");
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidator.java
index 9a647c0c9d..bd0ed139c9 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidator.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidator.java
@@ -81,15 +81,15 @@ public class RoundChangeMessageValidator {
return false;
}
- final MessageValidator messageValidator =
+ final SignedDataValidator signedDataValidator =
messageValidatorFactory.createAt(proposalRoundIdentifier);
- return validateConsistencyOfPrepareCertificateMessages(certificate, messageValidator);
+ return validateConsistencyOfPrepareCertificateMessages(certificate, signedDataValidator);
}
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.");
return false;
}
@@ -102,7 +102,7 @@ public class RoundChangeMessageValidator {
}
for (final SignedData prepareMsg : certificate.getPreparePayloads()) {
- if (!messageValidator.validatePrepareMessage(prepareMsg)) {
+ if (!signedDataValidator.validatePrepareMessage(prepareMsg)) {
LOG.info("Invalid RoundChange message, embedded Prepare message failed validation.");
return false;
}
@@ -130,6 +130,6 @@ public class RoundChangeMessageValidator {
@FunctionalInterface
public interface MessageValidatorForHeightFactory {
- MessageValidator createAt(final ConsensusRoundIdentifier roundIdentifier);
+ SignedDataValidator createAt(final ConsensusRoundIdentifier roundIdentifier);
}
}
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidator.java
new file mode 100644
index 0000000000..0be260a5c8
--- /dev/null
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidator.java
@@ -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 validators;
+ private final Address expectedProposer;
+ private final ConsensusRoundIdentifier roundIdentifier;
+ private final BlockValidator blockValidator;
+ private final ProtocolContext protocolContext;
+
+ private Optional> proposal = Optional.empty();
+
+ public SignedDataValidator(
+ final Collection validators,
+ final Address expectedProposer,
+ final ConsensusRoundIdentifier roundIdentifier,
+ final BlockValidator blockValidator,
+ final ProtocolContext protocolContext) {
+ this.validators = validators;
+ this.expectedProposer = expectedProposer;
+ this.roundIdentifier = roundIdentifier;
+ this.blockValidator = blockValidator;
+ this.protocolContext = protocolContext;
+ }
+
+ public boolean addSignedProposalPayload(final SignedData 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 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 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 existingMsg, final SignedData 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 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 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;
+ }
+}
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
index c22388c455..5cd12cab8d 100644
--- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
@@ -127,7 +127,7 @@ public class IbftBlockHeightManagerTest {
final MessageValidator messageValidator = mock(MessageValidator.class);
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(finalState.getTransmitter()).thenReturn(messageTransmitter);
when(finalState.getBlockTimer()).thenReturn(blockTimer);
@@ -138,7 +138,7 @@ public class IbftBlockHeightManagerTest {
when(newRoundMessageValidator.validateNewRoundMessage(any())).thenReturn(true);
when(messageValidatorFactory.createNewRoundValidator(any()))
.thenReturn(newRoundMessageValidator);
- when(messageValidatorFactory.createMessageValidator(any(), any())).thenReturn(messageValidator);
+ when(messageValidatorFactory.createMessageValidator(any())).thenReturn(messageValidator);
protocolContext =
new ProtocolContext<>(null, null, new IbftContext(new VoteTally(validators), null));
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java
index 5b17ef8069..dc00243d6e 100644
--- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java
@@ -98,7 +98,7 @@ public class IbftRoundTest {
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
when(messageValidator.validatePrepareMessage(any())).thenReturn(true);
- when(messageValidator.validateCommmitMessage(any())).thenReturn(true);
+ when(messageValidator.validateCommitMessage(any())).thenReturn(true);
proposedExtraData =
new IbftExtraData(
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java
index c2e19f9632..d67cb7954c 100644
--- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java
@@ -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.PreparedCertificate;
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.SignedDataValidator;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.BlockValidator;
import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs;
@@ -88,33 +88,30 @@ public class RoundChangeManagerTest {
when(messageValidatorFactory.createAt(ri1))
.thenAnswer(
invocation ->
- new MessageValidator(
+ new SignedDataValidator(
validators,
Util.publicKeyToAddress(proposerKey.getPublicKey()),
ri1,
blockValidator,
- protocolContext,
- parentHeader));
+ protocolContext));
when(messageValidatorFactory.createAt(ri2))
.thenAnswer(
invocation ->
- new MessageValidator(
+ new SignedDataValidator(
validators,
Util.publicKeyToAddress(validator1Key.getPublicKey()),
ri2,
blockValidator,
- protocolContext,
- parentHeader));
+ protocolContext));
when(messageValidatorFactory.createAt(ri3))
.thenAnswer(
invocation ->
- new MessageValidator(
+ new SignedDataValidator(
validators,
Util.publicKeyToAddress(validator2Key.getPublicKey()),
ri3,
blockValidator,
- protocolContext,
- parentHeader));
+ protocolContext));
final RoundChangeMessageValidator roundChangeMessageValidator =
new RoundChangeMessageValidator(
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java
index 5735e0bc1b..25faa298fc 100644
--- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java
@@ -107,7 +107,7 @@ public class RoundStateTest {
@Test
public void singleValidatorRequiresCommitMessageToBeCommitted() {
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);
@@ -184,9 +184,8 @@ public class RoundStateTest {
final RoundState roundState = new RoundState(roundIdentifier, 3, messageValidator);
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
- when(messageValidator.validatePrepareMessage(firstPrepare.getSignedPayload())).thenReturn(true);
- when(messageValidator.validatePrepareMessage(secondPrepare.getSignedPayload()))
- .thenReturn(false);
+ when(messageValidator.validatePrepareMessage(firstPrepare)).thenReturn(true);
+ when(messageValidator.validatePrepareMessage(secondPrepare)).thenReturn(false);
roundState.addPrepareMessage(firstPrepare);
roundState.addPrepareMessage(secondPrepare);
@@ -219,10 +218,8 @@ public class RoundStateTest {
validatorMessageFactories.get(0).createSignedProposalPayload(roundIdentifier, block);
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
- when(messageValidator.validatePrepareMessage(firstPrepare.getSignedPayload()))
- .thenReturn(false);
- when(messageValidator.validatePrepareMessage(secondPrepare.getSignedPayload()))
- .thenReturn(true);
+ when(messageValidator.validatePrepareMessage(firstPrepare)).thenReturn(false);
+ when(messageValidator.validatePrepareMessage(secondPrepare)).thenReturn(true);
roundState.setProposedBlock(proposal);
assertThat(roundState.isPrepared()).isFalse();
@@ -237,7 +234,7 @@ public class RoundStateTest {
@Test
public void commitSealsAreExtractedFromReceivedMessages() {
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);
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java
index eb0a17fec3..058dbae2d1 100644
--- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java
@@ -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
@@ -12,256 +12,75 @@
*/
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.times;
+import static org.mockito.Mockito.verify;
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.TestHelpers;
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.AddressHelpers;
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.assertj.core.util.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 MessageValidatorTest {
- 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 KeyPair keyPair = KeyPair.generate();
+ private MessageFactory messageFactory = new MessageFactory(keyPair);
+ private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1);
- private final List validators = Lists.newArrayList();
+ private SignedDataValidator signedDataValidator = mock(SignedDataValidator.class);
- @Mock private BlockValidator blockValidator;
- private final BlockHeader parentHeader = mock(BlockHeader.class);
- private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(2, 0);
- private MessageValidator validator;
+ private MessageValidator messageValidator = new MessageValidator(signedDataValidator);
- private final Block block = mock(Block.class);
+ private final List 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
public void setup() {
- validators.add(Util.publicKeyToAddress(proposerKey.getPublicKey()));
- validators.add(Util.publicKeyToAddress(validatorKey.getPublicKey()));
-
- final ProtocolContext 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();
+ when(signedDataValidator.addSignedProposalPayload(any())).thenReturn(true);
+ when(signedDataValidator.validatePrepareMessage(any())).thenReturn(true);
+ when(signedDataValidator.validateCommmitMessage(any())).thenReturn(true);
}
@Test
- public void receivingProposalMessageFromNonProposerFails() {
- final Proposal proposalMsg =
- validatorMessageFactory.createSignedProposalPayload(roundIdentifier, mock(Block.class));
-
- assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isFalse();
- }
+ public void messageValidatorDefersToUnderlyingSignedDataValidator() {
+ final Proposal proposal = messageFactory.createSignedProposalPayload(roundIdentifier, block);
- @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 Prepare prepare =
+ messageFactory.createSignedPreparePayload(roundIdentifier, block.getHash());
- 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));
+ final Commit commit =
+ messageFactory.createSignedCommitPayload(
+ roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), keyPair));
- 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);
+ messageValidator.addSignedProposalPayload(proposal);
+ verify(signedDataValidator, times(1)).addSignedProposalPayload(proposal.getSignedPayload());
- final Proposal proposalMsg =
- proposerMessageFactory.createSignedProposalPayload(roundIdentifier, block);
+ messageValidator.validatePrepareMessage(prepare);
+ verify(signedDataValidator, times(1)).validatePrepareMessage(prepare.getSignedPayload());
- assertThat(validator.addSignedProposalPayload(proposalMsg.getSignedPayload())).isFalse();
+ messageValidator.validateCommitMessage(commit);
+ verify(signedDataValidator, times(1)).validateCommmitMessage(commit.getSignedPayload());
}
}
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java
similarity index 96%
rename from consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java
rename to consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java
index b420e62a8e..d5f02bf04a 100644
--- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java
@@ -41,7 +41,7 @@ import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
-public class NewRoundMessageValidatorTest {
+public class NewRoundSignedDataValidatorTest {
private final KeyPair proposerKey = KeyPair.generate();
private final KeyPair validatorKey = KeyPair.generate();
@@ -58,7 +58,7 @@ public class NewRoundMessageValidatorTest {
private final ProposerSelector proposerSelector = mock(ProposerSelector.class);
private final MessageValidatorForHeightFactory validatorFactory =
mock(MessageValidatorForHeightFactory.class);
- private final MessageValidator messageValidator = mock(MessageValidator.class);
+ private final SignedDataValidator signedDataValidator = mock(SignedDataValidator.class);
private Block proposedBlock;
private NewRound validMsg;
@@ -78,9 +78,9 @@ public class NewRoundMessageValidatorTest {
when(proposerSelector.selectProposerForRound(any())).thenReturn(proposerAddress);
- when(validatorFactory.createAt(any())).thenReturn(messageValidator);
- when(messageValidator.addSignedProposalPayload(any())).thenReturn(true);
- when(messageValidator.validatePrepareMessage(any())).thenReturn(true);
+ when(validatorFactory.createAt(any())).thenReturn(signedDataValidator);
+ when(signedDataValidator.addSignedProposalPayload(any())).thenReturn(true);
+ when(signedDataValidator.validatePrepareMessage(any())).thenReturn(true);
validator =
new NewRoundMessageValidator(
@@ -226,7 +226,7 @@ public class NewRoundMessageValidatorTest {
msgBuilder.setRoundChangeCertificate(roundChangeBuilder.buildCertificate());
// 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);
@@ -293,7 +293,7 @@ public class NewRoundMessageValidatorTest {
@Test
public void embeddedProposalFailsValidation() {
- when(messageValidator.addSignedProposalPayload(any())).thenReturn(false, true);
+ when(signedDataValidator.addSignedProposalPayload(any())).thenReturn(false, true);
final Proposal proposal =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock);
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java
similarity index 98%
rename from consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidatorTest.java
rename to consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java
index e62a3429e6..3560e1d956 100644
--- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidatorTest.java
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java
@@ -40,7 +40,7 @@ import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
-public class RoundChangeMessageValidatorTest {
+public class RoundChangeSignedDataValidatorTest {
private final KeyPair proposerKey = KeyPair.generate();
private final KeyPair validatorKey = KeyPair.generate();
@@ -56,7 +56,7 @@ public class RoundChangeMessageValidatorTest {
private final Block block = mock(Block.class);
- private final MessageValidator basicValidator = mock(MessageValidator.class);
+ private final SignedDataValidator basicValidator = mock(SignedDataValidator.class);
private final List validators = Lists.newArrayList();
private final MessageValidatorForHeightFactory validatorFactory =
diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java
new file mode 100644
index 0000000000..622947752d
--- /dev/null
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java
@@ -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 validators = Lists.newArrayList();
+
+ @Mock private BlockValidator 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 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();
+ }
+}