Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Commit

Permalink
NewRoundMessageValidator ignores Round Number when comparing blocks (#…
Browse files Browse the repository at this point in the history
…523)

* NewRoundMessageValidator ignores Round Number when comparing blocks

NewRoundMessageValidator previously ensured the block in the newest
PreparedCertificate was the same as that in the Proposal by comparing
block hashes.
However, the hash-function for received IBFT blocks includes round
number - thus the blocks will never be deemed 'equal'.

As such, the blocks must be compared using a hash function which
ignores the round number - i.e. the OnChain function.
  • Loading branch information
rain-on authored Jan 9, 2019
1 parent 7b8d8fd commit fbd933f
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static tech.pegasys.pantheon.consensus.ibft.IbftHelpers.prepareMessageCountForQuorum;

import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing;
import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparedCertificate;
Expand All @@ -25,6 +26,7 @@
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangeMessageValidator.MessageValidatorForHeightFactory;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;

import java.util.Collection;
import java.util.Optional;
Expand Down Expand Up @@ -77,15 +79,11 @@ public boolean validateNewRoundMessage(final SignedData<NewRoundPayload> msg) {
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.");
final SignedData<ProposalPayload> proposalPayload = payload.getProposalPayload();
final MessageValidator proposalValidator =
messageValidatorFactory.createAt(rootRoundIdentifier);
if (!proposalValidator.addSignedProposalPayload(proposalPayload)) {
LOG.info("Invalid NewRound message, embedded proposal failed validation");
return false;
}

Expand Down Expand Up @@ -152,13 +150,22 @@ private boolean validateProposalMessageMatchesLatestPrepareCertificate(
"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())) {

// Get the hash of the block in latest prepareCert, not including the Round field.
final Hash roundAgnosticBlockHashPreparedCert =
IbftBlockHashing.calculateHashOfIbftBlockOnChain(
latestPreparedCertificate
.get()
.getProposalPayload()
.getPayload()
.getBlock()
.getHeader());

final Hash roundAgnosticBlockHashProposal =
IbftBlockHashing.calculateHashOfIbftBlockOnChain(
payload.getProposalPayload().getPayload().getBlock().getHeader());

if (!roundAgnosticBlockHashPreparedCert.equals(roundAgnosticBlockHashProposal)) {
LOG.info(
"Invalid NewRound message, block in latest RoundChange does not match proposed block.");
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
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;
Expand All @@ -49,39 +48,38 @@ public class NewRoundMessageValidatorTest {
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 MessageValidatorForHeightFactory validatorFactory =
mock(MessageValidatorForHeightFactory.class);
private final MessageValidator messageValidator = mock(MessageValidator.class);

private final SignedData<NewRoundPayload> validMsg =
createValidNewRoundMessageSignedBy(proposerKey);
private final NewRoundPayload.Builder msgBuilder =
NewRoundPayload.Builder.fromExisting(validMsg.getPayload());
private Block proposedBlock;
private SignedData<NewRoundPayload> validMsg;
private NewRoundPayload.Builder msgBuilder;

@Before
public void setup() {
validators.add(Util.publicKeyToAddress(proposerKey.getPublicKey()));
validators.add(Util.publicKeyToAddress(validatorKey.getPublicKey()));
validators.add(Util.publicKeyToAddress(otherValidatorKey.getPublicKey()));

proposedBlock = TestHelpers.createProposalBlock(validators, roundIdentifier.getRoundNumber());
validMsg = createValidNewRoundMessageSignedBy(proposerKey);
msgBuilder = NewRoundPayload.Builder.fromExisting(validMsg.getPayload());

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);
Expand Down Expand Up @@ -147,16 +145,6 @@ public void newRoundTargetingDifferentSequenceNumberFails() {
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()));
Expand All @@ -166,23 +154,14 @@ public void newRoundWithEmptyRoundChangeCertificateFails() {
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"));
// The previous block has been constructed with less validators, so is thus not identical
// to the block in the new proposal (so should fail).
final Block prevProposedBlock =
TestHelpers.createProposalBlock(validators.subList(0, 1), roundIdentifier.getRoundNumber());

final PreparedCertificate misMatchedPreparedCertificate =
new PreparedCertificate(
proposerMessageFactory.createSignedProposalPayload(preparedRound, prevProposedBlock),
Expand Down Expand Up @@ -289,4 +268,25 @@ public void lastestPreparedCertificateMatchesNewRoundProposalIsSuccessful() {

assertThat(validator.validateNewRoundMessage(msg)).isTrue();
}

@Test
public void embeddedProposalFailsValidation() {
when(messageValidator.addSignedProposalPayload(any())).thenReturn(false, true);

final SignedData<ProposalPayload> proposal =
proposerMessageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock);

final SignedData<NewRoundPayload> msg =
proposerMessageFactory.createSignedNewRoundPayload(
roundIdentifier,
new RoundChangeCertificate(
Lists.newArrayList(
proposerMessageFactory.createSignedRoundChangePayload(
roundIdentifier, Optional.empty()),
validatorMessageFactory.createSignedRoundChangePayload(
roundIdentifier, Optional.empty()))),
proposal);

assertThat(validator.validateNewRoundMessage(msg)).isFalse();
}
}

0 comments on commit fbd933f

Please sign in to comment.