From cde9425cfbb431fe527385b51981c0bf7d9d7651 Mon Sep 17 00:00:00 2001 From: tmohay <37158202+rain-on@users.noreply.github.com> Date: Tue, 22 Jan 2019 19:50:45 +1100 Subject: [PATCH] IBFT ensure non-validator does not partake in consensus (#627) Nodes which are not validators in a network should not inject IBFT messages to the consensus round, and should not gossip received messages. I.e. all events should ensure that they are only handled if the node is a validator at the current height. Signed-off-by: Adrian Sutton --- .../ibft/statemachine/BlockHeightManager.java | 46 +++++++++++++ .../statemachine/IbftBlockHeightManager.java | 24 +++++-- .../IbftBlockHeightManagerFactory.java | 14 +++- .../ibft/statemachine/IbftController.java | 4 +- .../ibft/statemachine/IbftFinalState.java | 4 ++ .../statemachine/NoOpBlockHeightManager.java | 66 +++++++++++++++++++ .../ibft/statemachine/IbftControllerTest.java | 4 +- 7 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java new file mode 100644 index 0000000000..5177693ece --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java @@ -0,0 +1,46 @@ +/* + * 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.statemachine; + +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; +import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; +import tech.pegasys.pantheon.consensus.ibft.payload.CommitPayload; +import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload; +import tech.pegasys.pantheon.consensus.ibft.payload.PreparePayload; +import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload; +import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangePayload; +import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; + +public interface BlockHeightManager { + + void start(); + + void handleBlockTimerExpiry(ConsensusRoundIdentifier roundIdentifier); + + void roundExpired(RoundExpiry expire); + + void handleProposalPayload(SignedData signedPayload); + + void handlePreparePayload(SignedData signedPayload); + + void handleCommitPayload(SignedData payload); + + void handleRoundChangePayload(SignedData signedPayload); + + void handleNewRoundPayload(SignedData signedPayload); + + long getChainHeight(); + + BlockHeader getParentBlockHeader(); +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java index 0ae08bda9b..42a39e3cc1 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java @@ -53,13 +53,7 @@ import org.apache.logging.log4j.Logger; * and sends a Proposal message. If the round times out prior to importing a block, this class is * responsible for creating a RoundChange message and transmitting it. */ -public class IbftBlockHeightManager { - - protected enum MessageAge { - PRIOR_ROUND, - CURRENT_ROUND, - FUTURE_ROUND - } +public class IbftBlockHeightManager implements BlockHeightManager { private static final Logger LOG = LogManager.getLogger(); @@ -107,6 +101,7 @@ public class IbftBlockHeightManager { messageValidatorFactory.createMessageValidator(roundIdentifier, parentHeader)); } + @Override public void start() { startNewRound(0); if (finalState.isLocalNodeProposerForRound(currentRound.getRoundIdentifier())) { @@ -114,6 +109,7 @@ public class IbftBlockHeightManager { } } + @Override public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIdentifier) { if (roundIdentifier.equals(currentRound.getRoundIdentifier())) { currentRound.createAndSendProposalMessage(clock.millis() / 1000); @@ -125,6 +121,7 @@ public class IbftBlockHeightManager { } } + @Override public void roundExpired(final RoundExpiry expire) { if (!expire.getView().equals(currentRound.getRoundIdentifier())) { LOG.info("Ignoring Round timer expired which does not match current round."); @@ -151,18 +148,21 @@ public class IbftBlockHeightManager { handleRoundChangePayload(localRoundChange); } + @Override public void handleProposalPayload(final SignedData signedPayload) { LOG.info("Received a Proposal Payload."); actionOrBufferMessage( signedPayload, currentRound::handleProposalMessage, RoundState::setProposedBlock); } + @Override public void handlePreparePayload(final SignedData signedPayload) { LOG.info("Received a prepare Payload."); actionOrBufferMessage( signedPayload, currentRound::handlePrepareMessage, RoundState::addPrepareMessage); } + @Override public void handleCommitPayload(final SignedData payload) { LOG.info("Received a commit Payload."); actionOrBufferMessage(payload, currentRound::handleCommitMessage, RoundState::addCommitMessage); @@ -185,6 +185,7 @@ public class IbftBlockHeightManager { } } + @Override public void handleRoundChangePayload(final SignedData signedPayload) { final ConsensusRoundIdentifier targetRound = signedPayload.getPayload().getRoundIdentifier(); LOG.info("Received a RoundChange Payload for {}", targetRound.toString()); @@ -223,6 +224,7 @@ public class IbftBlockHeightManager { roundTimer.startTimer(currentRound.getRoundIdentifier()); } + @Override public void handleNewRoundPayload(final SignedData signedPayload) { final NewRoundPayload payload = signedPayload.getPayload(); final MessageAge messageAge = determineAgeOfPayload(payload); @@ -241,10 +243,12 @@ public class IbftBlockHeightManager { } } + @Override public long getChainHeight() { return currentRound.getRoundIdentifier().getSequenceNumber(); } + @Override public BlockHeader getParentBlockHeader() { return parentHeader; } @@ -259,4 +263,10 @@ public class IbftBlockHeightManager { } return PRIOR_ROUND; } + + public enum MessageAge { + PRIOR_ROUND, + CURRENT_ROUND, + FUTURE_ROUND + } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerFactory.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerFactory.java index 7c5d5b7bf1..1f600896c4 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerFactory.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerFactory.java @@ -31,7 +31,19 @@ public class IbftBlockHeightManagerFactory { this.messageValidatorFactory = messageValidatorFactory; } - public IbftBlockHeightManager create(final BlockHeader parentHeader) { + public BlockHeightManager create(final BlockHeader parentHeader) { + if (finalState.isLocalNodeValidator()) { + return createFullBlockHeightManager(parentHeader); + } else { + return createNoOpBlockHeightManager(parentHeader); + } + } + + private BlockHeightManager createNoOpBlockHeightManager(final BlockHeader parentHeader) { + return new NoOpBlockHeightManager(parentHeader); + } + + private BlockHeightManager createFullBlockHeightManager(final BlockHeader parentHeader) { return new IbftBlockHeightManager( parentHeader, finalState, diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java index 159fdd2374..749feb6171 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java @@ -50,7 +50,7 @@ public class IbftController { private final IbftFinalState ibftFinalState; private final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory; private final Map> futureMessages; - private IbftBlockHeightManager currentHeightManager; + private BlockHeightManager currentHeightManager; private final IbftGossip gossiper; public IbftController( @@ -188,7 +188,7 @@ public class IbftController { private boolean processMessage(final SignedData msg, final Message rawMsg) { final ConsensusRoundIdentifier msgRoundIdentifier = msg.getPayload().getRoundIdentifier(); if (isMsgForCurrentHeight(msgRoundIdentifier)) { - return isMsgFromKnownValidator(msg); + return isMsgFromKnownValidator(msg) && ibftFinalState.isLocalNodeValidator(); } else if (isMsgForFutureChainHeight(msgRoundIdentifier)) { addMessageToFutureMessageBuffer(msgRoundIdentifier.getSequenceNumber(), rawMsg); } else { diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftFinalState.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftFinalState.java index fa3d249daf..433225157a 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftFinalState.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftFinalState.java @@ -87,6 +87,10 @@ public class IbftFinalState { return getProposerForRound(roundIdentifier).equals(localAddress); } + public boolean isLocalNodeValidator() { + return getValidators().contains(localAddress); + } + public ValidatorMulticaster getValidatorMulticaster() { return validatorMulticaster; } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java new file mode 100644 index 0000000000..502c5d5af0 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java @@ -0,0 +1,66 @@ +/* + * 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.statemachine; + +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; +import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; +import tech.pegasys.pantheon.consensus.ibft.payload.CommitPayload; +import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload; +import tech.pegasys.pantheon.consensus.ibft.payload.PreparePayload; +import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload; +import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangePayload; +import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; + +public class NoOpBlockHeightManager implements BlockHeightManager { + + private final BlockHeader parentHeader; + + public NoOpBlockHeightManager(final BlockHeader parentHeader) { + this.parentHeader = parentHeader; + } + + @Override + public void start() {} + + @Override + public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIdentifier) {} + + @Override + public void roundExpired(final RoundExpiry expire) {} + + @Override + public void handleProposalPayload(final SignedData signedPayload) {} + + @Override + public void handlePreparePayload(final SignedData signedPayload) {} + + @Override + public void handleCommitPayload(final SignedData payload) {} + + @Override + public void handleRoundChangePayload(final SignedData signedPayload) {} + + @Override + public void handleNewRoundPayload(final SignedData signedPayload) {} + + @Override + public long getChainHeight() { + return parentHeader.getNumber() + 1; + } + + @Override + public BlockHeader getParentBlockHeader() { + return parentHeader; + } +} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java index ada2f0fbe4..9beaa38d9b 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java @@ -66,7 +66,7 @@ public class IbftControllerTest { @Mock private IbftBlockHeightManagerFactory blockHeightManagerFactory; @Mock private BlockHeader chainHeadBlockHeader; @Mock private BlockHeader nextBlock; - @Mock private IbftBlockHeightManager blockHeightManager; + @Mock private BlockHeightManager blockHeightManager; @Mock private SignedData signedProposal; private Message proposalMessage; @@ -116,6 +116,8 @@ public class IbftControllerTest { when(blockHeightManager.getParentBlockHeader()).thenReturn(chainHeadBlockHeader); when(nextBlock.getNumber()).thenReturn(2L); + + when(ibftFinalState.isLocalNodeValidator()).thenReturn(true); } @Test