mirror of https://github.com/hyperledger/besu
Ibft Consensus Round Classes (#405)
The IbftRound is responsible for sequencing the transmission of network packets based on received data.
parent
24aac90efb
commit
f99457bb3c
@ -0,0 +1,103 @@ |
||||
/* |
||||
* 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.ibftmessage.CommitMessage; |
||||
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.MessageFactory; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparedCertificate; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangeCertificate; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangePayload; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; |
||||
import tech.pegasys.pantheon.consensus.ibft.network.IbftNetworkPeers; |
||||
import tech.pegasys.pantheon.crypto.SECP256K1.Signature; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
public class IbftMessageTransmitter { |
||||
|
||||
private final MessageFactory messageFactory; |
||||
private final IbftNetworkPeers networkPeers; |
||||
|
||||
public IbftMessageTransmitter( |
||||
final MessageFactory messageFactory, final IbftNetworkPeers networkPeers) { |
||||
this.messageFactory = messageFactory; |
||||
this.networkPeers = networkPeers; |
||||
} |
||||
|
||||
public void multicastProposal(final ConsensusRoundIdentifier roundIdentifier, final Block block) { |
||||
final SignedData<ProposalPayload> signedPayload = |
||||
messageFactory.createSignedProposalPayload(roundIdentifier, block); |
||||
|
||||
final ProposalMessage message = ProposalMessage.create(signedPayload); |
||||
|
||||
networkPeers.multicastToValidators(message); |
||||
} |
||||
|
||||
public void multicastPrepare(final ConsensusRoundIdentifier roundIdentifier, final Hash digest) { |
||||
final SignedData<PreparePayload> signedPayload = |
||||
messageFactory.createSignedPreparePayload(roundIdentifier, digest); |
||||
|
||||
final PrepareMessage message = PrepareMessage.create(signedPayload); |
||||
|
||||
networkPeers.multicastToValidators(message); |
||||
} |
||||
|
||||
public void multicastCommit( |
||||
final ConsensusRoundIdentifier roundIdentifier, |
||||
final Hash digest, |
||||
final Signature commitSeal) { |
||||
final SignedData<CommitPayload> signedPayload = |
||||
messageFactory.createSignedCommitPayload(roundIdentifier, digest, commitSeal); |
||||
|
||||
final CommitMessage message = CommitMessage.create(signedPayload); |
||||
|
||||
networkPeers.multicastToValidators(message); |
||||
} |
||||
|
||||
public void multicastRoundChange( |
||||
final ConsensusRoundIdentifier roundIdentifier, |
||||
final Optional<PreparedCertificate> preparedCertificate) { |
||||
|
||||
final SignedData<RoundChangePayload> signedPayload = |
||||
messageFactory.createSignedRoundChangePayload(roundIdentifier, preparedCertificate); |
||||
|
||||
final RoundChangeMessage message = RoundChangeMessage.create(signedPayload); |
||||
|
||||
networkPeers.multicastToValidators(message); |
||||
} |
||||
|
||||
public void multicastNewRound( |
||||
final ConsensusRoundIdentifier roundIdentifier, |
||||
final RoundChangeCertificate roundChangeCertificate, |
||||
final SignedData<ProposalPayload> proposalPayload) { |
||||
|
||||
final SignedData<NewRoundPayload> signedPayload = |
||||
messageFactory.createSignedNewRoundPayload( |
||||
roundIdentifier, roundChangeCertificate, proposalPayload); |
||||
|
||||
final NewRoundMessage message = NewRoundMessage.create(signedPayload); |
||||
|
||||
networkPeers.multicastToValidators(message); |
||||
} |
||||
} |
@ -0,0 +1,187 @@ |
||||
/* |
||||
* 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.IbftBlockHashing; |
||||
import tech.pegasys.pantheon.consensus.ibft.IbftContext; |
||||
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.ibftmessagedata.CommitPayload; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparedCertificate; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; |
||||
import tech.pegasys.pantheon.crypto.SECP256K1; |
||||
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; |
||||
import tech.pegasys.pantheon.crypto.SECP256K1.Signature; |
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockImporter; |
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; |
||||
import tech.pegasys.pantheon.util.Subscribers; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.apache.logging.log4j.LogManager; |
||||
import org.apache.logging.log4j.Logger; |
||||
|
||||
public class IbftRound { |
||||
|
||||
private static final Logger LOG = LogManager.getLogger(); |
||||
|
||||
private final Subscribers<MinedBlockObserver> observers; |
||||
private final RoundState roundState; |
||||
private final IbftBlockCreator blockCreator; |
||||
private final ProtocolContext<IbftContext> protocolContext; |
||||
private final BlockImporter<IbftContext> blockImporter; |
||||
private final KeyPair nodeKeys; |
||||
private final MessageFactory messageFactory; // used only to create stored local msgs
|
||||
private final IbftMessageTransmitter transmitter; |
||||
|
||||
public IbftRound( |
||||
final RoundState roundState, |
||||
final IbftBlockCreator blockCreator, |
||||
final ProtocolContext<IbftContext> protocolContext, |
||||
final BlockImporter<IbftContext> blockImporter, |
||||
final Subscribers<MinedBlockObserver> observers, |
||||
final KeyPair nodeKeys, |
||||
final MessageFactory messageFactory, |
||||
final IbftMessageTransmitter transmitter) { |
||||
this.roundState = roundState; |
||||
this.blockCreator = blockCreator; |
||||
this.protocolContext = protocolContext; |
||||
this.blockImporter = blockImporter; |
||||
this.observers = observers; |
||||
this.nodeKeys = nodeKeys; |
||||
this.messageFactory = messageFactory; |
||||
this.transmitter = transmitter; |
||||
} |
||||
|
||||
public ConsensusRoundIdentifier getRoundIdentifier() { |
||||
return roundState.getRoundIdentifier(); |
||||
} |
||||
|
||||
public void createAndSendProposalMessage(final long headerTimeStampSeconds) { |
||||
LOG.info("Creating proposed block."); |
||||
final Block block = blockCreator.createBlock(headerTimeStampSeconds); |
||||
transmitter.multicastProposal(roundState.getRoundIdentifier(), block); |
||||
|
||||
updateStateWithProposedBlock( |
||||
messageFactory.createSignedProposalPayload(roundState.getRoundIdentifier(), block)); |
||||
} |
||||
|
||||
public void handleProposalMessage(final SignedData<ProposalPayload> msg) { |
||||
LOG.info("Received a Proposal message."); |
||||
final Block block = msg.getPayload().getBlock(); |
||||
final boolean wasCommitted = roundState.isCommitted(); |
||||
|
||||
if (updateStateWithProposedBlock(msg)) { |
||||
LOG.info("Sending prepare message."); |
||||
transmitter.multicastPrepare(getRoundIdentifier(), block.getHash()); |
||||
final SignedData<PreparePayload> localPrepareMessage = |
||||
messageFactory.createSignedPreparePayload( |
||||
roundState.getRoundIdentifier(), block.getHash()); |
||||
peerIsPrepared(localPrepareMessage); |
||||
} |
||||
|
||||
if (wasCommitted != roundState.isCommitted()) { |
||||
importBlockToChain(); |
||||
} |
||||
} |
||||
|
||||
public void handlePrepareMessage(final SignedData<PreparePayload> msg) { |
||||
LOG.info("Received a prepare message."); |
||||
peerIsPrepared(msg); |
||||
} |
||||
|
||||
public void handleCommitMessage(final SignedData<CommitPayload> msg) { |
||||
LOG.info("Received a commit message."); |
||||
peerIsCommitted(msg); |
||||
} |
||||
|
||||
public Optional<PreparedCertificate> createPrepareCertificate() { |
||||
return roundState.constructPreparedCertificate(); |
||||
} |
||||
|
||||
private boolean updateStateWithProposedBlock(final SignedData<ProposalPayload> msg) { |
||||
final boolean wasPrepared = roundState.isPrepared(); |
||||
final boolean blockAccepted = roundState.setProposedBlock(msg); |
||||
if (blockAccepted) { |
||||
// There are times handling a proposed block is enough to enter prepared.
|
||||
if (wasPrepared != roundState.isPrepared()) { |
||||
LOG.info("Sending commit message."); |
||||
final Block block = roundState.getProposedBlock().get(); |
||||
transmitter.multicastCommit(getRoundIdentifier(), block.getHash(), createCommitSeal(block)); |
||||
} |
||||
final SignedData<CommitPayload> localCommitMessage = |
||||
messageFactory.createSignedCommitPayload( |
||||
roundState.getRoundIdentifier(), |
||||
msg.getPayload().getBlock().getHash(), |
||||
createCommitSeal(roundState.getProposedBlock().get())); |
||||
peerIsCommitted(localCommitMessage); |
||||
} |
||||
|
||||
return blockAccepted; |
||||
} |
||||
|
||||
private void peerIsPrepared(final SignedData<PreparePayload> msg) { |
||||
final boolean wasPrepared = roundState.isPrepared(); |
||||
roundState.addPrepareMessage(msg); |
||||
if (wasPrepared != roundState.isPrepared()) { |
||||
LOG.info("Sending commit message."); |
||||
final Block block = roundState.getProposedBlock().get(); |
||||
transmitter.multicastCommit(getRoundIdentifier(), block.getHash(), createCommitSeal(block)); |
||||
} |
||||
} |
||||
|
||||
private void peerIsCommitted(final SignedData<CommitPayload> msg) { |
||||
final boolean wasCommitted = roundState.isCommitted(); |
||||
roundState.addCommitMessage(msg); |
||||
if (wasCommitted != roundState.isCommitted()) { |
||||
importBlockToChain(); |
||||
} |
||||
} |
||||
|
||||
private void importBlockToChain() { |
||||
final Block blockToImport = |
||||
IbftHelpers.createSealedBlock( |
||||
roundState.getProposedBlock().get(), roundState.getCommitSeals()); |
||||
|
||||
LOG.info("Importing block to chain."); |
||||
boolean result = |
||||
blockImporter.importBlock(protocolContext, blockToImport, HeaderValidationMode.FULL); |
||||
if (!result) { |
||||
LOG.info("Failed to import block to chain."); |
||||
} else { |
||||
notifyNewBlockListeners(blockToImport); |
||||
} |
||||
} |
||||
|
||||
private Signature createCommitSeal(final Block block) { |
||||
final BlockHeader proposedHeader = block.getHeader(); |
||||
final IbftExtraData extraData = IbftExtraData.decode(proposedHeader.getExtraData()); |
||||
final Hash commitHash = |
||||
IbftBlockHashing.calculateDataHashForCommittedSeal(proposedHeader, extraData); |
||||
return SECP256K1.sign(commitHash, nodeKeys); |
||||
} |
||||
|
||||
private void notifyNewBlockListeners(final Block block) { |
||||
observers.forEach(obs -> obs.blockMined(block)); |
||||
} |
||||
} |
@ -0,0 +1,73 @@ |
||||
/* |
||||
* 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.IbftContext; |
||||
import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator; |
||||
import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreatorFactory; |
||||
import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidator; |
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; |
||||
import tech.pegasys.pantheon.util.Subscribers; |
||||
|
||||
public class IbftRoundFactory { |
||||
|
||||
private final IbftFinalState finalState; |
||||
|
||||
private final IbftBlockCreatorFactory blockCreatorFactory; |
||||
private final ProtocolContext<IbftContext> protocolContext; |
||||
private final ProtocolSchedule<IbftContext> protocolSchedule; |
||||
private final Subscribers<MinedBlockObserver> minedBlockObservers = new Subscribers<>(); |
||||
|
||||
public IbftRoundFactory( |
||||
final IbftFinalState finalState, |
||||
final ProtocolContext<IbftContext> protocolContext, |
||||
final ProtocolSchedule<IbftContext> protocolSchedule) { |
||||
this.finalState = finalState; |
||||
this.blockCreatorFactory = finalState.getBlockCreatorFactory(); |
||||
this.protocolContext = protocolContext; |
||||
this.protocolSchedule = protocolSchedule; |
||||
} |
||||
|
||||
public IbftRound createNewRound(final BlockHeader parentHeader, final int round) { |
||||
long nextBlockHeight = parentHeader.getNumber() + 1; |
||||
final ConsensusRoundIdentifier roundIdentifier = |
||||
new ConsensusRoundIdentifier(nextBlockHeight, round); |
||||
final IbftBlockCreator blockCreator = blockCreatorFactory.create(parentHeader, round); |
||||
|
||||
final RoundState roundContext = |
||||
new RoundState( |
||||
roundIdentifier, |
||||
finalState.getQuorumSize(), |
||||
new MessageValidator( |
||||
finalState.getValidators(), |
||||
finalState.getProposerForRound(roundIdentifier), |
||||
roundIdentifier, |
||||
finalState.getBlockHeaderValidator(), |
||||
protocolContext, |
||||
parentHeader)); |
||||
|
||||
return new IbftRound( |
||||
roundContext, |
||||
blockCreator, |
||||
protocolContext, |
||||
protocolSchedule.getByBlockNumber(nextBlockHeight).getBlockImporter(), |
||||
minedBlockObservers, |
||||
finalState.getNodeKeys(), |
||||
finalState.getMessageFactory(), |
||||
finalState.getTransmitter()); |
||||
} |
||||
} |
@ -0,0 +1,251 @@ |
||||
/* |
||||
* 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.Assertions.assertThat; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.anyLong; |
||||
import static org.mockito.Mockito.never; |
||||
import static org.mockito.Mockito.times; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import tech.pegasys.pantheon.consensus.common.VoteProposer; |
||||
import tech.pegasys.pantheon.consensus.common.VoteTally; |
||||
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; |
||||
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing; |
||||
import tech.pegasys.pantheon.consensus.ibft.IbftContext; |
||||
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; |
||||
import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator; |
||||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; |
||||
import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidator; |
||||
import tech.pegasys.pantheon.crypto.SECP256K1; |
||||
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; |
||||
import tech.pegasys.pantheon.crypto.SECP256K1.Signature; |
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.chain.MinedBlockObserver; |
||||
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockBody; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockImporter; |
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.db.WorldStateArchive; |
||||
import tech.pegasys.pantheon.util.Subscribers; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Collections; |
||||
import java.util.Optional; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.ArgumentCaptor; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class IbftRoundTest { |
||||
|
||||
private final KeyPair localNodeKeys = KeyPair.generate(); |
||||
private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1); |
||||
private final MessageFactory messageFactory = new MessageFactory(localNodeKeys); |
||||
private ProtocolContext<IbftContext> protocolContext; |
||||
|
||||
@Mock private MutableBlockchain blockChain; |
||||
@Mock private WorldStateArchive worldStateArchive; |
||||
@Mock private BlockImporter<IbftContext> blockImporter; |
||||
@Mock private Subscribers<MinedBlockObserver> subscribers; |
||||
@Mock private IbftMessageTransmitter transmitter; |
||||
|
||||
@Mock private IbftBlockCreator blockCreator; |
||||
@Mock private MessageValidator messageValidator; |
||||
|
||||
private Block proposedBlock; |
||||
private IbftExtraData proposedExtraData; |
||||
|
||||
final Signature remoteCommitSeal = Signature.create(BigInteger.ONE, BigInteger.ONE, (byte) 1); |
||||
|
||||
@Before |
||||
public void setup() { |
||||
protocolContext = |
||||
new ProtocolContext<>( |
||||
blockChain, |
||||
worldStateArchive, |
||||
new IbftContext(new VoteTally(Collections.emptyList()), new VoteProposer())); |
||||
|
||||
when(messageValidator.addSignedProposalPayload(any())).thenReturn(true); |
||||
when(messageValidator.validatePrepareMessage(any())).thenReturn(true); |
||||
when(messageValidator.validateCommmitMessage(any())).thenReturn(true); |
||||
|
||||
proposedExtraData = |
||||
new IbftExtraData( |
||||
BytesValue.wrap(new byte[32]), |
||||
Collections.emptyList(), |
||||
Optional.empty(), |
||||
0, |
||||
Collections.emptyList()); |
||||
BlockHeaderTestFixture headerTestFixture = new BlockHeaderTestFixture(); |
||||
headerTestFixture.extraData(proposedExtraData.encode()); |
||||
headerTestFixture.number(1); |
||||
|
||||
BlockHeader header = headerTestFixture.buildHeader(); |
||||
proposedBlock = |
||||
new Block(header, new BlockBody(Collections.emptyList(), Collections.emptyList())); |
||||
|
||||
when(blockCreator.createBlock(anyLong())).thenReturn(proposedBlock); |
||||
|
||||
when(blockImporter.importBlock(any(), any(), any())).thenReturn(true); |
||||
} |
||||
|
||||
@Test |
||||
public void onReceptionOfValidProposalSendsAPrepareToNetworkPeers() { |
||||
final RoundState roundState = new RoundState(roundIdentifier, 3, messageValidator); |
||||
final IbftRound round = |
||||
new IbftRound( |
||||
roundState, |
||||
blockCreator, |
||||
protocolContext, |
||||
blockImporter, |
||||
subscribers, |
||||
localNodeKeys, |
||||
messageFactory, |
||||
transmitter); |
||||
|
||||
round.handleProposalMessage( |
||||
messageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock)); |
||||
verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash()); |
||||
verify(transmitter, never()).multicastCommit(any(), any(), any()); |
||||
} |
||||
|
||||
@Test |
||||
public void sendsAProposalWhenRequested() { |
||||
final RoundState roundState = new RoundState(roundIdentifier, 3, messageValidator); |
||||
final IbftRound round = |
||||
new IbftRound( |
||||
roundState, |
||||
blockCreator, |
||||
protocolContext, |
||||
blockImporter, |
||||
subscribers, |
||||
localNodeKeys, |
||||
messageFactory, |
||||
transmitter); |
||||
|
||||
round.createAndSendProposalMessage(15); |
||||
verify(transmitter, times(1)).multicastProposal(roundIdentifier, proposedBlock); |
||||
verify(transmitter, never()).multicastPrepare(any(), any()); |
||||
verify(transmitter, never()).multicastCommit(any(), any(), any()); |
||||
} |
||||
|
||||
@Test |
||||
public void singleValidatorImportBlocksImmediatelyOnProposalCreation() { |
||||
final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator); |
||||
final IbftRound round = |
||||
new IbftRound( |
||||
roundState, |
||||
blockCreator, |
||||
protocolContext, |
||||
blockImporter, |
||||
subscribers, |
||||
localNodeKeys, |
||||
messageFactory, |
||||
transmitter); |
||||
round.createAndSendProposalMessage(15); |
||||
verify(transmitter, times(1)).multicastProposal(roundIdentifier, proposedBlock); |
||||
verify(transmitter, never()).multicastPrepare(any(), any()); |
||||
verify(transmitter, times(1)).multicastCommit(any(), any(), any()); |
||||
verify(blockImporter, times(1)).importBlock(any(), any(), any()); |
||||
} |
||||
|
||||
@Test |
||||
public void twoValidatorNetworkSendsPrepareOnProposalReceptionThenSendsCommitOnCommitReceive() { |
||||
final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); |
||||
final IbftRound round = |
||||
new IbftRound( |
||||
roundState, |
||||
blockCreator, |
||||
protocolContext, |
||||
blockImporter, |
||||
subscribers, |
||||
localNodeKeys, |
||||
messageFactory, |
||||
transmitter); |
||||
|
||||
final Hash commitSealHash = |
||||
IbftBlockHashing.calculateDataHashForCommittedSeal( |
||||
proposedBlock.getHeader(), proposedExtraData); |
||||
final Signature localCommitSeal = SECP256K1.sign(commitSealHash, localNodeKeys); |
||||
|
||||
// Receive Proposal Message
|
||||
round.handleProposalMessage( |
||||
messageFactory.createSignedProposalPayload(roundIdentifier, proposedBlock)); |
||||
verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash()); |
||||
verify(transmitter, times(1)) |
||||
.multicastCommit(roundIdentifier, proposedBlock.getHash(), localCommitSeal); |
||||
verify(blockImporter, never()).importBlock(any(), any(), any()); |
||||
|
||||
// Receive Commit Message
|
||||
|
||||
round.handleCommitMessage( |
||||
messageFactory.createSignedCommitPayload( |
||||
roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); |
||||
|
||||
// Should import block when both commit seals are available.
|
||||
ArgumentCaptor<Block> capturedBlock = ArgumentCaptor.forClass(Block.class); |
||||
verify(blockImporter, times(1)).importBlock(any(), capturedBlock.capture(), any()); |
||||
|
||||
// Ensure imported block contains both commit seals.
|
||||
IbftExtraData importedExtraData = |
||||
IbftExtraData.decode(capturedBlock.getValue().getHeader().getExtraData()); |
||||
assertThat(importedExtraData.getSeals()).containsOnly(remoteCommitSeal, localCommitSeal); |
||||
} |
||||
|
||||
@Test |
||||
public void localNodeProposesToNetworkOfTwoValidatorsImportsOnReceptionOfCommitFromPeer() { |
||||
final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); |
||||
final IbftRound round = |
||||
new IbftRound( |
||||
roundState, |
||||
blockCreator, |
||||
protocolContext, |
||||
blockImporter, |
||||
subscribers, |
||||
localNodeKeys, |
||||
messageFactory, |
||||
transmitter); |
||||
|
||||
final Hash commitSealHash = |
||||
IbftBlockHashing.calculateDataHashForCommittedSeal( |
||||
proposedBlock.getHeader(), proposedExtraData); |
||||
final Signature localCommitSeal = SECP256K1.sign(commitSealHash, localNodeKeys); |
||||
|
||||
round.createAndSendProposalMessage(15); |
||||
verify(transmitter, never()).multicastCommit(any(), any(), any()); |
||||
verify(blockImporter, never()).importBlock(any(), any(), any()); |
||||
|
||||
round.handlePrepareMessage( |
||||
messageFactory.createSignedPreparePayload(roundIdentifier, proposedBlock.getHash())); |
||||
|
||||
verify(transmitter, times(1)) |
||||
.multicastCommit(roundIdentifier, proposedBlock.getHash(), localCommitSeal); |
||||
verify(blockImporter, never()).importBlock(any(), any(), any()); |
||||
|
||||
round.handleCommitMessage( |
||||
messageFactory.createSignedCommitPayload( |
||||
roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); |
||||
verify(blockImporter, times(1)).importBlock(any(), any(), any()); |
||||
} |
||||
} |
Loading…
Reference in new issue