forked from PegaSysEng/pantheon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ibft Integration test framework (PegaSysEng#502)
To test the IBFT implementation at an integration level, an amount of "setup" is required. This commit creates a framework of "n" 'external' nodes which are able to inject messages to the IbftController, while also exposing the messages received from the local node. Thus integration tests are able to be written in terms of input events (including messages), expected (network) responses and changes in the blockchain state.
- Loading branch information
Showing
16 changed files
with
974 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
...ation-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* 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.support; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; | ||
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; | ||
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; | ||
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; | ||
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; | ||
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; | ||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.Payload; | ||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; | ||
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; | ||
|
||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
public class MessageReceptionHelpers { | ||
|
||
public static void assertPeersReceivedNoMessages(final Collection<ValidatorPeer> nodes) { | ||
nodes.forEach(n -> assertThat(n.getReceivedMessages()).isEmpty()); | ||
} | ||
|
||
@SafeVarargs | ||
public static void assertPeersReceivedExactly( | ||
final Collection<ValidatorPeer> allPeers, final SignedData<? extends Payload>... msgs) { | ||
allPeers.forEach(n -> assertThat(n.getReceivedMessages().size()).isEqualTo(msgs.length)); | ||
|
||
List<SignedData<? extends Payload>> msgList = Arrays.asList(msgs); | ||
|
||
for (int i = 0; i < msgList.size(); i++) { | ||
final int index = i; | ||
final SignedData<? extends Payload> msg = msgList.get(index); | ||
allPeers.forEach( | ||
n -> { | ||
final List<MessageData> rxMsgs = n.getReceivedMessages(); | ||
final MessageData rxMsgData = rxMsgs.get(index); | ||
assertThat(msgMatchesExpected(rxMsgData, msg)).isTrue(); | ||
}); | ||
} | ||
allPeers.forEach(p -> p.clearReceivedMessages()); | ||
} | ||
|
||
public static boolean msgMatchesExpected( | ||
final MessageData actual, final SignedData<? extends Payload> expected) { | ||
final Payload expectedPayload = expected.getPayload(); | ||
|
||
switch (expectedPayload.getMessageType()) { | ||
case IbftV2.PROPOSAL: | ||
return ProposalMessage.fromMessage(actual).decode().equals(expected); | ||
case IbftV2.PREPARE: | ||
return PrepareMessage.fromMessage(actual).decode().equals(expected); | ||
case IbftV2.COMMIT: | ||
return CommitMessage.fromMessage(actual).decode().equals(expected); | ||
case IbftV2.NEW_ROUND: | ||
return NewRoundMessage.fromMessage(actual).decode().equals(expected); | ||
case IbftV2.ROUND_CHANGE: | ||
return RoundChangeMessage.fromMessage(actual).decode().equals(expected); | ||
default: | ||
return false; | ||
} | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
...src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
* 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.support; | ||
|
||
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; | ||
import tech.pegasys.pantheon.ethereum.core.Address; | ||
import tech.pegasys.pantheon.ethereum.core.Util; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.TreeMap; | ||
|
||
import com.google.common.collect.Iterables; | ||
|
||
public class NetworkLayout { | ||
|
||
private final NodeParams localNode; | ||
private final TreeMap<Address, NodeParams> addressKeyMap; | ||
private final List<NodeParams> remotePeers; | ||
|
||
public NetworkLayout( | ||
final NodeParams localNode, final TreeMap<Address, NodeParams> addressKeyMap) { | ||
this.localNode = localNode; | ||
this.addressKeyMap = addressKeyMap; | ||
this.remotePeers = new ArrayList<>(addressKeyMap.values()); | ||
this.remotePeers.remove(localNode); | ||
} | ||
|
||
public static NetworkLayout createNetworkLayout( | ||
final int validatorCount, final int firstLocalNodeBlockNum) { | ||
final TreeMap<Address, NodeParams> addressKeyMap = createValidators(validatorCount); | ||
|
||
final NodeParams localNode = Iterables.get(addressKeyMap.values(), firstLocalNodeBlockNum); | ||
|
||
return new NetworkLayout(localNode, addressKeyMap); | ||
} | ||
|
||
private static TreeMap<Address, NodeParams> createValidators(final int validatorCount) { | ||
// Map is required to be sorted by address | ||
final TreeMap<Address, NodeParams> addressKeyMap = new TreeMap<>(); | ||
|
||
for (int i = 0; i < validatorCount; i++) { | ||
final KeyPair newKeyPair = KeyPair.generate(); | ||
final Address nodeAddress = Util.publicKeyToAddress(newKeyPair.getPublicKey()); | ||
addressKeyMap.put(nodeAddress, new NodeParams(nodeAddress, newKeyPair)); | ||
} | ||
|
||
return addressKeyMap; | ||
} | ||
|
||
public Set<Address> getValidatorAddresses() { | ||
return addressKeyMap.keySet(); | ||
} | ||
|
||
public NodeParams getLocalNode() { | ||
return localNode; | ||
} | ||
|
||
public List<NodeParams> getRemotePeers() { | ||
return remotePeers; | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
...ft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* 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.support; | ||
|
||
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; | ||
import tech.pegasys.pantheon.ethereum.core.Address; | ||
|
||
public class NodeParams { | ||
private final Address address; | ||
private final KeyPair nodeKeys; | ||
|
||
public NodeParams(final Address address, final KeyPair nodeKeys) { | ||
this.address = address; | ||
this.nodeKeys = nodeKeys; | ||
} | ||
|
||
public Address getAddress() { | ||
return address; | ||
} | ||
|
||
public KeyPair getNodeKeyPair() { | ||
return nodeKeys; | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
...ration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* 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.support; | ||
|
||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
public class RoundSpecificNodeRoles { | ||
|
||
private final ValidatorPeer proposer; | ||
private final Collection<ValidatorPeer> peers; | ||
private final List<ValidatorPeer> nonProposingPeers; | ||
|
||
public RoundSpecificNodeRoles( | ||
final ValidatorPeer proposer, | ||
final Collection<ValidatorPeer> peers, | ||
final List<ValidatorPeer> nonProposingPeers) { | ||
this.proposer = proposer; | ||
this.peers = peers; | ||
this.nonProposingPeers = nonProposingPeers; | ||
} | ||
|
||
public ValidatorPeer getProposer() { | ||
return proposer; | ||
} | ||
|
||
public Collection<ValidatorPeer> getAllPeers() { | ||
return peers; | ||
} | ||
|
||
public List<ValidatorPeer> getNonProposingPeers() { | ||
return nonProposingPeers; | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
...tegration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* 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.support; | ||
|
||
import tech.pegasys.pantheon.consensus.ibft.network.IbftMulticaster; | ||
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; | ||
|
||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
import com.google.common.collect.Lists; | ||
|
||
public class StubIbftMulticaster implements IbftMulticaster { | ||
|
||
private final List<ValidatorPeer> validatorNodes = Lists.newArrayList(); | ||
|
||
public StubIbftMulticaster() {} | ||
|
||
public void addNetworkPeers(final Collection<ValidatorPeer> nodes) { | ||
validatorNodes.addAll(nodes); | ||
} | ||
|
||
@Override | ||
public void multicastToValidators(final MessageData message) { | ||
validatorNodes.forEach(v -> v.handleReceivedMessage(message)); | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
...t/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* 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.support; | ||
|
||
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; | ||
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; | ||
import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftController; | ||
import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftFinalState; | ||
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; | ||
import tech.pegasys.pantheon.ethereum.core.Address; | ||
import tech.pegasys.pantheon.ethereum.core.Block; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
/* | ||
Responsible for creating an environment in which integration testing can be conducted. | ||
The test setup is an 'n' node network, one of which is the local node (i.e. the Unit Under Test). | ||
There is some complexity with determining the which node is the proposer etc. THus necessitating | ||
NetworkLayout and RoundSpecificNodeRoles concepts. | ||
*/ | ||
public class TestContext { | ||
|
||
private Map<Address, ValidatorPeer> remotePeers; | ||
private final MutableBlockchain blockchain; | ||
private final IbftController controller; | ||
private final IbftFinalState finalState; | ||
|
||
public TestContext( | ||
final Map<Address, ValidatorPeer> remotePeers, | ||
final MutableBlockchain blockchain, | ||
final IbftController controller, | ||
final IbftFinalState finalState) { | ||
this.remotePeers = remotePeers; | ||
this.blockchain = blockchain; | ||
this.controller = controller; | ||
this.finalState = finalState; | ||
} | ||
|
||
public Collection<ValidatorPeer> getRemotePeers() { | ||
return remotePeers.values(); | ||
} | ||
|
||
public MutableBlockchain getBlockchain() { | ||
return blockchain; | ||
} | ||
|
||
public IbftController getController() { | ||
return controller; | ||
} | ||
|
||
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 RoundSpecificNodeRoles getRoundSpecificRoles(final ConsensusRoundIdentifier roundId) { | ||
// This will return NULL if the LOCAL node is the proposer for the specified round | ||
final Address proposerAddress = finalState.getProposerForRound(roundId); | ||
final ValidatorPeer proposer = remotePeers.getOrDefault(proposerAddress, null); | ||
|
||
final List<ValidatorPeer> nonProposers = new ArrayList<>(remotePeers.values()); | ||
nonProposers.remove(proposer); | ||
|
||
return new RoundSpecificNodeRoles(proposer, remotePeers.values(), nonProposers); | ||
} | ||
|
||
public NodeParams getLocalNodeParams() { | ||
return new NodeParams(finalState.getLocalAddress(), finalState.getNodeKeys()); | ||
} | ||
|
||
public long getCurrentChainHeight() { | ||
return blockchain.getChainHeadBlockNumber(); | ||
} | ||
} |
Oops, something went wrong.