mirror of https://github.com/hyperledger/besu
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.
parent
08cc58236e
commit
51ffe8a923
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue