mirror of https://github.com/hyperledger/besu
Ibft2: Replace NewRound with extended Proposal (#872)
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
8e7347fb35
commit
e279f1f726
@ -1,44 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.messagedata; |
|
||||||
|
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; |
|
||||||
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; |
|
||||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
|
||||||
|
|
||||||
public class NewRoundMessageData extends AbstractIbftMessageData { |
|
||||||
|
|
||||||
private static final int MESSAGE_CODE = IbftV2.NEW_ROUND; |
|
||||||
|
|
||||||
private NewRoundMessageData(final BytesValue data) { |
|
||||||
super(data); |
|
||||||
} |
|
||||||
|
|
||||||
public static NewRoundMessageData fromMessageData(final MessageData messageData) { |
|
||||||
return fromMessageData( |
|
||||||
messageData, MESSAGE_CODE, NewRoundMessageData.class, NewRoundMessageData::new); |
|
||||||
} |
|
||||||
|
|
||||||
public NewRound decode() { |
|
||||||
return NewRound.decode(data); |
|
||||||
} |
|
||||||
|
|
||||||
public static NewRoundMessageData create(final NewRound newRound) { |
|
||||||
return new NewRoundMessageData(newRound.encode()); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public int getCode() { |
|
||||||
return MESSAGE_CODE; |
|
||||||
} |
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2019 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.messagewrappers; |
|
||||||
|
|
||||||
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.Block; |
|
||||||
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; |
|
||||||
import tech.pegasys.pantheon.ethereum.rlp.RLP; |
|
||||||
import tech.pegasys.pantheon.ethereum.rlp.RLPInput; |
|
||||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
|
||||||
|
|
||||||
public class NewRound extends IbftMessage<NewRoundPayload> { |
|
||||||
|
|
||||||
private final Block proposedBlock; |
|
||||||
|
|
||||||
public NewRound(final SignedData<NewRoundPayload> payload, final Block proposedBlock) { |
|
||||||
super(payload); |
|
||||||
this.proposedBlock = proposedBlock; |
|
||||||
} |
|
||||||
|
|
||||||
public RoundChangeCertificate getRoundChangeCertificate() { |
|
||||||
return getPayload().getRoundChangeCertificate(); |
|
||||||
} |
|
||||||
|
|
||||||
public SignedData<ProposalPayload> getProposalPayload() { |
|
||||||
return getPayload().getProposalPayload(); |
|
||||||
} |
|
||||||
|
|
||||||
public Block getBlock() { |
|
||||||
return proposedBlock; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public BytesValue encode() { |
|
||||||
final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); |
|
||||||
rlpOut.startList(); |
|
||||||
getSignedPayload().writeTo(rlpOut); |
|
||||||
proposedBlock.writeTo(rlpOut); |
|
||||||
rlpOut.endList(); |
|
||||||
return rlpOut.encoded(); |
|
||||||
} |
|
||||||
|
|
||||||
public static NewRound decode(final BytesValue data) { |
|
||||||
RLPInput rlpIn = RLP.input(data); |
|
||||||
rlpIn.enterList(); |
|
||||||
final SignedData<NewRoundPayload> payload = SignedData.readSignedNewRoundPayloadFrom(rlpIn); |
|
||||||
final Block proposedBlock = |
|
||||||
Block.readFrom(rlpIn, IbftBlockHashing::calculateDataHashForCommittedSeal); |
|
||||||
rlpIn.leaveList(); |
|
||||||
return new NewRound(payload, proposedBlock); |
|
||||||
} |
|
||||||
} |
|
@ -1,145 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.payload; |
|
||||||
|
|
||||||
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; |
|
||||||
import tech.pegasys.pantheon.ethereum.rlp.RLPInput; |
|
||||||
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; |
|
||||||
|
|
||||||
import java.util.Collections; |
|
||||||
import java.util.Objects; |
|
||||||
import java.util.StringJoiner; |
|
||||||
|
|
||||||
public class NewRoundPayload implements Payload { |
|
||||||
private static final int TYPE = IbftV2.NEW_ROUND; |
|
||||||
private final ConsensusRoundIdentifier roundChangeIdentifier; |
|
||||||
private final RoundChangeCertificate roundChangeCertificate; |
|
||||||
private final SignedData<ProposalPayload> proposalPayload; |
|
||||||
|
|
||||||
public NewRoundPayload( |
|
||||||
final ConsensusRoundIdentifier roundIdentifier, |
|
||||||
final RoundChangeCertificate roundChangeCertificate, |
|
||||||
final SignedData<ProposalPayload> proposalPayload) { |
|
||||||
this.roundChangeIdentifier = roundIdentifier; |
|
||||||
this.roundChangeCertificate = roundChangeCertificate; |
|
||||||
this.proposalPayload = proposalPayload; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public ConsensusRoundIdentifier getRoundIdentifier() { |
|
||||||
return roundChangeIdentifier; |
|
||||||
} |
|
||||||
|
|
||||||
public RoundChangeCertificate getRoundChangeCertificate() { |
|
||||||
return roundChangeCertificate; |
|
||||||
} |
|
||||||
|
|
||||||
public SignedData<ProposalPayload> getProposalPayload() { |
|
||||||
return proposalPayload; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void writeTo(final RLPOutput rlpOutput) { |
|
||||||
// RLP encode of the message data content (round identifier and prepared certificate)
|
|
||||||
rlpOutput.startList(); |
|
||||||
roundChangeIdentifier.writeTo(rlpOutput); |
|
||||||
roundChangeCertificate.writeTo(rlpOutput); |
|
||||||
proposalPayload.writeTo(rlpOutput); |
|
||||||
rlpOutput.endList(); |
|
||||||
} |
|
||||||
|
|
||||||
public static NewRoundPayload readFrom(final RLPInput rlpInput) { |
|
||||||
|
|
||||||
rlpInput.enterList(); |
|
||||||
final ConsensusRoundIdentifier roundIdentifier = ConsensusRoundIdentifier.readFrom(rlpInput); |
|
||||||
final RoundChangeCertificate roundChangeCertificate = RoundChangeCertificate.readFrom(rlpInput); |
|
||||||
final SignedData<ProposalPayload> proposalPayload = |
|
||||||
SignedData.readSignedProposalPayloadFrom(rlpInput); |
|
||||||
rlpInput.leaveList(); |
|
||||||
|
|
||||||
return new NewRoundPayload(roundIdentifier, roundChangeCertificate, proposalPayload); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean equals(final Object o) { |
|
||||||
if (this == o) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
if (o == null || getClass() != o.getClass()) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
final NewRoundPayload that = (NewRoundPayload) o; |
|
||||||
return Objects.equals(roundChangeIdentifier, that.roundChangeIdentifier) |
|
||||||
&& Objects.equals(roundChangeCertificate, that.roundChangeCertificate) |
|
||||||
&& Objects.equals(proposalPayload, that.proposalPayload); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public int hashCode() { |
|
||||||
return Objects.hash(roundChangeIdentifier, roundChangeCertificate, proposalPayload); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String toString() { |
|
||||||
return new StringJoiner(", ", NewRoundPayload.class.getSimpleName() + "[", "]") |
|
||||||
.add("roundChangeIdentifier=" + roundChangeIdentifier) |
|
||||||
.add("roundChangeCertificate=" + roundChangeCertificate) |
|
||||||
.add("proposalPayload=" + proposalPayload) |
|
||||||
.toString(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
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( |
|
||||||
final ConsensusRoundIdentifier roundChangeIdentifier, |
|
||||||
final RoundChangeCertificate roundChangeCertificate, |
|
||||||
final SignedData<ProposalPayload> proposalPayload) { |
|
||||||
this.roundChangeIdentifier = roundChangeIdentifier; |
|
||||||
this.roundChangeCertificate = roundChangeCertificate; |
|
||||||
this.proposalPayload = proposalPayload; |
|
||||||
} |
|
||||||
|
|
||||||
public static Builder fromExisting(final NewRound payload) { |
|
||||||
return new Builder( |
|
||||||
payload.getRoundIdentifier(), |
|
||||||
payload.getRoundChangeCertificate(), |
|
||||||
payload.getProposalPayload()); |
|
||||||
} |
|
||||||
|
|
||||||
public void setRoundChangeIdentifier(final ConsensusRoundIdentifier roundChangeIdentifier) { |
|
||||||
this.roundChangeIdentifier = roundChangeIdentifier; |
|
||||||
} |
|
||||||
|
|
||||||
public void setRoundChangeCertificate(final RoundChangeCertificate roundChangeCertificate) { |
|
||||||
this.roundChangeCertificate = roundChangeCertificate; |
|
||||||
} |
|
||||||
|
|
||||||
public NewRoundPayload build() { |
|
||||||
return new NewRoundPayload(roundChangeIdentifier, roundChangeCertificate, proposalPayload); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,53 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2019 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.messagewrappers.Proposal; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager; |
||||||
|
import org.apache.logging.log4j.Logger; |
||||||
|
|
||||||
|
/* One of these will be created by the IbftBlockHeightManager and will exist for the life of the |
||||||
|
chainheight, and used to ensure supplied Proposals are suitable for starting a new round. |
||||||
|
*/ |
||||||
|
public class FutureRoundProposalMessageValidator { |
||||||
|
|
||||||
|
private static final Logger LOG = LogManager.getLogger(); |
||||||
|
|
||||||
|
private final MessageValidatorFactory messageValidatorFactory; |
||||||
|
private final long chainHeight; |
||||||
|
private final BlockHeader parentHeader; |
||||||
|
|
||||||
|
public FutureRoundProposalMessageValidator( |
||||||
|
final MessageValidatorFactory messageValidatorFactory, |
||||||
|
final long chainHeight, |
||||||
|
final BlockHeader parentHeader) { |
||||||
|
this.messageValidatorFactory = messageValidatorFactory; |
||||||
|
this.chainHeight = chainHeight; |
||||||
|
this.parentHeader = parentHeader; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean validateProposalMessage(final Proposal msg) { |
||||||
|
|
||||||
|
if (msg.getRoundIdentifier().getSequenceNumber() != chainHeight) { |
||||||
|
LOG.debug("Illegal Proposal message, does not target the correct round height."); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
final MessageValidator messageValidator = |
||||||
|
messageValidatorFactory.createMessageValidator(msg.getRoundIdentifier(), parentHeader); |
||||||
|
|
||||||
|
return messageValidator.validateProposal(msg); |
||||||
|
} |
||||||
|
} |
@ -1,84 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2019 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.IbftContext; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; |
|
||||||
import tech.pegasys.pantheon.ethereum.BlockValidator; |
|
||||||
import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs; |
|
||||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.Block; |
|
||||||
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; |
|
||||||
|
|
||||||
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 NewRoundPayloadValidator payloadValidator; |
|
||||||
private final ProposalBlockConsistencyValidator proposalConsistencyValidator; |
|
||||||
private final BlockValidator<IbftContext> blockValidator; |
|
||||||
private final ProtocolContext<IbftContext> protocolContext; |
|
||||||
private final RoundChangeCertificateValidator roundChangeCertificateValidator; |
|
||||||
|
|
||||||
public NewRoundMessageValidator( |
|
||||||
final NewRoundPayloadValidator payloadValidator, |
|
||||||
final ProposalBlockConsistencyValidator proposalConsistencyValidator, |
|
||||||
final BlockValidator<IbftContext> blockValidator, |
|
||||||
final ProtocolContext<IbftContext> protocolContext, |
|
||||||
final RoundChangeCertificateValidator roundChangeCertificateValidator) { |
|
||||||
this.payloadValidator = payloadValidator; |
|
||||||
this.proposalConsistencyValidator = proposalConsistencyValidator; |
|
||||||
this.blockValidator = blockValidator; |
|
||||||
this.protocolContext = protocolContext; |
|
||||||
this.roundChangeCertificateValidator = roundChangeCertificateValidator; |
|
||||||
} |
|
||||||
|
|
||||||
public boolean validateNewRoundMessage(final NewRound msg) { |
|
||||||
if (!payloadValidator.validateNewRoundMessage(msg.getSignedPayload())) { |
|
||||||
LOG.debug("Illegal NewRound message, embedded signed data failed validation."); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (!roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( |
|
||||||
msg.getRoundChangeCertificate(), msg.getBlock())) { |
|
||||||
LOG.debug( |
|
||||||
"Illegal NewRound message, piggybacked block does not match latest PrepareCertificate"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (!validateBlock(msg.getBlock())) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
return proposalConsistencyValidator.validateProposalMatchesBlock( |
|
||||||
msg.getProposalPayload(), msg.getBlock()); |
|
||||||
} |
|
||||||
|
|
||||||
private boolean validateBlock(final Block block) { |
|
||||||
final Optional<BlockProcessingOutputs> validationResult = |
|
||||||
blockValidator.validateAndProcessBlock( |
|
||||||
protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL); |
|
||||||
|
|
||||||
if (!validationResult.isPresent()) { |
|
||||||
LOG.info("Invalid Proposal message, block did not pass validation."); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
@ -1,84 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.payload.NewRoundPayload; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangePayloadValidator.MessageValidatorForHeightFactory; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.Address; |
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager; |
|
||||||
import org.apache.logging.log4j.Logger; |
|
||||||
|
|
||||||
public class NewRoundPayloadValidator { |
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(); |
|
||||||
|
|
||||||
private final ProposerSelector proposerSelector; |
|
||||||
private final MessageValidatorForHeightFactory messageValidatorFactory; |
|
||||||
private final long chainHeight; |
|
||||||
private final RoundChangeCertificateValidator roundChangeCertificateValidator; |
|
||||||
|
|
||||||
public NewRoundPayloadValidator( |
|
||||||
final ProposerSelector proposerSelector, |
|
||||||
final MessageValidatorForHeightFactory messageValidatorFactory, |
|
||||||
final long chainHeight, |
|
||||||
final RoundChangeCertificateValidator roundChangeCertificateValidator) { |
|
||||||
this.proposerSelector = proposerSelector; |
|
||||||
this.messageValidatorFactory = messageValidatorFactory; |
|
||||||
this.chainHeight = chainHeight; |
|
||||||
this.roundChangeCertificateValidator = roundChangeCertificateValidator; |
|
||||||
} |
|
||||||
|
|
||||||
public boolean validateNewRoundMessage(final SignedData<NewRoundPayload> msg) { |
|
||||||
|
|
||||||
final NewRoundPayload payload = msg.getPayload(); |
|
||||||
final ConsensusRoundIdentifier rootRoundIdentifier = payload.getRoundIdentifier(); |
|
||||||
final Address expectedProposer = proposerSelector.selectProposerForRound(rootRoundIdentifier); |
|
||||||
final RoundChangeCertificate roundChangeCert = payload.getRoundChangeCertificate(); |
|
||||||
|
|
||||||
if (!expectedProposer.equals(msg.getAuthor())) { |
|
||||||
LOG.info("Invalid NewRound message, did not originate from expected proposer."); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (msg.getPayload().getRoundIdentifier().getSequenceNumber() != chainHeight) { |
|
||||||
LOG.info("Invalid NewRound message, not valid for local chain height."); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (msg.getPayload().getRoundIdentifier().getRoundNumber() == 0) { |
|
||||||
LOG.info("Invalid NewRound message, illegally targets a new round of 0."); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
final SignedData<ProposalPayload> proposalPayload = payload.getProposalPayload(); |
|
||||||
final SignedDataValidator proposalValidator = |
|
||||||
messageValidatorFactory.createAt(rootRoundIdentifier); |
|
||||||
if (!proposalValidator.validateProposal(proposalPayload)) { |
|
||||||
LOG.info("Invalid NewRound message, embedded proposal failed validation"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (!roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( |
|
||||||
rootRoundIdentifier, roundChangeCert)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
@ -1,69 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.messagedata; |
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat; |
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
import static org.mockito.Mockito.when; |
|
||||||
|
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; |
|
||||||
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; |
|
||||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
import org.mockito.Mock; |
|
||||||
import org.mockito.junit.MockitoJUnitRunner; |
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class) |
|
||||||
public class NewRoundMessageTest { |
|
||||||
@Mock private NewRound newRoundPayload; |
|
||||||
@Mock private BytesValue messageBytes; |
|
||||||
@Mock private MessageData messageData; |
|
||||||
@Mock private NewRoundMessageData newRoundMessage; |
|
||||||
|
|
||||||
@Test |
|
||||||
public void createMessageFromNewRoundChangeMessageData() { |
|
||||||
when(newRoundPayload.encode()).thenReturn(messageBytes); |
|
||||||
NewRoundMessageData prepareMessage = NewRoundMessageData.create(newRoundPayload); |
|
||||||
|
|
||||||
assertThat(prepareMessage.getData()).isEqualTo(messageBytes); |
|
||||||
assertThat(prepareMessage.getCode()).isEqualTo(IbftV2.NEW_ROUND); |
|
||||||
verify(newRoundPayload).encode(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void createMessageFromNewRoundMessage() { |
|
||||||
NewRoundMessageData message = NewRoundMessageData.fromMessageData(newRoundMessage); |
|
||||||
assertThat(message).isSameAs(newRoundMessage); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void createMessageFromGenericMessageData() { |
|
||||||
when(messageData.getData()).thenReturn(messageBytes); |
|
||||||
when(messageData.getCode()).thenReturn(IbftV2.NEW_ROUND); |
|
||||||
NewRoundMessageData newRoundMessage = NewRoundMessageData.fromMessageData(messageData); |
|
||||||
|
|
||||||
assertThat(newRoundMessage.getData()).isEqualTo(messageData.getData()); |
|
||||||
assertThat(newRoundMessage.getCode()).isEqualTo(IbftV2.NEW_ROUND); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void createMessageFailsWhenIncorrectMessageCode() { |
|
||||||
when(messageData.getCode()).thenReturn(42); |
|
||||||
assertThatThrownBy(() -> NewRoundMessageData.fromMessageData(messageData)) |
|
||||||
.isInstanceOf(IllegalArgumentException.class) |
|
||||||
.hasMessageContaining("MessageData has code 42 and thus is not a NewRoundMessageData"); |
|
||||||
} |
|
||||||
} |
|
@ -1,98 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.payload; |
|
||||||
|
|
||||||
import static java.util.Collections.singletonList; |
|
||||||
import static org.assertj.core.api.Assertions.assertThat; |
|
||||||
|
|
||||||
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.TestHelpers; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2; |
|
||||||
import tech.pegasys.pantheon.crypto.SECP256K1.Signature; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.AddressHelpers; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.Block; |
|
||||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
|
||||||
import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; |
|
||||||
import tech.pegasys.pantheon.ethereum.rlp.RLP; |
|
||||||
import tech.pegasys.pantheon.ethereum.rlp.RLPInput; |
|
||||||
|
|
||||||
import java.math.BigInteger; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.Optional; |
|
||||||
|
|
||||||
import org.assertj.core.util.Lists; |
|
||||||
import org.junit.Test; |
|
||||||
|
|
||||||
public class NewRoundPayloadTest { |
|
||||||
|
|
||||||
private static final ConsensusRoundIdentifier ROUND_IDENTIFIER = |
|
||||||
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98); |
|
||||||
|
|
||||||
@Test |
|
||||||
public void roundTripRlpWithNoRoundChangePayloads() { |
|
||||||
final Block block = |
|
||||||
TestHelpers.createProposalBlock(singletonList(AddressHelpers.ofValue(1)), ROUND_IDENTIFIER); |
|
||||||
final ProposalPayload proposalPayload = new ProposalPayload(ROUND_IDENTIFIER, block.getHash()); |
|
||||||
final Signature signature = Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0); |
|
||||||
final SignedData<ProposalPayload> proposalPayloadSignedData = |
|
||||||
SignedData.from(proposalPayload, signature); |
|
||||||
|
|
||||||
final RoundChangeCertificate roundChangeCertificate = |
|
||||||
new RoundChangeCertificate(Collections.emptyList()); |
|
||||||
final NewRoundPayload expectedNewRoundPayload = |
|
||||||
new NewRoundPayload(ROUND_IDENTIFIER, roundChangeCertificate, proposalPayloadSignedData); |
|
||||||
final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); |
|
||||||
expectedNewRoundPayload.writeTo(rlpOut); |
|
||||||
|
|
||||||
final RLPInput rlpInput = RLP.input(rlpOut.encoded()); |
|
||||||
final NewRoundPayload newRoundPayload = NewRoundPayload.readFrom(rlpInput); |
|
||||||
assertThat(newRoundPayload.getProposalPayload()).isEqualTo(proposalPayloadSignedData); |
|
||||||
assertThat(newRoundPayload.getRoundChangeCertificate()).isEqualTo(roundChangeCertificate); |
|
||||||
assertThat(newRoundPayload.getRoundIdentifier()).isEqualTo(ROUND_IDENTIFIER); |
|
||||||
assertThat(newRoundPayload.getMessageType()).isEqualTo(IbftV2.NEW_ROUND); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void roundTripRlpWithRoundChangePayloads() { |
|
||||||
final Block block = |
|
||||||
TestHelpers.createProposalBlock(singletonList(AddressHelpers.ofValue(1)), ROUND_IDENTIFIER); |
|
||||||
final ProposalPayload proposalPayload = new ProposalPayload(ROUND_IDENTIFIER, block.getHash()); |
|
||||||
final Signature signature = Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0); |
|
||||||
final SignedData<ProposalPayload> signedProposal = SignedData.from(proposalPayload, signature); |
|
||||||
|
|
||||||
final PreparePayload preparePayload = |
|
||||||
new PreparePayload(ROUND_IDENTIFIER, Hash.fromHexStringLenient("0x8523ba6e7c5f59ae87")); |
|
||||||
final SignedData<PreparePayload> signedPrepare = SignedData.from(preparePayload, signature); |
|
||||||
final PreparedCertificate preparedCert = |
|
||||||
new PreparedCertificate(signedProposal, Lists.newArrayList(signedPrepare)); |
|
||||||
|
|
||||||
final RoundChangePayload roundChangePayload = |
|
||||||
new RoundChangePayload(ROUND_IDENTIFIER, Optional.of(preparedCert)); |
|
||||||
SignedData<RoundChangePayload> signedRoundChange = |
|
||||||
SignedData.from(roundChangePayload, signature); |
|
||||||
|
|
||||||
final RoundChangeCertificate roundChangeCertificate = |
|
||||||
new RoundChangeCertificate(Lists.list(signedRoundChange)); |
|
||||||
final NewRoundPayload expectedNewRoundPayload = |
|
||||||
new NewRoundPayload(ROUND_IDENTIFIER, roundChangeCertificate, signedProposal); |
|
||||||
final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); |
|
||||||
expectedNewRoundPayload.writeTo(rlpOut); |
|
||||||
|
|
||||||
final RLPInput rlpInput = RLP.input(rlpOut.encoded()); |
|
||||||
final NewRoundPayload newRoundPayload = NewRoundPayload.readFrom(rlpInput); |
|
||||||
assertThat(newRoundPayload.getProposalPayload()).isEqualTo(signedProposal); |
|
||||||
assertThat(newRoundPayload.getRoundChangeCertificate()).isEqualTo(roundChangeCertificate); |
|
||||||
assertThat(newRoundPayload.getRoundIdentifier()).isEqualTo(ROUND_IDENTIFIER); |
|
||||||
assertThat(newRoundPayload.getMessageType()).isEqualTo(IbftV2.NEW_ROUND); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,89 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2019 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.emptyList; |
||||||
|
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.messagewrappers.Proposal; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; |
||||||
|
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Block; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||||
|
|
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class FutureRoundProposalMessageValidatorTest { |
||||||
|
|
||||||
|
private final KeyPair proposerKey = KeyPair.generate(); |
||||||
|
private final MessageFactory messageFactoy = new MessageFactory(proposerKey); |
||||||
|
private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1); |
||||||
|
private final Block proposedBlock = TestHelpers.createProposalBlock(emptyList(), roundIdentifier); |
||||||
|
|
||||||
|
private FutureRoundProposalMessageValidator validator; |
||||||
|
|
||||||
|
private final MessageValidatorFactory messageValidatorFactory = |
||||||
|
mock(MessageValidatorFactory.class); |
||||||
|
private final MessageValidator messageValidator = mock(MessageValidator.class); |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setup() { |
||||||
|
|
||||||
|
when(messageValidatorFactory.createMessageValidator(any(), any())).thenReturn(messageValidator); |
||||||
|
when(messageValidator.validateProposal(any())).thenReturn(true); |
||||||
|
|
||||||
|
final BlockHeader parentHeader = mock(BlockHeader.class); |
||||||
|
|
||||||
|
validator = |
||||||
|
new FutureRoundProposalMessageValidator( |
||||||
|
messageValidatorFactory, roundIdentifier.getSequenceNumber(), parentHeader); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void validProposalMatchingCurrentChainHeightPassesValidation() { |
||||||
|
final Proposal proposal = |
||||||
|
messageFactoy.createProposal(roundIdentifier, proposedBlock, Optional.empty()); |
||||||
|
|
||||||
|
assertThat(validator.validateProposalMessage(proposal)).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void proposalTargettingDifferentChainHeightFailsValidation() { |
||||||
|
final ConsensusRoundIdentifier futureChainIdentifier = |
||||||
|
TestHelpers.createFrom(roundIdentifier, 1, 0); |
||||||
|
final Proposal proposal = |
||||||
|
messageFactoy.createProposal(futureChainIdentifier, proposedBlock, Optional.empty()); |
||||||
|
|
||||||
|
assertThat(validator.validateProposalMessage(proposal)).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void proposalWhichFailsMessageValidationFailsFutureRoundValidation() { |
||||||
|
final Proposal proposal = |
||||||
|
messageFactoy.createProposal(roundIdentifier, proposedBlock, Optional.empty()); |
||||||
|
when(messageValidator.validateProposal(any())).thenReturn(false); |
||||||
|
|
||||||
|
assertThat(validator.validateProposalMessage(proposal)).isFalse(); |
||||||
|
} |
||||||
|
} |
@ -1,182 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2019 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.emptyList; |
|
||||||
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.times; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
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.TestHelpers; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; |
|
||||||
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; |
|
||||||
import tech.pegasys.pantheon.ethereum.BlockValidator; |
|
||||||
import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs; |
|
||||||
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.Util; |
|
||||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
import java.util.Optional; |
|
||||||
|
|
||||||
import org.assertj.core.util.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 NewRoundMessageValidatorTest { |
|
||||||
|
|
||||||
private final NewRoundPayloadValidator payloadValidator = mock(NewRoundPayloadValidator.class); |
|
||||||
|
|
||||||
private final KeyPair proposerKey = KeyPair.generate(); |
|
||||||
private final KeyPair validatorKey = KeyPair.generate(); |
|
||||||
private final MessageFactory proposerMessageFactory = new MessageFactory(proposerKey); |
|
||||||
private final MessageFactory validatorMessageFactory = new MessageFactory(validatorKey); |
|
||||||
private final Address proposerAddress = Util.publicKeyToAddress(proposerKey.getPublicKey()); |
|
||||||
private final Address validatorAddress = Util.publicKeyToAddress(validatorKey.getPublicKey()); |
|
||||||
private final List<Address> validators = Lists.newArrayList(proposerAddress, validatorAddress); |
|
||||||
|
|
||||||
private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1); |
|
||||||
private final Block proposedBlock = TestHelpers.createProposalBlock(validators, roundIdentifier); |
|
||||||
|
|
||||||
private ProposalBlockConsistencyValidator proposalBlockConsistencyValidator = |
|
||||||
mock(ProposalBlockConsistencyValidator.class); |
|
||||||
private final RoundChangeCertificateValidator roundChangeCertificateValidator = |
|
||||||
mock(RoundChangeCertificateValidator.class); |
|
||||||
|
|
||||||
@Mock private BlockValidator<IbftContext> blockValidator; |
|
||||||
private ProtocolContext<IbftContext> protocolContext; |
|
||||||
|
|
||||||
private NewRoundMessageValidator validator; |
|
||||||
|
|
||||||
@Before |
|
||||||
public void setup() { |
|
||||||
|
|
||||||
when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) |
|
||||||
.thenReturn(Optional.of(new BlockProcessingOutputs(null, null))); |
|
||||||
|
|
||||||
when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( |
|
||||||
any(), any())) |
|
||||||
.thenReturn(true); |
|
||||||
|
|
||||||
protocolContext = |
|
||||||
new ProtocolContext<>( |
|
||||||
mock(MutableBlockchain.class), mock(WorldStateArchive.class), mock(IbftContext.class)); |
|
||||||
|
|
||||||
validator = |
|
||||||
new NewRoundMessageValidator( |
|
||||||
payloadValidator, |
|
||||||
proposalBlockConsistencyValidator, |
|
||||||
blockValidator, |
|
||||||
protocolContext, |
|
||||||
roundChangeCertificateValidator); |
|
||||||
|
|
||||||
when(proposalBlockConsistencyValidator.validateProposalMatchesBlock(any(), any())) |
|
||||||
.thenReturn(true); |
|
||||||
|
|
||||||
when(payloadValidator.validateNewRoundMessage(any())).thenReturn(true); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void underlyingPayloadValidatorIsInvokedWithCorrectParameters() { |
|
||||||
final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); |
|
||||||
final NewRound message = |
|
||||||
proposerMessageFactory.createNewRound( |
|
||||||
roundIdentifier, |
|
||||||
new RoundChangeCertificate(emptyList()), |
|
||||||
proposal.getSignedPayload(), |
|
||||||
proposal.getBlock()); |
|
||||||
|
|
||||||
assertThat(validator.validateNewRoundMessage(message)).isTrue(); |
|
||||||
verify(payloadValidator, times(1)).validateNewRoundMessage(message.getSignedPayload()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void failedBlockValidationFailsMessageValidation() { |
|
||||||
final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); |
|
||||||
final NewRound message = |
|
||||||
proposerMessageFactory.createNewRound( |
|
||||||
roundIdentifier, |
|
||||||
new RoundChangeCertificate(emptyList()), |
|
||||||
proposal.getSignedPayload(), |
|
||||||
proposal.getBlock()); |
|
||||||
|
|
||||||
when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) |
|
||||||
.thenReturn(Optional.empty()); |
|
||||||
assertThat(validator.validateNewRoundMessage(message)).isFalse(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void ifProposalConsistencyChecksFailsProposalIsIllegal() { |
|
||||||
final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); |
|
||||||
final NewRound message = |
|
||||||
proposerMessageFactory.createNewRound( |
|
||||||
roundIdentifier, |
|
||||||
new RoundChangeCertificate(emptyList()), |
|
||||||
proposal.getSignedPayload(), |
|
||||||
proposal.getBlock()); |
|
||||||
|
|
||||||
when(proposalBlockConsistencyValidator.validateProposalMatchesBlock(any(), any())) |
|
||||||
.thenReturn(false); |
|
||||||
when(payloadValidator.validateNewRoundMessage(any())).thenReturn(true); |
|
||||||
|
|
||||||
assertThat(validator.validateNewRoundMessage(message)).isFalse(); |
|
||||||
verify(proposalBlockConsistencyValidator, times(1)) |
|
||||||
.validateProposalMatchesBlock(proposal.getSignedPayload(), proposal.getBlock()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void validationFailsIfUnderlyingSignedDataValidatorFails() { |
|
||||||
when(payloadValidator.validateNewRoundMessage(any())).thenReturn(false); |
|
||||||
final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); |
|
||||||
final NewRound message = |
|
||||||
proposerMessageFactory.createNewRound( |
|
||||||
roundIdentifier, |
|
||||||
new RoundChangeCertificate(emptyList()), |
|
||||||
proposal.getSignedPayload(), |
|
||||||
proposal.getBlock()); |
|
||||||
|
|
||||||
assertThat(validator.validateNewRoundMessage(message)).isFalse(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void roundChangeCertificateDoesntContainSuppliedBlockFails() { |
|
||||||
when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( |
|
||||||
any(), any())) |
|
||||||
.thenReturn(false); |
|
||||||
|
|
||||||
final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); |
|
||||||
final NewRound message = |
|
||||||
proposerMessageFactory.createNewRound( |
|
||||||
roundIdentifier, |
|
||||||
new RoundChangeCertificate(emptyList()), |
|
||||||
proposal.getSignedPayload(), |
|
||||||
proposal.getBlock()); |
|
||||||
|
|
||||||
assertThat(validator.validateNewRoundMessage(message)).isFalse(); |
|
||||||
} |
|
||||||
} |
|
@ -1,185 +0,0 @@ |
|||||||
/* |
|
||||||
* 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.TestHelpers; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; |
|
||||||
import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangePayloadValidator.MessageValidatorForHeightFactory; |
|
||||||
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.Util; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
import java.util.Optional; |
|
||||||
|
|
||||||
import com.google.common.collect.Lists; |
|
||||||
import org.junit.Before; |
|
||||||
import org.junit.Test; |
|
||||||
|
|
||||||
public class NewRoundSignedDataValidatorTest { |
|
||||||
|
|
||||||
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 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 NewRoundPayloadValidator validator; |
|
||||||
|
|
||||||
private final ProposerSelector proposerSelector = mock(ProposerSelector.class); |
|
||||||
private final MessageValidatorForHeightFactory validatorFactory = |
|
||||||
mock(MessageValidatorForHeightFactory.class); |
|
||||||
private final SignedDataValidator signedDataValidator = mock(SignedDataValidator.class); |
|
||||||
private final RoundChangeCertificateValidator roundChangeCertificateValidator = |
|
||||||
mock(RoundChangeCertificateValidator.class); |
|
||||||
|
|
||||||
private Block proposedBlock; |
|
||||||
private NewRound validMsg; |
|
||||||
private NewRoundPayload validPayload; |
|
||||||
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); |
|
||||||
validMsg = createValidNewRoundMessageSignedBy(proposerKey); |
|
||||||
validPayload = validMsg.getSignedPayload().getPayload(); |
|
||||||
msgBuilder = NewRoundPayload.Builder.fromExisting(validMsg); |
|
||||||
|
|
||||||
when(proposerSelector.selectProposerForRound(any())).thenReturn(proposerAddress); |
|
||||||
|
|
||||||
when(validatorFactory.createAt(any())).thenReturn(signedDataValidator); |
|
||||||
when(signedDataValidator.validateProposal(any())).thenReturn(true); |
|
||||||
when(signedDataValidator.validatePrepare(any())).thenReturn(true); |
|
||||||
|
|
||||||
when(roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( |
|
||||||
any(), any())) |
|
||||||
.thenReturn(true); |
|
||||||
|
|
||||||
validator = |
|
||||||
new NewRoundPayloadValidator( |
|
||||||
proposerSelector, validatorFactory, chainHeight, roundChangeCertificateValidator); |
|
||||||
} |
|
||||||
|
|
||||||
/* NOTE: All test herein assume that the Proposer is the expected transmitter of the NewRound |
|
||||||
* message. |
|
||||||
*/ |
|
||||||
|
|
||||||
private NewRound createValidNewRoundMessageSignedBy(final KeyPair signingKey) { |
|
||||||
final MessageFactory messageCreator = new MessageFactory(signingKey); |
|
||||||
|
|
||||||
final RoundChangeCertificate.Builder builder = new RoundChangeCertificate.Builder(); |
|
||||||
builder.appendRoundChangeMessage( |
|
||||||
proposerMessageFactory.createRoundChange(roundIdentifier, Optional.empty())); |
|
||||||
|
|
||||||
return messageCreator.createNewRound( |
|
||||||
roundIdentifier, |
|
||||||
builder.buildCertificate(), |
|
||||||
messageCreator.createProposal(roundIdentifier, proposedBlock).getSignedPayload(), |
|
||||||
proposedBlock); |
|
||||||
} |
|
||||||
|
|
||||||
private NewRound signPayload( |
|
||||||
final NewRoundPayload payload, final KeyPair signingKey, final Block block) { |
|
||||||
|
|
||||||
final MessageFactory messageCreator = new MessageFactory(signingKey); |
|
||||||
|
|
||||||
return messageCreator.createNewRound( |
|
||||||
payload.getRoundIdentifier(), |
|
||||||
payload.getRoundChangeCertificate(), |
|
||||||
payload.getProposalPayload(), |
|
||||||
block); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void basicNewRoundMessageIsValid() { |
|
||||||
assertThat(validator.validateNewRoundMessage(validMsg.getSignedPayload())).isTrue(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void newRoundFromNonProposerFails() { |
|
||||||
final NewRound msg = signPayload(validPayload, validatorKey, proposedBlock); |
|
||||||
|
|
||||||
assertThat(validator.validateNewRoundMessage(msg.getSignedPayload())).isFalse(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void newRoundTargetingRoundZeroFails() { |
|
||||||
msgBuilder.setRoundChangeIdentifier( |
|
||||||
new ConsensusRoundIdentifier(roundIdentifier.getSequenceNumber(), 0)); |
|
||||||
|
|
||||||
final NewRound inValidMsg = signPayload(msgBuilder.build(), proposerKey, proposedBlock); |
|
||||||
|
|
||||||
assertThat(validator.validateNewRoundMessage(inValidMsg.getSignedPayload())).isFalse(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void newRoundTargetingDifferentSequenceNumberFails() { |
|
||||||
final ConsensusRoundIdentifier futureRound = TestHelpers.createFrom(roundIdentifier, 1, 0); |
|
||||||
msgBuilder.setRoundChangeIdentifier(futureRound); |
|
||||||
|
|
||||||
final NewRound inValidMsg = signPayload(msgBuilder.build(), proposerKey, proposedBlock); |
|
||||||
|
|
||||||
assertThat(validator.validateNewRoundMessage(inValidMsg.getSignedPayload())).isFalse(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void roundChangeCertificateFailsValidation() { |
|
||||||
when(roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( |
|
||||||
any(), any())) |
|
||||||
.thenReturn(false); |
|
||||||
assertThat(validator.validateNewRoundMessage(validMsg.getSignedPayload())).isFalse(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void embeddedProposalFailsValidation() { |
|
||||||
when(signedDataValidator.validateProposal(any())).thenReturn(false, true); |
|
||||||
|
|
||||||
final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); |
|
||||||
|
|
||||||
final NewRound msg = |
|
||||||
proposerMessageFactory.createNewRound( |
|
||||||
roundIdentifier, |
|
||||||
new RoundChangeCertificate( |
|
||||||
Lists.newArrayList( |
|
||||||
proposerMessageFactory |
|
||||||
.createRoundChange(roundIdentifier, Optional.empty()) |
|
||||||
.getSignedPayload(), |
|
||||||
validatorMessageFactory |
|
||||||
.createRoundChange(roundIdentifier, Optional.empty()) |
|
||||||
.getSignedPayload())), |
|
||||||
proposal.getSignedPayload(), |
|
||||||
proposedBlock); |
|
||||||
|
|
||||||
assertThat(validator.validateNewRoundMessage(msg.getSignedPayload())).isFalse(); |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue