mirror of https://github.com/hyperledger/besu
ibft controller and future msgs handling (#431)
parent
5425a92c0d
commit
e5b859eaf5
@ -0,0 +1,34 @@ |
|||||||
|
/* |
||||||
|
* 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.ibftevent; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents.Type; |
||||||
|
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; |
||||||
|
|
||||||
|
public class IbftReceivedMessageEvent implements IbftEvent { |
||||||
|
|
||||||
|
private final MessageData messageData; |
||||||
|
|
||||||
|
public IbftReceivedMessageEvent(final MessageData messageData) { |
||||||
|
this.messageData = messageData; |
||||||
|
} |
||||||
|
|
||||||
|
public MessageData getMessageData() { |
||||||
|
return messageData; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Type getType() { |
||||||
|
return Type.MESSAGE; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
/* |
||||||
|
* 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.statemachine; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangePayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; |
||||||
|
|
||||||
|
/** This no-op version will be replaced with an implementation in another PR */ |
||||||
|
public class IbftBlockHeightManager { |
||||||
|
|
||||||
|
public void handleProposalMessage(final SignedData<ProposalPayload> proposalMsg) {} |
||||||
|
|
||||||
|
public void handlePrepareMessage(final SignedData<PreparePayload> prepareMsg) {} |
||||||
|
|
||||||
|
public void handleCommitMessage(final SignedData<CommitPayload> commitMsg) {} |
||||||
|
|
||||||
|
public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIndentifier) {} |
||||||
|
|
||||||
|
public void handleRoundChangeMessage(final SignedData<RoundChangePayload> roundChangeMsg) {} |
||||||
|
|
||||||
|
public void handleNewRoundMessage(final SignedData<NewRoundPayload> newRoundMsg) {} |
||||||
|
|
||||||
|
public void start() {} |
||||||
|
|
||||||
|
public long getChainHeight() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
public void roundExpired(final RoundExpiry expired) {} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
/* |
||||||
|
* 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.statemachine; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||||
|
|
||||||
|
/** This no-op version will be replaced with an implementation in another PR */ |
||||||
|
public class IbftBlockHeightManagerFactory { |
||||||
|
|
||||||
|
public IbftBlockHeightManager create(final BlockHeader parentHeader) { |
||||||
|
return new IbftBlockHeightManager(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,190 @@ |
|||||||
|
/* |
||||||
|
* 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.statemachine; |
||||||
|
|
||||||
|
import static java.util.Collections.emptyList; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.BlockTimerExpiry; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.Payload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangePayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; |
||||||
|
import tech.pegasys.pantheon.ethereum.chain.Blockchain; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||||
|
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting; |
||||||
|
import com.google.common.collect.Lists; |
||||||
|
import com.google.common.collect.Maps; |
||||||
|
import org.apache.logging.log4j.LogManager; |
||||||
|
import org.apache.logging.log4j.Logger; |
||||||
|
|
||||||
|
public class IbftController { |
||||||
|
private static final Logger LOG = LogManager.getLogger(); |
||||||
|
private final Blockchain blockchain; |
||||||
|
private final IbftFinalState ibftFinalState; |
||||||
|
private final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory; |
||||||
|
private final Map<Long, List<MessageData>> futureMessages; |
||||||
|
private IbftBlockHeightManager currentHeightManager; |
||||||
|
|
||||||
|
public IbftController( |
||||||
|
final Blockchain blockchain, |
||||||
|
final IbftFinalState ibftFinalState, |
||||||
|
final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory) { |
||||||
|
this(blockchain, ibftFinalState, ibftBlockHeightManagerFactory, Maps.newHashMap()); |
||||||
|
} |
||||||
|
|
||||||
|
@VisibleForTesting |
||||||
|
IbftController( |
||||||
|
final Blockchain blockchain, |
||||||
|
final IbftFinalState ibftFinalState, |
||||||
|
final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory, |
||||||
|
final Map<Long, List<MessageData>> futureMessages) { |
||||||
|
this.blockchain = blockchain; |
||||||
|
this.ibftFinalState = ibftFinalState; |
||||||
|
this.ibftBlockHeightManagerFactory = ibftBlockHeightManagerFactory; |
||||||
|
this.futureMessages = futureMessages; |
||||||
|
} |
||||||
|
|
||||||
|
public void start() { |
||||||
|
startNewHeightManager(blockchain.getChainHeadHeader()); |
||||||
|
} |
||||||
|
|
||||||
|
public void handleMessageEvent(final IbftReceivedMessageEvent msg) { |
||||||
|
handleMessage(msg.getMessageData()); |
||||||
|
} |
||||||
|
|
||||||
|
private void handleMessage(final MessageData messageData) { |
||||||
|
switch (messageData.getCode()) { |
||||||
|
case IbftV2.PROPOSAL: |
||||||
|
final SignedData<ProposalPayload> proposalMsg = |
||||||
|
ProposalMessage.fromMessage(messageData).decode(); |
||||||
|
if (processMessage(proposalMsg, messageData)) { |
||||||
|
currentHeightManager.handleProposalMessage(proposalMsg); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case IbftV2.PREPARE: |
||||||
|
final SignedData<PreparePayload> prepareMsg = |
||||||
|
PrepareMessage.fromMessage(messageData).decode(); |
||||||
|
if (processMessage(prepareMsg, messageData)) { |
||||||
|
currentHeightManager.handlePrepareMessage(prepareMsg); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case IbftV2.COMMIT: |
||||||
|
final SignedData<CommitPayload> commitMsg = CommitMessage.fromMessage(messageData).decode(); |
||||||
|
if (processMessage(commitMsg, messageData)) { |
||||||
|
currentHeightManager.handleCommitMessage(commitMsg); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case IbftV2.ROUND_CHANGE: |
||||||
|
final SignedData<RoundChangePayload> roundChangeMsg = |
||||||
|
RoundChangeMessage.fromMessage(messageData).decode(); |
||||||
|
if (processMessage(roundChangeMsg, messageData)) { |
||||||
|
currentHeightManager.handleRoundChangeMessage(roundChangeMsg); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case IbftV2.NEW_ROUND: |
||||||
|
final SignedData<NewRoundPayload> newRoundMsg = |
||||||
|
NewRoundMessage.fromMessage(messageData).decode(); |
||||||
|
if (processMessage(newRoundMsg, messageData)) { |
||||||
|
currentHeightManager.handleNewRoundMessage(newRoundMsg); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Received message does not conform to any recognised IBFT message structure."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void handleNewBlockEvent(final NewChainHead newChainHead) { |
||||||
|
startNewHeightManager(newChainHead.getNewChainHeadHeader()); |
||||||
|
} |
||||||
|
|
||||||
|
public void handleBlockTimerExpiry(final BlockTimerExpiry blockTimerExpiry) { |
||||||
|
if (isMsgForCurrentHeight(blockTimerExpiry.getRoundIndentifier())) { |
||||||
|
currentHeightManager.handleBlockTimerExpiry(blockTimerExpiry.getRoundIndentifier()); |
||||||
|
} else { |
||||||
|
LOG.info("Block timer event discarded as it is not for current block height"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void handleRoundExpiry(final RoundExpiry roundExpiry) { |
||||||
|
if (isMsgForCurrentHeight(roundExpiry.getView())) { |
||||||
|
currentHeightManager.roundExpired(roundExpiry); |
||||||
|
} else { |
||||||
|
LOG.info("Round expiry event discarded as it is not for current block height"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void startNewHeightManager(final BlockHeader parentHeader) { |
||||||
|
currentHeightManager = ibftBlockHeightManagerFactory.create(parentHeader); |
||||||
|
currentHeightManager.start(); |
||||||
|
final long newChainHeight = currentHeightManager.getChainHeight(); |
||||||
|
List<MessageData> orDefault = futureMessages.getOrDefault(newChainHeight, emptyList()); |
||||||
|
orDefault.forEach(this::handleMessage); |
||||||
|
futureMessages.remove(newChainHeight); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean processMessage( |
||||||
|
final SignedData<? extends Payload> msg, final MessageData rawMsg) { |
||||||
|
final ConsensusRoundIdentifier msgRoundIdentifier = msg.getPayload().getRoundIdentifier(); |
||||||
|
if (isMsgForCurrentHeight(msgRoundIdentifier)) { |
||||||
|
return isMsgFromKnownValidator(msg); |
||||||
|
} else if (isMsgForFutureChainHeight(msgRoundIdentifier)) { |
||||||
|
addMessageToFutureMessageBuffer(msgRoundIdentifier.getSequenceNumber(), rawMsg); |
||||||
|
} else { |
||||||
|
LOG.info("IBFT message discarded as it is not for the current block height"); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isMsgFromKnownValidator(final SignedData<? extends Payload> msg) { |
||||||
|
return ibftFinalState.getValidators().contains(msg.getSender()); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isMsgForCurrentHeight(final ConsensusRoundIdentifier roundIdentifier) { |
||||||
|
return roundIdentifier.getSequenceNumber() == currentHeightManager.getChainHeight(); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isMsgForFutureChainHeight(final ConsensusRoundIdentifier roundIdentifier) { |
||||||
|
return roundIdentifier.getSequenceNumber() > currentHeightManager.getChainHeight(); |
||||||
|
} |
||||||
|
|
||||||
|
private void addMessageToFutureMessageBuffer(final long chainHeight, final MessageData rawMsg) { |
||||||
|
if (!futureMessages.containsKey(chainHeight)) { |
||||||
|
futureMessages.put(chainHeight, Lists.newArrayList()); |
||||||
|
} |
||||||
|
futureMessages.get(chainHeight).add(rawMsg); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,443 @@ |
|||||||
|
/* |
||||||
|
* 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.statemachine; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Java6Assertions.assertThat; |
||||||
|
import static org.assertj.core.util.Lists.newArrayList; |
||||||
|
import static org.mockito.ArgumentMatchers.any; |
||||||
|
import static org.mockito.Mockito.atLeastOnce; |
||||||
|
import static org.mockito.Mockito.never; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.BlockTimerExpiry; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangePayload; |
||||||
|
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; |
||||||
|
import tech.pegasys.pantheon.ethereum.chain.Blockchain; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.Address; |
||||||
|
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||||
|
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList; |
||||||
|
import com.google.common.collect.ImmutableMap; |
||||||
|
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 IbftControllerTest { |
||||||
|
@Mock private Blockchain blockChain; |
||||||
|
@Mock private IbftFinalState ibftFinalState; |
||||||
|
@Mock private IbftBlockHeightManagerFactory blockHeightManagerFactory; |
||||||
|
@Mock private BlockHeader blockHeader; |
||||||
|
@Mock private IbftBlockHeightManager blockHeightManager; |
||||||
|
|
||||||
|
@Mock private SignedData<ProposalPayload> signedProposal; |
||||||
|
@Mock private ProposalMessage proposalMessage; |
||||||
|
@Mock private ProposalPayload proposalPayload; |
||||||
|
|
||||||
|
@Mock private SignedData<PreparePayload> signedPrepare; |
||||||
|
@Mock private PrepareMessage prepareMessage; |
||||||
|
@Mock private PreparePayload preparePayload; |
||||||
|
|
||||||
|
@Mock private SignedData<CommitPayload> signedCommit; |
||||||
|
@Mock private CommitMessage commitMessage; |
||||||
|
@Mock private CommitPayload commitPayload; |
||||||
|
|
||||||
|
@Mock private SignedData<NewRoundPayload> signedNewRound; |
||||||
|
@Mock private NewRoundMessage newRoundMessage; |
||||||
|
@Mock private NewRoundPayload newRoundPayload; |
||||||
|
|
||||||
|
@Mock private SignedData<RoundChangePayload> signedRoundChange; |
||||||
|
@Mock private RoundChangeMessage roundChangeMessage; |
||||||
|
@Mock private RoundChangePayload roundChangePayload; |
||||||
|
|
||||||
|
private final Map<Long, List<MessageData>> futureMessages = new HashMap<>(); |
||||||
|
private final Address validator = Address.fromHexString("0x0"); |
||||||
|
private final Address unknownValidator = Address.fromHexString("0x2"); |
||||||
|
private final ConsensusRoundIdentifier futureRoundIdentifier = new ConsensusRoundIdentifier(2, 0); |
||||||
|
private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(0, 0); |
||||||
|
private IbftController ibftController; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setup() { |
||||||
|
ibftController = |
||||||
|
new IbftController(blockChain, ibftFinalState, blockHeightManagerFactory, futureMessages); |
||||||
|
when(blockChain.getChainHeadHeader()).thenReturn(blockHeader); |
||||||
|
when(blockHeightManagerFactory.create(blockHeader)).thenReturn(blockHeightManager); |
||||||
|
when(ibftFinalState.getValidators()).thenReturn(ImmutableList.of(validator)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void createsNewBlockHeightManagerWhenStarted() { |
||||||
|
ibftController.start(); |
||||||
|
assertThat(futureMessages).isEmpty(); |
||||||
|
verify(blockHeightManagerFactory).create(blockHeader); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void startsNewBlockHeightManagerAndReplaysFutureMessages() { |
||||||
|
final ConsensusRoundIdentifier roundIdentifierHeight3 = new ConsensusRoundIdentifier(3, 0); |
||||||
|
setupPrepare(futureRoundIdentifier, validator); |
||||||
|
setupProposal(roundIdentifierHeight3, validator); |
||||||
|
setupCommit(futureRoundIdentifier, validator); |
||||||
|
setupRoundChange(futureRoundIdentifier, validator); |
||||||
|
setupNewRound(roundIdentifierHeight3, validator); |
||||||
|
|
||||||
|
final List<MessageData> height2Msgs = |
||||||
|
newArrayList(prepareMessage, commitMessage, roundChangeMessage); |
||||||
|
final List<MessageData> height3Msgs = newArrayList(proposalMessage, newRoundMessage); |
||||||
|
futureMessages.put(2L, height2Msgs); |
||||||
|
futureMessages.put(3L, height3Msgs); |
||||||
|
when(blockHeightManager.getChainHeight()).thenReturn(2L); |
||||||
|
|
||||||
|
ibftController.start(); |
||||||
|
assertThat(futureMessages.keySet()).hasSize(1); |
||||||
|
assertThat(futureMessages.get(3L)).isEqualTo(height3Msgs); |
||||||
|
verify(blockHeightManagerFactory).create(blockHeader); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
verify(blockHeightManager, never()).handleProposalMessage(signedProposal); |
||||||
|
verify(blockHeightManager).handlePrepareMessage(signedPrepare); |
||||||
|
verify(blockHeightManager).handleCommitMessage(signedCommit); |
||||||
|
verify(blockHeightManager).handleRoundChangeMessage(signedRoundChange); |
||||||
|
verify(blockHeightManager, never()).handleNewRoundMessage(signedNewRound); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void createsNewBlockHeightManagerAndReplaysFutureMessagesOnNewChainHeadEvent() { |
||||||
|
setupPrepare(futureRoundIdentifier, validator); |
||||||
|
setupProposal(futureRoundIdentifier, validator); |
||||||
|
setupCommit(futureRoundIdentifier, validator); |
||||||
|
setupRoundChange(futureRoundIdentifier, validator); |
||||||
|
setupNewRound(futureRoundIdentifier, validator); |
||||||
|
|
||||||
|
futureMessages.put( |
||||||
|
2L, |
||||||
|
ImmutableList.of( |
||||||
|
prepareMessage, proposalMessage, commitMessage, roundChangeMessage, newRoundMessage)); |
||||||
|
when(blockHeightManager.getChainHeight()).thenReturn(2L); |
||||||
|
|
||||||
|
final NewChainHead newChainHead = new NewChainHead(blockHeader); |
||||||
|
ibftController.handleNewBlockEvent(newChainHead); |
||||||
|
|
||||||
|
verify(blockHeightManagerFactory).create(blockHeader); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
verify(blockHeightManager).handleProposalMessage(signedProposal); |
||||||
|
verify(blockHeightManager).handlePrepareMessage(signedPrepare); |
||||||
|
verify(blockHeightManager).handleCommitMessage(signedCommit); |
||||||
|
verify(blockHeightManager).handleRoundChangeMessage(signedRoundChange); |
||||||
|
verify(blockHeightManager).handleNewRoundMessage(signedNewRound); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void handlesRoundExpiry() { |
||||||
|
final RoundExpiry roundExpiry = new RoundExpiry(roundIdentifier); |
||||||
|
|
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleRoundExpiry(roundExpiry); |
||||||
|
|
||||||
|
verify(blockHeightManager).roundExpired(roundExpiry); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void handlesBlockTimerExpiry() { |
||||||
|
final BlockTimerExpiry blockTimerExpiry = new BlockTimerExpiry(roundIdentifier); |
||||||
|
|
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleBlockTimerExpiry(blockTimerExpiry); |
||||||
|
|
||||||
|
verify(blockHeightManager).handleBlockTimerExpiry(roundIdentifier); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void proposalForCurrentHeightIsPassedToBlockHeightManager() { |
||||||
|
setupProposal(roundIdentifier, validator); |
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(proposalMessage)); |
||||||
|
|
||||||
|
assertThat(futureMessages).isEmpty(); |
||||||
|
verify(blockHeightManager).handleProposalMessage(signedProposal); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
verifyNoMoreInteractions(blockHeightManager); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void prepareForCurrentHeightIsPassedToBlockHeightManager() { |
||||||
|
setupPrepare(roundIdentifier, validator); |
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(prepareMessage)); |
||||||
|
|
||||||
|
assertThat(futureMessages).isEmpty(); |
||||||
|
verify(blockHeightManager).handlePrepareMessage(signedPrepare); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
verifyNoMoreInteractions(blockHeightManager); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void commitForCurrentHeightIsPassedToBlockHeightManager() { |
||||||
|
setupCommit(roundIdentifier, validator); |
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(commitMessage)); |
||||||
|
|
||||||
|
assertThat(futureMessages).isEmpty(); |
||||||
|
verify(blockHeightManager).handleCommitMessage(signedCommit); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
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).handleNewRoundMessage(signedNewRound); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
verifyNoMoreInteractions(blockHeightManager); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void roundChangeForCurrentHeightIsPassedToBlockHeightManager() { |
||||||
|
roundIdentifier = new ConsensusRoundIdentifier(0, 1); |
||||||
|
setupRoundChange(roundIdentifier, validator); |
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(roundChangeMessage)); |
||||||
|
|
||||||
|
assertThat(futureMessages).isEmpty(); |
||||||
|
verify(blockHeightManager).handleRoundChangeMessage(signedRoundChange); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
verifyNoMoreInteractions(blockHeightManager); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void proposalForPastHeightIsDiscarded() { |
||||||
|
setupProposal(roundIdentifier, validator); |
||||||
|
when(blockHeightManager.getChainHeight()).thenReturn(1L); |
||||||
|
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(proposalMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void prepareForPastHeightIsDiscarded() { |
||||||
|
setupPrepare(roundIdentifier, validator); |
||||||
|
when(blockHeightManager.getChainHeight()).thenReturn(1L); |
||||||
|
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(prepareMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void commitForPastHeightIsDiscarded() { |
||||||
|
setupCommit(roundIdentifier, validator); |
||||||
|
when(blockHeightManager.getChainHeight()).thenReturn(1L); |
||||||
|
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); |
||||||
|
when(blockHeightManager.getChainHeight()).thenReturn(1L); |
||||||
|
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(roundChangeMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void roundExpiryForPastHeightIsDiscarded() { |
||||||
|
final RoundExpiry roundExpiry = new RoundExpiry(roundIdentifier); |
||||||
|
when(blockHeightManager.getChainHeight()).thenReturn(1L); |
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleRoundExpiry(roundExpiry); |
||||||
|
assertThat(futureMessages).isEmpty(); |
||||||
|
verify(blockHeightManager, never()).roundExpired(any()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void blockTimerForPastHeightIsDiscarded() { |
||||||
|
final BlockTimerExpiry blockTimerExpiry = new BlockTimerExpiry(roundIdentifier); |
||||||
|
when(blockHeightManager.getChainHeight()).thenReturn(1L); |
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleBlockTimerExpiry(blockTimerExpiry); |
||||||
|
assertThat(futureMessages).isEmpty(); |
||||||
|
verify(blockHeightManager, never()).handleBlockTimerExpiry(any()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void proposalForUnknownValidatorIsDiscarded() { |
||||||
|
setupProposal(roundIdentifier, unknownValidator); |
||||||
|
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(proposalMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void prepareForUnknownValidatorIsDiscarded() { |
||||||
|
setupPrepare(roundIdentifier, unknownValidator); |
||||||
|
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(prepareMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void commitForUnknownValidatorIsDiscarded() { |
||||||
|
setupCommit(roundIdentifier, unknownValidator); |
||||||
|
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(commitMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void newRoundForUnknownValidatorIsDiscarded() { |
||||||
|
setupNewRound(roundIdentifier, unknownValidator); |
||||||
|
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(newRoundMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void roundChangeForUnknownValidatorIsDiscarded() { |
||||||
|
setupRoundChange(roundIdentifier, unknownValidator); |
||||||
|
verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(roundChangeMessage)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void proposalForFutureHeightIsBuffered() { |
||||||
|
setupProposal(futureRoundIdentifier, validator); |
||||||
|
final Map<Long, List<MessageData>> expectedFutureMsgs = |
||||||
|
ImmutableMap.of(2L, ImmutableList.of(proposalMessage)); |
||||||
|
verifyHasFutureMessages(new IbftReceivedMessageEvent(proposalMessage), expectedFutureMsgs); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void prepareForFutureHeightIsBuffered() { |
||||||
|
setupPrepare(futureRoundIdentifier, validator); |
||||||
|
final Map<Long, List<MessageData>> expectedFutureMsgs = |
||||||
|
ImmutableMap.of(2L, ImmutableList.of(prepareMessage)); |
||||||
|
verifyHasFutureMessages(new IbftReceivedMessageEvent(prepareMessage), expectedFutureMsgs); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void commitForFutureHeightIsBuffered() { |
||||||
|
setupCommit(futureRoundIdentifier, validator); |
||||||
|
final Map<Long, List<MessageData>> expectedFutureMsgs = |
||||||
|
ImmutableMap.of(2L, ImmutableList.of(commitMessage)); |
||||||
|
verifyHasFutureMessages(new IbftReceivedMessageEvent(commitMessage), expectedFutureMsgs); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void newRoundForFutureHeightIsBuffered() { |
||||||
|
setupNewRound(futureRoundIdentifier, validator); |
||||||
|
final Map<Long, List<MessageData>> expectedFutureMsgs = |
||||||
|
ImmutableMap.of(2L, ImmutableList.of(newRoundMessage)); |
||||||
|
verifyHasFutureMessages(new IbftReceivedMessageEvent(newRoundMessage), expectedFutureMsgs); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void roundChangeForFutureHeightIsBuffered() { |
||||||
|
setupRoundChange(futureRoundIdentifier, validator); |
||||||
|
final Map<Long, List<MessageData>> expectedFutureMsgs = |
||||||
|
ImmutableMap.of(2L, ImmutableList.of(roundChangeMessage)); |
||||||
|
verifyHasFutureMessages(new IbftReceivedMessageEvent(roundChangeMessage), expectedFutureMsgs); |
||||||
|
} |
||||||
|
|
||||||
|
private void verifyNotHandledAndNoFutureMsgs(final IbftReceivedMessageEvent msg) { |
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleMessageEvent(msg); |
||||||
|
|
||||||
|
assertThat(futureMessages).isEmpty(); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
verifyNoMoreInteractions(blockHeightManager); |
||||||
|
} |
||||||
|
|
||||||
|
private void verifyHasFutureMessages( |
||||||
|
final IbftReceivedMessageEvent msg, final Map<Long, List<MessageData>> expectedFutureMsgs) { |
||||||
|
ibftController.start(); |
||||||
|
ibftController.handleMessageEvent(msg); |
||||||
|
|
||||||
|
assertThat(futureMessages).hasSize(expectedFutureMsgs.size()); |
||||||
|
assertThat(futureMessages).isEqualTo(expectedFutureMsgs); |
||||||
|
verify(blockHeightManager, atLeastOnce()).getChainHeight(); |
||||||
|
verify(blockHeightManager).start(); |
||||||
|
verifyNoMoreInteractions(blockHeightManager); |
||||||
|
} |
||||||
|
|
||||||
|
private void setupProposal( |
||||||
|
final ConsensusRoundIdentifier roundIdentifier, final Address validator) { |
||||||
|
when(signedProposal.getPayload()).thenReturn(proposalPayload); |
||||||
|
when(signedProposal.getSender()).thenReturn(validator); |
||||||
|
when(proposalPayload.getRoundIdentifier()).thenReturn(roundIdentifier); |
||||||
|
when(proposalMessage.getCode()).thenReturn(IbftV2.PROPOSAL); |
||||||
|
when(proposalMessage.decode()).thenReturn(signedProposal); |
||||||
|
} |
||||||
|
|
||||||
|
private void setupPrepare( |
||||||
|
final ConsensusRoundIdentifier roundIdentifier, final Address validator) { |
||||||
|
when(signedPrepare.getPayload()).thenReturn(preparePayload); |
||||||
|
when(signedPrepare.getSender()).thenReturn(validator); |
||||||
|
when(preparePayload.getRoundIdentifier()).thenReturn(roundIdentifier); |
||||||
|
when(prepareMessage.getCode()).thenReturn(IbftV2.PREPARE); |
||||||
|
when(prepareMessage.decode()).thenReturn(signedPrepare); |
||||||
|
} |
||||||
|
|
||||||
|
private void setupCommit( |
||||||
|
final ConsensusRoundIdentifier roundIdentifier, final Address validator) { |
||||||
|
when(signedCommit.getPayload()).thenReturn(commitPayload); |
||||||
|
when(signedCommit.getSender()).thenReturn(validator); |
||||||
|
when(commitPayload.getRoundIdentifier()).thenReturn(roundIdentifier); |
||||||
|
when(commitMessage.getCode()).thenReturn(IbftV2.COMMIT); |
||||||
|
when(commitMessage.decode()).thenReturn(signedCommit); |
||||||
|
} |
||||||
|
|
||||||
|
private void setupNewRound( |
||||||
|
final ConsensusRoundIdentifier roundIdentifier, final Address validator) { |
||||||
|
when(signedNewRound.getPayload()).thenReturn(newRoundPayload); |
||||||
|
when(signedNewRound.getSender()).thenReturn(validator); |
||||||
|
when(newRoundPayload.getRoundIdentifier()).thenReturn(roundIdentifier); |
||||||
|
when(newRoundMessage.getCode()).thenReturn(IbftV2.NEW_ROUND); |
||||||
|
when(newRoundMessage.decode()).thenReturn(signedNewRound); |
||||||
|
} |
||||||
|
|
||||||
|
private void setupRoundChange( |
||||||
|
final ConsensusRoundIdentifier roundIdentifier, final Address validator) { |
||||||
|
when(signedRoundChange.getPayload()).thenReturn(roundChangePayload); |
||||||
|
when(signedRoundChange.getSender()).thenReturn(validator); |
||||||
|
when(roundChangePayload.getRoundIdentifier()).thenReturn(roundIdentifier); |
||||||
|
when(roundChangeMessage.getCode()).thenReturn(IbftV2.ROUND_CHANGE); |
||||||
|
when(roundChangeMessage.decode()).thenReturn(signedRoundChange); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue