Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

IBFT Integration Tests - Future Height #591

Merged
merged 6 commits into from Jan 21, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -19,6 +19,7 @@
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -62,11 +63,13 @@ public MessageFactory getLocalNodeMessageFactory() {
return finalState.getMessageFactory();
}

public Block createBlockForProposal(final int round, final long timestamp) {
return finalState
.getBlockCreatorFactory()
.create(blockchain.getChainHeadHeader(), round)
.createBlock(timestamp);
public Block createBlockForProposal(
final BlockHeader parent, final int round, final long timestamp) {
return finalState.getBlockCreatorFactory().create(parent, round).createBlock(timestamp);
}

public Block createBlockForProposalFromChainHead(final int round, final long timestamp) {
return createBlockForProposal(blockchain.getChainHeadHeader(), round, timestamp);
}

public RoundSpecificNodeRoles getRoundSpecificRoles(final ConsensusRoundIdentifier roundId) {
Expand Down
Expand Up @@ -37,7 +37,7 @@
public class TestHelpers {

public static SignedData<CommitPayload> createSignedCommentPayload(
final Block block, final KeyPair signingKeyPair, final ConsensusRoundIdentifier roundId) {
final ConsensusRoundIdentifier roundId, final Block block, final KeyPair signingKeyPair) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the consistency with other methods 👍


final IbftExtraData extraData = IbftExtraData.decode(block.getHeader().getExtraData());

Expand Down
Expand Up @@ -88,9 +88,13 @@ public SignedData<PreparePayload> injectPrepare(
return payload;
}

public Signature getBlockSignature(final Hash digest) {
return SECP256K1.sign(digest, nodeKeys);
}

public SignedData<CommitPayload> injectCommit(
final ConsensusRoundIdentifier rId, final Hash digest) {
final Signature commitSeal = SECP256K1.sign(digest, nodeKeys);
final Signature commitSeal = getBlockSignature(digest);
final SignedData<CommitPayload> payload =
messageFactory.createSignedCommitPayload(rId, digest, commitSeal);
injectMessage(CommitMessageData.create(payload));
Expand Down
@@ -0,0 +1,175 @@
/*
* Copyright 2019 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.consensus.ibft.tests;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Lists.emptyList;
import static tech.pegasys.pantheon.consensus.ibft.support.MessageReceptionHelpers.assertPeersReceivedExactly;
import static tech.pegasys.pantheon.consensus.ibft.support.MessageReceptionHelpers.assertPeersReceivedNoMessages;
import static tech.pegasys.pantheon.consensus.ibft.support.TestHelpers.createSignedCommentPayload;

import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftHelpers;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead;
import tech.pegasys.pantheon.consensus.ibft.payload.CommitPayload;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparePayload;
import tech.pegasys.pantheon.consensus.ibft.payload.SignedData;
import tech.pegasys.pantheon.consensus.ibft.support.RoundSpecificNodeRoles;
import tech.pegasys.pantheon.consensus.ibft.support.TestContext;
import tech.pegasys.pantheon.consensus.ibft.support.TestContextFactory;
import tech.pegasys.pantheon.ethereum.core.Block;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.stream.Collectors;

import org.junit.Before;
import org.junit.Test;

public class FutureHeightTest {

private final long blockTimeStamp = 100;
private final Clock fixedClock =
Clock.fixed(Instant.ofEpochSecond(blockTimeStamp), ZoneId.systemDefault());

private final int NETWORK_SIZE = 5;

// Local node is not the first proposer for the current round.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this message correct? where or what is local node ...I don't see a field with that name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to handle this - but all integration tests are exercising the ibftController embedded in the TestContext - which I treat/consider the localNode.

Would love better nomenclature.

private final TestContext context =
TestContextFactory.createTestEnvironment(NETWORK_SIZE, 0, fixedClock);

private final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(1, 0);
private final RoundSpecificNodeRoles roles = context.getRoundSpecificRoles(roundId);

private final ConsensusRoundIdentifier futureHeightRoundId = new ConsensusRoundIdentifier(2, 0);

private final MessageFactory localNodeMessageFactory = context.getLocalNodeMessageFactory();

@Before
public void setup() {
context.getController().start();
}

@Test
public void messagesForFutureHeightAreBufferedUntilChainHeightCatchesUp() {
jframe marked this conversation as resolved.
Show resolved Hide resolved
final Block currentHeightBlock = context.createBlockForProposalFromChainHead(0, 30);
final Block signedCurrentHeightBlock =
IbftHelpers.createSealedBlock(
currentHeightBlock,
roles
.getAllPeers()
.stream()
.map(peer -> peer.getBlockSignature(currentHeightBlock.getHash()))
.collect(Collectors.toList()));

final Block futureHeightBlock =
context.createBlockForProposal(signedCurrentHeightBlock.getHeader(), 0, 60);

roles.getProposer().injectProposal(futureHeightRoundId, futureHeightBlock);
assertPeersReceivedNoMessages(roles.getAllPeers());

// Inject prepares and commits from all peers
roles
.getNonProposingPeers()
.forEach(
p -> {
p.injectPrepare(futureHeightRoundId, futureHeightBlock.getHash());
p.injectCommit(futureHeightRoundId, futureHeightBlock.getHash());
});
assertPeersReceivedNoMessages(roles.getAllPeers());
assertThat(context.getCurrentChainHeight()).isEqualTo(0);

// Add block to chain, and notify system of its arrival.
context.getBlockchain().appendBlock(signedCurrentHeightBlock, emptyList());
assertThat(context.getCurrentChainHeight()).isEqualTo(1);
context
.getController()
.handleNewBlockEvent(new NewChainHead(signedCurrentHeightBlock.getHeader()));

final SignedData<PreparePayload> expectedPrepareMessage =
localNodeMessageFactory.createSignedPreparePayload(
futureHeightRoundId, futureHeightBlock.getHash());

final SignedData<CommitPayload> expectedCommitMessage =
createSignedCommentPayload(
futureHeightRoundId, futureHeightBlock, context.getLocalNodeParams().getNodeKeyPair());

assertPeersReceivedExactly(roles.getAllPeers(), expectedPrepareMessage, expectedCommitMessage);
assertThat(context.getCurrentChainHeight()).isEqualTo(2);
}

@Test
public void messagesFromPreviousHeightAreDiscarded() {
final Block currentHeightBlock = context.createBlockForProposalFromChainHead(0, 30);
final Block signedCurrentHeightBlock =
IbftHelpers.createSealedBlock(
currentHeightBlock,
roles
.getAllPeers()
.stream()
.map(peer -> peer.getBlockSignature(currentHeightBlock.getHash()))
.collect(Collectors.toList()));

roles.getProposer().injectProposal(roundId, currentHeightBlock);
roles.getNonProposingPeer(0).injectPrepare(roundId, currentHeightBlock.getHash());

final SignedData<PreparePayload> expectedPrepareMessage =
localNodeMessageFactory.createSignedPreparePayload(roundId, currentHeightBlock.getHash());

assertPeersReceivedExactly(roles.getAllPeers(), expectedPrepareMessage);

// Add block to chain, and notify system of its arrival.
context.getBlockchain().appendBlock(signedCurrentHeightBlock, emptyList());
assertThat(context.getCurrentChainHeight()).isEqualTo(1);
context
.getController()
.handleNewBlockEvent(new NewChainHead(signedCurrentHeightBlock.getHeader()));

// Inject prepares and commits from all peers for the 'previous' round (i.e. the height
// from before the block arrived).
roles
.getNonProposingPeers()
.forEach(
p -> {
p.injectPrepare(roundId, currentHeightBlock.getHash());
p.injectCommit(roundId, currentHeightBlock.getHash());
});
assertPeersReceivedNoMessages(roles.getAllPeers());
assertThat(context.getCurrentChainHeight()).isEqualTo(1);
}

@Test
public void multipleNewChainHeadEventsDoesNotRestartCurrentHeightManager() {
final Block currentHeightBlock = context.createBlockForProposalFromChainHead(0, 30);

roles.getProposer().injectProposal(roundId, currentHeightBlock);
roles.getNonProposingPeer(0).injectPrepare(roundId, currentHeightBlock.getHash());

roles.getAllPeers().forEach(peer -> peer.clearReceivedMessages());

// inject a NewHeight FOR THE CURRENT HEIGHT
context
.getController()
.handleNewBlockEvent(new NewChainHead(context.getBlockchain().getChainHeadHeader()));

// Should only require 1 more prepare to close it out
roles.getNonProposingPeer(1).injectPrepare(roundId, currentHeightBlock.getHash());

final SignedData<CommitPayload> expectedCommitMessage =
createSignedCommentPayload(
roundId, currentHeightBlock, context.getLocalNodeParams().getNodeKeyPair());
assertPeersReceivedExactly(roles.getAllPeers(), expectedCommitMessage);
}
}
Expand Up @@ -64,7 +64,8 @@ public void setup() {

@Test
public void messagesForFutureRoundAreNotActionedUntilRoundIsActive() {
final Block futureBlock = context.createBlockForProposal(futureRoundId.getRoundNumber(), 60);
final Block futureBlock =
context.createBlockForProposalFromChainHead(futureRoundId.getRoundNumber(), 60);
final int quorum = IbftHelpers.calculateRequiredValidatorQuorum(NETWORK_SIZE);
final ConsensusRoundIdentifier subsequentRoundId = new ConsensusRoundIdentifier(1, 6);
final RoundSpecificNodeRoles subsequentRoles = context.getRoundSpecificRoles(subsequentRoundId);
Expand Down Expand Up @@ -117,8 +118,10 @@ public void messagesForFutureRoundAreNotActionedUntilRoundIsActive() {

@Test
public void priorRoundsCannotBeCompletedAfterReceptionOfNewRound() {
final Block initialBlock = context.createBlockForProposal(roundId.getRoundNumber(), 30);
final Block futureBlock = context.createBlockForProposal(futureRoundId.getRoundNumber(), 60);
final Block initialBlock =
context.createBlockForProposalFromChainHead(roundId.getRoundNumber(), 30);
final Block futureBlock =
context.createBlockForProposalFromChainHead(futureRoundId.getRoundNumber(), 60);

roles.getProposer().injectProposal(roundId, initialBlock);
for (final ValidatorPeer peer : roles.getNonProposingPeers()) {
Expand Down
Expand Up @@ -58,13 +58,13 @@ public class LocalNodeIsProposerTest {

@Before
public void setup() {
expectedProposedBlock = context.createBlockForProposal(0, blockTimeStamp);
expectedProposedBlock = context.createBlockForProposalFromChainHead(0, blockTimeStamp);
expectedTxProposal =
localNodeMessageFactory.createSignedProposalPayload(roundId, expectedProposedBlock);

expectedTxCommit =
createSignedCommentPayload(
expectedProposedBlock, context.getLocalNodeParams().getNodeKeyPair(), roundId);
roundId, expectedProposedBlock, context.getLocalNodeParams().getNodeKeyPair());

// Start the Controller, and trigger "block timer" to send proposal.
context.getController().start();
Expand Down
Expand Up @@ -15,10 +15,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.pantheon.consensus.ibft.support.MessageReceptionHelpers.assertPeersReceivedExactly;
import static tech.pegasys.pantheon.consensus.ibft.support.MessageReceptionHelpers.assertPeersReceivedNoMessages;
import static tech.pegasys.pantheon.consensus.ibft.support.TestHelpers.createSignedCommentPayload;

import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.payload.CommitPayload;
import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.payload.PreparePayload;
Expand All @@ -27,8 +26,6 @@
import tech.pegasys.pantheon.consensus.ibft.support.TestContext;
import tech.pegasys.pantheon.consensus.ibft.support.TestContextFactory;
import tech.pegasys.pantheon.consensus.ibft.support.ValidatorPeer;
import tech.pegasys.pantheon.crypto.SECP256K1;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Block;

import org.junit.Before;
Expand All @@ -44,7 +41,7 @@ public class LocalNodeNotProposerTest {

private final MessageFactory localNodeMessageFactory = context.getLocalNodeMessageFactory();

private final Block blockToPropose = context.createBlockForProposal(0, 15);
private final Block blockToPropose = context.createBlockForProposalFromChainHead(0, 15);

private SignedData<PreparePayload> expectedTxPrepare;
private SignedData<CommitPayload> expectedTxCommit;
Expand All @@ -54,16 +51,9 @@ public void setup() {
expectedTxPrepare =
localNodeMessageFactory.createSignedPreparePayload(roundId, blockToPropose.getHash());

final IbftExtraData extraData = IbftExtraData.decode(blockToPropose.getHeader().getExtraData());
final Signature commitSeal =
SECP256K1.sign(
IbftBlockHashing.calculateDataHashForCommittedSeal(
blockToPropose.getHeader(), extraData),
context.getLocalNodeParams().getNodeKeyPair());

expectedTxCommit =
localNodeMessageFactory.createSignedCommitPayload(
roundId, blockToPropose.getHash(), commitSeal);
createSignedCommentPayload(
roundId, blockToPropose, context.getLocalNodeParams().getNodeKeyPair());

context.getController().start();
}
Expand Down
Expand Up @@ -58,7 +58,8 @@ public void setup() {
@Test
public void newRoundMessageWithEmptyPrepareCertificatesOfferNewBlock() {
final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1);
final Block blockToPropose = context.createBlockForProposal(nextRoundId.getRoundNumber(), 15);
final Block blockToPropose =
context.createBlockForProposalFromChainHead(nextRoundId.getRoundNumber(), 15);
final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 1);

final List<SignedData<RoundChangePayload>> roundChanges =
Expand All @@ -84,7 +85,8 @@ public void newRoundMessageWithEmptyPrepareCertificatesOfferNewBlock() {
@Test
public void newRoundMessageFromIllegalSenderIsDiscardedAndNoPrepareForNewRoundIsSent() {
final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1);
final Block blockToPropose = context.createBlockForProposal(nextRoundId.getRoundNumber(), 15);
final Block blockToPropose =
context.createBlockForProposalFromChainHead(nextRoundId.getRoundNumber(), 15);

final List<SignedData<RoundChangePayload>> roundChanges =
roles
Expand All @@ -108,8 +110,8 @@ public void newRoundMessageFromIllegalSenderIsDiscardedAndNoPrepareForNewRoundIs

@Test
public void newRoundWithPrepareCertificateResultsInNewRoundStartingWithExpectedBlock() {
final Block initialBlock = context.createBlockForProposal(0, 15);
final Block reproposedBlock = context.createBlockForProposal(1, 15);
final Block initialBlock = context.createBlockForProposalFromChainHead(0, 15);
final Block reproposedBlock = context.createBlockForProposalFromChainHead(1, 15);
final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1);

final PreparedCertificate preparedCertificate =
Expand Down Expand Up @@ -163,7 +165,8 @@ public void newRoundMessageForPriorRoundIsNotActioned() {
final SignedData<ProposalPayload> proposal =
interimRoundProposer
.getMessageFactory()
.createSignedProposalPayload(interimRound, context.createBlockForProposal(1, 30));
.createSignedProposalPayload(
interimRound, context.createBlockForProposalFromChainHead(1, 30));

interimRoundProposer.injectNewRound(
interimRound, new RoundChangeCertificate(roundChangePayloads), proposal);
Expand All @@ -173,8 +176,8 @@ public void newRoundMessageForPriorRoundIsNotActioned() {

@Test
public void receiveRoundStateIsNotLostIfASecondNewRoundMessageIsReceivedForCurrentRound() {
final Block initialBlock = context.createBlockForProposal(0, 15);
final Block reproposedBlock = context.createBlockForProposal(1, 15);
final Block initialBlock = context.createBlockForProposalFromChainHead(0, 15);
final Block reproposedBlock = context.createBlockForProposalFromChainHead(1, 15);
final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1);

final PreparedCertificate preparedCertificate =
Expand Down Expand Up @@ -224,7 +227,7 @@ public void receiveRoundStateIsNotLostIfASecondNewRoundMessageIsReceivedForCurre

final SignedData<CommitPayload> expectedCommit =
TestHelpers.createSignedCommentPayload(
reproposedBlock, context.getLocalNodeParams().getNodeKeyPair(), nextRoundId);
nextRoundId, reproposedBlock, context.getLocalNodeParams().getNodeKeyPair());

assertPeersReceivedExactly(nextRoles.getAllPeers(), expectedCommit);
}
Expand Down