From ad6ed07d555fc7b23b1eca435fdb3f2dffc52ecc Mon Sep 17 00:00:00 2001 From: tmohay Date: Thu, 3 Jan 2019 11:41:29 +1100 Subject: [PATCH 1/8] Ibft Integration test framework --- consensus/ibft/build.gradle | 8 +- .../ibft/support/MessageReceptionHelpers.java | 81 +++++ .../consensus/ibft/support/NetworkLayout.java | 73 +++++ .../consensus/ibft/support/NodeParams.java | 34 +++ .../RoundChangeCertificateCreator.java | 23 ++ .../ibft/support/RoundSpecificNodeRoles.java | 44 +++ .../ibft/support/StubIbftMulticaster.java | 36 +++ .../consensus/ibft/support/TestContext.java | 289 ++++++++++++++++++ .../consensus/ibft/support/ValidatorPeer.java | 132 ++++++++ .../ibft/tests/LocalNodeNotProposerTest.java | 165 ++++++++++ .../consensus/ibft/IbftBlockInterface.java | 4 +- .../consensus/ibft/IbftExtraData.java | 6 +- .../ibft/network/IbftMulticaster.java | 20 ++ .../ibft/network/IbftNetworkPeers.java | 3 +- .../ibft/statemachine/IbftFinalState.java | 8 +- .../statemachine/IbftMessageTransmitter.java | 18 +- 16 files changed, 924 insertions(+), 20 deletions(-) create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundChangeCertificateCreator.java create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMulticaster.java diff --git a/consensus/ibft/build.gradle b/consensus/ibft/build.gradle index 6d6a764cda..8852844017 100644 --- a/consensus/ibft/build.gradle +++ b/consensus/ibft/build.gradle @@ -40,8 +40,14 @@ dependencies { implementation 'io.vertx:vertx-core' implementation 'com.google.guava:guava' + integrationTestImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') + integrationTestImplementation project(path: ':config:', configuration: 'testSupportArtifacts') + testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') - testImplementation project(path: ':config:', configuration:'testSupportArtifacts') + testImplementation project(path: ':config:', configuration: 'testSupportArtifacts') + integrationTestImplementation 'junit:junit' + integrationTestImplementation 'org.assertj:assertj-core' + integrationTestImplementation 'org.mockito:mockito-core' testImplementation 'junit:junit' testImplementation 'org.awaitility:awaitility' diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java new file mode 100644 index 0000000000..b6bc76d489 --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java @@ -0,0 +1,81 @@ +/* + * 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.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.NewRoundPayload; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.Payload; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangePayload; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; +import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; + +import java.util.Collection; +import java.util.List; + +public class MessageReceptionHelpers { + + public static void assertPeersReceivedNoMessages(final Collection nodes) { + nodes.forEach(n -> assertThat(n.getReceivedMessages()).isEmpty()); + } + + @SafeVarargs + public static void assertPeersReceivedExactly( + final Collection allPeers, final SignedData... msgs) { + allPeers.forEach(n -> assertThat(n.getReceivedMessages().size()).isEqualTo(msgs.length)); + + for (SignedData msg : msgs) { + allPeers.forEach( + n -> { + final List rxMsgs = n.getReceivedMessages(); + final MessageData rxMsgData = rxMsgs.get(0); + rxMsgs.remove(0); + assertThat(msgMatchesExpected(rxMsgData, msg)).isTrue(); + }); + } + } + + public static boolean msgMatchesExpected( + final MessageData actual, SignedData expected) { + final Payload expectedPayload = expected.getPayload(); + if (expectedPayload instanceof ProposalPayload) { + final SignedData actualPayload = + ProposalMessage.fromMessage(actual).decode(); + return actualPayload.equals(expected); + } else if (expectedPayload instanceof PreparePayload) { + final SignedData actualPayload = PrepareMessage.fromMessage(actual).decode(); + return actualPayload.equals(expected); + } else if (expectedPayload instanceof CommitPayload) { + final SignedData actualPayload = CommitMessage.fromMessage(actual).decode(); + return actualPayload.equals(expected); + } else if (expectedPayload instanceof RoundChangePayload) { + final SignedData actualPayload = + RoundChangeMessage.fromMessage(actual).decode(); + return actualPayload.equals(expected); + } else if (expectedPayload instanceof NewRoundPayload) { + final SignedData actualPayload = + NewRoundMessage.fromMessage(actual).decode(); + return actualPayload.equals(expected); + } else { + return false; + } + } +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java new file mode 100644 index 0000000000..cf69903079 --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java @@ -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; +import com.google.common.collect.Maps; + +public class NetworkLayout { + + final NodeParams localNode; + final TreeMap addressKeyMap; + final List remotePeers; + + public NetworkLayout(NodeParams localNode, TreeMap 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 addressKeyMap = createValidators(validatorCount); + + final NodeParams localNode = Iterables.get(addressKeyMap.values(), firstLocalNodeBlockNum); + + return new NetworkLayout(localNode, addressKeyMap); + } + + private static TreeMap createValidators(final int validatorCount) { + // Map is required to be sorted by address + final TreeMap addressKeyMap = Maps.newTreeMap(); + + 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
getValidatorAddresses() { + return addressKeyMap.keySet(); + } + + public NodeParams getLocalNode() { + return localNode; + } + + public List getRemotePeers() { + return remotePeers; + } +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java new file mode 100644 index 0000000000..45fba734b4 --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java @@ -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 Address address; + private KeyPair nodeKeys; + + public NodeParams(Address address, KeyPair nodeKeys) { + this.address = address; + this.nodeKeys = nodeKeys; + } + + public Address getAddress() { + return address; + } + + public KeyPair getNodeKeys() { + return nodeKeys; + } +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundChangeCertificateCreator.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundChangeCertificateCreator.java new file mode 100644 index 0000000000..fd46d96f50 --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundChangeCertificateCreator.java @@ -0,0 +1,23 @@ +/* + * 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.List; + +public class RoundChangeCertificateCreator { + + public static void createRoundChangeCerificate( + final ValidatorPeer from, + final ValidatorPeer proposalFrom, + final List preparedPeers) {} +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java new file mode 100644 index 0000000000..b86b57ba18 --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java @@ -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 peers; + private final List nonProposingPeers; + + public RoundSpecificNodeRoles( + ValidatorPeer proposer, + Collection peers, + List nonProposingPeers) { + this.proposer = proposer; + this.peers = peers; + this.nonProposingPeers = nonProposingPeers; + } + + public ValidatorPeer getProposer() { + return proposer; + } + + public Collection getAllPeers() { + return peers; + } + + public List getNonProposingPeers() { + return nonProposingPeers; + } +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java new file mode 100644 index 0000000000..93c254b1f6 --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java @@ -0,0 +1,36 @@ +/* + * 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 validatorNodes = Lists.newArrayList(); + + public StubIbftMulticaster() {} + + public void addNetworkPeers(final Collection nodes) { + validatorNodes.addAll(nodes); + } + + public void multicastToValidators(final MessageData message) { + validatorNodes.stream().forEach(v -> v.handleReceivedMessage(message)); + } +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java new file mode 100644 index 0000000000..f6099b478d --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java @@ -0,0 +1,289 @@ +/* + * 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 tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; +import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; + +import tech.pegasys.pantheon.config.StubGenesisConfigOptions; +import tech.pegasys.pantheon.consensus.common.BlockInterface; +import tech.pegasys.pantheon.consensus.common.EpochManager; +import tech.pegasys.pantheon.consensus.common.VoteProposer; +import tech.pegasys.pantheon.consensus.common.VoteTally; +import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater; +import tech.pegasys.pantheon.consensus.ibft.BlockTimer; +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; +import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing; +import tech.pegasys.pantheon.consensus.ibft.IbftBlockHeaderValidationRulesetFactory; +import tech.pegasys.pantheon.consensus.ibft.IbftBlockInterface; +import tech.pegasys.pantheon.consensus.ibft.IbftContext; +import tech.pegasys.pantheon.consensus.ibft.IbftEventQueue; +import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; +import tech.pegasys.pantheon.consensus.ibft.IbftHelpers; +import tech.pegasys.pantheon.consensus.ibft.IbftProtocolSchedule; +import tech.pegasys.pantheon.consensus.ibft.RoundTimer; +import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreatorFactory; +import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; +import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftBlockHeightManagerFactory; +import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftController; +import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftFinalState; +import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftRoundFactory; +import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory; +import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; +import tech.pegasys.pantheon.ethereum.ProtocolContext; +import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.AddressHelpers; +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.Hash; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; +import tech.pegasys.pantheon.ethereum.core.PendingTransactions; +import tech.pegasys.pantheon.ethereum.core.Util; +import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.ethereum.db.WorldStateArchive; +import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.uint.UInt256; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import com.google.common.collect.Iterables; + +/* +Responsible for creating an environment in which integration testing can be conducted. + +The test setup is a 'n' node network, the first of which can be considered the "local" node. +All allValidators are in the genesis block's extraData. Validator + + */ +public class TestContext { + + public static final int EPOCH_LENGTH = 10_000; + public static final int BLOCK_TIMER_SEC = 3; + public static final int ROUND_TIMER_SEC = 12; + + private Map remotePeers; + private final MutableBlockchain blockchain; + private final IbftController controller; + private final IbftFinalState finalState; + + public TestContext( + final Map remotePeers, + final MutableBlockchain blockchain, + final IbftController controller, + final IbftFinalState finalState) { + this.remotePeers = remotePeers; + this.blockchain = blockchain; + this.controller = controller; + this.finalState = finalState; + } + + public Collection getRemotePeers() { + return remotePeers.values(); + } + + public MutableBlockchain getBlockchain() { + return blockchain; + } + + public IbftController getController() { + return controller; + } + + public MessageFactory getLocalNodeMessageFactory() { + return finalState.getMessageFactory(); + } + + public Block createBlockForProposal(int round, 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 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 static TestContext createTestEnvWithUtcClock( + final int validatorCount, final int indexOfFirstLocallyProposedBlock) { + return createTestEnvironment( + validatorCount, indexOfFirstLocallyProposedBlock, Clock.systemUTC()); + } + + public static TestContext createTestEnvironment( + final int validatorCount, final int indexOfFirstLocallyProposedBlock, final Clock clock) { + + final NetworkLayout networkNodes = + NetworkLayout.createNetworkLayout(validatorCount, indexOfFirstLocallyProposedBlock); + + final Block genesisBlock = createGenesisBlock(networkNodes.getValidatorAddresses()); + final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); + final MutableBlockchain blockChain = + createInMemoryBlockchain(genesisBlock, IbftBlockHashing::calculateHashOfIbftBlockOnChain); + + final MiningParameters miningParams = + new MiningParameters( + AddressHelpers.ofValue(1), + Wei.ZERO, + BytesValue.wrap("Ibft Int tests".getBytes()), + true); + + final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions(); + genesisConfigOptions.byzantiumBlock(0); + + final ProtocolSchedule protocolSchedule = + IbftProtocolSchedule.create(genesisConfigOptions); + + final KeyPair nodeKeys = networkNodes.getLocalNode().getNodeKeys(); + + ///////////////////////////////////////////////////////////////////////////////////// + // From here down IS BASICALLY taken from IbftPantheonController (consts are tweaked) + final EpochManager epochManager = new EpochManager(EPOCH_LENGTH); + + final BlockInterface blockInterface = new IbftBlockInterface(); + final VoteTally voteTally = + new VoteTallyUpdater(epochManager, blockInterface).buildVoteTallyFromBlockchain(blockChain); + + final VoteProposer voteProposer = new VoteProposer(); + + final ProtocolContext protocolContext = + new ProtocolContext<>( + blockChain, worldStateArchive, new IbftContext(voteTally, voteProposer)); + + final IbftEventQueue ibftEventQueue = new IbftEventQueue(); + + final IbftBlockCreatorFactory blockCreatorFactory = + new IbftBlockCreatorFactory( + (gasLimit) -> gasLimit, + new PendingTransactions(1), // changed from IbftPantheonController + protocolContext, + protocolSchedule, + miningParams, + Util.publicKeyToAddress(nodeKeys.getPublicKey())); + + final ProposerSelector proposerSelector = + new ProposerSelector(blockChain, voteTally, blockInterface, true); + + // Use a stubbed version of the multicaster, to prevent creating PeerConnections etc. + final StubIbftMulticaster stubbedNetworkPeers = new StubIbftMulticaster(); + + final BlockHeaderValidator blockHeaderValidator = + IbftBlockHeaderValidationRulesetFactory.ibftProposedBlockValidator(BLOCK_TIMER_SEC); + + final IbftFinalState finalState = + new IbftFinalState( + voteTally, + nodeKeys, + Util.publicKeyToAddress(nodeKeys.getPublicKey()), + proposerSelector, + stubbedNetworkPeers, + new RoundTimer( + ibftEventQueue, ROUND_TIMER_SEC * 1000, Executors.newScheduledThreadPool(1)), + new BlockTimer( + ibftEventQueue, + BLOCK_TIMER_SEC * 1000, + Executors.newScheduledThreadPool(1), + Clock.systemUTC()), + blockCreatorFactory, + new MessageFactory(nodeKeys), + blockHeaderValidator, + clock); + + final MessageValidatorFactory messageValidatorFactory = + new MessageValidatorFactory(proposerSelector, blockHeaderValidator, protocolContext); + + final IbftController ibftController = + new IbftController( + blockChain, + finalState, + new IbftBlockHeightManagerFactory( + finalState, + new IbftRoundFactory(finalState, protocolContext, protocolSchedule), + messageValidatorFactory, + protocolContext)); + //////////////////////////// END IBFT PantheonController //////////////////////////// + + // Add each networkNode to the Multicaster (such that each can receive msgs from local node). + // NOTE: the remotePeers needs to be ordered based on Address (as this is used to determine + // the proposer order which must be managed in test). + final Map remotePeers = + networkNodes + .getRemotePeers() + .stream() + .collect( + Collectors.toMap( + NodeParams::getAddress, + np -> + new ValidatorPeer(np, new MessageFactory(np.getNodeKeys()), ibftController), + (u, v) -> { + throw new IllegalStateException(String.format("Duplicate key %s", u)); + }, + LinkedHashMap::new)); + + stubbedNetworkPeers.addNetworkPeers(remotePeers.values()); + + return new TestContext(remotePeers, blockChain, ibftController, finalState); + } + + private static Block createGenesisBlock(final Set
validators) { + final Address coinbase = Iterables.get(validators, 0); + final BlockHeaderTestFixture headerTestFixture = new BlockHeaderTestFixture(); + final IbftExtraData extraData = + new IbftExtraData( + BytesValue.wrap(new byte[32]), + Collections.emptyList(), + Optional.empty(), + 0, + validators); + headerTestFixture.extraData(extraData.encode()); + headerTestFixture.mixHash(IbftHelpers.EXPECTED_MIX_HASH); + headerTestFixture.difficulty(UInt256.ONE); + headerTestFixture.ommersHash(Hash.EMPTY_LIST_HASH); + headerTestFixture.nonce(0); + headerTestFixture.timestamp(0); + headerTestFixture.parentHash(Hash.ZERO); + headerTestFixture.gasLimit(5000); + headerTestFixture.coinbase(coinbase); + + final BlockHeader genesisHeader = headerTestFixture.buildHeader(); + return new Block( + genesisHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); + } +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java new file mode 100644 index 0000000000..eab8f4464a --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java @@ -0,0 +1,132 @@ +/* + * 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.ibftevent.IbftEvents; +import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent; +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.statemachine.IbftController; +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.core.Address; +import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; +import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage; + +import java.util.List; +import java.util.Optional; + +import com.google.common.collect.Lists; + +// Each "inject" function returns the SignedPayload representation of the transmitted message. +public class ValidatorPeer { + + private final Address nodeAddress; + private final KeyPair nodeKeys; + private final MessageFactory messageFactory; + private List receivedMessages = Lists.newArrayList(); + + private final IbftController localNodeController; + + public ValidatorPeer( + final NodeParams nodeParams, + final MessageFactory messageFactory, + final IbftController localNodeController) { + this.nodeKeys = nodeParams.getNodeKeys(); + this.nodeAddress = nodeParams.getAddress(); + this.messageFactory = messageFactory; + this.localNodeController = localNodeController; + } + + public SignedData injectProposal( + final ConsensusRoundIdentifier rId, final Block block) { + final SignedData payload = + messageFactory.createSignedProposalPayload(rId, block); + injectMessage(ProposalMessage.create(payload)); + return payload; + } + + public SignedData injectPrepare( + final ConsensusRoundIdentifier rId, final Hash digest) { + final SignedData payload = + messageFactory.createSignedPreparePayload(rId, digest); + injectMessage(PrepareMessage.create(payload)); + return payload; + } + + public SignedData injectCommit( + final ConsensusRoundIdentifier rId, final Hash digest) { + final Signature commitSeal = SECP256K1.sign(digest, nodeKeys); + final SignedData payload = + messageFactory.createSignedCommitPayload(rId, digest, commitSeal); + injectMessage(CommitMessage.create(payload)); + return payload; + } + + public SignedData injectNewRound( + final ConsensusRoundIdentifier rId, + final RoundChangeCertificate roundChangeCertificate, + final SignedData proposalPayload) { + + final SignedData payload = + messageFactory.createSignedNewRoundPayload(rId, roundChangeCertificate, proposalPayload); + injectMessage(NewRoundMessage.create(payload)); + return payload; + } + + public SignedData injectRoundChange( + final ConsensusRoundIdentifier rId, final Optional preparedCertificate) { + final SignedData payload = + messageFactory.createSignedRoundChangePayload(rId, preparedCertificate); + injectMessage(RoundChangeMessage.create(payload)); + return payload; + } + + public void handleReceivedMessage(final MessageData message) { + receivedMessages.add(message); + } + + public List getReceivedMessages() { + return receivedMessages; + } + + public void clearReceivedMessages() { + receivedMessages.clear(); + } + + public void injectMessage(final MessageData msgData) { + final DefaultMessage message = new DefaultMessage(null, msgData); + localNodeController.handleMessageEvent( + (IbftReceivedMessageEvent) IbftEvents.fromMessage(message)); + } + + public MessageFactory getMessageFactory() { + return messageFactory; + } +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java new file mode 100644 index 0000000000..13260b18cf --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java @@ -0,0 +1,165 @@ +/* + * 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 tech.pegasys.pantheon.consensus.ibft.support.MessageReceptionHelpers.assertPeersReceivedExactly; +import static tech.pegasys.pantheon.consensus.ibft.support.MessageReceptionHelpers.assertPeersReceivedNoMessages; + +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.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.SignedData; +import tech.pegasys.pantheon.consensus.ibft.support.RoundSpecificNodeRoles; +import tech.pegasys.pantheon.consensus.ibft.support.TestContext; +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; +import org.junit.Test; + +public class LocalNodeNotProposerTest { + + // This ensures the localNode (i.e. the UUT) will not create the first block + private final TestContext context = TestContext.createTestEnvWithUtcClock(4, 0); + private final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(1, 0); + private final RoundSpecificNodeRoles roles = context.getRoundSpecificRoles(roundId); + + private final MessageFactory localNodeMessageFactory = context.getLocalNodeMessageFactory(); + + private final Block blockToPropose = context.createBlockForProposal(0, 15); + + private SignedData expectedTxPrepare; + private SignedData expectedTxCommit; + + @Before + 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().getNodeKeys()); + + expectedTxCommit = + localNodeMessageFactory.createSignedCommitPayload( + roundId, blockToPropose.getHash(), commitSeal); + + context.getController().start(); + } + + @Test + public void basicCase() { + roles.getProposer().injectProposal(roundId, blockToPropose); + + assertPeersReceivedExactly(roles.getAllPeers(), expectedTxPrepare); + + roles.getNonProposingPeers().get(0).injectPrepare(roundId, blockToPropose.getHash()); + assertPeersReceivedExactly(roles.getAllPeers(), expectedTxCommit); + + // Ensure the local blockchain has NOT incremented yet. + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + + // NO further messages should be transmitted when another Prepare is received. + roles.getNonProposingPeers().get(1).injectPrepare(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + + // Inject a commit, ensure blockChain is not updated, and no message are sent (not quorum yet) + roles.getNonProposingPeers().get(0).injectCommit(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + + // A second commit message means quorum is reached, and blockchain should be updated. + roles.getNonProposingPeers().get(1).injectCommit(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + + // ensure any further commit messages do not affect the system + roles.getProposer().injectCommit(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + } + + @Test + public void prepareFromProposerIsIgnored() { + roles.getProposer().injectProposal(roundId, blockToPropose); + assertPeersReceivedExactly(roles.getAllPeers(), expectedTxPrepare); + + // No commit message transmitted after receiving prepare from proposer + roles.getProposer().injectPrepare(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + + roles.getNonProposingPeers().get(1).injectPrepare(roundId, blockToPropose.getHash()); + assertPeersReceivedExactly(roles.getAllPeers(), expectedTxCommit); + + // Inject a commit, ensure blockChain is not updated, and no message are sent (not quorum yet) + roles.getNonProposingPeers().get(0).injectCommit(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + + // A second commit message means quorum is reached, and blockchain should be updated. + roles.getNonProposingPeers().get(1).injectCommit(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + } + + @Test + public void commitMessagesReceivedBeforePrepareCorrectlyImports() { + // All peers send a commit, then all non-proposing peers send a prepare, when then Proposal + // arrives last, the chain is updated, and a prepare and commit message are transmitted. + for (final ValidatorPeer peer : roles.getAllPeers()) { + peer.injectCommit(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + } + + for (final ValidatorPeer peer : roles.getNonProposingPeers()) { + peer.injectPrepare(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + } + + roles.getProposer().injectProposal(roundId, blockToPropose); + // TODO(tmm): Unfortunatley, there are times that the Commit will go out BEFORE the prepare + // This is one of them :( Maybe fix the testing to be ignorant of ordering? + assertPeersReceivedExactly(roles.getAllPeers(), expectedTxCommit, expectedTxPrepare); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + } + + @Test + public void + fullQuorumOfCommitMessagesReceivedThenProposalImportsBlockCommitSentAfterFinalPrepare() { + for (final ValidatorPeer peer : roles.getAllPeers()) { + peer.injectCommit(roundId, blockToPropose.getHash()); + assertPeersReceivedNoMessages(roles.getAllPeers()); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + } + + roles.getProposer().injectProposal(roundId, blockToPropose); + assertPeersReceivedExactly(roles.getAllPeers(), expectedTxPrepare); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + + roles.getNonProposingPeers().get(0).injectPrepare(roundId, blockToPropose.getHash()); + assertPeersReceivedExactly(roles.getAllPeers(), expectedTxCommit); + assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockInterface.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockInterface.java index 7bae8f8c64..e8e8807e3a 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockInterface.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftBlockInterface.java @@ -18,7 +18,7 @@ import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import java.util.List; +import java.util.Collection; import java.util.Optional; public class IbftBlockInterface implements BlockInterface { @@ -45,7 +45,7 @@ public Optional extractVoteFromHeader(final BlockHeader header) { } @Override - public List
validatorsInBlock(final BlockHeader header) { + public Collection
validatorsInBlock(final BlockHeader header) { final IbftExtraData ibftExtraData = IbftExtraData.decode(header.getExtraData()); return ibftExtraData.getValidators(); } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java index 748637a717..4d30e31e4f 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java @@ -38,14 +38,14 @@ public class IbftExtraData { private final Collection seals; private final Optional vote; private final int round; - private final List
validators; + private final Collection
validators; public IbftExtraData( final BytesValue vanityData, final Collection seals, final Optional vote, final int round, - final List
validators) { + final Collection
validators) { checkNotNull(vanityData); checkNotNull(seals); @@ -132,7 +132,7 @@ public Collection getSeals() { return seals; } - public List
getValidators() { + public Collection
getValidators() { return validators; } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMulticaster.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMulticaster.java new file mode 100644 index 0000000000..bdb8350a44 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMulticaster.java @@ -0,0 +1,20 @@ +/* + * 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.network; + +import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; + +public interface IbftMulticaster { + + void multicastToValidators(final MessageData message); +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftNetworkPeers.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftNetworkPeers.java index fa5c15f6e2..92f10b0129 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftNetworkPeers.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftNetworkPeers.java @@ -29,7 +29,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class IbftNetworkPeers { +public class IbftNetworkPeers implements IbftMulticaster { private static final Logger LOG = LogManager.getLogger(); @@ -52,6 +52,7 @@ public void peerRemoved(final PeerConnection removedConnection) { peerConnections.remove(peerAddress); } + @Override public void multicastToValidators(final MessageData message) { final Collection
validators = validatorProvider.getValidators(); sendMessageToSpecificAddresses(validators, message); diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftFinalState.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftFinalState.java index e41c2db91b..68a916f575 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftFinalState.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftFinalState.java @@ -22,7 +22,7 @@ import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreatorFactory; import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; -import tech.pegasys.pantheon.consensus.ibft.network.IbftNetworkPeers; +import tech.pegasys.pantheon.consensus.ibft.network.IbftMulticaster; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; @@ -36,7 +36,7 @@ public class IbftFinalState { private final KeyPair nodeKeys; private final Address localAddress; private final ProposerSelector proposerSelector; - private final IbftNetworkPeers peers; + private final IbftMulticaster peers; private final RoundTimer roundTimer; private final BlockTimer blockTimer; private final IbftBlockCreatorFactory blockCreatorFactory; @@ -50,7 +50,7 @@ public IbftFinalState( final KeyPair nodeKeys, final Address localAddress, final ProposerSelector proposerSelector, - final IbftNetworkPeers peers, + final IbftMulticaster peers, final RoundTimer roundTimer, final BlockTimer blockTimer, final IbftBlockCreatorFactory blockCreatorFactory, @@ -95,7 +95,7 @@ public boolean isLocalNodeProposerForRound(final ConsensusRoundIdentifier roundI return getProposerForRound(roundIdentifier).equals(localAddress); } - public IbftNetworkPeers getPeers() { + public IbftMulticaster getPeers() { return peers; } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftMessageTransmitter.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftMessageTransmitter.java index d91aad559a..4649df1920 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftMessageTransmitter.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftMessageTransmitter.java @@ -27,7 +27,7 @@ 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.consensus.ibft.network.IbftMulticaster; import tech.pegasys.pantheon.crypto.SECP256K1.Signature; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Hash; @@ -37,12 +37,12 @@ public class IbftMessageTransmitter { private final MessageFactory messageFactory; - private final IbftNetworkPeers networkPeers; + private final IbftMulticaster multicaster; public IbftMessageTransmitter( - final MessageFactory messageFactory, final IbftNetworkPeers networkPeers) { + final MessageFactory messageFactory, final IbftMulticaster multicaster) { this.messageFactory = messageFactory; - this.networkPeers = networkPeers; + this.multicaster = multicaster; } public void multicastProposal(final ConsensusRoundIdentifier roundIdentifier, final Block block) { @@ -51,7 +51,7 @@ public void multicastProposal(final ConsensusRoundIdentifier roundIdentifier, fi final ProposalMessage message = ProposalMessage.create(signedPayload); - networkPeers.multicastToValidators(message); + multicaster.multicastToValidators(message); } public void multicastPrepare(final ConsensusRoundIdentifier roundIdentifier, final Hash digest) { @@ -60,7 +60,7 @@ public void multicastPrepare(final ConsensusRoundIdentifier roundIdentifier, fin final PrepareMessage message = PrepareMessage.create(signedPayload); - networkPeers.multicastToValidators(message); + multicaster.multicastToValidators(message); } public void multicastCommit( @@ -72,7 +72,7 @@ public void multicastCommit( final CommitMessage message = CommitMessage.create(signedPayload); - networkPeers.multicastToValidators(message); + multicaster.multicastToValidators(message); } public void multicastRoundChange( @@ -84,7 +84,7 @@ public void multicastRoundChange( final RoundChangeMessage message = RoundChangeMessage.create(signedPayload); - networkPeers.multicastToValidators(message); + multicaster.multicastToValidators(message); } public void multicastNewRound( @@ -98,6 +98,6 @@ public void multicastNewRound( final NewRoundMessage message = NewRoundMessage.create(signedPayload); - networkPeers.multicastToValidators(message); + multicaster.multicastToValidators(message); } } From 8358827a039c74549145ca2ff3bf47b812181eb6 Mon Sep 17 00:00:00 2001 From: tmohay Date: Thu, 3 Jan 2019 17:10:35 +1100 Subject: [PATCH 2/8] Add missing @Override --- .../pantheon/consensus/ibft/support/StubIbftMulticaster.java | 1 + 1 file changed, 1 insertion(+) diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java index 93c254b1f6..91cadbfe0d 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java @@ -30,6 +30,7 @@ public void addNetworkPeers(final Collection nodes) { validatorNodes.addAll(nodes); } + @Override public void multicastToValidators(final MessageData message) { validatorNodes.stream().forEach(v -> v.handleReceivedMessage(message)); } From f19ff16b5f6b547ab90b6efd254da7c185a0e545 Mon Sep 17 00:00:00 2001 From: tmohay Date: Fri, 4 Jan 2019 08:30:04 +1100 Subject: [PATCH 3/8] repair errorprone checks --- .../ibft/support/MessageReceptionHelpers.java | 2 +- .../consensus/ibft/support/NetworkLayout.java | 3 ++- .../consensus/ibft/support/NodeParams.java | 2 +- .../RoundChangeCertificateCreator.java | 23 ------------------- .../ibft/support/RoundSpecificNodeRoles.java | 6 ++--- .../consensus/ibft/support/TestContext.java | 5 ++-- 6 files changed, 10 insertions(+), 31 deletions(-) delete mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundChangeCertificateCreator.java diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java index b6bc76d489..56175ebbb0 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java @@ -54,7 +54,7 @@ public static void assertPeersReceivedExactly( } public static boolean msgMatchesExpected( - final MessageData actual, SignedData expected) { + final MessageData actual, final SignedData expected) { final Payload expectedPayload = expected.getPayload(); if (expectedPayload instanceof ProposalPayload) { final SignedData actualPayload = diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java index cf69903079..88f9922c34 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java @@ -30,7 +30,8 @@ public class NetworkLayout { final TreeMap addressKeyMap; final List remotePeers; - public NetworkLayout(NodeParams localNode, TreeMap addressKeyMap) { + public NetworkLayout( + final NodeParams localNode, final TreeMap addressKeyMap) { this.localNode = localNode; this.addressKeyMap = addressKeyMap; this.remotePeers = new ArrayList<>(addressKeyMap.values()); diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java index 45fba734b4..db37a0aee3 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java @@ -19,7 +19,7 @@ public class NodeParams { private Address address; private KeyPair nodeKeys; - public NodeParams(Address address, KeyPair nodeKeys) { + public NodeParams(final Address address, final KeyPair nodeKeys) { this.address = address; this.nodeKeys = nodeKeys; } diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundChangeCertificateCreator.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundChangeCertificateCreator.java deleted file mode 100644 index fd46d96f50..0000000000 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundChangeCertificateCreator.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.List; - -public class RoundChangeCertificateCreator { - - public static void createRoundChangeCerificate( - final ValidatorPeer from, - final ValidatorPeer proposalFrom, - final List preparedPeers) {} -} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java index b86b57ba18..b514367c8e 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificNodeRoles.java @@ -22,9 +22,9 @@ public class RoundSpecificNodeRoles { private final List nonProposingPeers; public RoundSpecificNodeRoles( - ValidatorPeer proposer, - Collection peers, - List nonProposingPeers) { + final ValidatorPeer proposer, + final Collection peers, + final List nonProposingPeers) { this.proposer = proposer; this.peers = peers; this.nonProposingPeers = nonProposingPeers; diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java index f6099b478d..9f40311257 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java @@ -12,6 +12,7 @@ */ package tech.pegasys.pantheon.consensus.ibft.support; +import static java.nio.charset.StandardCharsets.UTF_8; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; @@ -119,7 +120,7 @@ public MessageFactory getLocalNodeMessageFactory() { return finalState.getMessageFactory(); } - public Block createBlockForProposal(int round, long timestamp) { + public Block createBlockForProposal(final int round, final long timestamp) { return finalState .getBlockCreatorFactory() .create(blockchain.getChainHeadHeader(), round) @@ -162,7 +163,7 @@ public static TestContext createTestEnvironment( new MiningParameters( AddressHelpers.ofValue(1), Wei.ZERO, - BytesValue.wrap("Ibft Int tests".getBytes()), + BytesValue.wrap("Ibft Int tests".getBytes(UTF_8)), true); final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions(); From 6131a746cf0a5eed8643fb5f2d8569b047dd5b48 Mon Sep 17 00:00:00 2001 From: tmohay Date: Mon, 7 Jan 2019 17:00:28 +1100 Subject: [PATCH 4/8] Post review --- .../ibft/support/MessageReceptionHelpers.java | 46 +++++++++---------- .../consensus/ibft/support/NetworkLayout.java | 8 ++-- .../consensus/ibft/support/NodeParams.java | 4 +- .../ibft/support/StubIbftMulticaster.java | 2 +- .../consensus/ibft/support/TestContext.java | 9 +++- .../consensus/ibft/support/ValidatorPeer.java | 4 +- .../ibft/tests/LocalNodeNotProposerTest.java | 29 ++++++------ 7 files changed, 55 insertions(+), 47 deletions(-) diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java index 56175ebbb0..2878d1183c 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java @@ -14,7 +14,9 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Arrays; 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; @@ -42,40 +44,38 @@ public static void assertPeersReceivedExactly( final Collection allPeers, final SignedData... msgs) { allPeers.forEach(n -> assertThat(n.getReceivedMessages().size()).isEqualTo(msgs.length)); - for (SignedData msg : msgs) { + List> msgList = Arrays.asList(msgs); + + for(int i = 0; i < msgList.size(); i++) { + final int index = i; + final SignedData msg = msgList.get(index); allPeers.forEach( n -> { final List rxMsgs = n.getReceivedMessages(); - final MessageData rxMsgData = rxMsgs.get(0); - rxMsgs.remove(0); + 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 expected) { final Payload expectedPayload = expected.getPayload(); - if (expectedPayload instanceof ProposalPayload) { - final SignedData actualPayload = - ProposalMessage.fromMessage(actual).decode(); - return actualPayload.equals(expected); - } else if (expectedPayload instanceof PreparePayload) { - final SignedData actualPayload = PrepareMessage.fromMessage(actual).decode(); - return actualPayload.equals(expected); - } else if (expectedPayload instanceof CommitPayload) { - final SignedData actualPayload = CommitMessage.fromMessage(actual).decode(); - return actualPayload.equals(expected); - } else if (expectedPayload instanceof RoundChangePayload) { - final SignedData actualPayload = - RoundChangeMessage.fromMessage(actual).decode(); - return actualPayload.equals(expected); - } else if (expectedPayload instanceof NewRoundPayload) { - final SignedData actualPayload = - NewRoundMessage.fromMessage(actual).decode(); - return actualPayload.equals(expected); - } else { - return false; + + 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; } } } diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java index 88f9922c34..76740a3497 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java @@ -26,9 +26,9 @@ public class NetworkLayout { - final NodeParams localNode; - final TreeMap addressKeyMap; - final List remotePeers; + private final NodeParams localNode; + private final TreeMap addressKeyMap; + private final List remotePeers; public NetworkLayout( final NodeParams localNode, final TreeMap addressKeyMap) { @@ -49,7 +49,7 @@ public static NetworkLayout createNetworkLayout( private static TreeMap createValidators(final int validatorCount) { // Map is required to be sorted by address - final TreeMap addressKeyMap = Maps.newTreeMap(); + final TreeMap addressKeyMap = new TreeMap<>(); for (int i = 0; i < validatorCount; i++) { final KeyPair newKeyPair = KeyPair.generate(); diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java index db37a0aee3..2efb84fb45 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java @@ -16,8 +16,8 @@ import tech.pegasys.pantheon.ethereum.core.Address; public class NodeParams { - private Address address; - private KeyPair nodeKeys; + private final Address address; + private final KeyPair nodeKeys; public NodeParams(final Address address, final KeyPair nodeKeys) { this.address = address; diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java index 91cadbfe0d..24f41065a2 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java @@ -32,6 +32,6 @@ public void addNetworkPeers(final Collection nodes) { @Override public void multicastToValidators(final MessageData message) { - validatorNodes.stream().forEach(v -> v.handleReceivedMessage(message)); + validatorNodes.forEach(v -> v.handleReceivedMessage(message)); } } diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java index 9f40311257..983ae102f3 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java @@ -78,9 +78,10 @@ /* Responsible for creating an environment in which integration testing can be conducted. -The test setup is a 'n' node network, the first of which can be considered the "local" node. -All allValidators are in the genesis block's extraData. Validator +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 { @@ -142,6 +143,10 @@ public NodeParams getLocalNodeParams() { return new NodeParams(finalState.getLocalAddress(), finalState.getNodeKeys()); } + public long getCurrentChainHeight() { + return blockchain.getChainHeadBlockNumber(); + } + public static TestContext createTestEnvWithUtcClock( final int validatorCount, final int indexOfFirstLocallyProposedBlock) { return createTestEnvironment( diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java index eab8f4464a..99d1e24f65 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.consensus.ibft.support; +import com.google.common.collect.ImmutableList; +import java.util.Collections; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent; @@ -113,7 +115,7 @@ public void handleReceivedMessage(final MessageData message) { } public List getReceivedMessages() { - return receivedMessages; + return Collections.unmodifiableList(receivedMessages); } public void clearReceivedMessages() { diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java index 13260b18cf..7ee2c0e34f 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java @@ -35,7 +35,8 @@ public class LocalNodeNotProposerTest { - // This ensures the localNode (i.e. the UUT) will not create the first block + // By setting the indexOfFirstLocallyProposedBlock to 0 (and that the blockchain has only the + // genesis block) guarantees the local node is not responsible for proposing the first block). private final TestContext context = TestContext.createTestEnvWithUtcClock(4, 0); private final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(1, 0); private final RoundSpecificNodeRoles roles = context.getRoundSpecificRoles(roundId); @@ -76,7 +77,7 @@ public void basicCase() { assertPeersReceivedExactly(roles.getAllPeers(), expectedTxCommit); // Ensure the local blockchain has NOT incremented yet. - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + assertThat(context.getCurrentChainHeight()).isEqualTo(0); // NO further messages should be transmitted when another Prepare is received. roles.getNonProposingPeers().get(1).injectPrepare(roundId, blockToPropose.getHash()); @@ -85,17 +86,17 @@ public void basicCase() { // Inject a commit, ensure blockChain is not updated, and no message are sent (not quorum yet) roles.getNonProposingPeers().get(0).injectCommit(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + assertThat(context.getCurrentChainHeight()).isEqualTo(0); // A second commit message means quorum is reached, and blockchain should be updated. roles.getNonProposingPeers().get(1).injectCommit(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + assertThat(context.getCurrentChainHeight()).isEqualTo(1); // ensure any further commit messages do not affect the system roles.getProposer().injectCommit(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + assertThat(context.getCurrentChainHeight()).isEqualTo(1); } @Test @@ -106,7 +107,7 @@ public void prepareFromProposerIsIgnored() { // No commit message transmitted after receiving prepare from proposer roles.getProposer().injectPrepare(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + assertThat(context.getCurrentChainHeight()).isEqualTo(0); roles.getNonProposingPeers().get(1).injectPrepare(roundId, blockToPropose.getHash()); assertPeersReceivedExactly(roles.getAllPeers(), expectedTxCommit); @@ -114,12 +115,12 @@ public void prepareFromProposerIsIgnored() { // Inject a commit, ensure blockChain is not updated, and no message are sent (not quorum yet) roles.getNonProposingPeers().get(0).injectCommit(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + assertThat(context.getCurrentChainHeight()).isEqualTo(0); // A second commit message means quorum is reached, and blockchain should be updated. roles.getNonProposingPeers().get(1).injectCommit(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + assertThat(context.getCurrentChainHeight()).isEqualTo(1); } @Test @@ -129,20 +130,20 @@ public void commitMessagesReceivedBeforePrepareCorrectlyImports() { for (final ValidatorPeer peer : roles.getAllPeers()) { peer.injectCommit(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + assertThat(context.getCurrentChainHeight()).isEqualTo(0); } for (final ValidatorPeer peer : roles.getNonProposingPeers()) { peer.injectPrepare(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + assertThat(context.getCurrentChainHeight()).isEqualTo(0); } roles.getProposer().injectProposal(roundId, blockToPropose); // TODO(tmm): Unfortunatley, there are times that the Commit will go out BEFORE the prepare // This is one of them :( Maybe fix the testing to be ignorant of ordering? assertPeersReceivedExactly(roles.getAllPeers(), expectedTxCommit, expectedTxPrepare); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + assertThat(context.getCurrentChainHeight()).isEqualTo(1); } @Test @@ -151,15 +152,15 @@ public void commitMessagesReceivedBeforePrepareCorrectlyImports() { for (final ValidatorPeer peer : roles.getAllPeers()) { peer.injectCommit(roundId, blockToPropose.getHash()); assertPeersReceivedNoMessages(roles.getAllPeers()); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(0); + assertThat(context.getCurrentChainHeight()).isEqualTo(0); } roles.getProposer().injectProposal(roundId, blockToPropose); assertPeersReceivedExactly(roles.getAllPeers(), expectedTxPrepare); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + assertThat(context.getCurrentChainHeight()).isEqualTo(1); roles.getNonProposingPeers().get(0).injectPrepare(roundId, blockToPropose.getHash()); assertPeersReceivedExactly(roles.getAllPeers(), expectedTxCommit); - assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1); + assertThat(context.getCurrentChainHeight()).isEqualTo(1); } } From 90adcdfeaafaa092bed55e44aa0463dd9dc136c8 Mon Sep 17 00:00:00 2001 From: tmohay Date: Tue, 8 Jan 2019 08:34:50 +1100 Subject: [PATCH 5/8] Split out TestContext creation --- .../ibft/support/MessageReceptionHelpers.java | 9 +- .../consensus/ibft/support/NetworkLayout.java | 1 - .../consensus/ibft/support/TestContext.java | 200 ------------- .../ibft/support/TestContextFactory.java | 264 ++++++++++++++++++ .../consensus/ibft/support/ValidatorPeer.java | 3 +- .../ibft/tests/LocalNodeNotProposerTest.java | 3 +- 6 files changed, 269 insertions(+), 211 deletions(-) create mode 100644 consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java index 2878d1183c..0a922fce95 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java @@ -14,22 +14,17 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.Arrays; 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.CommitPayload; -import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.Payload; -import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload; -import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload; -import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.RoundChangePayload; 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; @@ -46,7 +41,7 @@ public static void assertPeersReceivedExactly( List> msgList = Arrays.asList(msgs); - for(int i = 0; i < msgList.size(); i++) { + for (int i = 0; i < msgList.size(); i++) { final int index = i; final SignedData msg = msgList.get(index); allPeers.forEach( diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java index 76740a3497..321e16fc61 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NetworkLayout.java @@ -22,7 +22,6 @@ import java.util.TreeMap; import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; public class NetworkLayout { diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java index 983ae102f3..22938397ef 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContext.java @@ -12,68 +12,18 @@ */ package tech.pegasys.pantheon.consensus.ibft.support; -import static java.nio.charset.StandardCharsets.UTF_8; -import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; -import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; - -import tech.pegasys.pantheon.config.StubGenesisConfigOptions; -import tech.pegasys.pantheon.consensus.common.BlockInterface; -import tech.pegasys.pantheon.consensus.common.EpochManager; -import tech.pegasys.pantheon.consensus.common.VoteProposer; -import tech.pegasys.pantheon.consensus.common.VoteTally; -import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater; -import tech.pegasys.pantheon.consensus.ibft.BlockTimer; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; -import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing; -import tech.pegasys.pantheon.consensus.ibft.IbftBlockHeaderValidationRulesetFactory; -import tech.pegasys.pantheon.consensus.ibft.IbftBlockInterface; -import tech.pegasys.pantheon.consensus.ibft.IbftContext; -import tech.pegasys.pantheon.consensus.ibft.IbftEventQueue; -import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; -import tech.pegasys.pantheon.consensus.ibft.IbftHelpers; -import tech.pegasys.pantheon.consensus.ibft.IbftProtocolSchedule; -import tech.pegasys.pantheon.consensus.ibft.RoundTimer; -import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreatorFactory; -import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; -import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftBlockHeightManagerFactory; import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftController; import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftFinalState; -import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftRoundFactory; -import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory; -import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; -import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; import tech.pegasys.pantheon.ethereum.core.Address; -import tech.pegasys.pantheon.ethereum.core.AddressHelpers; 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.Hash; -import tech.pegasys.pantheon.ethereum.core.MiningParameters; -import tech.pegasys.pantheon.ethereum.core.PendingTransactions; -import tech.pegasys.pantheon.ethereum.core.Util; -import tech.pegasys.pantheon.ethereum.core.Wei; -import tech.pegasys.pantheon.ethereum.db.WorldStateArchive; -import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; -import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; -import tech.pegasys.pantheon.util.bytes.BytesValue; -import tech.pegasys.pantheon.util.uint.UInt256; -import java.time.Clock; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; - -import com.google.common.collect.Iterables; /* Responsible for creating an environment in which integration testing can be conducted. @@ -85,10 +35,6 @@ */ public class TestContext { - public static final int EPOCH_LENGTH = 10_000; - public static final int BLOCK_TIMER_SEC = 3; - public static final int ROUND_TIMER_SEC = 12; - private Map remotePeers; private final MutableBlockchain blockchain; private final IbftController controller; @@ -146,150 +92,4 @@ public NodeParams getLocalNodeParams() { public long getCurrentChainHeight() { return blockchain.getChainHeadBlockNumber(); } - - public static TestContext createTestEnvWithUtcClock( - final int validatorCount, final int indexOfFirstLocallyProposedBlock) { - return createTestEnvironment( - validatorCount, indexOfFirstLocallyProposedBlock, Clock.systemUTC()); - } - - public static TestContext createTestEnvironment( - final int validatorCount, final int indexOfFirstLocallyProposedBlock, final Clock clock) { - - final NetworkLayout networkNodes = - NetworkLayout.createNetworkLayout(validatorCount, indexOfFirstLocallyProposedBlock); - - final Block genesisBlock = createGenesisBlock(networkNodes.getValidatorAddresses()); - final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); - final MutableBlockchain blockChain = - createInMemoryBlockchain(genesisBlock, IbftBlockHashing::calculateHashOfIbftBlockOnChain); - - final MiningParameters miningParams = - new MiningParameters( - AddressHelpers.ofValue(1), - Wei.ZERO, - BytesValue.wrap("Ibft Int tests".getBytes(UTF_8)), - true); - - final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions(); - genesisConfigOptions.byzantiumBlock(0); - - final ProtocolSchedule protocolSchedule = - IbftProtocolSchedule.create(genesisConfigOptions); - - final KeyPair nodeKeys = networkNodes.getLocalNode().getNodeKeys(); - - ///////////////////////////////////////////////////////////////////////////////////// - // From here down IS BASICALLY taken from IbftPantheonController (consts are tweaked) - final EpochManager epochManager = new EpochManager(EPOCH_LENGTH); - - final BlockInterface blockInterface = new IbftBlockInterface(); - final VoteTally voteTally = - new VoteTallyUpdater(epochManager, blockInterface).buildVoteTallyFromBlockchain(blockChain); - - final VoteProposer voteProposer = new VoteProposer(); - - final ProtocolContext protocolContext = - new ProtocolContext<>( - blockChain, worldStateArchive, new IbftContext(voteTally, voteProposer)); - - final IbftEventQueue ibftEventQueue = new IbftEventQueue(); - - final IbftBlockCreatorFactory blockCreatorFactory = - new IbftBlockCreatorFactory( - (gasLimit) -> gasLimit, - new PendingTransactions(1), // changed from IbftPantheonController - protocolContext, - protocolSchedule, - miningParams, - Util.publicKeyToAddress(nodeKeys.getPublicKey())); - - final ProposerSelector proposerSelector = - new ProposerSelector(blockChain, voteTally, blockInterface, true); - - // Use a stubbed version of the multicaster, to prevent creating PeerConnections etc. - final StubIbftMulticaster stubbedNetworkPeers = new StubIbftMulticaster(); - - final BlockHeaderValidator blockHeaderValidator = - IbftBlockHeaderValidationRulesetFactory.ibftProposedBlockValidator(BLOCK_TIMER_SEC); - - final IbftFinalState finalState = - new IbftFinalState( - voteTally, - nodeKeys, - Util.publicKeyToAddress(nodeKeys.getPublicKey()), - proposerSelector, - stubbedNetworkPeers, - new RoundTimer( - ibftEventQueue, ROUND_TIMER_SEC * 1000, Executors.newScheduledThreadPool(1)), - new BlockTimer( - ibftEventQueue, - BLOCK_TIMER_SEC * 1000, - Executors.newScheduledThreadPool(1), - Clock.systemUTC()), - blockCreatorFactory, - new MessageFactory(nodeKeys), - blockHeaderValidator, - clock); - - final MessageValidatorFactory messageValidatorFactory = - new MessageValidatorFactory(proposerSelector, blockHeaderValidator, protocolContext); - - final IbftController ibftController = - new IbftController( - blockChain, - finalState, - new IbftBlockHeightManagerFactory( - finalState, - new IbftRoundFactory(finalState, protocolContext, protocolSchedule), - messageValidatorFactory, - protocolContext)); - //////////////////////////// END IBFT PantheonController //////////////////////////// - - // Add each networkNode to the Multicaster (such that each can receive msgs from local node). - // NOTE: the remotePeers needs to be ordered based on Address (as this is used to determine - // the proposer order which must be managed in test). - final Map remotePeers = - networkNodes - .getRemotePeers() - .stream() - .collect( - Collectors.toMap( - NodeParams::getAddress, - np -> - new ValidatorPeer(np, new MessageFactory(np.getNodeKeys()), ibftController), - (u, v) -> { - throw new IllegalStateException(String.format("Duplicate key %s", u)); - }, - LinkedHashMap::new)); - - stubbedNetworkPeers.addNetworkPeers(remotePeers.values()); - - return new TestContext(remotePeers, blockChain, ibftController, finalState); - } - - private static Block createGenesisBlock(final Set
validators) { - final Address coinbase = Iterables.get(validators, 0); - final BlockHeaderTestFixture headerTestFixture = new BlockHeaderTestFixture(); - final IbftExtraData extraData = - new IbftExtraData( - BytesValue.wrap(new byte[32]), - Collections.emptyList(), - Optional.empty(), - 0, - validators); - headerTestFixture.extraData(extraData.encode()); - headerTestFixture.mixHash(IbftHelpers.EXPECTED_MIX_HASH); - headerTestFixture.difficulty(UInt256.ONE); - headerTestFixture.ommersHash(Hash.EMPTY_LIST_HASH); - headerTestFixture.nonce(0); - headerTestFixture.timestamp(0); - headerTestFixture.parentHash(Hash.ZERO); - headerTestFixture.gasLimit(5000); - headerTestFixture.coinbase(coinbase); - - final BlockHeader genesisHeader = headerTestFixture.buildHeader(); - return new Block( - genesisHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); - } } diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java new file mode 100644 index 0000000000..bb855ddcb0 --- /dev/null +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java @@ -0,0 +1,264 @@ +/* + * 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 java.nio.charset.StandardCharsets.UTF_8; +import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; +import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; + +import tech.pegasys.pantheon.config.StubGenesisConfigOptions; +import tech.pegasys.pantheon.consensus.common.BlockInterface; +import tech.pegasys.pantheon.consensus.common.EpochManager; +import tech.pegasys.pantheon.consensus.common.VoteProposer; +import tech.pegasys.pantheon.consensus.common.VoteTally; +import tech.pegasys.pantheon.consensus.common.VoteTallyUpdater; +import tech.pegasys.pantheon.consensus.ibft.BlockTimer; +import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing; +import tech.pegasys.pantheon.consensus.ibft.IbftBlockHeaderValidationRulesetFactory; +import tech.pegasys.pantheon.consensus.ibft.IbftBlockInterface; +import tech.pegasys.pantheon.consensus.ibft.IbftContext; +import tech.pegasys.pantheon.consensus.ibft.IbftEventQueue; +import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; +import tech.pegasys.pantheon.consensus.ibft.IbftHelpers; +import tech.pegasys.pantheon.consensus.ibft.IbftProtocolSchedule; +import tech.pegasys.pantheon.consensus.ibft.RoundTimer; +import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreatorFactory; +import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; +import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftBlockHeightManagerFactory; +import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftController; +import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftFinalState; +import tech.pegasys.pantheon.consensus.ibft.statemachine.IbftRoundFactory; +import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory; +import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; +import tech.pegasys.pantheon.ethereum.ProtocolContext; +import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.AddressHelpers; +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.Hash; +import tech.pegasys.pantheon.ethereum.core.MiningParameters; +import tech.pegasys.pantheon.ethereum.core.PendingTransactions; +import tech.pegasys.pantheon.ethereum.core.Util; +import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.ethereum.db.WorldStateArchive; +import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.util.bytes.BytesValue; +import tech.pegasys.pantheon.util.uint.UInt256; + +import java.time.Clock; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import com.google.common.collect.Iterables; + +public class TestContextFactory { + + private static class ControllerAndState { + + private IbftController controller; + private IbftFinalState finalState; + + public ControllerAndState(final IbftController controller, final IbftFinalState finalState) { + this.controller = controller; + this.finalState = finalState; + } + + public IbftController getController() { + return controller; + } + + public IbftFinalState getFinalState() { + return finalState; + } + } + + public static final int EPOCH_LENGTH = 10_000; + public static final int BLOCK_TIMER_SEC = 3; + public static final int ROUND_TIMER_SEC = 12; + + public static TestContext createTestEnvWithUtcClock( + final int validatorCount, final int indexOfFirstLocallyProposedBlock) { + return createTestEnvironment( + validatorCount, indexOfFirstLocallyProposedBlock, Clock.systemUTC()); + } + + public static TestContext createTestEnvironment( + final int validatorCount, final int indexOfFirstLocallyProposedBlock, final Clock clock) { + + final NetworkLayout networkNodes = + NetworkLayout.createNetworkLayout(validatorCount, indexOfFirstLocallyProposedBlock); + + final Block genesisBlock = createGenesisBlock(networkNodes.getValidatorAddresses()); + final MutableBlockchain blockChain = + createInMemoryBlockchain(genesisBlock, IbftBlockHashing::calculateHashOfIbftBlockOnChain); + + final KeyPair nodeKeys = networkNodes.getLocalNode().getNodeKeys(); + + // Use a stubbed version of the multicaster, to prevent creating PeerConnections etc. + final StubIbftMulticaster stubbedNetworkPeers = new StubIbftMulticaster(); + + final ControllerAndState controllerAndState = + createControllerAndFinalState(blockChain, stubbedNetworkPeers, nodeKeys, clock); + + // Add each networkNode to the Multicaster (such that each can receive msgs from local node). + // NOTE: the remotePeers needs to be ordered based on Address (as this is used to determine + // the proposer order which must be managed in test). + final Map remotePeers = + networkNodes + .getRemotePeers() + .stream() + .collect( + Collectors.toMap( + NodeParams::getAddress, + np -> + new ValidatorPeer( + np, + new MessageFactory(np.getNodeKeys()), + controllerAndState.getController()), + (u, v) -> { + throw new IllegalStateException(String.format("Duplicate key %s", u)); + }, + LinkedHashMap::new)); + + stubbedNetworkPeers.addNetworkPeers(remotePeers.values()); + + return new TestContext( + remotePeers, + blockChain, + controllerAndState.getController(), + controllerAndState.getFinalState()); + } + + private static Block createGenesisBlock(final Set
validators) { + final Address coinbase = Iterables.get(validators, 0); + final BlockHeaderTestFixture headerTestFixture = new BlockHeaderTestFixture(); + final IbftExtraData extraData = + new IbftExtraData( + BytesValue.wrap(new byte[32]), + Collections.emptyList(), + Optional.empty(), + 0, + validators); + headerTestFixture.extraData(extraData.encode()); + headerTestFixture.mixHash(IbftHelpers.EXPECTED_MIX_HASH); + headerTestFixture.difficulty(UInt256.ONE); + headerTestFixture.ommersHash(Hash.EMPTY_LIST_HASH); + headerTestFixture.nonce(0); + headerTestFixture.timestamp(0); + headerTestFixture.parentHash(Hash.ZERO); + headerTestFixture.gasLimit(5000); + headerTestFixture.coinbase(coinbase); + + final BlockHeader genesisHeader = headerTestFixture.buildHeader(); + return new Block( + genesisHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); + } + + private static ControllerAndState createControllerAndFinalState( + final MutableBlockchain blockChain, + final StubIbftMulticaster stubbedNetworkPeers, + final KeyPair nodeKeys, + final Clock clock) { + + final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); + + final MiningParameters miningParams = + new MiningParameters( + AddressHelpers.ofValue(1), + Wei.ZERO, + BytesValue.wrap("Ibft Int tests".getBytes(UTF_8)), + true); + + final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions(); + genesisConfigOptions.byzantiumBlock(0); + + final ProtocolSchedule protocolSchedule = + IbftProtocolSchedule.create(genesisConfigOptions); + + ///////////////////////////////////////////////////////////////////////////////////// + // From here down is BASICALLY taken from IbftPantheonController + final EpochManager epochManager = new EpochManager(EPOCH_LENGTH); + + final BlockInterface blockInterface = new IbftBlockInterface(); + final VoteTally voteTally = + new VoteTallyUpdater(epochManager, blockInterface).buildVoteTallyFromBlockchain(blockChain); + + final VoteProposer voteProposer = new VoteProposer(); + + final ProtocolContext protocolContext = + new ProtocolContext<>( + blockChain, worldStateArchive, new IbftContext(voteTally, voteProposer)); + + final IbftEventQueue ibftEventQueue = new IbftEventQueue(); + + final IbftBlockCreatorFactory blockCreatorFactory = + new IbftBlockCreatorFactory( + (gasLimit) -> gasLimit, + new PendingTransactions(1), // changed from IbftPantheonController + protocolContext, + protocolSchedule, + miningParams, + Util.publicKeyToAddress(nodeKeys.getPublicKey())); + + final ProposerSelector proposerSelector = + new ProposerSelector(blockChain, voteTally, blockInterface, true); + + final BlockHeaderValidator blockHeaderValidator = + IbftBlockHeaderValidationRulesetFactory.ibftProposedBlockValidator(BLOCK_TIMER_SEC); + + final IbftFinalState finalState = + new IbftFinalState( + voteTally, + nodeKeys, + Util.publicKeyToAddress(nodeKeys.getPublicKey()), + proposerSelector, + stubbedNetworkPeers, + new RoundTimer( + ibftEventQueue, ROUND_TIMER_SEC * 1000, Executors.newScheduledThreadPool(1)), + new BlockTimer( + ibftEventQueue, + BLOCK_TIMER_SEC * 1000, + Executors.newScheduledThreadPool(1), + Clock.systemUTC()), + blockCreatorFactory, + new MessageFactory(nodeKeys), + blockHeaderValidator, + clock); + + final MessageValidatorFactory messageValidatorFactory = + new MessageValidatorFactory(proposerSelector, blockHeaderValidator, protocolContext); + + final IbftController ibftController = + new IbftController( + blockChain, + finalState, + new IbftBlockHeightManagerFactory( + finalState, + new IbftRoundFactory(finalState, protocolContext, protocolSchedule), + messageValidatorFactory, + protocolContext)); + //////////////////////////// END IBFT PantheonController //////////////////////////// + + return new ControllerAndState(ibftController, finalState); + } +} diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java index 99d1e24f65..9f31ac1d36 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java @@ -12,8 +12,6 @@ */ package tech.pegasys.pantheon.consensus.ibft.support; -import com.google.common.collect.ImmutableList; -import java.util.Collections; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent; @@ -41,6 +39,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage; +import java.util.Collections; import java.util.List; import java.util.Optional; diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java index 7ee2c0e34f..d7472a2264 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java @@ -25,6 +25,7 @@ import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.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.consensus.ibft.support.ValidatorPeer; import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1.Signature; @@ -37,7 +38,7 @@ public class LocalNodeNotProposerTest { // By setting the indexOfFirstLocallyProposedBlock to 0 (and that the blockchain has only the // genesis block) guarantees the local node is not responsible for proposing the first block). - private final TestContext context = TestContext.createTestEnvWithUtcClock(4, 0); + private final TestContext context = TestContextFactory.createTestEnvWithUtcClock(4, 0); private final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(1, 0); private final RoundSpecificNodeRoles roles = context.getRoundSpecificRoles(roundId); From 58147aa3c2e28b9482fc76a43237bdb7e10062d5 Mon Sep 17 00:00:00 2001 From: tmohay Date: Tue, 8 Jan 2019 15:42:17 +1100 Subject: [PATCH 6/8] Post Adrian review --- .../consensus/ibft/support/NodeParams.java | 2 +- .../ibft/support/TestContextFactory.java | 15 +++++++++------ .../consensus/ibft/support/ValidatorPeer.java | 2 +- .../ibft/tests/LocalNodeNotProposerTest.java | 4 ++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java index 2efb84fb45..58e720448f 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/NodeParams.java @@ -28,7 +28,7 @@ public Address getAddress() { return address; } - public KeyPair getNodeKeys() { + public KeyPair getNodeKeyPair() { return nodeKeys; } } diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java index bb855ddcb0..53153bfe32 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java @@ -16,6 +16,8 @@ import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; +import java.time.Instant; +import java.time.ZoneId; import tech.pegasys.pantheon.config.StubGenesisConfigOptions; import tech.pegasys.pantheon.consensus.common.BlockInterface; import tech.pegasys.pantheon.consensus.common.EpochManager; @@ -96,10 +98,11 @@ public IbftFinalState getFinalState() { public static final int BLOCK_TIMER_SEC = 3; public static final int ROUND_TIMER_SEC = 12; - public static TestContext createTestEnvWithUtcClock( + public static TestContext createTestEnvWithArbitraryClock( final int validatorCount, final int indexOfFirstLocallyProposedBlock) { return createTestEnvironment( - validatorCount, indexOfFirstLocallyProposedBlock, Clock.systemUTC()); + validatorCount, indexOfFirstLocallyProposedBlock, + Clock.fixed(Instant.now(), ZoneId.of("UTC"))); } public static TestContext createTestEnvironment( @@ -112,7 +115,7 @@ public static TestContext createTestEnvironment( final MutableBlockchain blockChain = createInMemoryBlockchain(genesisBlock, IbftBlockHashing::calculateHashOfIbftBlockOnChain); - final KeyPair nodeKeys = networkNodes.getLocalNode().getNodeKeys(); + final KeyPair nodeKeys = networkNodes.getLocalNode().getNodeKeyPair(); // Use a stubbed version of the multicaster, to prevent creating PeerConnections etc. final StubIbftMulticaster stubbedNetworkPeers = new StubIbftMulticaster(); @@ -130,10 +133,10 @@ public static TestContext createTestEnvironment( .collect( Collectors.toMap( NodeParams::getAddress, - np -> + nodeParams -> new ValidatorPeer( - np, - new MessageFactory(np.getNodeKeys()), + nodeParams, + new MessageFactory(nodeParams.getNodeKeyPair()), controllerAndState.getController()), (u, v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java index 9f31ac1d36..ccd2cc4180 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java @@ -59,7 +59,7 @@ public ValidatorPeer( final NodeParams nodeParams, final MessageFactory messageFactory, final IbftController localNodeController) { - this.nodeKeys = nodeParams.getNodeKeys(); + this.nodeKeys = nodeParams.getNodeKeyPair(); this.nodeAddress = nodeParams.getAddress(); this.messageFactory = messageFactory; this.localNodeController = localNodeController; diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java index d7472a2264..3241f94a45 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeNotProposerTest.java @@ -38,7 +38,7 @@ public class LocalNodeNotProposerTest { // By setting the indexOfFirstLocallyProposedBlock to 0 (and that the blockchain has only the // genesis block) guarantees the local node is not responsible for proposing the first block). - private final TestContext context = TestContextFactory.createTestEnvWithUtcClock(4, 0); + private final TestContext context = TestContextFactory.createTestEnvWithArbitraryClock(4, 0); private final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(1, 0); private final RoundSpecificNodeRoles roles = context.getRoundSpecificRoles(roundId); @@ -59,7 +59,7 @@ public void setup() { SECP256K1.sign( IbftBlockHashing.calculateDataHashForCommittedSeal( blockToPropose.getHeader(), extraData), - context.getLocalNodeParams().getNodeKeys()); + context.getLocalNodeParams().getNodeKeyPair()); expectedTxCommit = localNodeMessageFactory.createSignedCommitPayload( From 36c65dbcb0d6bf050f0e4809c3defd741d3f09cf Mon Sep 17 00:00:00 2001 From: tmohay Date: Tue, 8 Jan 2019 15:57:47 +1100 Subject: [PATCH 7/8] fix spotless --- .../consensus/ibft/support/TestContextFactory.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java index 53153bfe32..ab46af9654 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java @@ -16,8 +16,6 @@ import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; -import java.time.Instant; -import java.time.ZoneId; import tech.pegasys.pantheon.config.StubGenesisConfigOptions; import tech.pegasys.pantheon.consensus.common.BlockInterface; import tech.pegasys.pantheon.consensus.common.EpochManager; @@ -63,6 +61,8 @@ import tech.pegasys.pantheon.util.uint.UInt256; import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -101,7 +101,8 @@ public IbftFinalState getFinalState() { public static TestContext createTestEnvWithArbitraryClock( final int validatorCount, final int indexOfFirstLocallyProposedBlock) { return createTestEnvironment( - validatorCount, indexOfFirstLocallyProposedBlock, + validatorCount, + indexOfFirstLocallyProposedBlock, Clock.fixed(Instant.now(), ZoneId.of("UTC"))); } From 895f0bc9117b148ec8356f7492b6da13b973757c Mon Sep 17 00:00:00 2001 From: tmohay Date: Tue, 8 Jan 2019 15:59:10 +1100 Subject: [PATCH 8/8] Make fixed time of MIN Instant --- .../pantheon/consensus/ibft/support/TestContextFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java index ab46af9654..901660d2ee 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java @@ -103,7 +103,7 @@ public static TestContext createTestEnvWithArbitraryClock( return createTestEnvironment( validatorCount, indexOfFirstLocallyProposedBlock, - Clock.fixed(Instant.now(), ZoneId.of("UTC"))); + Clock.fixed(Instant.MIN, ZoneId.of("UTC"))); } public static TestContext createTestEnvironment(