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

New round validation #353

Merged
merged 4 commits into from
Dec 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;

import java.util.Objects;

public abstract class InRoundPayload extends AbstractPayload {
protected final ConsensusRoundIdentifier roundIdentifier;

Expand All @@ -24,4 +26,21 @@ protected InRoundPayload(final ConsensusRoundIdentifier roundIdentifier) {
public ConsensusRoundIdentifier getRoundIdentifier() {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,14 @@ private <M extends AbstractPayload> SignedData<M> createSignedMessage(final M pa
payload, Util.publicKeyToAddress(validatorKeyPair.getPublicKey()), signature);
}

static Hash hashForSignature(final AbstractPayload unsignedMessageData) {
public static Hash hashForSignature(final AbstractPayload unsignedMessageData) {
return Hash.hash(
BytesValues.concatenate(
BytesValues.ofUnsignedByte(unsignedMessageData.getMessageType()),
unsignedMessageData.encoded()));
}

private static Signature sign(final AbstractPayload unsignedMessageData, final KeyPair nodeKeys) {

return SECP256K1.sign(hashForSignature(unsignedMessageData), nodeKeys);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput;

import java.util.Collections;

public class NewRoundPayload extends AbstractPayload {

private static final int TYPE = IbftV2.NEW_ROUND;
Expand Down Expand Up @@ -74,4 +76,46 @@ public static NewRoundPayload readFrom(final RLPInput rlpInput) {
public int getMessageType() {
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput;

import java.util.Collection;
import java.util.List;

import com.google.common.collect.Lists;

public class RoundChangeCertificate {

Expand Down Expand Up @@ -45,4 +48,18 @@ public void writeTo(final RLPOutput rlpOutput) {
public Collection<SignedData<RoundChangePayload>> getRoundChangePayloads() {
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ public class RoundChangeMessageValidator {
private final MessageValidatorFactory messageValidatorFactory;
private final Collection<Address> validators;
private final long minimumPrepareMessages;
private final ConsensusRoundIdentifier currentRound;
private final long chainHeight;

public RoundChangeMessageValidator(
final MessageValidatorFactory messageValidatorFactory,
final Collection<Address> validators,
final long minimumPrepareMessages,
final ConsensusRoundIdentifier currentRound) {
final long chainHeight) {
this.messageValidatorFactory = messageValidatorFactory;
this.validators = validators;
this.minimumPrepareMessages = minimumPrepareMessages;
this.currentRound = currentRound;
this.chainHeight = chainHeight;
}

public boolean validateMessage(final SignedData<RoundChangePayload> msg) {
Expand All @@ -56,7 +56,7 @@ public boolean validateMessage(final SignedData<RoundChangePayload> msg) {

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.");
return false;
}
Expand All @@ -74,14 +74,15 @@ private boolean validatePrepareCertificate(
final PreparedCertificate certificate, final ConsensusRoundIdentifier roundChangeTarget) {
final SignedData<ProposalPayload> proposalMessage = certificate.getProposalPayload();

final ConsensusRoundIdentifier prepareCertRound =
final ConsensusRoundIdentifier proposalRoundIdentifier =
proposalMessage.getPayload().getRoundIdentifier();

if (!validatePreparedCertificateRound(prepareCertRound, roundChangeTarget)) {
if (!validatePreparedCertificateRound(proposalRoundIdentifier, roundChangeTarget)) {
return false;
}

final MessageValidator messageValidator = messageValidatorFactory.createAt(prepareCertRound);
final MessageValidator messageValidator =
messageValidatorFactory.createAt(proposalRoundIdentifier);
return validateConsistencyOfPrepareCertificateMessages(certificate, messageValidator);
}

Expand Down Expand Up @@ -121,7 +122,7 @@ private boolean validatePreparedCertificateRound(

if (prepareCertRound.getRoundNumber() >= roundChangeTarget.getRoundNumber()) {
LOG.info(
"Invalid RoundChange message, PreparedCertificate is newer than RoundChange target.");
"Invalid RoundChange message, PreparedCertificate not older than RoundChange target.");
return false;
}
return true;
Expand Down
Loading