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