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