Ibft2: Replace NewRound with extended Proposal (#872)

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Trent Mohay 6 years ago committed by GitHub
parent 8e7347fb35
commit e279f1f726
  1. 6
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/IntegrationTestHelpers.java
  2. 4
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificPeers.java
  3. 27
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java
  4. 13
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/FutureRoundTest.java
  5. 15
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/GossipTest.java
  6. 4
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeIsProposerTest.java
  7. 63
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedFutureProposalTest.java
  8. 90
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java
  9. 2
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/SpuriousBehaviourTest.java
  10. 4
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftGossip.java
  11. 3
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/IbftV2.java
  12. 44
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageData.java
  13. 66
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/NewRound.java
  14. 37
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java
  15. 27
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMessageTransmitter.java
  16. 19
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/MessageFactory.java
  17. 145
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayload.java
  18. 11
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/SignedData.java
  19. 3
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java
  20. 3
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java
  21. 47
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java
  22. 8
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java
  23. 70
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java
  24. 4
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java
  25. 2
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManager.java
  26. 53
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java
  27. 62
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java
  28. 30
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java
  29. 84
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java
  30. 84
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundPayloadValidator.java
  31. 4
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidator.java
  32. 7
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftGossipTest.java
  33. 5
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftHelpersTest.java
  34. 17
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/TestHelpers.java
  35. 69
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageTest.java
  36. 98
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayloadTest.java
  37. 5
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocolTest.java
  38. 60
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
  39. 62
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java
  40. 41
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java
  41. 2
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeArtifactsTest.java
  42. 2
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java
  43. 16
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java
  44. 89
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java
  45. 79
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java
  46. 182
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java
  47. 185
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java
  48. 10
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/ProposalBlockConsistencyValidatorTest.java
  49. 12
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java
  50. 12
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java
  51. 37
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java

@ -25,6 +25,7 @@ import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Block;
import java.util.Optional;
import java.util.stream.Collectors;
public class IntegrationTestHelpers {
@ -49,7 +50,10 @@ public class IntegrationTestHelpers {
final RoundSpecificPeers peers = context.roundSpecificPeers(preparedRound);
return new PreparedRoundArtifacts(
peers.getProposer().getMessageFactory().createProposal(preparedRound, block),
peers
.getProposer()
.getMessageFactory()
.createProposal(preparedRound, block, Optional.empty()),
peers.createSignedPreparePayloadOfNonProposing(preparedRound, block.getHash()).stream()
.map(Prepare::new)
.collect(Collectors.toList()));

@ -19,7 +19,6 @@ import static org.assertj.core.api.Fail.fail;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData;
@ -197,9 +196,6 @@ public class RoundSpecificPeers {
case IbftV2.COMMIT:
actualSignedPayload = CommitMessageData.fromMessageData(actual).decode();
break;
case IbftV2.NEW_ROUND:
actualSignedPayload = NewRoundMessageData.fromMessageData(actual).decode();
break;
case IbftV2.ROUND_CHANGE:
actualSignedPayload = RoundChangeMessageData.fromMessageData(actual).decode();
break;

@ -16,19 +16,15 @@ import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.EventMultiplexer;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents;
import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangePayload;
import tech.pegasys.pantheon.consensus.ibft.payload.SignedData;
import tech.pegasys.pantheon.consensus.ibft.statemachine.PreparedRoundArtifacts;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
@ -79,7 +75,7 @@ public class ValidatorPeer {
}
public Proposal injectProposal(final ConsensusRoundIdentifier rId, final Block block) {
final Proposal payload = messageFactory.createProposal(rId, block);
final Proposal payload = messageFactory.createProposal(rId, block, Optional.empty());
injectMessage(ProposalMessageData.create(payload));
return payload;
@ -108,26 +104,15 @@ public class ValidatorPeer {
return payload;
}
public NewRound injectNewRound(
public Proposal injectProposalForFutureRound(
final ConsensusRoundIdentifier rId,
final RoundChangeCertificate roundChangeCertificate,
final Proposal proposal) {
final NewRound payload =
messageFactory.createNewRound(
rId, roundChangeCertificate, proposal.getSignedPayload(), proposal.getBlock());
injectMessage(NewRoundMessageData.create(payload));
return payload;
}
public NewRound injectEmptyNewRound(
final ConsensusRoundIdentifier targetRoundId,
final List<SignedData<RoundChangePayload>> roundChangePayloads,
final Block blockToPropose) {
final Proposal proposal = messageFactory.createProposal(targetRoundId, blockToPropose);
return injectNewRound(targetRoundId, new RoundChangeCertificate(roundChangePayloads), proposal);
final Proposal payload =
messageFactory.createProposal(rId, blockToPropose, Optional.of(roundChangeCertificate));
injectMessage(ProposalMessageData.create(payload));
return payload;
}
public RoundChange injectRoundChange(

@ -19,6 +19,7 @@ import tech.pegasys.pantheon.consensus.ibft.IbftHelpers;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate;
import tech.pegasys.pantheon.consensus.ibft.support.RoundSpecificPeers;
import tech.pegasys.pantheon.consensus.ibft.support.TestContext;
import tech.pegasys.pantheon.consensus.ibft.support.TestContextBuilder;
@ -93,8 +94,10 @@ public class FutureRoundTest {
// and updates blockchain
futurePeers
.getProposer()
.injectEmptyNewRound(
futureRoundId, futurePeers.createSignedRoundChangePayload(futureRoundId), futureBlock);
.injectProposalForFutureRound(
futureRoundId,
new RoundChangeCertificate(futurePeers.createSignedRoundChangePayload(futureRoundId)),
futureBlock);
final Prepare expectedPrepare =
localNodeMessageFactory.createPrepare(futureRoundId, futureBlock.getHash());
@ -136,8 +139,10 @@ public class FutureRoundTest {
futurePeers
.getProposer()
.injectEmptyNewRound(
futureRoundId, futurePeers.createSignedRoundChangePayload(futureRoundId), futureBlock);
.injectProposalForFutureRound(
futureRoundId,
new RoundChangeCertificate(futurePeers.createSignedRoundChangePayload(futureRoundId)),
futureBlock);
final Prepare expectedFuturePrepare =
localNodeMessageFactory.createPrepare(futureRoundId, futureBlock.getHash());

@ -20,7 +20,6 @@ import tech.pegasys.pantheon.consensus.ibft.IbftHelpers;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead;
import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
@ -93,8 +92,10 @@ public class GossipTest {
final RoundChange roundChange = msgFactory.createRoundChange(roundId, Optional.empty());
final RoundChangeCertificate roundChangeCert =
new RoundChangeCertificate(singleton(roundChange.getSignedPayload()));
final NewRound newRound = sender.injectNewRound(roundId, roundChangeCert, proposal);
peers.verifyMessagesReceivedNonPropsing(newRound);
final Proposal nextRoundProposal =
sender.injectProposalForFutureRound(roundId, roundChangeCert, proposal.getBlock());
peers.verifyMessagesReceivedNonPropsing(nextRoundProposal);
peers.verifyNoMessagesReceivedProposer();
sender.injectRoundChange(roundId, Optional.empty());
@ -118,7 +119,8 @@ public class GossipTest {
public void messageWithUnknownValidatorIsNotGossiped() {
final KeyPair unknownKeyPair = KeyPair.generate();
final MessageFactory unknownMsgFactory = new MessageFactory(unknownKeyPair);
final Proposal unknownProposal = unknownMsgFactory.createProposal(roundId, block);
final Proposal unknownProposal =
unknownMsgFactory.createProposal(roundId, block, Optional.empty());
sender.injectMessage(ProposalMessageData.create(unknownProposal));
peers.verifyNoMessagesReceived();
@ -128,7 +130,8 @@ public class GossipTest {
public void messageIsNotGossipedToSenderOrCreator() {
final ValidatorPeer msgCreator = peers.getFirstNonProposer();
final MessageFactory peerMsgFactory = msgCreator.getMessageFactory();
final Proposal proposalFromPeer = peerMsgFactory.createProposal(roundId, block);
final Proposal proposalFromPeer =
peerMsgFactory.createProposal(roundId, block, Optional.empty());
sender.injectMessage(ProposalMessageData.create(proposalFromPeer));
@ -139,7 +142,7 @@ public class GossipTest {
@Test
public void futureMessageIsNotGossipedImmediately() {
ConsensusRoundIdentifier futureRoundId = new ConsensusRoundIdentifier(2, 0);
msgFactory.createProposal(futureRoundId, block);
msgFactory.createProposal(futureRoundId, block, Optional.empty());
sender.injectProposal(futureRoundId, block);
peers.verifyNoMessagesReceived();

@ -28,6 +28,7 @@ import tech.pegasys.pantheon.ethereum.core.Block;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
@ -63,7 +64,8 @@ public class LocalNodeIsProposerTest {
@Before
public void setup() {
expectedProposedBlock = context.createBlockForProposalFromChainHead(0, blockTimeStamp);
expectedTxProposal = localNodeMessageFactory.createProposal(roundId, expectedProposedBlock);
expectedTxProposal =
localNodeMessageFactory.createProposal(roundId, expectedProposedBlock, Optional.empty());
expectedTxCommit =
new Commit(

@ -17,7 +17,6 @@ import static tech.pegasys.pantheon.consensus.ibft.support.IntegrationTestHelper
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
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.consensus.ibft.payload.RoundChangePayload;
@ -35,8 +34,11 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
/** Ensure the Ibft component responds appropriately when a NewRound message is received. */
public class ReceivedNewRoundTest {
/**
* Ensure the Ibft component responds appropriately when a future round Proposal message is
* received.
*/
public class ReceivedFutureProposalTest {
private final int NETWORK_SIZE = 5;
@ -57,7 +59,7 @@ public class ReceivedNewRoundTest {
}
@Test
public void newRoundMessageWithEmptyPrepareCertificatesOfferNewBlock() {
public void proposalWithEmptyPrepareCertificatesOfferNewBlock() {
final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1);
final Block blockToPropose =
context.createBlockForProposalFromChainHead(nextRoundId.getRoundNumber(), 15);
@ -68,10 +70,8 @@ public class ReceivedNewRoundTest {
final ValidatorPeer nextProposer = context.roundSpecificPeers(nextRoundId).getProposer();
nextProposer.injectNewRound(
targetRound,
new RoundChangeCertificate(roundChanges),
nextProposer.getMessageFactory().createProposal(targetRound, blockToPropose));
nextProposer.injectProposalForFutureRound(
targetRound, new RoundChangeCertificate(roundChanges), blockToPropose);
final Prepare expectedPrepare =
localNodeMessageFactory.createPrepare(targetRound, blockToPropose.getHash());
@ -80,7 +80,7 @@ public class ReceivedNewRoundTest {
}
@Test
public void newRoundMessageFromIllegalSenderIsDiscardedAndNoPrepareForNewRoundIsSent() {
public void proposalFromIllegalSenderIsDiscardedAndNoPrepareForNewRoundIsSent() {
final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1);
final Block blockToPropose =
context.createBlockForProposalFromChainHead(nextRoundId.getRoundNumber(), 15);
@ -91,16 +91,14 @@ public class ReceivedNewRoundTest {
final ValidatorPeer illegalProposer =
context.roundSpecificPeers(nextRoundId).getNonProposing(0);
illegalProposer.injectNewRound(
nextRoundId,
new RoundChangeCertificate(roundChanges),
illegalProposer.getMessageFactory().createProposal(nextRoundId, blockToPropose));
illegalProposer.injectProposalForFutureRound(
nextRoundId, new RoundChangeCertificate(roundChanges), blockToPropose);
peers.verifyNoMessagesReceived();
}
@Test
public void newRoundWithPrepareCertificateResultsInNewRoundStartingWithExpectedBlock() {
public void proposalWithPrepareCertificateResultsInNewRoundStartingWithExpectedBlock() {
final Block initialBlock = context.createBlockForProposalFromChainHead(0, 15);
final Block reproposedBlock = context.createBlockForProposalFromChainHead(1, 15);
final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1);
@ -113,18 +111,16 @@ public class ReceivedNewRoundTest {
final ValidatorPeer nextProposer = context.roundSpecificPeers(nextRoundId).getProposer();
nextProposer.injectNewRound(
nextRoundId,
new RoundChangeCertificate(roundChanges),
peers.getNonProposing(0).getMessageFactory().createProposal(nextRoundId, reproposedBlock));
nextProposer.injectProposalForFutureRound(
nextRoundId, new RoundChangeCertificate(roundChanges), reproposedBlock);
peers.verifyMessagesReceived(
localNodeMessageFactory.createPrepare(nextRoundId, reproposedBlock.getHash()));
}
@Test
public void newRoundMessageForPriorRoundIsNotActioned() {
// first move to a future round, then inject a newRound for a prior round, local node
public void proposalMessageForPriorRoundIsNotActioned() {
// first move to a future round, then inject a proposal for a prior round, local node
// should send no messages.
final ConsensusRoundIdentifier futureRound = new ConsensusRoundIdentifier(1, 2);
peers.roundChange(futureRound);
@ -136,19 +132,16 @@ public class ReceivedNewRoundTest {
final ValidatorPeer interimRoundProposer =
context.roundSpecificPeers(interimRound).getProposer();
final Proposal proposal =
interimRoundProposer
.getMessageFactory()
.createProposal(interimRound, context.createBlockForProposalFromChainHead(1, 30));
interimRoundProposer.injectNewRound(
interimRound, new RoundChangeCertificate(roundChangePayloads), proposal);
interimRoundProposer.injectProposalForFutureRound(
interimRound,
new RoundChangeCertificate(roundChangePayloads),
context.createBlockForProposalFromChainHead(1, 30));
peers.verifyNoMessagesReceived();
}
@Test
public void receiveRoundStateIsNotLostIfASecondNewRoundMessageIsReceivedForCurrentRound() {
public void receiveRoundStateIsNotLostIfASecondProposalMessageIsReceivedForCurrentRound() {
final Block initialBlock = context.createBlockForProposalFromChainHead(0, 15);
final Block reproposedBlock = context.createBlockForProposalFromChainHead(1, 15);
final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1);
@ -162,22 +155,18 @@ public class ReceivedNewRoundTest {
final RoundSpecificPeers nextRoles = context.roundSpecificPeers(nextRoundId);
final ValidatorPeer nextProposer = nextRoles.getProposer();
nextProposer.injectNewRound(
nextRoundId,
new RoundChangeCertificate(roundChanges),
peers.getNonProposing(0).getMessageFactory().createProposal(nextRoundId, reproposedBlock));
nextProposer.injectProposalForFutureRound(
nextRoundId, new RoundChangeCertificate(roundChanges), reproposedBlock);
peers.verifyMessagesReceived(
localNodeMessageFactory.createPrepare(nextRoundId, reproposedBlock.getHash()));
// Inject a prepare, then re-inject the newRound - then ensure only a single prepare is enough
// Inject a prepare, then re-inject the proposal - then ensure only a single prepare is enough
// to trigger a Commit transmission from the local node
nextRoles.getNonProposing(0).injectPrepare(nextRoundId, reproposedBlock.getHash());
nextProposer.injectNewRound(
nextRoundId,
new RoundChangeCertificate(roundChanges),
peers.getNonProposing(0).getMessageFactory().createProposal(nextRoundId, reproposedBlock));
nextProposer.injectProposalForFutureRound(
nextRoundId, new RoundChangeCertificate(roundChanges), reproposedBlock);
peers.verifyNoMessagesReceived();

@ -19,7 +19,6 @@ import static tech.pegasys.pantheon.consensus.ibft.support.IntegrationTestHelper
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftHelpers;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
@ -138,7 +137,7 @@ public class RoundChangeTest {
}
@Test
public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCreatesNewRoundMsg() {
public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCreatesProposalMsg() {
// Note: Round-4 is the next round for which the local node is Proposer
final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 4);
final Block locallyProposedBlock =
@ -149,25 +148,23 @@ public class RoundChangeTest {
final RoundChange rc3 = peers.getNonProposing(2).injectRoundChange(targetRound, empty());
final RoundChange rc4 = peers.getProposer().injectRoundChange(targetRound, empty());
final NewRound expectedNewRound =
localNodeMessageFactory.createNewRound(
final Proposal expectedProposal =
localNodeMessageFactory.createProposal(
targetRound,
new RoundChangeCertificate(
Lists.newArrayList(
rc1.getSignedPayload(),
rc2.getSignedPayload(),
rc3.getSignedPayload(),
rc4.getSignedPayload())),
localNodeMessageFactory
.createProposal(targetRound, locallyProposedBlock)
.getSignedPayload(),
locallyProposedBlock);
peers.verifyMessagesReceived(expectedNewRound);
locallyProposedBlock,
Optional.of(
new RoundChangeCertificate(
Lists.newArrayList(
rc1.getSignedPayload(),
rc2.getSignedPayload(),
rc3.getSignedPayload(),
rc4.getSignedPayload()))));
peers.verifyMessagesReceived(expectedProposal);
}
@Test
public void newRoundMessageContainsBlockOnWhichPeerPrepared() {
public void proposalMessageContainsBlockOnWhichPeerPrepared() {
final long ARBITRARY_BLOCKTIME = 1500;
final PreparedRoundArtifacts earlierPrepCert =
@ -204,21 +201,19 @@ public class RoundChangeTest {
context.createBlockForProposalFromChainHead(
targetRound.getRoundNumber(), ARBITRARY_BLOCKTIME);
final NewRound expectedNewRound =
localNodeMessageFactory.createNewRound(
final Proposal expectedProposal =
localNodeMessageFactory.createProposal(
targetRound,
new RoundChangeCertificate(
Lists.newArrayList(
rc1.getSignedPayload(),
rc2.getSignedPayload(),
rc3.getSignedPayload(),
rc4.getSignedPayload())),
localNodeMessageFactory
.createProposal(targetRound, expectedBlockToPropose)
.getSignedPayload(),
expectedBlockToPropose);
peers.verifyMessagesReceived(expectedNewRound);
expectedBlockToPropose,
Optional.of(
new RoundChangeCertificate(
Lists.newArrayList(
rc1.getSignedPayload(),
rc2.getSignedPayload(),
rc3.getSignedPayload(),
rc4.getSignedPayload()))));
peers.verifyMessagesReceived(expectedProposal);
}
@Test
@ -234,16 +229,13 @@ public class RoundChangeTest {
final Block locallyProposedBlock =
context.createBlockForProposalFromChainHead(futureRound.getRoundNumber(), blockTimeStamp);
final NewRound expectedNewRound =
localNodeMessageFactory.createNewRound(
final Proposal expectedProposal =
localNodeMessageFactory.createProposal(
futureRound,
new RoundChangeCertificate(roundChangeMessages),
localNodeMessageFactory
.createProposal(futureRound, locallyProposedBlock)
.getSignedPayload(),
locallyProposedBlock);
locallyProposedBlock,
Optional.of(new RoundChangeCertificate(roundChangeMessages)));
peers.verifyMessagesReceived(expectedNewRound);
peers.verifyMessagesReceived(expectedProposal);
}
@Test
@ -289,16 +281,13 @@ public class RoundChangeTest {
context.createBlockForProposalFromChainHead(
targetRound.getRoundNumber(), ARBITRARY_BLOCKTIME);
final NewRound expectedNewRound =
localNodeMessageFactory.createNewRound(
final Proposal expectedProposal =
localNodeMessageFactory.createProposal(
targetRound,
new RoundChangeCertificate(Lists.newArrayList(roundChangeMessages)),
localNodeMessageFactory
.createProposal(targetRound, expectedBlockToPropose)
.getSignedPayload(),
expectedBlockToPropose);
expectedBlockToPropose,
Optional.of(new RoundChangeCertificate(Lists.newArrayList(roundChangeMessages))));
peers.verifyMessagesReceived(expectedNewRound);
peers.verifyMessagesReceived(expectedProposal);
}
@Test
@ -335,14 +324,17 @@ public class RoundChangeTest {
// create illegal RoundChangeMessage
final PreparedRoundArtifacts illegalPreparedRoundArtifacts =
new PreparedRoundArtifacts(
peers.getNonProposing(0).getMessageFactory().createProposal(roundId, blockToPropose),
peers
.getNonProposing(0)
.getMessageFactory()
.createProposal(roundId, blockToPropose, Optional.empty()),
emptyList());
peers
.getNonProposing(2)
.injectRoundChange(targetRound, Optional.of(illegalPreparedRoundArtifacts));
// Ensure no NewRound message is sent.
// Ensure no Proposal message is sent.
peers.verifyNoMessagesReceived();
}
}

@ -96,7 +96,7 @@ public class SpuriousBehaviourTest {
}
@Test
public void nonValidatorsCannotTriggerReponses() {
public void nonValidatorsCannotTriggerResponses() {
final KeyPair nonValidatorKeys = KeyPair.generate();
final NodeParams nonValidatorParams =
new NodeParams(Util.publicKeyToAddress(nonValidatorKeys.getPublicKey()), nonValidatorKeys);

@ -14,7 +14,6 @@ package tech.pegasys.pantheon.consensus.ibft;
import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData;
@ -64,9 +63,6 @@ public class IbftGossip implements Gossiper {
case IbftV2.ROUND_CHANGE:
decodedMessage = RoundChangeMessageData.fromMessageData(messageData).decode();
break;
case IbftV2.NEW_ROUND:
decodedMessage = NewRoundMessageData.fromMessageData(messageData).decode();
break;
default:
throw new IllegalArgumentException(
"Received message does not conform to any recognised IBFT message structure.");

@ -18,7 +18,6 @@ public class IbftV2 {
public static final int PREPARE = 1;
public static final int COMMIT = 2;
public static final int ROUND_CHANGE = 3;
public static final int NEW_ROUND = 4;
public static final int MESSAGE_SPACE = 5;
public static final int MESSAGE_SPACE = 4;
}

@ -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);
}
}

@ -14,6 +14,7 @@ package tech.pegasys.pantheon.consensus.ibft.messagewrappers;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing;
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.core.Hash;
@ -22,13 +23,21 @@ import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.Optional;
public class Proposal extends IbftMessage<ProposalPayload> {
private final Block proposedBlock;
public Proposal(final SignedData<ProposalPayload> payload, final Block proposedBlock) {
private final Optional<RoundChangeCertificate> roundChangeCertificate;
public Proposal(
final SignedData<ProposalPayload> payload,
final Block proposedBlock,
final Optional<RoundChangeCertificate> certificate) {
super(payload);
this.proposedBlock = proposedBlock;
this.roundChangeCertificate = certificate;
}
public Block getBlock() {
@ -39,12 +48,21 @@ public class Proposal extends IbftMessage<ProposalPayload> {
return getPayload().getDigest();
}
public Optional<RoundChangeCertificate> getRoundChangeCertificate() {
return roundChangeCertificate;
}
@Override
public BytesValue encode() {
final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
rlpOut.startList();
getSignedPayload().writeTo(rlpOut);
proposedBlock.writeTo(rlpOut);
if (roundChangeCertificate.isPresent()) {
roundChangeCertificate.get().writeTo(rlpOut);
} else {
rlpOut.writeNull();
}
rlpOut.endList();
return rlpOut.encoded();
}
@ -55,7 +73,22 @@ public class Proposal extends IbftMessage<ProposalPayload> {
final SignedData<ProposalPayload> payload = SignedData.readSignedProposalPayloadFrom(rlpIn);
final Block proposedBlock =
Block.readFrom(rlpIn, IbftBlockHashing::calculateDataHashForCommittedSeal);
final Optional<RoundChangeCertificate> roundChangeCertificate =
readRoundChangeCertificate(rlpIn);
rlpIn.leaveList();
return new Proposal(payload, proposedBlock);
return new Proposal(payload, proposedBlock, roundChangeCertificate);
}
private static Optional<RoundChangeCertificate> readRoundChangeCertificate(final RLPInput rlpIn) {
RoundChangeCertificate roundChangeCertificate = null;
if (!rlpIn.nextIsNull()) {
roundChangeCertificate = RoundChangeCertificate.readFrom(rlpIn);
} else {
rlpIn.skipNext();
}
return Optional.ofNullable(roundChangeCertificate);
}
}

@ -14,19 +14,15 @@ package tech.pegasys.pantheon.consensus.ibft.network;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
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.statemachine.PreparedRoundArtifacts;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Block;
@ -45,8 +41,12 @@ public class IbftMessageTransmitter {
this.multicaster = multicaster;
}
public void multicastProposal(final ConsensusRoundIdentifier roundIdentifier, final Block block) {
final Proposal data = messageFactory.createProposal(roundIdentifier, block);
public void multicastProposal(
final ConsensusRoundIdentifier roundIdentifier,
final Block block,
final Optional<RoundChangeCertificate> roundChangeCertificate) {
final Proposal data =
messageFactory.createProposal(roundIdentifier, block, roundChangeCertificate);
final ProposalMessageData message = ProposalMessageData.create(data);
@ -83,19 +83,4 @@ public class IbftMessageTransmitter {
multicaster.send(message);
}
public void multicastNewRound(
final ConsensusRoundIdentifier roundIdentifier,
final RoundChangeCertificate roundChangeCertificate,
final SignedData<ProposalPayload> proposalPayload,
final Block block) {
final NewRound signedPayload =
messageFactory.createNewRound(
roundIdentifier, roundChangeCertificate, proposalPayload, block);
final NewRoundMessageData message = NewRoundMessageData.create(signedPayload);
multicaster.send(message);
}
}

@ -14,7 +14,6 @@ package tech.pegasys.pantheon.consensus.ibft.payload;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
@ -38,11 +37,13 @@ public class MessageFactory {
}
public Proposal createProposal(
final ConsensusRoundIdentifier roundIdentifier, final Block block) {
final ConsensusRoundIdentifier roundIdentifier,
final Block block,
final Optional<RoundChangeCertificate> roundChangeCertificate) {
final ProposalPayload payload = new ProposalPayload(roundIdentifier, block.getHash());
return new Proposal(createSignedMessage(payload), block);
return new Proposal(createSignedMessage(payload), block, roundChangeCertificate);
}
public Prepare createPrepare(final ConsensusRoundIdentifier roundIdentifier, final Hash digest) {
@ -74,18 +75,6 @@ public class MessageFactory {
createSignedMessage(payload), preparedRoundArtifacts.map(PreparedRoundArtifacts::getBlock));
}
public NewRound createNewRound(
final ConsensusRoundIdentifier roundIdentifier,
final RoundChangeCertificate roundChangeCertificate,
final SignedData<ProposalPayload> proposalPayload,
final Block block) {
final NewRoundPayload payload =
new NewRoundPayload(roundIdentifier, roundChangeCertificate, proposalPayload);
return new NewRound(createSignedMessage(payload), block);
}
private <M extends Payload> SignedData<M> createSignedMessage(final M payload) {
final Signature signature = sign(payload, validatorKeyPair);

@ -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);
}
}
}

@ -24,6 +24,7 @@ import java.util.Objects;
import java.util.StringJoiner;
public class SignedData<M extends Payload> implements Authored {
private final Address sender;
private final Signature signature;
private final M unsignedPayload;
@ -98,16 +99,6 @@ public class SignedData<M extends Payload> implements Authored {
return from(unsignedMessageData, signature);
}
public static SignedData<NewRoundPayload> readSignedNewRoundPayloadFrom(final RLPInput rlpInput) {
rlpInput.enterList();
final NewRoundPayload unsignedMessageData = NewRoundPayload.readFrom(rlpInput);
final Signature signature = readSignature(rlpInput);
rlpInput.leaveList();
return from(unsignedMessageData, signature);
}
protected static <M extends Payload> SignedData<M> from(
final M unsignedMessageData, final Signature signature) {

@ -44,7 +44,6 @@ public class IbftSubProtocol implements SubProtocol {
case IbftV2.PREPARE:
case IbftV2.COMMIT:
case IbftV2.ROUND_CHANGE:
case IbftV2.NEW_ROUND:
return true;
default:
@ -63,8 +62,6 @@ public class IbftSubProtocol implements SubProtocol {
return "Commit";
case IbftV2.ROUND_CHANGE:
return "RoundChange";
case IbftV2.NEW_ROUND:
return "NewRound";
default:
return INVALID_MESSAGE_NAME;
}

@ -15,7 +15,6 @@ package tech.pegasys.pantheon.consensus.ibft.statemachine;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
@ -37,8 +36,6 @@ public interface BlockHeightManager {
void handleRoundChangePayload(RoundChange roundChange);
void handleNewRoundPayload(NewRound newRound);
long getChainHeight();
BlockHeader getParentBlockHeader();

@ -22,15 +22,14 @@ import tech.pegasys.pantheon.consensus.ibft.RoundTimer;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.IbftMessage;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
import tech.pegasys.pantheon.consensus.ibft.network.IbftMessageTransmitter;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.payload.Payload;
import tech.pegasys.pantheon.consensus.ibft.validation.FutureRoundProposalMessageValidator;
import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory;
import tech.pegasys.pantheon.consensus.ibft.validation.NewRoundMessageValidator;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import java.time.Clock;
@ -64,7 +63,7 @@ public class IbftBlockHeightManager implements BlockHeightManager {
private final IbftMessageTransmitter transmitter;
private final MessageFactory messageFactory;
private final Map<Integer, RoundState> futureRoundStateBuffer = Maps.newHashMap();
private final NewRoundMessageValidator newRoundMessageValidator;
private final FutureRoundProposalMessageValidator futureRoundProposalMessageValidator;
private final Clock clock;
private final Function<ConsensusRoundIdentifier, RoundState> roundStateCreator;
private final IbftFinalState finalState;
@ -90,8 +89,9 @@ public class IbftBlockHeightManager implements BlockHeightManager {
this.roundChangeManager = roundChangeManager;
this.finalState = finalState;
newRoundMessageValidator =
messageValidatorFactory.createNewRoundValidator(getChainHeight(), parentHeader);
futureRoundProposalMessageValidator =
messageValidatorFactory.createFutureRoundProposalMessageValidator(
getChainHeight(), parentHeader);
roundStateCreator =
(roundIdentifier) ->
@ -157,8 +157,21 @@ public class IbftBlockHeightManager implements BlockHeightManager {
@Override
public void handleProposalPayload(final Proposal proposal) {
LOG.trace("Received a Proposal Payload.");
actionOrBufferMessage(
proposal, currentRound::handleProposalMessage, RoundState::setProposedBlock);
final MessageAge messageAge =
determineAgeOfPayload(proposal.getRoundIdentifier().getRoundNumber());
if (messageAge == PRIOR_ROUND) {
LOG.trace("Received Proposal Payload for a prior round={}", proposal.getRoundIdentifier());
} else {
if (messageAge == FUTURE_ROUND) {
if (!futureRoundProposalMessageValidator.validateProposalMessage(proposal)) {
LOG.debug("Received future Proposal which is illegal, no round change triggered.");
return;
}
startNewRound(proposal.getRoundIdentifier().getRoundNumber());
}
currentRound.handleProposalMessage(proposal);
}
}
@Override
@ -237,26 +250,6 @@ public class IbftBlockHeightManager implements BlockHeightManager {
roundTimer.startTimer(currentRound.getRoundIdentifier());
}
@Override
public void handleNewRoundPayload(final NewRound newRound) {
// final NewRoundPayload payload = newRound.getSignedPayload().getPayload();
final MessageAge messageAge =
determineAgeOfPayload(newRound.getRoundIdentifier().getRoundNumber());
if (messageAge == PRIOR_ROUND) {
LOG.trace("Received NewRound Payload for a prior round={}", newRound.getRoundIdentifier());
return;
}
LOG.trace("Received NewRound Payload for {}", newRound.getRoundIdentifier());
if (newRoundMessageValidator.validateNewRoundMessage(newRound)) {
if (messageAge == FUTURE_ROUND) {
startNewRound(newRound.getRoundIdentifier().getRoundNumber());
}
currentRound.handleProposalFromNewRound(newRound);
}
}
@Override
public long getChainHeight() {
return parentHeader.getNumber() + 1;

@ -23,7 +23,6 @@ import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData;
@ -128,13 +127,6 @@ public class IbftController {
currentHeightManager::handleRoundChangePayload);
break;
case IbftV2.NEW_ROUND:
consumeMessage(
message,
NewRoundMessageData.fromMessageData(messageData).decode(),
currentHeightManager::handleNewRoundPayload);
break;
default:
throw new IllegalArgumentException(
String.format(

@ -20,11 +20,11 @@ import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.IbftHelpers;
import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.network.IbftMessageTransmitter;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
@ -86,72 +86,54 @@ public class IbftRound {
roundState.getRoundIdentifier(),
extraData,
block.getHeader());
transmitter.multicastProposal(roundState.getRoundIdentifier(), block);
updateStateWithProposedBlock(
messageFactory.createProposal(roundState.getRoundIdentifier(), block));
updateStateWithProposalAndTransmit(block, Optional.empty());
}
public void startRoundWith(
final RoundChangeArtifacts roundChangeArtifacts, final long headerTimestamp) {
final Optional<Block> bestBlockFromRoundChange = roundChangeArtifacts.getBlock();
Proposal proposal;
final RoundChangeCertificate roundChangeCertificate =
roundChangeArtifacts.getRoundChangeCertificate();
Block blockToPublish;
if (!bestBlockFromRoundChange.isPresent()) {
LOG.debug("Multicasting NewRound with new block. round={}", roundState.getRoundIdentifier());
final Block block = blockCreator.createBlock(headerTimestamp);
proposal = messageFactory.createProposal(getRoundIdentifier(), block);
LOG.debug("Multicasting Proposal with new block. round={}", roundState.getRoundIdentifier());
blockToPublish = blockCreator.createBlock(headerTimestamp);
} else {
LOG.debug(
"Multicasting NewRound from PreparedCertificate. round={}",
"Multicasting Proposal from PreparedCertificate. round={}",
roundState.getRoundIdentifier());
proposal = createProposalAroundBlock(bestBlockFromRoundChange.get());
blockToPublish =
IbftBlockInterface.replaceRoundInBlock(
bestBlockFromRoundChange.get(),
getRoundIdentifier().getRoundNumber(),
IbftBlockHashing::calculateDataHashForCommittedSeal);
}
transmitter.multicastNewRound(
getRoundIdentifier(),
roundChangeArtifacts.getRoundChangeCertificate(),
proposal.getSignedPayload(),
proposal.getBlock());
updateStateWithProposedBlock(proposal);
}
private Proposal createProposalAroundBlock(final Block block) {
final Block blockToPublish =
IbftBlockInterface.replaceRoundInBlock(
block,
getRoundIdentifier().getRoundNumber(),
IbftBlockHashing::calculateDataHashForCommittedSeal);
return messageFactory.createProposal(getRoundIdentifier(), blockToPublish);
updateStateWithProposalAndTransmit(blockToPublish, Optional.of(roundChangeCertificate));
}
public void handleProposalMessage(final Proposal msg) {
LOG.debug("Handling a Proposal message.");
private void updateStateWithProposalAndTransmit(
final Block block, final Optional<RoundChangeCertificate> roundChangeCertificate) {
final Proposal proposal =
messageFactory.createProposal(getRoundIdentifier(), block, roundChangeCertificate);
if (getRoundIdentifier().getRoundNumber() != 0) {
LOG.error("Illegally received a Proposal message when not in Round 0.");
return;
}
actionReceivedProposal(msg);
}
public void handleProposalFromNewRound(final NewRound msg) {
LOG.debug("Handling a New Round Proposal.");
if (getRoundIdentifier().getRoundNumber() == 0) {
LOG.error("Illegally received a NewRound message when in Round 0.");
return;
}
actionReceivedProposal(new Proposal(msg.getProposalPayload(), msg.getBlock()));
transmitter.multicastProposal(
proposal.getRoundIdentifier(), proposal.getBlock(), proposal.getRoundChangeCertificate());
updateStateWithProposedBlock(proposal);
}
private void actionReceivedProposal(final Proposal msg) {
public void handleProposalMessage(final Proposal msg) {
LOG.debug("Handling a Proposal message.");
final Block block = msg.getBlock();
if (updateStateWithProposedBlock(msg)) {
LOG.debug("Sending prepare message.");
transmitter.multicastPrepare(getRoundIdentifier(), block.getHash());
final Prepare localPrepareMessage =
messageFactory.createPrepare(roundState.getRoundIdentifier(), block.getHash());
messageFactory.createPrepare(getRoundIdentifier(), block.getHash());
transmitter.multicastPrepare(
localPrepareMessage.getRoundIdentifier(), localPrepareMessage.getDigest());
peerIsPrepared(localPrepareMessage);
}
}

@ -15,7 +15,6 @@ package tech.pegasys.pantheon.consensus.ibft.statemachine;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
@ -50,9 +49,6 @@ public class NoOpBlockHeightManager implements BlockHeightManager {
@Override
public void handleRoundChangePayload(final RoundChange roundChange) {}
@Override
public void handleNewRoundPayload(final NewRound newRound) {}
@Override
public long getChainHeight() {
return parentHeader.getNumber() + 1;

@ -32,7 +32,7 @@ import org.apache.logging.log4j.Logger;
* and messages for a future round should have been buffered).
*
* <p>If enough RoundChange messages all targeting a given round are received (and this node is the
* proposer for said round) - a newRound message is sent, and a new round should be started by the
* proposer for said round) - a proposal message is sent, and a new round should be started by the
* controlling class.
*/
public class RoundChangeManager {

@ -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);
}
}

@ -12,10 +12,12 @@
*/
package tech.pegasys.pantheon.consensus.ibft.validation;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate;
import tech.pegasys.pantheon.ethereum.BlockValidator;
import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
@ -35,19 +37,23 @@ public class MessageValidator {
private final ProposalBlockConsistencyValidator proposalConsistencyValidator;
private final BlockValidator<IbftContext> blockValidator;
private final ProtocolContext<IbftContext> protocolContext;
private RoundChangeCertificateValidator roundChangeCertificateValidator;
public MessageValidator(
final SignedDataValidator signedDataValidator,
final ProposalBlockConsistencyValidator proposalConsistencyValidator,
final BlockValidator<IbftContext> blockValidator,
final ProtocolContext<IbftContext> protocolContext) {
final ProtocolContext<IbftContext> protocolContext,
final RoundChangeCertificateValidator roundChangeCertificateValidator) {
this.signedDataValidator = signedDataValidator;
this.proposalConsistencyValidator = proposalConsistencyValidator;
this.blockValidator = blockValidator;
this.protocolContext = protocolContext;
this.roundChangeCertificateValidator = roundChangeCertificateValidator;
}
public boolean validateProposal(final Proposal msg) {
if (!signedDataValidator.validateProposal(msg.getSignedPayload())) {
LOG.debug("Illegal Proposal message, embedded signed data failed validation");
return false;
@ -57,6 +63,11 @@ public class MessageValidator {
return false;
}
if (!validateProposalAndRoundChangeAreConsistent(msg)) {
LOG.debug("Illegal Proposal message, embedded roundChange does not match proposal.");
return false;
}
return proposalConsistencyValidator.validateProposalMatchesBlock(
msg.getSignedPayload(), msg.getBlock());
}
@ -74,6 +85,55 @@ public class MessageValidator {
return true;
}
private boolean validateProposalAndRoundChangeAreConsistent(final Proposal proposal) {
final ConsensusRoundIdentifier proposalRoundIdentifier = proposal.getRoundIdentifier();
if (proposalRoundIdentifier.getRoundNumber() == 0) {
return validateRoundZeroProposalHasNoRoundChangeCertificate(proposal);
} else {
return validateRoundChangeCertificateIsValidAndMatchesProposedBlock(proposal);
}
}
private boolean validateRoundZeroProposalHasNoRoundChangeCertificate(final Proposal proposal) {
if (proposal.getRoundChangeCertificate().isPresent()) {
LOG.debug(
"Illegal Proposal message, round-0 proposal must not contain a round change certificate.");
return false;
}
return true;
}
private boolean validateRoundChangeCertificateIsValidAndMatchesProposedBlock(
final Proposal proposal) {
final Optional<RoundChangeCertificate> roundChangeCertificate =
proposal.getRoundChangeCertificate();
if (!roundChangeCertificate.isPresent()) {
LOG.debug(
"Illegal Proposal message, rounds other than 0 must contain a round change certificate.");
return false;
}
final RoundChangeCertificate roundChangeCert = roundChangeCertificate.get();
if (!roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
proposal.getRoundIdentifier(), roundChangeCert)) {
LOG.debug("Illegal Proposal message, embedded RoundChangeCertificate is not self-consistent");
return false;
}
if (!roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate(
roundChangeCert, proposal.getBlock())) {
LOG.debug(
"Illegal Proposal message, piggybacked block does not match latest PrepareCertificate");
return false;
}
return true;
}
public boolean validatePrepare(final Prepare msg) {
return signedDataValidator.validatePrepare(msg.getSignedPayload());
}

@ -62,12 +62,17 @@ public class MessageValidatorFactory {
final ConsensusRoundIdentifier roundIdentifier, final BlockHeader parentHeader) {
final BlockValidator<IbftContext> blockValidator =
protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockValidator();
final Collection<Address> validators = getValidatorsAfterBlock(parentHeader);
return new MessageValidator(
createSignedDataValidator(roundIdentifier, parentHeader),
new ProposalBlockConsistencyValidator(),
blockValidator,
protocolContext);
protocolContext,
new RoundChangeCertificateValidator(
validators,
(ri) -> createSignedDataValidator(ri, parentHeader),
roundIdentifier.getSequenceNumber()));
}
public RoundChangeMessageValidator createRoundChangeMessageValidator(
@ -84,28 +89,9 @@ public class MessageValidatorFactory {
new ProposalBlockConsistencyValidator());
}
public NewRoundMessageValidator createNewRoundValidator(
public FutureRoundProposalMessageValidator createFutureRoundProposalMessageValidator(
final long chainHeight, final BlockHeader parentHeader) {
final Collection<Address> validators = getValidatorsAfterBlock(parentHeader);
final BlockValidator<IbftContext> blockValidator =
protocolSchedule.getByBlockNumber(chainHeight).getBlockValidator();
final RoundChangeCertificateValidator roundChangeCertificateValidator =
new RoundChangeCertificateValidator(
validators,
(roundIdentifier) -> createSignedDataValidator(roundIdentifier, parentHeader),
chainHeight);
return new NewRoundMessageValidator(
new NewRoundPayloadValidator(
proposerSelector,
(roundIdentifier) -> createSignedDataValidator(roundIdentifier, parentHeader),
chainHeight,
roundChangeCertificateValidator),
new ProposalBlockConsistencyValidator(),
blockValidator,
protocolContext,
roundChangeCertificateValidator);
return new FutureRoundProposalMessageValidator(this, chainHeight, parentHeader);
}
}

@ -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;
}
}

@ -77,7 +77,7 @@ public class RoundChangeCertificateValidator {
if (!roundChangeCert.getRoundChangePayloads().stream()
.allMatch(roundChangeValidator::validateRoundChange)) {
LOG.info("Invalid NewRound message, embedded RoundChange message failed validation.");
LOG.info("Invalid RoundChangeCertificate, embedded RoundChange message failed validation.");
return false;
}
@ -116,7 +116,7 @@ public class RoundChangeCertificateValidator {
.getHash()
.equals(latestPreparedCertificate.get().getProposalPayload().getPayload().getDigest())) {
LOG.info(
"Invalid NewRound message, block in latest RoundChange does not match proposed block.");
"Invalid RoundChangeCertificate, block in latest RoundChange does not match proposed block.");
return false;
}

@ -15,7 +15,6 @@ package tech.pegasys.pantheon.consensus.ibft;
import static com.google.common.collect.Lists.newArrayList;
import static org.mockito.Mockito.verify;
import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.IbftMessage;
@ -73,10 +72,4 @@ public class IbftGossipTest {
assertRebroadcastToAllExceptSignerAndSender(
TestHelpers::createSignedRoundChangePayload, RoundChangeMessageData::create);
}
@Test
public void assertRebroadcastsNewRoundToAllExceptSignerAndSender() {
assertRebroadcastToAllExceptSignerAndSender(
TestHelpers::createSignedNewRoundPayload, NewRoundMessageData::create);
}
}

@ -91,7 +91,7 @@ public class IbftHelpersTest {
final ConsensusRoundIdentifier preparedRound = TestHelpers.createFrom(roundIdentifier, 0, -1);
final Proposal differentProposal =
proposerMessageFactory.createProposal(preparedRound, proposedBlock);
proposerMessageFactory.createProposal(preparedRound, proposedBlock, Optional.empty());
final Optional<PreparedRoundArtifacts> latterPreparedRoundArtifacts =
Optional.of(
@ -107,7 +107,8 @@ public class IbftHelpersTest {
final ConsensusRoundIdentifier earlierPreparedRound =
TestHelpers.createFrom(roundIdentifier, 0, -2);
final Proposal earlierProposal =
proposerMessageFactory.createProposal(earlierPreparedRound, proposedBlock);
proposerMessageFactory.createProposal(
earlierPreparedRound, proposedBlock, Optional.empty());
final Optional<PreparedRoundArtifacts> earlierPreparedRoundArtifacts =
Optional.of(
new PreparedRoundArtifacts(

@ -12,16 +12,13 @@
*/
package tech.pegasys.pantheon.consensus.ibft;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.singletonList;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
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.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Address;
@ -74,7 +71,7 @@ public class TestHelpers {
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, round);
final Block block =
TestHelpers.createProposalBlock(singletonList(AddressHelpers.ofValue(1)), roundIdentifier);
return messageFactory.createProposal(roundIdentifier, block);
return messageFactory.createProposal(roundIdentifier, block, Optional.empty());
}
public static Prepare createSignedPreparePayload(final KeyPair signerKeys) {
@ -100,16 +97,4 @@ public class TestHelpers {
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98);
return messageFactory.createRoundChange(roundIdentifier, Optional.empty());
}
public static NewRound createSignedNewRoundPayload(final KeyPair signerKeys) {
final MessageFactory messageFactory = new MessageFactory(signerKeys);
final ConsensusRoundIdentifier roundIdentifier =
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98);
final Proposal proposal = createSignedProposalPayload(signerKeys);
return messageFactory.createNewRound(
roundIdentifier,
new RoundChangeCertificate(newArrayList()),
proposal.getSignedPayload(),
proposal.getBlock());
}
}

@ -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);
}
}

@ -22,7 +22,7 @@ public class IbftSubProtocolTest {
public void messageSpaceReportsCorrectly() {
final IbftSubProtocol subProt = new IbftSubProtocol();
assertThat(subProt.messageSpace(1)).isEqualTo(5);
assertThat(subProt.messageSpace(1)).isEqualTo(4);
}
@Test
@ -33,13 +33,12 @@ public class IbftSubProtocolTest {
assertThat(subProt.isValidMessageCode(1, 1)).isTrue();
assertThat(subProt.isValidMessageCode(1, 2)).isTrue();
assertThat(subProt.isValidMessageCode(1, 3)).isTrue();
assertThat(subProt.isValidMessageCode(1, 4)).isTrue();
}
@Test
public void invalidMessageTypesAreNotAcceptedByTheSubprotocol() {
final IbftSubProtocol subProt = new IbftSubProtocol();
assertThat(subProt.isValidMessageCode(1, 5)).isFalse();
assertThat(subProt.isValidMessageCode(1, 4)).isFalse();
}
}

@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -35,15 +36,15 @@ import tech.pegasys.pantheon.consensus.ibft.RoundTimer;
import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
import tech.pegasys.pantheon.consensus.ibft.network.IbftMessageTransmitter;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate;
import tech.pegasys.pantheon.consensus.ibft.validation.FutureRoundProposalMessageValidator;
import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidator;
import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory;
import tech.pegasys.pantheon.consensus.ibft.validation.NewRoundMessageValidator;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
@ -90,7 +91,7 @@ public class IbftBlockHeightManagerTest {
@Mock private BlockImporter<IbftContext> blockImporter;
@Mock private BlockTimer blockTimer;
@Mock private RoundTimer roundTimer;
@Mock private NewRoundMessageValidator newRoundPayloadValidator;
@Mock private FutureRoundProposalMessageValidator futureRoundProposalMessageValidator;
@Captor private ArgumentCaptor<Optional<PreparedRoundArtifacts>> preparedRoundArtifactsCaptor;
@ -134,9 +135,10 @@ public class IbftBlockHeightManagerTest {
when(finalState.getQuorum()).thenReturn(3);
when(finalState.getMessageFactory()).thenReturn(messageFactory);
when(blockCreator.createBlock(anyLong())).thenReturn(createdBlock);
when(newRoundPayloadValidator.validateNewRoundMessage(any())).thenReturn(true);
when(messageValidatorFactory.createNewRoundValidator(anyLong(), any()))
.thenReturn(newRoundPayloadValidator);
when(futureRoundProposalMessageValidator.validateProposalMessage(any())).thenReturn(true);
when(messageValidatorFactory.createFutureRoundProposalMessageValidator(anyLong(), any()))
.thenReturn(futureRoundProposalMessageValidator);
when(messageValidatorFactory.createMessageValidator(any(), any())).thenReturn(messageValidator);
protocolContext = new ProtocolContext<>(null, null, setupContextWithValidators(validators));
@ -206,7 +208,7 @@ public class IbftBlockHeightManagerTest {
manager.start();
manager.handleBlockTimerExpiry(roundIdentifier);
verify(messageTransmitter, times(1)).multicastProposal(eq(roundIdentifier), any());
verify(messageTransmitter, times(1)).multicastProposal(eq(roundIdentifier), any(), any());
verify(messageTransmitter, never()).multicastPrepare(any(), any());
verify(messageTransmitter, never()).multicastPrepare(any(), any());
}
@ -256,7 +258,7 @@ public class IbftBlockHeightManagerTest {
}
@Test
public void whenSufficientRoundChangesAreReceivedANewRoundMessageIsTransmitted() {
public void whenSufficientRoundChangesAreReceivedAProposalMessageIsTransmitted() {
final ConsensusRoundIdentifier futureRoundIdentifier = createFrom(roundIdentifier, 0, +2);
final RoundChange roundChange =
messageFactory.createRoundChange(futureRoundIdentifier, Optional.empty());
@ -276,11 +278,12 @@ public class IbftBlockHeightManagerTest {
clock,
messageValidatorFactory);
manager.start();
reset(messageTransmitter);
manager.handleRoundChangePayload(roundChange);
verify(messageTransmitter, times(1))
.multicastNewRound(eq(futureRoundIdentifier), eq(roundChangCert), any(), any());
.multicastProposal(eq(futureRoundIdentifier), any(), eq(Optional.of(roundChangCert)));
}
@Test
@ -313,14 +316,13 @@ public class IbftBlockHeightManagerTest {
manager.handleCommitPayload(commit);
// Force a new round to be started at new round number.
final NewRound newRound =
messageFactory.createNewRound(
final Proposal futureRoundProposal =
messageFactory.createProposal(
futureRoundIdentifier,
new RoundChangeCertificate(Collections.emptyList()),
messageFactory.createProposal(futureRoundIdentifier, createdBlock).getSignedPayload(),
createdBlock);
createdBlock,
Optional.of(new RoundChangeCertificate(Collections.emptyList())));
manager.handleNewRoundPayload(newRound);
manager.handleProposalPayload(futureRoundProposal);
// Final state sets the Quorum Size to 3, so should send a Prepare and also a commit
verify(messageTransmitter, times(1)).multicastPrepare(eq(futureRoundIdentifier), any());
@ -364,4 +366,32 @@ public class IbftBlockHeightManagerTest {
assertThat(preparedCert.get().getPreparedCertificate().getPreparePayloads())
.containsOnly(firstPrepare.getSignedPayload(), secondPrepare.getSignedPayload());
}
@Test
public void illegalFutureRoundProposalDoesNotTriggerNewRound() {
when(futureRoundProposalMessageValidator.validateProposalMessage(any())).thenReturn(false);
final ConsensusRoundIdentifier futureRoundIdentifier = createFrom(roundIdentifier, 0, +2);
final IbftBlockHeightManager manager =
new IbftBlockHeightManager(
headerTestFixture.buildHeader(),
finalState,
roundChangeManager,
roundFactory,
clock,
messageValidatorFactory);
// Force a new round to be started at new round number.
final Proposal futureRoundProposal =
messageFactory.createProposal(
futureRoundIdentifier,
createdBlock,
Optional.of(new RoundChangeCertificate(Collections.emptyList())));
manager.start();
reset(roundFactory); // Discard the existing createNewRound invocation.
manager.handleProposalPayload(futureRoundProposal);
verify(roundFactory, never()).createNewRound(any(), anyInt());
}
}

@ -31,12 +31,10 @@ import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange;
@ -80,10 +78,6 @@ public class IbftControllerTest {
private Message commitMessage;
@Mock private CommitMessageData commitMessageData;
@Mock private NewRound newRound;
private Message newRoundMessage;
@Mock private NewRoundMessageData newRoundMessageData;
@Mock private RoundChange roundChange;
private Message roundChangeMessage;
@Mock private RoundChangeMessageData roundChangeMessageData;
@ -136,11 +130,10 @@ public class IbftControllerTest {
setupProposal(roundIdentifierHeight3, validator);
setupCommit(futureRoundIdentifier, validator);
setupRoundChange(futureRoundIdentifier, validator);
setupNewRound(roundIdentifierHeight3, validator);
final List<Message> height2Msgs =
newArrayList(prepareMessage, commitMessage, roundChangeMessage);
final List<Message> height3Msgs = newArrayList(proposalMessage, newRoundMessage);
final List<Message> height3Msgs = newArrayList(proposalMessage);
futureMessages.put(2L, height2Msgs);
futureMessages.put(3L, height3Msgs);
when(blockHeightManager.getChainHeight()).thenReturn(2L);
@ -158,7 +151,6 @@ public class IbftControllerTest {
verify(ibftGossip).send(commitMessage);
verify(blockHeightManager).handleRoundChangePayload(roundChange);
verify(ibftGossip).send(roundChangeMessage);
verify(blockHeightManager, never()).handleNewRoundPayload(newRound);
}
@Test
@ -167,12 +159,9 @@ public class IbftControllerTest {
setupProposal(futureRoundIdentifier, validator);
setupCommit(futureRoundIdentifier, validator);
setupRoundChange(futureRoundIdentifier, validator);
setupNewRound(futureRoundIdentifier, validator);
futureMessages.put(
2L,
ImmutableList.of(
prepareMessage, proposalMessage, commitMessage, roundChangeMessage, newRoundMessage));
2L, ImmutableList.of(prepareMessage, proposalMessage, commitMessage, roundChangeMessage));
when(blockHeightManager.getChainHeight()).thenReturn(2L);
ibftController.start();
@ -190,8 +179,6 @@ public class IbftControllerTest {
verify(ibftGossip).send(commitMessage);
verify(blockHeightManager).handleRoundChangePayload(roundChange);
verify(ibftGossip).send(roundChangeMessage);
verify(blockHeightManager).handleNewRoundPayload(newRound);
verify(ibftGossip).send(newRoundMessage);
}
@Test
@ -275,21 +262,6 @@ public class IbftControllerTest {
verifyNoMoreInteractions(blockHeightManager);
}
@Test
public void newRoundForCurrentHeightIsPassedToBlockHeightManager() {
roundIdentifier = new ConsensusRoundIdentifier(0, 1);
setupNewRound(roundIdentifier, validator);
ibftController.start();
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(newRoundMessage));
assertThat(futureMessages).isEmpty();
verify(blockHeightManager).handleNewRoundPayload(newRound);
verify(ibftGossip).send(newRoundMessage);
verify(blockHeightManager, atLeastOnce()).getChainHeight();
verify(blockHeightManager).start();
verifyNoMoreInteractions(blockHeightManager);
}
@Test
public void roundChangeForCurrentHeightIsPassedToBlockHeightManager() {
roundIdentifier = new ConsensusRoundIdentifier(0, 1);
@ -326,13 +298,6 @@ public class IbftControllerTest {
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(commitMessage));
}
@Test
public void newRoundForPastHeightIsDiscarded() {
setupNewRound(roundIdentifier, validator);
when(blockHeightManager.getChainHeight()).thenReturn(1L);
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(newRoundMessage));
}
@Test
public void roundChangeForPastHeightIsDiscarded() {
setupRoundChange(roundIdentifier, validator);
@ -378,12 +343,6 @@ public class IbftControllerTest {
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(commitMessage));
}
@Test
public void newRoundForUnknownValidatorIsDiscarded() {
setupNewRound(roundIdentifier, unknownValidator);
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(newRoundMessage));
}
@Test
public void roundChangeForUnknownValidatorIsDiscarded() {
setupRoundChange(roundIdentifier, unknownValidator);
@ -414,14 +373,6 @@ public class IbftControllerTest {
verifyHasFutureMessages(new IbftReceivedMessageEvent(commitMessage), expectedFutureMsgs);
}
@Test
public void newRoundForFutureHeightIsBuffered() {
setupNewRound(futureRoundIdentifier, validator);
final Map<Long, List<Message>> expectedFutureMsgs =
ImmutableMap.of(2L, ImmutableList.of(newRoundMessage));
verifyHasFutureMessages(new IbftReceivedMessageEvent(newRoundMessage), expectedFutureMsgs);
}
@Test
public void roundChangeForFutureHeightIsBuffered() {
setupRoundChange(futureRoundIdentifier, validator);
@ -497,15 +448,6 @@ public class IbftControllerTest {
commitMessage = new DefaultMessage(null, commitMessageData);
}
private void setupNewRound(
final ConsensusRoundIdentifier roundIdentifier, final Address validator) {
when(newRound.getAuthor()).thenReturn(validator);
when(newRound.getRoundIdentifier()).thenReturn(roundIdentifier);
when(newRoundMessageData.getCode()).thenReturn(IbftV2.NEW_ROUND);
when(newRoundMessageData.decode()).thenReturn(newRound);
newRoundMessage = new DefaultMessage(null, newRoundMessageData);
}
private void setupRoundChange(
final ConsensusRoundIdentifier roundIdentifier, final Address validator) {
when(roundChange.getAuthor()).thenReturn(validator);

@ -129,7 +129,8 @@ public class IbftRoundTest {
messageFactory,
transmitter);
round.handleProposalMessage(messageFactory.createProposal(roundIdentifier, proposedBlock));
round.handleProposalMessage(
messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty()));
verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash());
verify(transmitter, never()).multicastCommit(any(), any(), any());
}
@ -149,7 +150,8 @@ public class IbftRoundTest {
transmitter);
round.createAndSendProposalMessage(15);
verify(transmitter, times(1)).multicastProposal(roundIdentifier, proposedBlock);
verify(transmitter, times(1))
.multicastProposal(roundIdentifier, proposedBlock, Optional.empty());
verify(transmitter, never()).multicastPrepare(any(), any());
verify(transmitter, never()).multicastCommit(any(), any(), any());
}
@ -168,7 +170,8 @@ public class IbftRoundTest {
messageFactory,
transmitter);
round.createAndSendProposalMessage(15);
verify(transmitter, times(1)).multicastProposal(roundIdentifier, proposedBlock);
verify(transmitter, times(1))
.multicastProposal(roundIdentifier, proposedBlock, Optional.empty());
verify(transmitter, never()).multicastPrepare(any(), any());
verify(transmitter, times(1)).multicastCommit(any(), any(), any());
verify(blockImporter, times(1)).importBlock(any(), any(), any());
@ -194,7 +197,8 @@ public class IbftRoundTest {
final Signature localCommitSeal = SECP256K1.sign(commitSealHash, localNodeKeys);
// Receive Proposal Message
round.handleProposalMessage(messageFactory.createProposal(roundIdentifier, proposedBlock));
round.handleProposalMessage(
messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty()));
verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash());
verify(transmitter, times(1))
.multicastCommit(roundIdentifier, proposedBlock.getHash(), localCommitSeal);
@ -251,7 +255,7 @@ public class IbftRoundTest {
}
@Test
public void aNewRoundMessageWithAnewBlockIsSentUponReceptionOfARoundChangeWithNoCertificate() {
public void aProposalWithAnewBlockIsSentUponReceptionOfARoundChangeWithNoCertificate() {
final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator);
final IbftRound round =
new IbftRound(
@ -268,11 +272,11 @@ public class IbftRoundTest {
round.startRoundWith(new RoundChangeArtifacts(empty(), emptyList()), 15);
verify(transmitter, times(1))
.multicastNewRound(eq(roundIdentifier), eq(roundChangeCertificate), any(), any());
.multicastProposal(eq(roundIdentifier), any(), eq(Optional.of(roundChangeCertificate)));
}
@Test
public void aNewRoundMessageWithTheSameBlockIsSentUponReceptionOfARoundChangeWithCertificate() {
public void aProposalMessageWithTheSameBlockIsSentUponReceptionOfARoundChangeWithCertificate() {
final ConsensusRoundIdentifier priorRoundChange = new ConsensusRoundIdentifier(1, 0);
final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator);
final IbftRound round =
@ -293,18 +297,18 @@ public class IbftRoundTest {
roundIdentifier,
Optional.of(
new PreparedRoundArtifacts(
messageFactory.createProposal(priorRoundChange, proposedBlock),
messageFactory.createProposal(
priorRoundChange, proposedBlock, Optional.empty()),
emptyList())))));
// NOTE: IbftRound assumes the prepare's are valid
round.startRoundWith(roundChangeArtifacts, 15);
verify(transmitter, times(1))
.multicastNewRound(
.multicastProposal(
eq(roundIdentifier),
eq(roundChangeArtifacts.getRoundChangeCertificate()),
any(),
blockCaptor.capture());
blockCaptor.capture(),
eq(Optional.of(roundChangeArtifacts.getRoundChangeCertificate())));
final IbftExtraData proposedExtraData =
IbftExtraData.decode(blockCaptor.getValue().getHeader().getExtraData());
@ -337,11 +341,10 @@ public class IbftRoundTest {
round.startRoundWith(roundChangeArtifacts, 15);
verify(transmitter, times(1))
.multicastNewRound(
.multicastProposal(
eq(roundIdentifier),
eq(roundChangeArtifacts.getRoundChangeCertificate()),
payloadArgCaptor.capture(),
blockCaptor.capture());
blockCaptor.capture(),
eq(Optional.of(roundChangeArtifacts.getRoundChangeCertificate())));
// Inject a single Prepare message, and confirm the roundState has gone to Prepared (which
// indicates the block has entered the roundState (note: all msgs are deemed valid due to mocks)
@ -386,7 +389,8 @@ public class IbftRoundTest {
round.handleCommitMessage(
messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal));
round.handleProposalMessage(messageFactory.createProposal(roundIdentifier, proposedBlock));
round.handleProposalMessage(
messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty()));
verify(blockImporter, times(1)).importBlock(any(), any(), any());
}
@ -409,7 +413,8 @@ public class IbftRoundTest {
round.handleCommitMessage(
messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal));
round.handleProposalMessage(messageFactory.createProposal(roundIdentifier, proposedBlock));
round.handleProposalMessage(
messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty()));
verify(blockImporter, times(1)).importBlock(any(), any(), any());
}

@ -54,7 +54,7 @@ public class RoundChangeArtifactsTest {
final Block block = TestHelpers.createProposalBlock(emptyList(), preparedRound);
return new PreparedRoundArtifacts(
messageFactories.get(0).createProposal(preparedRound, block),
messageFactories.get(0).createProposal(preparedRound, block, Optional.empty()),
messageFactories.stream()
.map(factory -> factory.createPrepare(preparedRound, block.getHash()))
.collect(Collectors.toList()));

@ -134,7 +134,7 @@ public class RoundChangeManagerTest {
final ConsensusRoundIdentifier proposalRound = TestHelpers.createFrom(round, 0, -1);
final Block block = TestHelpers.createProposalBlock(validators, proposalRound);
// Proposal must come from an earlier round.
final Proposal proposal = messageFactory.createProposal(proposalRound, block);
final Proposal proposal = messageFactory.createProposal(proposalRound, block, Optional.empty());
final List<Prepare> preparePayloads =
prepareProviders.stream()

@ -33,6 +33,7 @@ import tech.pegasys.pantheon.ethereum.core.Util;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import com.google.common.collect.Lists;
import org.junit.Before;
@ -80,7 +81,7 @@ public class RoundStateTest {
final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator);
final Proposal proposal =
validatorMessageFactories.get(0).createProposal(roundIdentifier, block);
validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty());
assertThat(roundState.setProposedBlock(proposal)).isFalse();
assertThat(roundState.isPrepared()).isFalse();
@ -94,8 +95,7 @@ public class RoundStateTest {
final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator);
final Proposal proposal =
validatorMessageFactories.get(0).createProposal(roundIdentifier, block);
validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty());
assertThat(roundState.setProposedBlock(proposal)).isTrue();
assertThat(roundState.isPrepared()).isTrue();
assertThat(roundState.isCommitted()).isFalse();
@ -117,7 +117,7 @@ public class RoundStateTest {
final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator);
final Proposal proposal =
validatorMessageFactories.get(0).createProposal(roundIdentifier, block);
validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty());
assertThat(roundState.setProposedBlock(proposal)).isTrue();
assertThat(roundState.isPrepared()).isTrue();
@ -161,7 +161,7 @@ public class RoundStateTest {
assertThat(roundState.constructPreparedRoundArtifacts()).isEmpty();
final Proposal proposal =
validatorMessageFactories.get(0).createProposal(roundIdentifier, block);
validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty());
assertThat(roundState.setProposedBlock(proposal)).isTrue();
assertThat(roundState.isPrepared()).isTrue();
assertThat(roundState.isCommitted()).isFalse();
@ -189,7 +189,7 @@ public class RoundStateTest {
verify(messageValidator, never()).validatePrepare(any());
final Proposal proposal =
validatorMessageFactories.get(0).createProposal(roundIdentifier, block);
validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty());
assertThat(roundState.setProposedBlock(proposal)).isTrue();
assertThat(roundState.isPrepared()).isFalse();
@ -208,7 +208,7 @@ public class RoundStateTest {
validatorMessageFactories.get(2).createPrepare(roundIdentifier, block.getHash());
final Proposal proposal =
validatorMessageFactories.get(0).createProposal(roundIdentifier, block);
validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty());
when(messageValidator.validateProposal(any())).thenReturn(true);
when(messageValidator.validatePrepare(firstPrepare)).thenReturn(false);
@ -248,7 +248,7 @@ public class RoundStateTest {
Signature.create(BigInteger.TEN, BigInteger.TEN, (byte) 1));
final Proposal proposal =
validatorMessageFactories.get(0).createProposal(roundIdentifier, block);
validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty());
roundState.setProposedBlock(proposal);
roundState.addCommitMessage(firstCommit);

@ -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();
}
}

@ -12,9 +12,11 @@
*/
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.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -26,6 +28,7 @@ import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit;
import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare;
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;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.BlockValidator;
@ -52,7 +55,7 @@ public class MessageValidatorTest {
private KeyPair keyPair = KeyPair.generate();
private MessageFactory messageFactory = new MessageFactory(keyPair);
private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1);
private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 0);
private SignedDataValidator signedDataValidator = mock(SignedDataValidator.class);
private ProposalBlockConsistencyValidator proposalBlockConsistencyValidator =
@ -60,6 +63,8 @@ public class MessageValidatorTest {
@Mock private BlockValidator<IbftContext> blockValidator;
private ProtocolContext<IbftContext> protocolContext;
private final RoundChangeCertificateValidator roundChangeCertificateValidator =
mock(RoundChangeCertificateValidator.class);
private MessageValidator messageValidator;
@ -88,17 +93,26 @@ public class MessageValidatorTest {
when(blockValidator.validateAndProcessBlock(any(), any(), any(), any()))
.thenReturn(Optional.of(new BlockProcessingOutputs(null, null)));
when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate(
any(), any()))
.thenReturn(true);
when(roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
any(), any()))
.thenReturn(true);
messageValidator =
new MessageValidator(
signedDataValidator,
proposalBlockConsistencyValidator,
blockValidator,
protocolContext);
protocolContext,
roundChangeCertificateValidator);
}
@Test
public void messageValidatorDefersToUnderlyingSignedDataValidator() {
final Proposal proposal = messageFactory.createProposal(roundIdentifier, block);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, Optional.empty());
final Prepare prepare = messageFactory.createPrepare(roundIdentifier, block.getHash());
@ -118,7 +132,8 @@ public class MessageValidatorTest {
@Test
public void ifProposalConsistencyChecksFailProposalIsIllegal() {
final Proposal proposal = messageFactory.createProposal(roundIdentifier, block);
final Proposal proposal =
messageFactory.createProposal(roundIdentifier, block, Optional.empty());
when(proposalBlockConsistencyValidator.validateProposalMatchesBlock(any(), any()))
.thenReturn(false);
@ -132,8 +147,62 @@ public class MessageValidatorTest {
when(blockValidator.validateAndProcessBlock(any(), any(), any(), any()))
.thenReturn(Optional.empty());
final Proposal proposalMsg = messageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
messageFactory.createProposal(roundIdentifier, block, Optional.empty());
assertThat(messageValidator.validateProposal(proposalMsg)).isFalse();
}
@Test
public void proposalFailsValidationIfRoundChangeCertificateDoeNotMatchBlock() {
final ConsensusRoundIdentifier nonZeroRound = new ConsensusRoundIdentifier(1, 1);
when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate(
any(), any()))
.thenReturn(false);
final Proposal proposal =
messageFactory.createProposal(
nonZeroRound, block, Optional.of(new RoundChangeCertificate(emptyList())));
assertThat(messageValidator.validateProposal(proposal)).isFalse();
}
@Test
public void proposalFailsValidationIfRoundChangeIsNotSelfConsistent() {
final ConsensusRoundIdentifier nonZeroRound = new ConsensusRoundIdentifier(1, 1);
when(roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(
any(), any()))
.thenReturn(false);
final Proposal proposal =
messageFactory.createProposal(
nonZeroRound, block, Optional.of(new RoundChangeCertificate(emptyList())));
assertThat(messageValidator.validateProposal(proposal)).isFalse();
}
@Test
public void proposalForRoundZeroFailsIfItContainsARoundChangeCertificate() {
final Proposal proposal =
messageFactory.createProposal(
roundIdentifier, block, Optional.of(new RoundChangeCertificate(emptyList())));
assertThat(messageValidator.validateProposal(proposal)).isFalse();
verify(roundChangeCertificateValidator, never())
.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(any(), any());
verify(roundChangeCertificateValidator, never())
.validateProposalMessageMatchesLatestPrepareCertificate(any(), any());
}
@Test
public void proposalForRoundsGreaterThanZeroFailIfNoRoundChangeCertificateAvailable() {
final ConsensusRoundIdentifier nonZeroRound = new ConsensusRoundIdentifier(1, 1);
final Proposal proposal = messageFactory.createProposal(nonZeroRound, block, Optional.empty());
assertThat(messageValidator.validateProposal(proposal)).isFalse();
verify(roundChangeCertificateValidator, never())
.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(any(), any());
verify(roundChangeCertificateValidator, never())
.validateProposalMessageMatchesLatestPrepareCertificate(any(), any());
}
}

@ -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();
}
}

@ -24,6 +24,7 @@ import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.core.Block;
import java.util.Collections;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
@ -51,7 +52,8 @@ public class ProposalBlockConsistencyValidatorTest {
@Test
public void blockDigestMisMatchWithMessageRoundFails() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
final Block misMatchedBlock =
IbftBlockInterface.replaceRoundInBlock(
@ -68,7 +70,8 @@ public class ProposalBlockConsistencyValidatorTest {
@Test
public void blockDigestMatchesButRoundDiffersFails() {
final ConsensusRoundIdentifier futureRound = TestHelpers.createFrom(roundIdentifier, 0, +1);
final Proposal proposalMsg = proposerMessageFactory.createProposal(futureRound, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(futureRound, block, Optional.empty());
assertThat(
consistencyChecker.validateProposalMatchesBlock(proposalMsg.getSignedPayload(), block))
@ -78,7 +81,8 @@ public class ProposalBlockConsistencyValidatorTest {
@Test
public void blockWithMismatchedNumberFails() {
final ConsensusRoundIdentifier futureHeight = TestHelpers.createFrom(roundIdentifier, +1, 0);
final Proposal proposalMsg = proposerMessageFactory.createProposal(futureHeight, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(futureHeight, block, Optional.empty());
assertThat(
consistencyChecker.validateProposalMatchesBlock(proposalMsg.getSignedPayload(), block))

@ -69,7 +69,7 @@ public class RoundChangeCertificateValidatorTest {
}
@Test
public void newRoundWithEmptyRoundChangeCertificateFails() {
public void proposalWithEmptyRoundChangeCertificateFails() {
final RoundChangeCertificate cert = new RoundChangeCertificate(Collections.emptyList());
assertThat(
@ -104,7 +104,8 @@ public class RoundChangeCertificateValidatorTest {
roundIdentifier,
Optional.of(
new PreparedRoundArtifacts(
proposerMessageFactory.createProposal(prevRound, proposedBlock),
proposerMessageFactory.createProposal(
prevRound, proposedBlock, Optional.empty()),
Lists.newArrayList(
validatorMessageFactory.createPrepare(
prevRound, proposedBlock.getHash()))))));
@ -129,7 +130,8 @@ public class RoundChangeCertificateValidatorTest {
final PreparedRoundArtifacts mismatchedRoundArtefacts =
new PreparedRoundArtifacts(
proposerMessageFactory.createProposal(preparedRound, prevProposedBlock),
proposerMessageFactory.createProposal(
preparedRound, prevProposedBlock, Optional.empty()),
singletonList(
validatorMessageFactory.createPrepare(preparedRound, prevProposedBlock.getHash())));
@ -152,7 +154,7 @@ public class RoundChangeCertificateValidatorTest {
TestHelpers.createFrom(roundIdentifier, 0, -1);
final Block latterBlock = TestHelpers.createProposalBlock(validators, latterPrepareRound);
final Proposal latterProposal =
proposerMessageFactory.createProposal(latterPrepareRound, latterBlock);
proposerMessageFactory.createProposal(latterPrepareRound, latterBlock, Optional.empty());
final Optional<PreparedRoundArtifacts> latterTerminatedRoundArtefacts =
Optional.of(
new PreparedRoundArtifacts(
@ -169,7 +171,7 @@ public class RoundChangeCertificateValidatorTest {
final Block earlierBlock =
TestHelpers.createProposalBlock(validators.subList(0, 1), earlierPreparedRound);
final Proposal earlierProposal =
proposerMessageFactory.createProposal(earlierPreparedRound, earlierBlock);
proposerMessageFactory.createProposal(earlierPreparedRound, earlierBlock, Optional.empty());
final Optional<PreparedRoundArtifacts> earlierTerminatedRoundArtefacts =
Optional.of(
new PreparedRoundArtifacts(

@ -97,7 +97,8 @@ public class RoundChangeSignedDataValidatorTest {
public void roundChangeContainingInvalidProposalFails() {
final PreparedRoundArtifacts preparedRoundArtifacts =
new PreparedRoundArtifacts(
proposerMessageFactory.createProposal(currentRound, block), Collections.emptyList());
proposerMessageFactory.createProposal(currentRound, block, Optional.empty()),
Collections.emptyList());
final PreparedCertificate prepareCertificate = preparedRoundArtifacts.getPreparedCertificate();
@ -118,7 +119,8 @@ public class RoundChangeSignedDataValidatorTest {
public void roundChangeContainingValidProposalButNoPrepareMessagesFails() {
final PreparedRoundArtifacts preparedRoundArtifacts =
new PreparedRoundArtifacts(
proposerMessageFactory.createProposal(currentRound, block), Collections.emptyList());
proposerMessageFactory.createProposal(currentRound, block, Optional.empty()),
Collections.emptyList());
final RoundChange msg =
proposerMessageFactory.createRoundChange(targetRound, Optional.of(preparedRoundArtifacts));
@ -132,7 +134,7 @@ public class RoundChangeSignedDataValidatorTest {
final Prepare prepareMsg = validatorMessageFactory.createPrepare(currentRound, block.getHash());
final PreparedRoundArtifacts preparedRoundArtifacts =
new PreparedRoundArtifacts(
proposerMessageFactory.createProposal(currentRound, block),
proposerMessageFactory.createProposal(currentRound, block, Optional.empty()),
Lists.newArrayList(prepareMsg));
when(basicValidator.validateProposal(any())).thenReturn(true);
@ -168,7 +170,7 @@ public class RoundChangeSignedDataValidatorTest {
final Prepare prepareMsg = validatorMessageFactory.createPrepare(futureRound, block.getHash());
final PreparedRoundArtifacts preparedRoundArtifacts =
new PreparedRoundArtifacts(
proposerMessageFactory.createProposal(futureRound, block),
proposerMessageFactory.createProposal(futureRound, block, Optional.empty()),
Lists.newArrayList(prepareMsg));
final RoundChange msg =
@ -185,7 +187,7 @@ public class RoundChangeSignedDataValidatorTest {
final Prepare prepareMsg = validatorMessageFactory.createPrepare(currentRound, block.getHash());
final PreparedRoundArtifacts preparedRoundArtifacts =
new PreparedRoundArtifacts(
proposerMessageFactory.createProposal(currentRound, block),
proposerMessageFactory.createProposal(currentRound, block, Optional.empty()),
Lists.newArrayList(prepareMsg));
final PreparedCertificate prepareCertificate = preparedRoundArtifacts.getPreparedCertificate();

@ -31,6 +31,7 @@ import tech.pegasys.pantheon.ethereum.core.Hash;
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;
@ -86,14 +87,16 @@ public class SignedDataValidatorTest {
@Test
public void receivingProposalMessageFromNonProposerFails() {
final Block block = TestHelpers.createProposalBlock(emptyList(), roundIdentifier);
final Proposal proposalMsg = validatorMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
validatorMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
assertThat(validator.validateProposal(proposalMsg.getSignedPayload())).isFalse();
}
@Test
public void receivingPrepareFromProposerFails() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
final Prepare prepareMsg =
proposerMessageFactory.createPrepare(roundIdentifier, block.getHash());
@ -104,7 +107,8 @@ public class SignedDataValidatorTest {
@Test
public void receivingPrepareFromNonValidatorFails() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
final Prepare prepareMsg =
nonValidatorMessageFactory.createPrepare(roundIdentifier, block.getHash());
@ -115,7 +119,8 @@ public class SignedDataValidatorTest {
@Test
public void receivingMessagesWithDifferentRoundIdFromProposalFails() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
final ConsensusRoundIdentifier invalidRoundIdentifier =
new ConsensusRoundIdentifier(
@ -133,7 +138,8 @@ public class SignedDataValidatorTest {
@Test
public void receivingPrepareNonProposerValidatorWithCorrectRoundIsSuccessful() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
final Prepare prepareMsg =
validatorMessageFactory.createPrepare(roundIdentifier, block.getHash());
@ -143,7 +149,8 @@ public class SignedDataValidatorTest {
@Test
public void receivingACommitMessageWithAnInvalidCommitSealFails() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
final Commit commitMsg =
proposerMessageFactory.createCommit(
@ -155,7 +162,8 @@ public class SignedDataValidatorTest {
@Test
public void commitMessageContainingValidSealFromValidatorIsSuccessful() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
final Commit proposerCommitMsg =
proposerMessageFactory.createCommit(
@ -172,32 +180,35 @@ public class SignedDataValidatorTest {
@Test
public void subsequentProposalHasDifferentSenderFails() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
assertThat(validator.validateProposal(proposalMsg.getSignedPayload())).isTrue();
final Proposal secondProposalMsg =
validatorMessageFactory.createProposal(roundIdentifier, block);
validatorMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
assertThat(validator.validateProposal(secondProposalMsg.getSignedPayload())).isFalse();
}
@Test
public void subsequentProposalHasDifferentContentFails() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
assertThat(validator.validateProposal(proposalMsg.getSignedPayload())).isTrue();
final ConsensusRoundIdentifier newRoundIdentifier = new ConsensusRoundIdentifier(3, 0);
final Proposal secondProposalMsg =
proposerMessageFactory.createProposal(newRoundIdentifier, block);
proposerMessageFactory.createProposal(newRoundIdentifier, block, Optional.empty());
assertThat(validator.validateProposal(secondProposalMsg.getSignedPayload())).isFalse();
}
@Test
public void subsequentProposalHasIdenticalSenderAndContentIsSuccessful() {
final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block);
final Proposal proposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
assertThat(validator.validateProposal(proposalMsg.getSignedPayload())).isTrue();
final Proposal secondProposalMsg =
proposerMessageFactory.createProposal(roundIdentifier, block);
proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty());
assertThat(validator.validateProposal(secondProposalMsg.getSignedPayload())).isTrue();
}
}

Loading…
Cancel
Save