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 all 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,254 @@
/*
* 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;

// Context is configured such that a remote peer is responsible for proposing all block
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);
}

@Test
public void correctMessagesAreExtractedFromFutureHeightBuffer() {
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 nextHeightBlock =
context.createBlockForProposal(signedCurrentHeightBlock.getHeader(), 0, 60);
final Block signedNextHeightBlock =
IbftHelpers.createSealedBlock(
nextHeightBlock,
roles
.getAllPeers()
.stream()
.map(peer -> peer.getBlockSignature(nextHeightBlock.getHash()))
.collect(Collectors.toList()));

final Block futureHeightBlock =
context.createBlockForProposal(signedNextHeightBlock.getHeader(), 0, 90);

final ConsensusRoundIdentifier nextHeightRoundId = new ConsensusRoundIdentifier(2, 0);
final ConsensusRoundIdentifier futureHeightRoundId = new ConsensusRoundIdentifier(3, 0);

// Inject prepares and commits from all peers into FutureHeight (2 height time)
roles
.getNonProposingPeers()
.forEach(
p -> {
p.injectPrepare(futureHeightRoundId, futureHeightBlock.getHash());
p.injectCommit(futureHeightRoundId, futureHeightBlock.getHash());
});

// Add the "interim" 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()));

assertPeersReceivedNoMessages(roles.getAllPeers());
roles.getProposer().injectProposal(nextHeightRoundId, nextHeightBlock);

final SignedData<PreparePayload> expectedPrepareMessage =
localNodeMessageFactory.createSignedPreparePayload(
nextHeightRoundId, nextHeightBlock.getHash());

// Assert ONLY a prepare message was received, not any commits (i.e. futureHeightRoundId
// messages have not been used.
assertPeersReceivedExactly(roles.getAllPeers(), expectedPrepareMessage);

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

// Change to the FutureRound, and confirm prepare and commit msgs are sent
context.getBlockchain().appendBlock(signedNextHeightBlock, emptyList());
assertThat(context.getCurrentChainHeight()).isEqualTo(2);
context
.getController()
.handleNewBlockEvent(new NewChainHead(signedNextHeightBlock.getHeader()));

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

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

// Assert ONLY a prepare message was received, not any commits (i.e. futureHeightRoundId
// messages have not been used.
assertPeersReceivedExactly(
roles.getAllPeers(), expectedCommitMessage, expectedFuturePrepareMessage);
}
}
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