New round validation (#353)

Received New Round messages require significant validation to ensure their content is consistent and valid with regard the current state of the system.

This commit includes a class (and tests) which ensures illegally constructed NewRound messages are identified.

This does not include the action to be taken upon detection of an illegal New Round message.
tmohay 6 years ago committed by GitHub
parent 08cc58236e
commit 51ffe8a923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/InRoundPayload.java
  2. 3
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/MessageFactory.java
  3. 44
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/NewRoundPayload.java
  4. 17
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/RoundChangeCertificate.java
  5. 187
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java
  6. 17
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidator.java
  7. 22
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/TestHelpers.java
  8. 297
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java
  9. 16
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeMessageValidatorTest.java
  10. 1
      ethereum/rlp/src/jmh/java/tech/pegasys/pantheon/ethereum/rlp/RLPBench.java

@ -14,6 +14,8 @@ package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import java.util.Objects;
public abstract class InRoundPayload extends AbstractPayload { public abstract class InRoundPayload extends AbstractPayload {
protected final ConsensusRoundIdentifier roundIdentifier; protected final ConsensusRoundIdentifier roundIdentifier;
@ -24,4 +26,21 @@ public abstract class InRoundPayload extends AbstractPayload {
public ConsensusRoundIdentifier getRoundIdentifier() { public ConsensusRoundIdentifier getRoundIdentifier() {
return roundIdentifier; return roundIdentifier;
} }
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final InRoundPayload that = (InRoundPayload) o;
return Objects.equals(roundIdentifier, that.roundIdentifier);
}
@Override
public int hashCode() {
return Objects.hash(roundIdentifier);
}
} }

@ -84,7 +84,7 @@ public class MessageFactory {
payload, Util.publicKeyToAddress(validatorKeyPair.getPublicKey()), signature); payload, Util.publicKeyToAddress(validatorKeyPair.getPublicKey()), signature);
} }
static Hash hashForSignature(final AbstractPayload unsignedMessageData) { public static Hash hashForSignature(final AbstractPayload unsignedMessageData) {
return Hash.hash( return Hash.hash(
BytesValues.concatenate( BytesValues.concatenate(
BytesValues.ofUnsignedByte(unsignedMessageData.getMessageType()), BytesValues.ofUnsignedByte(unsignedMessageData.getMessageType()),
@ -92,7 +92,6 @@ public class MessageFactory {
} }
private static Signature sign(final AbstractPayload unsignedMessageData, final KeyPair nodeKeys) { private static Signature sign(final AbstractPayload unsignedMessageData, final KeyPair nodeKeys) {
return SECP256K1.sign(hashForSignature(unsignedMessageData), nodeKeys); return SECP256K1.sign(hashForSignature(unsignedMessageData), nodeKeys);
} }
} }

@ -17,6 +17,8 @@ import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2;
import tech.pegasys.pantheon.ethereum.rlp.RLPInput; import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; import tech.pegasys.pantheon.ethereum.rlp.RLPOutput;
import java.util.Collections;
public class NewRoundPayload extends AbstractPayload { public class NewRoundPayload extends AbstractPayload {
private static final int TYPE = IbftV2.NEW_ROUND; private static final int TYPE = IbftV2.NEW_ROUND;
@ -74,4 +76,46 @@ public class NewRoundPayload extends AbstractPayload {
public int getMessageType() { public int getMessageType() {
return TYPE; return TYPE;
} }
public static class Builder {
private ConsensusRoundIdentifier roundChangeIdentifier = new ConsensusRoundIdentifier(1, 0);
private RoundChangeCertificate roundChangeCertificate =
new RoundChangeCertificate(Collections.emptyList());
private SignedData<ProposalPayload> proposalPayload = null;
public Builder() {}
public Builder(
final ConsensusRoundIdentifier roundChangeIdentifier,
final RoundChangeCertificate roundChangeCertificate,
final SignedData<ProposalPayload> proposalPayload) {
this.roundChangeIdentifier = roundChangeIdentifier;
this.roundChangeCertificate = roundChangeCertificate;
this.proposalPayload = proposalPayload;
}
public static Builder fromExisting(final NewRoundPayload payload) {
return new Builder(
payload.roundChangeIdentifier, payload.roundChangeCertificate, payload.proposalPayload);
}
public void setRoundChangeIdentifier(final ConsensusRoundIdentifier roundChangeIdentifier) {
this.roundChangeIdentifier = roundChangeIdentifier;
}
public void setRoundChangeCertificate(final RoundChangeCertificate roundChangeCertificate) {
this.roundChangeCertificate = roundChangeCertificate;
}
public void setProposalPayload(final SignedData<ProposalPayload> proposalPayload) {
this.proposalPayload = proposalPayload;
}
public NewRoundPayload build() {
return new NewRoundPayload(roundChangeIdentifier, roundChangeCertificate, proposalPayload);
}
}
} }

@ -16,6 +16,9 @@ import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; import tech.pegasys.pantheon.ethereum.rlp.RLPOutput;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import com.google.common.collect.Lists;
public class RoundChangeCertificate { public class RoundChangeCertificate {
@ -45,4 +48,18 @@ public class RoundChangeCertificate {
public Collection<SignedData<RoundChangePayload>> getRoundChangePayloads() { public Collection<SignedData<RoundChangePayload>> getRoundChangePayloads() {
return roundChangePayloads; return roundChangePayloads;
} }
public static class Builder {
private final List<SignedData<RoundChangePayload>> roundChangePayloads = Lists.newArrayList();
public Builder() {}
public void appendRoundChangeMessage(final SignedData<RoundChangePayload> msg) {
roundChangePayloads.add(msg);
}
public RoundChangeCertificate buildCertificate() {
return new RoundChangeCertificate(roundChangePayloads);
}
}
} }

@ -0,0 +1,187 @@
/*
* 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.blockcreation.ProposerSelector;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparedCertificate;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangeCertificate;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangePayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangeMessageValidator.MessageValidatorFactory;
import tech.pegasys.pantheon.ethereum.core.Address;
import java.util.Collection;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class NewRoundMessageValidator {
private static final Logger LOG = LogManager.getLogger();
private final Collection<Address> validators;
private final ProposerSelector proposerSelector;
private final MessageValidatorFactory messageValidatorFactory;
private final long quorumSize;
private final long chainHeight;
public NewRoundMessageValidator(
final Collection<Address> validators,
final ProposerSelector proposerSelector,
final MessageValidatorFactory messageValidatorFactory,
final long quorumSize,
final long chainHeight) {
this.validators = validators;
this.proposerSelector = proposerSelector;
this.messageValidatorFactory = messageValidatorFactory;
this.quorumSize = quorumSize;
this.chainHeight = chainHeight;
}
public boolean validateNewRoundMessage(final SignedData<NewRoundPayload> msg) {
final NewRoundPayload payload = msg.getPayload();
final ConsensusRoundIdentifier rootRoundIdentifier = payload.getRoundChangeIdentifier();
final Address expectedProposer = proposerSelector.selectProposerForRound(rootRoundIdentifier);
final RoundChangeCertificate roundChangeCert = payload.getRoundChangeCertificate();
if (!expectedProposer.equals(msg.getSender())) {
LOG.info("Invalid NewRound message, did not originate from expected proposer.");
return false;
}
if (msg.getPayload().getRoundChangeIdentifier().getSequenceNumber() != chainHeight) {
LOG.info("Invalid NewRound message, not valid for local chain height.");
return false;
}
if (msg.getPayload().getRoundChangeIdentifier().getRoundNumber() == 0) {
LOG.info("Invalid NewRound message, illegally targets a new round of 0.");
return false;
}
final SignedData<ProposalPayload> proposalMessage = payload.getProposalPayload();
if (!msg.getSender().equals(proposalMessage.getSender())) {
LOG.info("Invalid NewRound message, embedded Proposal message not signed by sender.");
return false;
}
if (!proposalMessage.getPayload().getRoundIdentifier().equals(rootRoundIdentifier)) {
LOG.info("Invalid NewRound message, embedded Proposal has mismatched round.");
return false;
}
if (!validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
rootRoundIdentifier, roundChangeCert)) {
return false;
}
return validateProposalMessageMatchesLatestPrepareCertificate(payload);
}
private boolean validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
final ConsensusRoundIdentifier expectedRound, final RoundChangeCertificate roundChangeCert) {
final Collection<SignedData<RoundChangePayload>> roundChangeMsgs =
roundChangeCert.getRoundChangePayloads();
if (roundChangeMsgs.size() < quorumSize) {
LOG.info(
"Invalid NewRound message, RoundChange certificate has insufficient "
+ "RoundChange messages.");
return false;
}
if (!roundChangeCert
.getRoundChangePayloads()
.stream()
.allMatch(p -> p.getPayload().getRoundChangeIdentifier().equals(expectedRound))) {
LOG.info(
"Invalid NewRound message, not all embedded RoundChange messages have a "
+ "matching target round.");
return false;
}
for (final SignedData<RoundChangePayload> roundChangeMsg :
roundChangeCert.getRoundChangePayloads()) {
final RoundChangeMessageValidator roundChangeValidator =
new RoundChangeMessageValidator(
messageValidatorFactory, validators, quorumSize, chainHeight);
if (!roundChangeValidator.validateMessage(roundChangeMsg)) {
LOG.info("Invalid NewRound message, embedded RoundChange message failed validation.");
return false;
}
}
return true;
}
private boolean validateProposalMessageMatchesLatestPrepareCertificate(
final NewRoundPayload payload) {
final RoundChangeCertificate roundChangeCert = payload.getRoundChangeCertificate();
final Collection<SignedData<RoundChangePayload>> roundChangeMsgs =
roundChangeCert.getRoundChangePayloads();
final Optional<PreparedCertificate> latestPreparedCertificate =
findLatestPreparedCertificate(roundChangeMsgs);
if (!latestPreparedCertificate.isPresent()) {
LOG.info(
"No round change messages have a preparedCertificate, any valid block may be proposed.");
return true;
}
if (!latestPreparedCertificate
.get()
.getProposalPayload()
.getPayload()
.getBlock()
.getHash()
.equals(payload.getProposalPayload().getPayload().getBlock().getHash())) {
LOG.info(
"Invalid NewRound message, block in latest RoundChange does not match proposed block.");
return false;
}
return true;
}
private Optional<PreparedCertificate> findLatestPreparedCertificate(
final Collection<SignedData<RoundChangePayload>> msgs) {
Optional<PreparedCertificate> result = Optional.empty();
for (SignedData<RoundChangePayload> roundChangeMsg : msgs) {
final RoundChangePayload payload = roundChangeMsg.getPayload();
if (payload.getPreparedCertificate().isPresent()) {
if (!result.isPresent()) {
result = Optional.of(payload.getPreparedCertificate().get());
} else {
final PreparedCertificate currentLatest = result.get();
final PreparedCertificate nextCert = payload.getPreparedCertificate().get();
if (currentLatest.getProposalPayload().getPayload().getRoundIdentifier().getRoundNumber()
< nextCert.getProposalPayload().getPayload().getRoundIdentifier().getRoundNumber()) {
result = Optional.of(nextCert);
}
}
}
}
return result;
}
}

@ -32,17 +32,17 @@ public class RoundChangeMessageValidator {
private final MessageValidatorFactory messageValidatorFactory; private final MessageValidatorFactory messageValidatorFactory;
private final Collection<Address> validators; private final Collection<Address> validators;
private final long minimumPrepareMessages; private final long minimumPrepareMessages;
private final ConsensusRoundIdentifier currentRound; private final long chainHeight;
public RoundChangeMessageValidator( public RoundChangeMessageValidator(
final MessageValidatorFactory messageValidatorFactory, final MessageValidatorFactory messageValidatorFactory,
final Collection<Address> validators, final Collection<Address> validators,
final long minimumPrepareMessages, final long minimumPrepareMessages,
final ConsensusRoundIdentifier currentRound) { final long chainHeight) {
this.messageValidatorFactory = messageValidatorFactory; this.messageValidatorFactory = messageValidatorFactory;
this.validators = validators; this.validators = validators;
this.minimumPrepareMessages = minimumPrepareMessages; this.minimumPrepareMessages = minimumPrepareMessages;
this.currentRound = currentRound; this.chainHeight = chainHeight;
} }
public boolean validateMessage(final SignedData<RoundChangePayload> msg) { public boolean validateMessage(final SignedData<RoundChangePayload> msg) {
@ -56,7 +56,7 @@ public class RoundChangeMessageValidator {
final ConsensusRoundIdentifier targetRound = msg.getPayload().getRoundChangeIdentifier(); final ConsensusRoundIdentifier targetRound = msg.getPayload().getRoundChangeIdentifier();
if (targetRound.getSequenceNumber() != currentRound.getSequenceNumber()) { if (targetRound.getSequenceNumber() != chainHeight) {
LOG.info("Invalid RoundChange message, not valid for local chain height."); LOG.info("Invalid RoundChange message, not valid for local chain height.");
return false; return false;
} }
@ -74,14 +74,15 @@ public class RoundChangeMessageValidator {
final PreparedCertificate certificate, final ConsensusRoundIdentifier roundChangeTarget) { final PreparedCertificate certificate, final ConsensusRoundIdentifier roundChangeTarget) {
final SignedData<ProposalPayload> proposalMessage = certificate.getProposalPayload(); final SignedData<ProposalPayload> proposalMessage = certificate.getProposalPayload();
final ConsensusRoundIdentifier prepareCertRound = final ConsensusRoundIdentifier proposalRoundIdentifier =
proposalMessage.getPayload().getRoundIdentifier(); proposalMessage.getPayload().getRoundIdentifier();
if (!validatePreparedCertificateRound(prepareCertRound, roundChangeTarget)) { if (!validatePreparedCertificateRound(proposalRoundIdentifier, roundChangeTarget)) {
return false; return false;
} }
final MessageValidator messageValidator = messageValidatorFactory.createAt(prepareCertRound); final MessageValidator messageValidator =
messageValidatorFactory.createAt(proposalRoundIdentifier);
return validateConsistencyOfPrepareCertificateMessages(certificate, messageValidator); return validateConsistencyOfPrepareCertificateMessages(certificate, messageValidator);
} }
@ -121,7 +122,7 @@ public class RoundChangeMessageValidator {
if (prepareCertRound.getRoundNumber() >= roundChangeTarget.getRoundNumber()) { if (prepareCertRound.getRoundNumber() >= roundChangeTarget.getRoundNumber()) {
LOG.info( LOG.info(
"Invalid RoundChange message, PreparedCertificate is newer than RoundChange target."); "Invalid RoundChange message, PreparedCertificate not older than RoundChange target.");
return false; return false;
} }
return true; return true;

@ -0,0 +1,22 @@
/*
* 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;
public class TestHelpers {
public static ConsensusRoundIdentifier createFrom(
final ConsensusRoundIdentifier initial, final int offSetSequence, final int offSetRound) {
return new ConsensusRoundIdentifier(
initial.getSequenceNumber() + offSetSequence, initial.getRoundNumber() + offSetRound);
}
}

@ -0,0 +1,297 @@
/*
* 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.Collections.singletonList;
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.TestHelpers;
import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload.Builder;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparedCertificate;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangeCertificate;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangeMessageValidator.MessageValidatorFactory;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
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 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;
public class NewRoundMessageValidatorTest {
private final KeyPair proposerKey = KeyPair.generate();
private final KeyPair validatorKey = KeyPair.generate();
private final KeyPair otherValidatorKey = KeyPair.generate();
private final MessageFactory proposerMessageFactory = new MessageFactory(proposerKey);
private final MessageFactory validatorMessageFactory = new MessageFactory(validatorKey);
private final MessageFactory otherValidatorMessageFactory = new MessageFactory(otherValidatorKey);
private final Address proposerAddress = Util.publicKeyToAddress(proposerKey.getPublicKey());
private final List<Address> validators = Lists.newArrayList();
private final long chainHeight = 2;
private final ConsensusRoundIdentifier roundIdentifier =
new ConsensusRoundIdentifier(chainHeight, 4);
private NewRoundMessageValidator validator;
private final Block proposedBlock = mock(Block.class);
private final ProposerSelector proposerSelector = mock(ProposerSelector.class);
private final MessageValidatorFactory validatorFactory = mock(MessageValidatorFactory.class);
private final MessageValidator messageValidator = mock(MessageValidator.class);
private final SignedData<NewRoundPayload> validMsg =
createValidNewRoundMessageSignedBy(proposerKey);
private final NewRoundPayload.Builder msgBuilder = Builder.fromExisting(validMsg.getPayload());
@Before
public void setup() {
validators.add(Util.publicKeyToAddress(proposerKey.getPublicKey()));
validators.add(Util.publicKeyToAddress(validatorKey.getPublicKey()));
validators.add(Util.publicKeyToAddress(otherValidatorKey.getPublicKey()));
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(proposedBlock.getHash()).thenReturn(Hash.fromHexStringLenient("1"));
validator =
new NewRoundMessageValidator(
validators, proposerSelector, validatorFactory, 1, chainHeight);
}
/* NOTE: All test herein assume that the Proposer is the expected transmitter of the NewRound
* message.
*/
private SignedData<NewRoundPayload> createValidNewRoundMessageSignedBy(final KeyPair signingKey) {
final MessageFactory messageCreator = new MessageFactory(signingKey);
final RoundChangeCertificate.Builder builder = new RoundChangeCertificate.Builder();
builder.appendRoundChangeMessage(
proposerMessageFactory.createSignedRoundChangePayload(roundIdentifier, Optional.empty()));
return messageCreator.createSignedNewRoundPayload(
roundIdentifier,
builder.buildCertificate(),
messageCreator.createSignedProposalPayload(roundIdentifier, proposedBlock));
}
private SignedData<NewRoundPayload> signPayload(
final NewRoundPayload payload, final KeyPair signingKey) {
final MessageFactory messageCreator = new MessageFactory(signingKey);
return messageCreator.createSignedNewRoundPayload(
payload.getRoundChangeIdentifier(),
payload.getRoundChangeCertificate(),
payload.getProposalPayload());
}
@Test
public void basicNewRoundMessageIsValid() {
assertThat(validator.validateNewRoundMessage(validMsg)).isTrue();
}
@Test
public void newRoundFromNonProposerFails() {
final SignedData<NewRoundPayload> msg = signPayload(validMsg.getPayload(), validatorKey);
assertThat(validator.validateNewRoundMessage(msg)).isFalse();
}
@Test
public void newRoundTargettingRoundZeroFails() {
msgBuilder.setRoundChangeIdentifier(
new ConsensusRoundIdentifier(roundIdentifier.getSequenceNumber(), 0));
final SignedData<NewRoundPayload> inValidMsg = signPayload(msgBuilder.build(), proposerKey);
assertThat(validator.validateNewRoundMessage(inValidMsg)).isFalse();
}
@Test
public void newRoundTargetingDifferentSequenceNumberFails() {
final ConsensusRoundIdentifier futureRound = TestHelpers.createFrom(roundIdentifier, 1, 0);
msgBuilder.setRoundChangeIdentifier(futureRound);
final SignedData<NewRoundPayload> inValidMsg = signPayload(msgBuilder.build(), proposerKey);
assertThat(validator.validateNewRoundMessage(inValidMsg)).isFalse();
}
@Test
public void newRoundContainingProposalFromDifferentValidatorFails() {
msgBuilder.setProposalPayload(
validatorMessageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock));
final SignedData<NewRoundPayload> inValidMsg = signPayload(msgBuilder.build(), proposerKey);
assertThat(validator.validateNewRoundMessage(inValidMsg)).isFalse();
}
@Test
public void newRoundWithEmptyRoundChangeCertificateFails() {
msgBuilder.setRoundChangeCertificate(new RoundChangeCertificate(Collections.emptyList()));
final SignedData<NewRoundPayload> inValidMsg = signPayload(msgBuilder.build(), proposerKey);
assertThat(validator.validateNewRoundMessage(inValidMsg)).isFalse();
}
@Test
public void newRoundWithMismatchOfRoundIdentifierToProposalMessageFails() {
final ConsensusRoundIdentifier mismatchedRound = TestHelpers.createFrom(roundIdentifier, 0, -1);
msgBuilder.setProposalPayload(
proposerMessageFactory.createSignedProposalPayload(mismatchedRound, proposedBlock));
final SignedData<NewRoundPayload> inValidMsg = signPayload(msgBuilder.build(), proposerKey);
assertThat(validator.validateNewRoundMessage(inValidMsg)).isFalse();
}
@Test
public void newRoundWithProposalNotMatchingLatestRoundChangeFails() {
final ConsensusRoundIdentifier preparedRound = TestHelpers.createFrom(roundIdentifier, 0, -1);
final Block prevProposedBlock = mock(Block.class);
when(prevProposedBlock.getHash()).thenReturn(Hash.fromHexStringLenient("2"));
final PreparedCertificate misMatchedPreparedCertificate =
new PreparedCertificate(
proposerMessageFactory.createSignedProposalPayload(preparedRound, prevProposedBlock),
singletonList(
validatorMessageFactory.createSignedPreparePayload(
preparedRound, prevProposedBlock.getHash())));
msgBuilder.setRoundChangeCertificate(
new RoundChangeCertificate(
singletonList(
validatorMessageFactory.createSignedRoundChangePayload(
roundIdentifier, Optional.of(misMatchedPreparedCertificate)))));
final SignedData<NewRoundPayload> invalidMsg = signPayload(msgBuilder.build(), proposerKey);
assertThat(validator.validateNewRoundMessage(invalidMsg)).isFalse();
}
@Test
public void roundChangeMessagesDoNotAllTargetRoundOfNewRoundMsgFails() {
final ConsensusRoundIdentifier prevRound = TestHelpers.createFrom(roundIdentifier, 0, -1);
RoundChangeCertificate.Builder roundChangeBuilder = new RoundChangeCertificate.Builder();
roundChangeBuilder.appendRoundChangeMessage(
proposerMessageFactory.createSignedRoundChangePayload(roundIdentifier, Optional.empty()));
roundChangeBuilder.appendRoundChangeMessage(
proposerMessageFactory.createSignedRoundChangePayload(prevRound, Optional.empty()));
msgBuilder.setRoundChangeCertificate(roundChangeBuilder.buildCertificate());
final SignedData<NewRoundPayload> msg = signPayload(msgBuilder.build(), proposerKey);
assertThat(validator.validateNewRoundMessage(msg)).isFalse();
}
@Test
public void invalidEmbeddedRoundChangeMessageFails() {
final ConsensusRoundIdentifier prevRound = TestHelpers.createFrom(roundIdentifier, 0, -1);
RoundChangeCertificate.Builder roundChangeBuilder = new RoundChangeCertificate.Builder();
roundChangeBuilder.appendRoundChangeMessage(
proposerMessageFactory.createSignedRoundChangePayload(
roundIdentifier,
Optional.of(
new PreparedCertificate(
proposerMessageFactory.createSignedProposalPayload(prevRound, proposedBlock),
Lists.newArrayList(
validatorMessageFactory.createSignedPreparePayload(
prevRound, proposedBlock.getHash()))))));
msgBuilder.setRoundChangeCertificate(roundChangeBuilder.buildCertificate());
// The prepare Message in the RoundChange Cert will be deemed illegal.
when(messageValidator.validatePrepareMessage(any())).thenReturn(false);
final SignedData<NewRoundPayload> msg = signPayload(msgBuilder.build(), proposerKey);
assertThat(validator.validateNewRoundMessage(msg)).isFalse();
}
@Test
public void lastestPreparedCertificateMatchesNewRoundProposalIsSuccessful() {
final SignedData<ProposalPayload> proposal =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock);
final ConsensusRoundIdentifier preparedRound =
new ConsensusRoundIdentifier(
roundIdentifier.getSequenceNumber(), roundIdentifier.getRoundNumber() - 1);
final SignedData<ProposalPayload> differentProposal =
proposerMessageFactory.createSignedProposalPayload(preparedRound, proposedBlock);
final Optional<PreparedCertificate> preparedCert =
Optional.of(
new PreparedCertificate(
differentProposal,
Lists.newArrayList(
validatorMessageFactory.createSignedPreparePayload(
roundIdentifier, proposedBlock.getHash()),
otherValidatorMessageFactory.createSignedPreparePayload(
roundIdentifier, proposedBlock.getHash()))));
// An earlier PrepareCert is added to ensure the path to find the latest PrepareCert
// is correctly followed.
final ConsensusRoundIdentifier earlierPreparedRound =
new ConsensusRoundIdentifier(
roundIdentifier.getSequenceNumber(), roundIdentifier.getRoundNumber() - 2);
final SignedData<ProposalPayload> earlierProposal =
proposerMessageFactory.createSignedProposalPayload(earlierPreparedRound, proposedBlock);
final Optional<PreparedCertificate> earlierPreparedCert =
Optional.of(
new PreparedCertificate(
earlierProposal,
Lists.newArrayList(
validatorMessageFactory.createSignedPreparePayload(
earlierPreparedRound, proposedBlock.getHash()),
otherValidatorMessageFactory.createSignedPreparePayload(
earlierPreparedRound, proposedBlock.getHash()))));
final SignedData<NewRoundPayload> msg =
proposerMessageFactory.createSignedNewRoundPayload(
roundIdentifier,
new RoundChangeCertificate(
Lists.newArrayList(
proposerMessageFactory.createSignedRoundChangePayload(
roundIdentifier, earlierPreparedCert),
proposerMessageFactory.createSignedRoundChangePayload(
roundIdentifier, preparedCert))),
proposal);
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock);
assertThat(validator.validateNewRoundMessage(msg)).isTrue();
}
}

@ -50,8 +50,10 @@ public class RoundChangeMessageValidatorTest {
private final MessageFactory validatorMessageFactory = new MessageFactory(validatorKey); private final MessageFactory validatorMessageFactory = new MessageFactory(validatorKey);
private final MessageFactory nonValidatorMessageFactory = new MessageFactory(nonValidatorKey); private final MessageFactory nonValidatorMessageFactory = new MessageFactory(nonValidatorKey);
private final ConsensusRoundIdentifier currentRound = new ConsensusRoundIdentifier(2, 3); private final long chainHeight = 2;
private final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(2, 4); private final ConsensusRoundIdentifier currentRound =
new ConsensusRoundIdentifier(chainHeight, 3);
private final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(chainHeight, 4);
private final Block block = mock(Block.class); private final Block block = mock(Block.class);
@ -60,7 +62,7 @@ public class RoundChangeMessageValidatorTest {
private final MessageValidatorFactory validatorFactory = mock(MessageValidatorFactory.class); private final MessageValidatorFactory validatorFactory = mock(MessageValidatorFactory.class);
private final RoundChangeMessageValidator validator = private final RoundChangeMessageValidator validator =
new RoundChangeMessageValidator(validatorFactory, validators, 1, currentRound); new RoundChangeMessageValidator(validatorFactory, validators, 1, chainHeight);
@Before @Before
public void setup() { public void setup() {
@ -92,7 +94,7 @@ public class RoundChangeMessageValidatorTest {
} }
@Test @Test
public void roundChangeContainingInvalidPreprepareFails() { public void roundChangeContainingInvalidProposalFails() {
final PreparedCertificate prepareCertificate = final PreparedCertificate prepareCertificate =
new PreparedCertificate( new PreparedCertificate(
proposerMessageFactory.createSignedProposalPayload(currentRound, block), proposerMessageFactory.createSignedProposalPayload(currentRound, block),
@ -114,7 +116,7 @@ public class RoundChangeMessageValidatorTest {
} }
@Test @Test
public void roundChangeContainingValidPreprepareButNoPrepareMessagesFails() { public void roundChangeContainingValidProposalButNoPrepareMessagesFails() {
final PreparedCertificate prepareCertificate = final PreparedCertificate prepareCertificate =
new PreparedCertificate( new PreparedCertificate(
proposerMessageFactory.createSignedProposalPayload(currentRound, block), proposerMessageFactory.createSignedProposalPayload(currentRound, block),
@ -164,7 +166,7 @@ public class RoundChangeMessageValidatorTest {
} }
@Test @Test
public void roundChangeWithPreprepareFromARoundAheadOfRoundChangeTargetFails() { public void roundChangeWithProposalFromARoundAheadOfRoundChangeTargetFails() {
final ConsensusRoundIdentifier futureRound = final ConsensusRoundIdentifier futureRound =
new ConsensusRoundIdentifier( new ConsensusRoundIdentifier(
currentRound.getSequenceNumber(), currentRound.getRoundNumber() + 2); currentRound.getSequenceNumber(), currentRound.getRoundNumber() + 2);
@ -187,7 +189,7 @@ public class RoundChangeMessageValidatorTest {
} }
@Test @Test
public void roudnChangeWithPastPreprepareForCurrentHeightIsSuccessful() { public void roundChangeWithPastProposalForCurrentHeightIsSuccessful() {
final SignedData<PreparePayload> prepareMsg = final SignedData<PreparePayload> prepareMsg =
validatorMessageFactory.createSignedPreparePayload(currentRound, block.getHash()); validatorMessageFactory.createSignedPreparePayload(currentRound, block.getHash());
final PreparedCertificate prepareCertificate = final PreparedCertificate prepareCertificate =

@ -27,6 +27,7 @@ import org.openjdk.jmh.annotations.State;
@State(Scope.Benchmark) @State(Scope.Benchmark)
public class RLPBench { public class RLPBench {
private static Object generate(final int depth, final int width, final int size) { private static Object generate(final int depth, final int width, final int size) {
final byte[] bytes = new byte[size]; final byte[] bytes = new byte[size];
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {

Loading…
Cancel
Save