From a8b52e19f553f077e891e96f215ca2763c706b97 Mon Sep 17 00:00:00 2001
From: tmohay <37158202+rain-on@users.noreply.github.com>
Date: Wed, 28 Nov 2018 13:35:27 +1100
Subject: [PATCH] Basic Ibft message validators (#314)
---
.../consensus/ibft/IbftBlockHashing.java | 5 +
.../IbftUnsignedPrePrepareMessageData.java | 3 +-
.../ibft/validation/MessageValidator.java | 204 +++++++++++++
.../ibft/validation/MessageValidatorTest.java | 274 ++++++++++++++++++
4 files changed, 485 insertions(+), 1 deletion(-)
create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java
create mode 100644 consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockHashing.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockHashing.java
index 69f9541a61..4e8d880c1b 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockHashing.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockHashing.java
@@ -41,6 +41,11 @@ public class IbftBlockHashing {
return Hash.hash(serializeHeader(header, ibftExtraData::encodeWithoutCommitSeals));
}
+ public static Hash calculateDataHashForCommittedSeal(final BlockHeader header) {
+ final IbftExtraData ibftExtraData = IbftExtraData.decode(header.getExtraData());
+ return Hash.hash(serializeHeader(header, ibftExtraData::encodeWithoutCommitSeals));
+ }
+
/**
* Constructs a hash of the block header, but omits the committerSeals and sets round number to 0
* (as these change on each of the potentially circulated blocks at the current chain height).
diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrePrepareMessageData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrePrepareMessageData.java
index 4378b46880..11c2541132 100644
--- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrePrepareMessageData.java
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrePrepareMessageData.java
@@ -36,7 +36,8 @@ public class IbftUnsignedPrePrepareMessageData extends AbstractIbftUnsignedInRou
rlpInput.enterList();
final ConsensusRoundIdentifier roundIdentifier = ConsensusRoundIdentifier.readFrom(rlpInput);
- final Block block = Block.readFrom(rlpInput, IbftBlockHashing::calculateHashOfIbftBlockOnChain);
+ final Block block =
+ Block.readFrom(rlpInput, IbftBlockHashing::calculateDataHashForCommittedSeal);
rlpInput.leaveList();
return new IbftUnsignedPrePrepareMessageData(roundIdentifier, block);
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
new file mode 100644
index 0000000000..1a643429a0
--- /dev/null
+++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java
@@ -0,0 +1,204 @@
+/*
+ * 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 tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode.FULL;
+
+import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
+import tech.pegasys.pantheon.consensus.ibft.IbftContext;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.AbstractIbftUnsignedInRoundMessageData;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftSignedMessageData;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedCommitMessageData;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedPrePrepareMessageData;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedPrepareMessageData;
+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.BlockHeaderValidator;
+
+import java.util.Collection;
+import java.util.Optional;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class MessageValidator {
+
+ private static final Logger LOG = LogManager.getLogger();
+
+ private final Collection
validators;
+ private final Address expectedProposer;
+ private final ConsensusRoundIdentifier roundIdentifier;
+ private final BlockHeaderValidator headerValidator;
+ private final ProtocolContext protocolContext;
+ private final BlockHeader parentHeader;
+
+ private Optional> preprepareMessage =
+ Optional.empty();
+
+ public MessageValidator(
+ final Collection validators,
+ final Address expectedProposer,
+ final ConsensusRoundIdentifier roundIdentifier,
+ final BlockHeaderValidator headerValidator,
+ final ProtocolContext protocolContext,
+ final BlockHeader parentHeader) {
+ this.validators = validators;
+ this.expectedProposer = expectedProposer;
+ this.roundIdentifier = roundIdentifier;
+ this.headerValidator = headerValidator;
+ this.protocolContext = protocolContext;
+ this.parentHeader = parentHeader;
+ }
+
+ public boolean addPreprepareMessage(
+ final IbftSignedMessageData msg) {
+
+ if (preprepareMessage.isPresent()) {
+ return handleSubsequentPreprepareMessage(preprepareMessage.get(), msg);
+ }
+
+ if (!validatePreprepareMessage(msg)) {
+ return false;
+ }
+
+ preprepareMessage = Optional.of(msg);
+ return true;
+ }
+
+ private boolean validatePreprepareMessage(
+ final IbftSignedMessageData msg) {
+
+ if (!msg.getUnsignedMessageData().getRoundIdentifier().equals(roundIdentifier)) {
+ LOG.info("Invalid Preprepare message, does not match current round.");
+ return false;
+ }
+
+ if (!msg.getSender().equals(expectedProposer)) {
+ LOG.info(
+ "Invalid Preprepare message, was not created by the proposer expected for the "
+ + "associated round.");
+ return false;
+ }
+
+ final Block proposedBlock = msg.getUnsignedMessageData().getBlock();
+ if (!headerValidator.validateHeader(
+ proposedBlock.getHeader(), parentHeader, protocolContext, FULL)) {
+ LOG.info("Invalid Prepare message, block did not pass header validation.");
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean handleSubsequentPreprepareMessage(
+ final IbftSignedMessageData existingMsg,
+ final IbftSignedMessageData newMsg) {
+ if (!existingMsg.getSender().equals(newMsg.getSender())) {
+ LOG.debug("Received subsequent invalid Preprepare message; sender differs from original.");
+ return false;
+ }
+
+ final IbftUnsignedPrePrepareMessageData existingData = existingMsg.getUnsignedMessageData();
+ final IbftUnsignedPrePrepareMessageData newData = newMsg.getUnsignedMessageData();
+
+ if (!preprepareMessagesAreIdentical(existingData, newData)) {
+ LOG.debug("Received subsequent invalid Preprepare message; content differs from original.");
+ return false;
+ }
+
+ return true;
+ }
+
+ public boolean validatePrepareMessage(
+ final IbftSignedMessageData msg) {
+ final String msgType = "Prepare";
+
+ if (!isMessageForCurrentRoundFromValidatorAndPreprareMessageAvailable(msg, msgType)) {
+ return false;
+ }
+
+ if (msg.getSender().equals(expectedProposer)) {
+ LOG.info("Illegal Prepare message; was sent by the round's proposer.");
+ return false;
+ }
+
+ return validateDigestMatchesPreprepareBlock(msg.getUnsignedMessageData().getDigest(), msgType);
+ }
+
+ public boolean validateCommmitMessage(
+ final IbftSignedMessageData msg) {
+ final String msgType = "Commit";
+
+ if (!isMessageForCurrentRoundFromValidatorAndPreprareMessageAvailable(msg, msgType)) {
+ return false;
+ }
+
+ final Block proposedBlock = preprepareMessage.get().getUnsignedMessageData().getBlock();
+ final Address commitSealCreator =
+ Util.signatureToAddress(
+ msg.getUnsignedMessageData().getCommitSeal(), proposedBlock.getHash());
+
+ if (!commitSealCreator.equals(msg.getSender())) {
+ LOG.info("Invalid Commit message. Seal was not created by the message transmitter.");
+ return false;
+ }
+
+ return validateDigestMatchesPreprepareBlock(msg.getUnsignedMessageData().getDigest(), msgType);
+ }
+
+ private boolean isMessageForCurrentRoundFromValidatorAndPreprareMessageAvailable(
+ final IbftSignedMessageData extends AbstractIbftUnsignedInRoundMessageData> msg,
+ final String msgType) {
+
+ if (!msg.getUnsignedMessageData().getRoundIdentifier().equals(roundIdentifier)) {
+ LOG.info("Invalid {} message, does not match current round.", msgType);
+ return false;
+ }
+
+ if (!validators.contains(msg.getSender())) {
+ LOG.info(
+ "Invalid {} message, was not transmitted by a validator for the " + "associated round.",
+ msgType);
+ return false;
+ }
+
+ if (!preprepareMessage.isPresent()) {
+ LOG.info(
+ "Unable to validate {} message. No Preprepare message exists against "
+ + "which to validate block digest.",
+ msgType);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean validateDigestMatchesPreprepareBlock(final Hash digest, final String msgType) {
+ final Block proposedBlock = preprepareMessage.get().getUnsignedMessageData().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 preprepareMessagesAreIdentical(
+ final IbftUnsignedPrePrepareMessageData right, final IbftUnsignedPrePrepareMessageData left) {
+ return right.getBlock().getHash().equals(left.getBlock().getHash())
+ && (right.getRoundIdentifier().compareTo(left.getRoundIdentifier()) == 0);
+ }
+}
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
new file mode 100644
index 0000000000..f6b7a65f0f
--- /dev/null
+++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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 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.ibftmessagedata.IbftMessageFactory;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftSignedMessageData;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedCommitMessageData;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedPrePrepareMessageData;
+import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedPrepareMessageData;
+import tech.pegasys.pantheon.crypto.SECP256K1;
+import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
+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.db.WorldStateArchive;
+import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator;
+
+import java.util.List;
+
+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 MessageValidatorTest {
+
+ private final KeyPair proposerKey = KeyPair.generate();
+ private final KeyPair validatorKey = KeyPair.generate();
+ private final KeyPair nonValidatorKey = KeyPair.generate();
+ private final IbftMessageFactory proposerMessageFactory = new IbftMessageFactory(proposerKey);
+ private final IbftMessageFactory validatorMessageFactory = new IbftMessageFactory(validatorKey);
+ private final IbftMessageFactory nonValidatorMessageFactory =
+ new IbftMessageFactory(nonValidatorKey);
+
+ private List validators = Lists.newArrayList();
+
+ @Mock private BlockHeaderValidator headerValidator;
+ private BlockHeader parentHeader = mock(BlockHeader.class);
+ private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(2, 0);
+ private MessageValidator 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 MessageValidator(
+ validators,
+ Util.publicKeyToAddress(proposerKey.getPublicKey()),
+ roundIdentifier,
+ headerValidator,
+ protocolContext,
+ parentHeader);
+
+ when(block.getHash()).thenReturn(Hash.fromHexStringLenient("1"));
+ }
+
+ @Test
+ public void receivingAPrepareMessageBeforePrePrepareFails() {
+ final IbftSignedMessageData prepareMsg =
+ proposerMessageFactory.createIbftSignedPrepareMessageData(roundIdentifier, Hash.ZERO);
+
+ assertThat(validator.validatePrepareMessage(prepareMsg)).isFalse();
+ }
+
+ @Test
+ public void receivingACommitMessageBeforePreprepareFails() {
+ final IbftSignedMessageData commitMsg =
+ proposerMessageFactory.createIbftSignedCommitMessageData(
+ roundIdentifier, Hash.ZERO, SECP256K1.sign(Hash.ZERO, proposerKey));
+
+ assertThat(validator.validateCommmitMessage(commitMsg)).isFalse();
+ }
+
+ @Test
+ public void receivingPreprepareMessageFromNonProposerFails() {
+ final IbftSignedMessageData preprepareMsg =
+ validatorMessageFactory.createIbftSignedPrePrepareMessageData(
+ roundIdentifier, mock(Block.class));
+
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isFalse();
+ }
+
+ @Test
+ public void receivingPreprepareMessageWithIllegalBlockFails() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(false);
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(
+ roundIdentifier, mock(Block.class));
+
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isFalse();
+ }
+
+ @Test
+ public void receivingPrepareFromProposerFails() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+
+ final IbftSignedMessageData prepareMsg =
+ proposerMessageFactory.createIbftSignedPrepareMessageData(roundIdentifier, block.getHash());
+
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+ assertThat(validator.validatePrepareMessage(prepareMsg)).isFalse();
+ }
+
+ @Test
+ public void receivingPrepareFromNonValidatorFails() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+ final Block block = mock(Block.class);
+ final BlockHeader header = mock(BlockHeader.class);
+ when(header.getHash()).thenReturn(Hash.fromHexStringLenient("1")); // arbitrary hash value.
+ when(block.getHeader()).thenReturn(header);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+
+ final IbftSignedMessageData prepareMsg =
+ nonValidatorMessageFactory.createIbftSignedPrepareMessageData(
+ roundIdentifier, header.getHash());
+
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+ assertThat(validator.validatePrepareMessage(prepareMsg)).isFalse();
+ }
+
+ @Test
+ public void receivingMessagesWithDifferentRoundIdFromPreprepareFails() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+
+ final ConsensusRoundIdentifier invalidRoundIdentifier =
+ new ConsensusRoundIdentifier(
+ roundIdentifier.getSequenceNumber(), roundIdentifier.getRoundNumber() + 1);
+ final IbftSignedMessageData prepareMsg =
+ validatorMessageFactory.createIbftSignedPrepareMessageData(
+ invalidRoundIdentifier, block.getHash());
+ final IbftSignedMessageData commitMsg =
+ validatorMessageFactory.createIbftSignedCommitMessageData(
+ invalidRoundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), proposerKey));
+
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+ assertThat(validator.validatePrepareMessage(prepareMsg)).isFalse();
+ assertThat(validator.validateCommmitMessage(commitMsg)).isFalse();
+ }
+
+ @Test
+ public void receivingPrepareNonProposerValidatorWithCorrectRoundIsSuccessful() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+ final Block block = mock(Block.class);
+ final BlockHeader header = mock(BlockHeader.class);
+ final Hash blockHash = Hash.fromHexStringLenient("1");
+ when(header.getHash()).thenReturn(blockHash); // arbitrary hash value.
+ when(block.getHeader()).thenReturn(header);
+ when(block.getHash()).thenReturn(blockHash);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+ final IbftSignedMessageData prepareMsg =
+ validatorMessageFactory.createIbftSignedPrepareMessageData(
+ roundIdentifier, header.getHash());
+
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+ assertThat(validator.validatePrepareMessage(prepareMsg)).isTrue();
+ }
+
+ @Test
+ public void receivingACommitMessageWithAnInvalidCommitSealFails() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+
+ final IbftSignedMessageData commitMsg =
+ proposerMessageFactory.createIbftSignedCommitMessageData(
+ roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), nonValidatorKey));
+
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+ assertThat(validator.validateCommmitMessage(commitMsg)).isFalse();
+ }
+
+ @Test
+ public void commitMessageContainingValidSealFromValidatorIsSuccessful() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+
+ final IbftSignedMessageData proposerCommitMsg =
+ proposerMessageFactory.createIbftSignedCommitMessageData(
+ roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), proposerKey));
+
+ final IbftSignedMessageData validatorCommitMsg =
+ validatorMessageFactory.createIbftSignedCommitMessageData(
+ roundIdentifier, block.getHash(), SECP256K1.sign(block.getHash(), validatorKey));
+
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+ assertThat(validator.validateCommmitMessage(proposerCommitMsg)).isTrue();
+ assertThat(validator.validateCommmitMessage(validatorCommitMsg)).isTrue();
+ }
+
+ @Test
+ public void subsequentPreprepareHasDifferentSenderFails() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+
+ final IbftSignedMessageData secondPreprepareMsg =
+ validatorMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+ assertThat(validator.addPreprepareMessage(secondPreprepareMsg)).isFalse();
+ }
+
+ @Test
+ public void subsequentPreprepareHasDifferentContentFails() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+
+ final ConsensusRoundIdentifier newRoundIdentifier = new ConsensusRoundIdentifier(3, 0);
+ final IbftSignedMessageData secondPreprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(newRoundIdentifier, block);
+ assertThat(validator.addPreprepareMessage(secondPreprepareMsg)).isFalse();
+ }
+
+ @Test
+ public void subsequentPreprepareHasIdenticalSenderAndContentIsSuccessful() {
+ when(headerValidator.validateHeader(any(), any(), any(), any())).thenReturn(true);
+
+ final IbftSignedMessageData preprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+ assertThat(validator.addPreprepareMessage(preprepareMsg)).isTrue();
+
+ final IbftSignedMessageData secondPreprepareMsg =
+ proposerMessageFactory.createIbftSignedPrePrepareMessageData(roundIdentifier, block);
+ assertThat(validator.addPreprepareMessage(secondPreprepareMsg)).isTrue();
+ }
+}