[NC-1909] IBFT message gossiping (#501)

Chris Mckay 6 years ago committed by GitHub
parent 637af126ca
commit bd9ffac033
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/MessageReceptionHelpers.java
  2. 12
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubIbftMulticaster.java
  3. 58
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/StubbedPeerConnection.java
  4. 10
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/TestContextFactory.java
  5. 29
      consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java
  6. 108
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftGossip.java
  7. 20
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftMessages.java
  8. 2
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftevent/IbftEvents.java
  9. 12
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftevent/IbftReceivedMessageEvent.java
  10. 22
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/AbstractIbftMessageData.java
  11. 13
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/CommitMessageData.java
  12. 13
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/NewRoundMessageData.java
  13. 13
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/PrepareMessageData.java
  14. 13
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/ProposalMessageData.java
  15. 13
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/RoundChangeMessageData.java
  16. 6
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMulticaster.java
  17. 26
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftNetworkPeers.java
  18. 56
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java
  19. 102
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java
  20. 20
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftMessageTransmitter.java
  21. 194
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftGossipTest.java
  22. 65
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/TestHelpers.java
  23. 12
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/CommitMessageTest.java
  24. 12
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/NewRoundMessageTest.java
  25. 12
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/PrepareMessageTest.java
  26. 20
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/ProposalMessageTest.java
  27. 12
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/RoundChangeMessageTest.java
  28. 36
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/network/IbftNetworkPeersTest.java
  29. 40
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/network/MockPeerFactory.java
  30. 14
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
  31. 123
      consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java
  32. 19
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/wire/AbstractMessageData.java
  33. 8
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/wire/PeerInfo.java

@ -14,12 +14,12 @@ package tech.pegasys.pantheon.consensus.ibft.support;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.Payload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.Payload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
@ -60,15 +60,15 @@ public class MessageReceptionHelpers {
switch (expectedPayload.getMessageType()) { switch (expectedPayload.getMessageType()) {
case IbftV2.PROPOSAL: case IbftV2.PROPOSAL:
return ProposalMessage.fromMessage(actual).decode().equals(expected); return ProposalMessageData.fromMessageData(actual).decode().equals(expected);
case IbftV2.PREPARE: case IbftV2.PREPARE:
return PrepareMessage.fromMessage(actual).decode().equals(expected); return PrepareMessageData.fromMessageData(actual).decode().equals(expected);
case IbftV2.COMMIT: case IbftV2.COMMIT:
return CommitMessage.fromMessage(actual).decode().equals(expected); return CommitMessageData.fromMessageData(actual).decode().equals(expected);
case IbftV2.NEW_ROUND: case IbftV2.NEW_ROUND:
return NewRoundMessage.fromMessage(actual).decode().equals(expected); return NewRoundMessageData.fromMessageData(actual).decode().equals(expected);
case IbftV2.ROUND_CHANGE: case IbftV2.ROUND_CHANGE:
return RoundChangeMessage.fromMessage(actual).decode().equals(expected); return RoundChangeMessageData.fromMessageData(actual).decode().equals(expected);
default: default:
return false; return false;
} }

@ -13,6 +13,7 @@
package tech.pegasys.pantheon.consensus.ibft.support; package tech.pegasys.pantheon.consensus.ibft.support;
import tech.pegasys.pantheon.consensus.ibft.network.IbftMulticaster; import tech.pegasys.pantheon.consensus.ibft.network.IbftMulticaster;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import java.util.Collection; import java.util.Collection;
@ -32,6 +33,15 @@ public class StubIbftMulticaster implements IbftMulticaster {
@Override @Override
public void multicastToValidators(final MessageData message) { public void multicastToValidators(final MessageData message) {
validatorNodes.forEach(v -> v.handleReceivedMessage(message)); validatorNodes.forEach(peer -> peer.handleReceivedMessage(message));
}
@Override
public void multicastToValidatorsExcept(
final MessageData message, final Collection<Address> exceptAddresses) {
validatorNodes
.stream()
.filter(peer -> !exceptAddresses.contains(peer.getNodeAddress()))
.forEach(peer -> peer.handleReceivedMessage(message));
} }
} }

@ -0,0 +1,58 @@
/*
* 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.util.Collections.emptyList;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.p2p.wire.PeerInfo;
import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.net.SocketAddress;
import java.util.Set;
public class StubbedPeerConnection implements PeerConnection {
@Override
public void send(final Capability capability, final MessageData message)
throws PeerNotConnected {}
@Override
public Set<Capability> getAgreedCapabilities() {
return null;
}
@Override
public PeerInfo getPeer() {
return new PeerInfo(0, "IbftIntTestPeer", emptyList(), 0, BytesValue.EMPTY);
}
@Override
public void terminateConnection(final DisconnectReason reason, final boolean peerInitiated) {}
@Override
public void disconnect(final DisconnectReason reason) {}
@Override
public SocketAddress getLocalAddress() {
return null;
}
@Override
public SocketAddress getRemoteAddress() {
return null;
}
}

@ -13,6 +13,7 @@
package tech.pegasys.pantheon.consensus.ibft.support; package tech.pegasys.pantheon.consensus.ibft.support;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Mockito.mock;
import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain;
import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive;
@ -29,6 +30,7 @@ import tech.pegasys.pantheon.consensus.ibft.IbftBlockInterface;
import tech.pegasys.pantheon.consensus.ibft.IbftContext; import tech.pegasys.pantheon.consensus.ibft.IbftContext;
import tech.pegasys.pantheon.consensus.ibft.IbftEventQueue; import tech.pegasys.pantheon.consensus.ibft.IbftEventQueue;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData; import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.consensus.ibft.IbftGossip;
import tech.pegasys.pantheon.consensus.ibft.IbftHelpers; import tech.pegasys.pantheon.consensus.ibft.IbftHelpers;
import tech.pegasys.pantheon.consensus.ibft.IbftProtocolSchedule; import tech.pegasys.pantheon.consensus.ibft.IbftProtocolSchedule;
import tech.pegasys.pantheon.consensus.ibft.RoundTimer; import tech.pegasys.pantheon.consensus.ibft.RoundTimer;
@ -64,6 +66,7 @@ import java.time.Clock;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -252,6 +255,9 @@ public class TestContextFactory {
final MessageValidatorFactory messageValidatorFactory = final MessageValidatorFactory messageValidatorFactory =
new MessageValidatorFactory(proposerSelector, blockHeaderValidator, protocolContext); new MessageValidatorFactory(proposerSelector, blockHeaderValidator, protocolContext);
// Disable Gossiping for integration tests.
final IbftGossip gossiper = mock(IbftGossip.class);
final IbftController ibftController = final IbftController ibftController =
new IbftController( new IbftController(
blockChain, blockChain,
@ -260,7 +266,9 @@ public class TestContextFactory {
finalState, finalState,
new IbftRoundFactory(finalState, protocolContext, protocolSchedule), new IbftRoundFactory(finalState, protocolContext, protocolSchedule),
messageValidatorFactory, messageValidatorFactory,
protocolContext)); protocolContext),
new HashMap<>(),
gossiper);
//////////////////////////// END IBFT PantheonController //////////////////////////// //////////////////////////// END IBFT PantheonController ////////////////////////////
return new ControllerAndState(ibftController, finalState); return new ControllerAndState(ibftController, finalState);

@ -15,11 +15,11 @@ package tech.pegasys.pantheon.consensus.ibft.support;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload;
@ -37,6 +37,7 @@ import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection;
import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage; import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage;
import java.util.Collections; import java.util.Collections;
@ -51,6 +52,7 @@ public class ValidatorPeer {
private final Address nodeAddress; private final Address nodeAddress;
private final KeyPair nodeKeys; private final KeyPair nodeKeys;
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
private final PeerConnection peerConnection = new StubbedPeerConnection();
private List<MessageData> receivedMessages = Lists.newArrayList(); private List<MessageData> receivedMessages = Lists.newArrayList();
private final IbftController localNodeController; private final IbftController localNodeController;
@ -65,11 +67,16 @@ public class ValidatorPeer {
this.localNodeController = localNodeController; this.localNodeController = localNodeController;
} }
public Address getNodeAddress() {
return nodeAddress;
}
public SignedData<ProposalPayload> injectProposal( public SignedData<ProposalPayload> injectProposal(
final ConsensusRoundIdentifier rId, final Block block) { final ConsensusRoundIdentifier rId, final Block block) {
final SignedData<ProposalPayload> payload = final SignedData<ProposalPayload> payload =
messageFactory.createSignedProposalPayload(rId, block); messageFactory.createSignedProposalPayload(rId, block);
injectMessage(ProposalMessage.create(payload));
injectMessage(ProposalMessageData.create(payload));
return payload; return payload;
} }
@ -77,7 +84,7 @@ public class ValidatorPeer {
final ConsensusRoundIdentifier rId, final Hash digest) { final ConsensusRoundIdentifier rId, final Hash digest) {
final SignedData<PreparePayload> payload = final SignedData<PreparePayload> payload =
messageFactory.createSignedPreparePayload(rId, digest); messageFactory.createSignedPreparePayload(rId, digest);
injectMessage(PrepareMessage.create(payload)); injectMessage(PrepareMessageData.create(payload));
return payload; return payload;
} }
@ -86,7 +93,7 @@ public class ValidatorPeer {
final Signature commitSeal = SECP256K1.sign(digest, nodeKeys); final Signature commitSeal = SECP256K1.sign(digest, nodeKeys);
final SignedData<CommitPayload> payload = final SignedData<CommitPayload> payload =
messageFactory.createSignedCommitPayload(rId, digest, commitSeal); messageFactory.createSignedCommitPayload(rId, digest, commitSeal);
injectMessage(CommitMessage.create(payload)); injectMessage(CommitMessageData.create(payload));
return payload; return payload;
} }
@ -97,7 +104,7 @@ public class ValidatorPeer {
final SignedData<NewRoundPayload> payload = final SignedData<NewRoundPayload> payload =
messageFactory.createSignedNewRoundPayload(rId, roundChangeCertificate, proposalPayload); messageFactory.createSignedNewRoundPayload(rId, roundChangeCertificate, proposalPayload);
injectMessage(NewRoundMessage.create(payload)); injectMessage(NewRoundMessageData.create(payload));
return payload; return payload;
} }
@ -105,7 +112,7 @@ public class ValidatorPeer {
final ConsensusRoundIdentifier rId, final Optional<PreparedCertificate> preparedCertificate) { final ConsensusRoundIdentifier rId, final Optional<PreparedCertificate> preparedCertificate) {
final SignedData<RoundChangePayload> payload = final SignedData<RoundChangePayload> payload =
messageFactory.createSignedRoundChangePayload(rId, preparedCertificate); messageFactory.createSignedRoundChangePayload(rId, preparedCertificate);
injectMessage(RoundChangeMessage.create(payload)); injectMessage(RoundChangeMessageData.create(payload));
return payload; return payload;
} }
@ -122,7 +129,7 @@ public class ValidatorPeer {
} }
public void injectMessage(final MessageData msgData) { public void injectMessage(final MessageData msgData) {
final DefaultMessage message = new DefaultMessage(null, msgData); final DefaultMessage message = new DefaultMessage(peerConnection, msgData);
localNodeController.handleMessageEvent( localNodeController.handleMessageEvent(
(IbftReceivedMessageEvent) IbftEvents.fromMessage(message)); (IbftReceivedMessageEvent) IbftEvents.fromMessage(message));
} }

@ -0,0 +1,108 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.consensus.ibft;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.consensus.ibft.network.IbftMulticaster;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.p2p.api.Message;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Lists;
/** Class responsible for rebroadcasting IBFT messages to known validators */
public class IbftGossip {
private final IbftMulticaster peers;
// Size of the seenMessages cache, should end up utilising 65bytes * this number + some meta data
private final int maxSeenMessages;
// Set that starts evicting members when it hits capacity
private final Set<Signature> seenMessages =
Collections.newSetFromMap(
new LinkedHashMap<Signature, Boolean>() {
@Override
protected boolean removeEldestEntry(final Map.Entry<Signature, Boolean> eldest) {
return size() > maxSeenMessages;
}
});
IbftGossip(final IbftMulticaster peers, final int maxSeenMessages) {
this.maxSeenMessages = maxSeenMessages;
this.peers = peers;
}
/**
* Constructor that attaches gossip logic to a set of peers
*
* @param peers The always up to date set of connected peers that understand IBFT
*/
public IbftGossip(final IbftMulticaster peers) {
this(peers, 10_000);
}
/**
* Retransmit a given IBFT message to other known validators nodes
*
* @param message The raw message to be gossiped
* @return Whether the message was rebroadcast or has been ignored as a repeat
*/
public boolean gossipMessage(final Message message) {
final MessageData messageData = message.getData();
final SignedData<?> signedData;
switch (messageData.getCode()) {
case IbftV2.PROPOSAL:
signedData = ProposalMessageData.fromMessageData(messageData).decode();
break;
case IbftV2.PREPARE:
signedData = PrepareMessageData.fromMessageData(messageData).decode();
break;
case IbftV2.COMMIT:
signedData = CommitMessageData.fromMessageData(messageData).decode();
break;
case IbftV2.ROUND_CHANGE:
signedData = RoundChangeMessageData.fromMessageData(messageData).decode();
break;
case IbftV2.NEW_ROUND:
signedData = NewRoundMessageData.fromMessageData(messageData).decode();
break;
default:
throw new IllegalArgumentException(
"Received message does not conform to any recognised IBFT message structure.");
}
final Signature signature = signedData.getSignature();
if (seenMessages.contains(signature)) {
return false;
} else {
final List<Address> excludeAddressesList =
Lists.newArrayList(
message.getConnection().getPeer().getAddress(), signedData.getSender());
peers.multicastToValidatorsExcept(messageData, excludeAddressesList);
seenMessages.add(signature);
return true;
}
}
}

@ -12,12 +12,12 @@
*/ */
package tech.pegasys.pantheon.consensus.ibft; package tech.pegasys.pantheon.consensus.ibft;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.ethereum.p2p.api.Message; import tech.pegasys.pantheon.ethereum.p2p.api.Message;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
@ -29,19 +29,19 @@ public class IbftMessages {
switch (messageData.getCode()) { switch (messageData.getCode()) {
case IbftV2.PROPOSAL: case IbftV2.PROPOSAL:
return ProposalMessage.fromMessage(messageData).decode(); return ProposalMessageData.fromMessageData(messageData).decode();
case IbftV2.PREPARE: case IbftV2.PREPARE:
return PrepareMessage.fromMessage(messageData).decode(); return PrepareMessageData.fromMessageData(messageData).decode();
case IbftV2.COMMIT: case IbftV2.COMMIT:
return CommitMessage.fromMessage(messageData).decode(); return CommitMessageData.fromMessageData(messageData).decode();
case IbftV2.ROUND_CHANGE: case IbftV2.ROUND_CHANGE:
return RoundChangeMessage.fromMessage(messageData).decode(); return RoundChangeMessageData.fromMessageData(messageData).decode();
case IbftV2.NEW_ROUND: case IbftV2.NEW_ROUND:
return NewRoundMessage.fromMessage(messageData).decode(); return NewRoundMessageData.fromMessageData(messageData).decode();
default: default:
throw new IllegalArgumentException( throw new IllegalArgumentException(

@ -17,7 +17,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.Message;
/** Static helper functions for producing and working with IbftEvent objects */ /** Static helper functions for producing and working with IbftEvent objects */
public class IbftEvents { public class IbftEvents {
public static IbftEvent fromMessage(final Message message) { public static IbftEvent fromMessage(final Message message) {
return new IbftReceivedMessageEvent(message.getData()); return new IbftReceivedMessageEvent(message);
} }
public enum Type { public enum Type {

@ -13,18 +13,18 @@
package tech.pegasys.pantheon.consensus.ibft.ibftevent; package tech.pegasys.pantheon.consensus.ibft.ibftevent;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents.Type; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents.Type;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.Message;
public class IbftReceivedMessageEvent implements IbftEvent { public class IbftReceivedMessageEvent implements IbftEvent {
private final MessageData messageData; private final Message message;
public IbftReceivedMessageEvent(final MessageData messageData) { public IbftReceivedMessageEvent(final Message message) {
this.messageData = messageData; this.message = message;
} }
public MessageData getMessageData() { public Message getMessage() {
return messageData; return message;
} }
@Override @Override

@ -12,6 +12,7 @@
*/ */
package tech.pegasys.pantheon.consensus.ibft.ibftmessage; package tech.pegasys.pantheon.consensus.ibft.ibftmessage;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.Payload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.p2p.wire.AbstractMessageData; import tech.pegasys.pantheon.ethereum.p2p.wire.AbstractMessageData;
@ -19,29 +20,30 @@ import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.function.Function; import java.util.function.Function;
public abstract class AbstractIbftMessage extends AbstractMessageData { public abstract class AbstractIbftMessageData extends AbstractMessageData {
protected AbstractIbftMessage(final BytesValue data) { protected AbstractIbftMessageData(final BytesValue data) {
super(data); super(data);
} }
public abstract SignedData<?> decode(); public abstract SignedData<? extends Payload> decode();
protected static <T extends AbstractIbftMessage> T fromMessage( protected static <T extends AbstractIbftMessageData> T fromMessageData(
final MessageData message, final MessageData messageData,
final int messageCode, final int messageCode,
final Class<T> clazz, final Class<T> clazz,
final Function<BytesValue, T> constructor) { final Function<BytesValue, T> constructor) {
if (clazz.isInstance(message)) { if (clazz.isInstance(messageData)) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
T castMessage = (T) message; T castMessage = (T) messageData;
return castMessage; return castMessage;
} }
final int code = message.getCode(); final int code = messageData.getCode();
if (code != messageCode) { if (code != messageCode) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
String.format("Message has code %d and thus is not a %s", code, clazz.getSimpleName())); String.format(
"MessageData has code %d and thus is not a %s", code, clazz.getSimpleName()));
} }
return constructor.apply(message.getData()); return constructor.apply(messageData.getData());
} }
} }

@ -18,16 +18,17 @@ import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.rlp.RLP; import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
public class CommitMessage extends AbstractIbftMessage { public class CommitMessageData extends AbstractIbftMessageData {
private static final int MESSAGE_CODE = IbftV2.COMMIT; private static final int MESSAGE_CODE = IbftV2.COMMIT;
private CommitMessage(final BytesValue data) { private CommitMessageData(final BytesValue data) {
super(data); super(data);
} }
public static CommitMessage fromMessage(final MessageData message) { public static CommitMessageData fromMessageData(final MessageData messageData) {
return fromMessage(message, MESSAGE_CODE, CommitMessage.class, CommitMessage::new); return fromMessageData(
messageData, MESSAGE_CODE, CommitMessageData.class, CommitMessageData::new);
} }
@Override @Override
@ -35,9 +36,9 @@ public class CommitMessage extends AbstractIbftMessage {
return SignedData.readSignedCommitPayloadFrom(RLP.input(data)); return SignedData.readSignedCommitPayloadFrom(RLP.input(data));
} }
public static CommitMessage create(final SignedData<CommitPayload> signedPayload) { public static CommitMessageData create(final SignedData<CommitPayload> signedPayload) {
return new CommitMessage(signedPayload.encode()); return new CommitMessageData(signedPayload.encode());
} }
@Override @Override

@ -18,16 +18,17 @@ import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.rlp.RLP; import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
public class NewRoundMessage extends AbstractIbftMessage { public class NewRoundMessageData extends AbstractIbftMessageData {
private static final int MESSAGE_CODE = IbftV2.NEW_ROUND; private static final int MESSAGE_CODE = IbftV2.NEW_ROUND;
private NewRoundMessage(final BytesValue data) { private NewRoundMessageData(final BytesValue data) {
super(data); super(data);
} }
public static NewRoundMessage fromMessage(final MessageData message) { public static NewRoundMessageData fromMessageData(final MessageData messageData) {
return fromMessage(message, MESSAGE_CODE, NewRoundMessage.class, NewRoundMessage::new); return fromMessageData(
messageData, MESSAGE_CODE, NewRoundMessageData.class, NewRoundMessageData::new);
} }
@Override @Override
@ -35,9 +36,9 @@ public class NewRoundMessage extends AbstractIbftMessage {
return SignedData.readSignedNewRoundPayloadFrom(RLP.input(data)); return SignedData.readSignedNewRoundPayloadFrom(RLP.input(data));
} }
public static NewRoundMessage create(final SignedData<NewRoundPayload> signedPayload) { public static NewRoundMessageData create(final SignedData<NewRoundPayload> signedPayload) {
return new NewRoundMessage(signedPayload.encode()); return new NewRoundMessageData(signedPayload.encode());
} }
@Override @Override

@ -18,16 +18,17 @@ import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.rlp.RLP; import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
public class PrepareMessage extends AbstractIbftMessage { public class PrepareMessageData extends AbstractIbftMessageData {
private static final int MESSAGE_CODE = IbftV2.PREPARE; private static final int MESSAGE_CODE = IbftV2.PREPARE;
private PrepareMessage(final BytesValue data) { private PrepareMessageData(final BytesValue data) {
super(data); super(data);
} }
public static PrepareMessage fromMessage(final MessageData message) { public static PrepareMessageData fromMessageData(final MessageData messageData) {
return fromMessage(message, MESSAGE_CODE, PrepareMessage.class, PrepareMessage::new); return fromMessageData(
messageData, MESSAGE_CODE, PrepareMessageData.class, PrepareMessageData::new);
} }
@Override @Override
@ -35,9 +36,9 @@ public class PrepareMessage extends AbstractIbftMessage {
return SignedData.readSignedPreparePayloadFrom(RLP.input(data)); return SignedData.readSignedPreparePayloadFrom(RLP.input(data));
} }
public static PrepareMessage create(final SignedData<PreparePayload> signedPayload) { public static PrepareMessageData create(final SignedData<PreparePayload> signedPayload) {
return new PrepareMessage(signedPayload.encode()); return new PrepareMessageData(signedPayload.encode());
} }
@Override @Override

@ -18,16 +18,17 @@ import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.rlp.RLP; import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
public class ProposalMessage extends AbstractIbftMessage { public class ProposalMessageData extends AbstractIbftMessageData {
private static final int MESSAGE_CODE = IbftV2.PROPOSAL; private static final int MESSAGE_CODE = IbftV2.PROPOSAL;
private ProposalMessage(final BytesValue data) { private ProposalMessageData(final BytesValue data) {
super(data); super(data);
} }
public static ProposalMessage fromMessage(final MessageData message) { public static ProposalMessageData fromMessageData(final MessageData messageData) {
return fromMessage(message, MESSAGE_CODE, ProposalMessage.class, ProposalMessage::new); return fromMessageData(
messageData, MESSAGE_CODE, ProposalMessageData.class, ProposalMessageData::new);
} }
@Override @Override
@ -35,9 +36,9 @@ public class ProposalMessage extends AbstractIbftMessage {
return SignedData.readSignedProposalPayloadFrom(RLP.input(data)); return SignedData.readSignedProposalPayloadFrom(RLP.input(data));
} }
public static ProposalMessage create(final SignedData<ProposalPayload> signedPayload) { public static ProposalMessageData create(final SignedData<ProposalPayload> signedPayload) {
return new ProposalMessage(signedPayload.encode()); return new ProposalMessageData(signedPayload.encode());
} }
@Override @Override

@ -18,16 +18,17 @@ import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.rlp.RLP; import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
public class RoundChangeMessage extends AbstractIbftMessage { public class RoundChangeMessageData extends AbstractIbftMessageData {
private static final int MESSAGE_CODE = IbftV2.ROUND_CHANGE; private static final int MESSAGE_CODE = IbftV2.ROUND_CHANGE;
private RoundChangeMessage(final BytesValue data) { private RoundChangeMessageData(final BytesValue data) {
super(data); super(data);
} }
public static RoundChangeMessage fromMessage(final MessageData message) { public static RoundChangeMessageData fromMessageData(final MessageData messageData) {
return fromMessage(message, MESSAGE_CODE, RoundChangeMessage.class, RoundChangeMessage::new); return fromMessageData(
messageData, MESSAGE_CODE, RoundChangeMessageData.class, RoundChangeMessageData::new);
} }
@Override @Override
@ -35,9 +36,9 @@ public class RoundChangeMessage extends AbstractIbftMessage {
return SignedData.readSignedRoundChangePayloadFrom(RLP.input(data)); return SignedData.readSignedRoundChangePayloadFrom(RLP.input(data));
} }
public static RoundChangeMessage create(final SignedData<RoundChangePayload> signedPayload) { public static RoundChangeMessageData create(final SignedData<RoundChangePayload> signedPayload) {
return new RoundChangeMessage(signedPayload.encode()); return new RoundChangeMessageData(signedPayload.encode());
} }
@Override @Override

@ -12,9 +12,15 @@
*/ */
package tech.pegasys.pantheon.consensus.ibft.network; package tech.pegasys.pantheon.consensus.ibft.network;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import java.util.Collection;
public interface IbftMulticaster { public interface IbftMulticaster {
void multicastToValidators(final MessageData message); void multicastToValidators(final MessageData message);
void multicastToValidatorsExcept(
final MessageData message, final Collection<Address> exceptAddresses);
} }

@ -13,17 +13,15 @@
package tech.pegasys.pantheon.consensus.ibft.network; package tech.pegasys.pantheon.consensus.ibft.network;
import tech.pegasys.pantheon.consensus.common.ValidatorProvider; import tech.pegasys.pantheon.consensus.common.ValidatorProvider;
import tech.pegasys.pantheon.crypto.SECP256K1.PublicKey;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection;
import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -43,12 +41,12 @@ public class IbftNetworkPeers implements IbftMulticaster {
} }
public void peerAdded(final PeerConnection newConnection) { public void peerAdded(final PeerConnection newConnection) {
final Address peerAddress = getAddressFrom(newConnection); final Address peerAddress = newConnection.getPeer().getAddress();
peerConnections.put(peerAddress, newConnection); peerConnections.put(peerAddress, newConnection);
} }
public void peerRemoved(final PeerConnection removedConnection) { public void peerRemoved(final PeerConnection removedConnection) {
final Address peerAddress = getAddressFrom(removedConnection); final Address peerAddress = removedConnection.getPeer().getAddress();
peerConnections.remove(peerAddress); peerConnections.remove(peerAddress);
} }
@ -58,6 +56,18 @@ public class IbftNetworkPeers implements IbftMulticaster {
sendMessageToSpecificAddresses(validators, message); sendMessageToSpecificAddresses(validators, message);
} }
@Override
public void multicastToValidatorsExcept(
final MessageData message, final Collection<Address> exceptAddresses) {
final Collection<Address> includedValidators =
validatorProvider
.getValidators()
.stream()
.filter(a -> !exceptAddresses.contains(a))
.collect(Collectors.toSet());
sendMessageToSpecificAddresses(includedValidators, message);
}
private void sendMessageToSpecificAddresses( private void sendMessageToSpecificAddresses(
final Collection<Address> recipients, final MessageData message) { final Collection<Address> recipients, final MessageData message) {
recipients recipients
@ -73,10 +83,4 @@ public class IbftNetworkPeers implements IbftMulticaster {
} }
}); });
} }
private Address getAddressFrom(final PeerConnection connection) {
final BytesValue peerNodeId = connection.getPeer().getNodeId();
final PublicKey remotePublicKey = PublicKey.create(peerNodeId);
return Util.publicKeyToAddress(remotePublicKey);
}
} }

@ -146,51 +146,53 @@ public class IbftBlockHeightManager {
// Its possible the locally created RoundChange triggers the transmission of a NewRound // Its possible the locally created RoundChange triggers the transmission of a NewRound
// message - so it must be handled accordingly. // message - so it must be handled accordingly.
handleRoundChangeMessage(localRoundChange); handleRoundChangePayload(localRoundChange);
} }
public void handleProposalMessage(final SignedData<ProposalPayload> msg) { public void handleProposalPayload(final SignedData<ProposalPayload> signedPayload) {
LOG.info("Received a Proposal message."); LOG.info("Received a Proposal Payload.");
actionOrBufferMessage(msg, currentRound::handleProposalMessage, RoundState::setProposedBlock); actionOrBufferMessage(
signedPayload, currentRound::handleProposalMessage, RoundState::setProposedBlock);
} }
public void handlePrepareMessage(final SignedData<PreparePayload> msg) { public void handlePreparePayload(final SignedData<PreparePayload> signedPayload) {
LOG.info("Received a prepare message."); LOG.info("Received a prepare Payload.");
actionOrBufferMessage(msg, currentRound::handlePrepareMessage, RoundState::addPrepareMessage); actionOrBufferMessage(
signedPayload, currentRound::handlePrepareMessage, RoundState::addPrepareMessage);
} }
public void handleCommitMessage(final SignedData<CommitPayload> msg) { public void handleCommitPayload(final SignedData<CommitPayload> payload) {
LOG.info("Received a commit message."); LOG.info("Received a commit Payload.");
actionOrBufferMessage(msg, currentRound::handleCommitMessage, RoundState::addCommitMessage); actionOrBufferMessage(payload, currentRound::handleCommitMessage, RoundState::addCommitMessage);
} }
private <T extends Payload> void actionOrBufferMessage( private <T extends Payload> void actionOrBufferMessage(
final SignedData<T> msg, final SignedData<T> msgData,
final Consumer<SignedData<T>> inRoundHandler, final Consumer<SignedData<T>> inRoundHandler,
final BiConsumer<RoundState, SignedData<T>> buffer) { final BiConsumer<RoundState, SignedData<T>> buffer) {
final Payload payload = msg.getPayload(); final Payload payload = msgData.getPayload();
final MessageAge messageAge = determineAgeOfPayload(payload); final MessageAge messageAge = determineAgeOfPayload(payload);
if (messageAge == CURRENT_ROUND) { if (messageAge == CURRENT_ROUND) {
inRoundHandler.accept(msg); inRoundHandler.accept(msgData);
} else if (messageAge == FUTURE_ROUND) { } else if (messageAge == FUTURE_ROUND) {
final ConsensusRoundIdentifier msgRoundId = payload.getRoundIdentifier(); final ConsensusRoundIdentifier msgRoundId = payload.getRoundIdentifier();
final RoundState roundstate = final RoundState roundstate =
futureRoundStateBuffer.computeIfAbsent( futureRoundStateBuffer.computeIfAbsent(
msgRoundId.getRoundNumber(), k -> roundStateCreator.apply(msgRoundId)); msgRoundId.getRoundNumber(), k -> roundStateCreator.apply(msgRoundId));
buffer.accept(roundstate, msg); buffer.accept(roundstate, msgData);
} }
} }
public void handleRoundChangeMessage(final SignedData<RoundChangePayload> msg) { public void handleRoundChangePayload(final SignedData<RoundChangePayload> signedPayload) {
final Optional<RoundChangeCertificate> result = final Optional<RoundChangeCertificate> result =
roundChangeManager.appendRoundChangeMessage(msg); roundChangeManager.appendRoundChangeMessage(signedPayload);
final MessageAge messageAge = determineAgeOfPayload(msg.getPayload()); final MessageAge messageAge = determineAgeOfPayload(signedPayload.getPayload());
if (messageAge == PRIOR_ROUND) { if (messageAge == PRIOR_ROUND) {
LOG.info("Received RoundChange Message for a prior round."); LOG.info("Received RoundChange Payload for a prior round.");
return; return;
} }
ConsensusRoundIdentifier targetRound = msg.getPayload().getRoundIdentifier(); final ConsensusRoundIdentifier targetRound = signedPayload.getPayload().getRoundIdentifier();
LOG.info("Received a RoundChange message for {}", targetRound.toString()); LOG.info("Received a RoundChange Payload for {}", targetRound.toString());
if (result.isPresent()) { if (result.isPresent()) {
if (messageAge == FUTURE_ROUND) { if (messageAge == FUTURE_ROUND) {
@ -218,17 +220,17 @@ public class IbftBlockHeightManager {
roundTimer.startTimer(currentRound.getRoundIdentifier()); roundTimer.startTimer(currentRound.getRoundIdentifier());
} }
public void handleNewRoundMessage(final SignedData<NewRoundPayload> msg) { public void handleNewRoundPayload(final SignedData<NewRoundPayload> signedPayload) {
final NewRoundPayload payload = msg.getPayload(); final NewRoundPayload payload = signedPayload.getPayload();
final MessageAge messageAge = determineAgeOfPayload(payload); final MessageAge messageAge = determineAgeOfPayload(payload);
if (messageAge == PRIOR_ROUND) { if (messageAge == PRIOR_ROUND) {
LOG.info("Received NewRound Message for a prior round."); LOG.info("Received NewRound Payload for a prior round.");
return; return;
} }
LOG.info("Received NewRound Message for {}", payload.getRoundIdentifier().toString()); LOG.info("Received NewRound Payload for {}", payload.getRoundIdentifier().toString());
if (newRoundMessageValidator.validateNewRoundMessage(msg)) { if (newRoundMessageValidator.validateNewRoundMessage(signedPayload)) {
if (messageAge == FUTURE_ROUND) { if (messageAge == FUTURE_ROUND) {
startNewRound(payload.getRoundIdentifier().getRoundNumber()); startNewRound(payload.getRoundIdentifier().getRoundNumber());
} }
@ -241,8 +243,8 @@ public class IbftBlockHeightManager {
} }
private MessageAge determineAgeOfPayload(final Payload payload) { private MessageAge determineAgeOfPayload(final Payload payload) {
int messageRoundNumber = payload.getRoundIdentifier().getRoundNumber(); final int messageRoundNumber = payload.getRoundIdentifier().getRoundNumber();
int currentRoundNumber = currentRound.getRoundIdentifier().getRoundNumber(); final int currentRoundNumber = currentRound.getRoundIdentifier().getRoundNumber();
if (messageRoundNumber > currentRoundNumber) { if (messageRoundNumber > currentRoundNumber) {
return FUTURE_ROUND; return FUTURE_ROUND;
} else if (messageRoundNumber == currentRoundNumber) { } else if (messageRoundNumber == currentRoundNumber) {

@ -15,29 +15,27 @@ package tech.pegasys.pantheon.consensus.ibft.statemachine;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftGossip;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.BlockTimerExpiry; import tech.pegasys.pantheon.consensus.ibft.ibftevent.BlockTimerExpiry;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead; import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessageData;
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.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.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.p2p.api.Message;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -50,26 +48,34 @@ public class IbftController {
private final Blockchain blockchain; private final Blockchain blockchain;
private final IbftFinalState ibftFinalState; private final IbftFinalState ibftFinalState;
private final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory; private final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory;
private final Map<Long, List<MessageData>> futureMessages; private final Map<Long, List<Message>> futureMessages;
private IbftBlockHeightManager currentHeightManager; private IbftBlockHeightManager currentHeightManager;
private final IbftGossip gossiper;
public IbftController( public IbftController(
final Blockchain blockchain, final Blockchain blockchain,
final IbftFinalState ibftFinalState, final IbftFinalState ibftFinalState,
final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory) { final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory) {
this(blockchain, ibftFinalState, ibftBlockHeightManagerFactory, Maps.newHashMap()); this(
blockchain,
ibftFinalState,
ibftBlockHeightManagerFactory,
Maps.newHashMap(),
new IbftGossip(ibftFinalState.getPeers()));
} }
@VisibleForTesting @VisibleForTesting
IbftController( public IbftController(
final Blockchain blockchain, final Blockchain blockchain,
final IbftFinalState ibftFinalState, final IbftFinalState ibftFinalState,
final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory, final IbftBlockHeightManagerFactory ibftBlockHeightManagerFactory,
final Map<Long, List<MessageData>> futureMessages) { final Map<Long, List<Message>> futureMessages,
final IbftGossip gossiper) {
this.blockchain = blockchain; this.blockchain = blockchain;
this.ibftFinalState = ibftFinalState; this.ibftFinalState = ibftFinalState;
this.ibftBlockHeightManagerFactory = ibftBlockHeightManagerFactory; this.ibftBlockHeightManagerFactory = ibftBlockHeightManagerFactory;
this.futureMessages = futureMessages; this.futureMessages = futureMessages;
this.gossiper = gossiper;
} }
public void start() { public void start() {
@ -77,48 +83,45 @@ public class IbftController {
} }
public void handleMessageEvent(final IbftReceivedMessageEvent msg) { public void handleMessageEvent(final IbftReceivedMessageEvent msg) {
handleMessage(msg.getMessageData()); handleMessage(msg.getMessage());
} }
private void handleMessage(final MessageData messageData) { private void handleMessage(final Message message) {
final MessageData messageData = message.getData();
switch (messageData.getCode()) { switch (messageData.getCode()) {
case IbftV2.PROPOSAL: case IbftV2.PROPOSAL:
final SignedData<ProposalPayload> proposalMsg = consumeMessage(
ProposalMessage.fromMessage(messageData).decode(); message,
if (processMessage(proposalMsg, messageData)) { ProposalMessageData.fromMessageData(messageData).decode(),
currentHeightManager.handleProposalMessage(proposalMsg); currentHeightManager::handleProposalPayload);
}
break; break;
case IbftV2.PREPARE: case IbftV2.PREPARE:
final SignedData<PreparePayload> prepareMsg = consumeMessage(
PrepareMessage.fromMessage(messageData).decode(); message,
if (processMessage(prepareMsg, messageData)) { PrepareMessageData.fromMessageData(messageData).decode(),
currentHeightManager.handlePrepareMessage(prepareMsg); currentHeightManager::handlePreparePayload);
}
break; break;
case IbftV2.COMMIT: case IbftV2.COMMIT:
final SignedData<CommitPayload> commitMsg = CommitMessage.fromMessage(messageData).decode(); consumeMessage(
if (processMessage(commitMsg, messageData)) { message,
currentHeightManager.handleCommitMessage(commitMsg); CommitMessageData.fromMessageData(messageData).decode(),
} currentHeightManager::handleCommitPayload);
break; break;
case IbftV2.ROUND_CHANGE: case IbftV2.ROUND_CHANGE:
final SignedData<RoundChangePayload> roundChangeMsg = consumeMessage(
RoundChangeMessage.fromMessage(messageData).decode(); message,
if (processMessage(roundChangeMsg, messageData)) { RoundChangeMessageData.fromMessageData(messageData).decode(),
currentHeightManager.handleRoundChangeMessage(roundChangeMsg); currentHeightManager::handleRoundChangePayload);
}
break; break;
case IbftV2.NEW_ROUND: case IbftV2.NEW_ROUND:
final SignedData<NewRoundPayload> newRoundMsg = consumeMessage(
NewRoundMessage.fromMessage(messageData).decode(); message,
if (processMessage(newRoundMsg, messageData)) { NewRoundMessageData.fromMessageData(messageData).decode(),
currentHeightManager.handleNewRoundMessage(newRoundMsg); currentHeightManager::handleNewRoundPayload);
}
break; break;
default: default:
@ -127,6 +130,16 @@ public class IbftController {
} }
} }
private <P extends Payload> void consumeMessage(
final Message message,
final SignedData<P> signedPayload,
final Consumer<SignedData<P>> handleMessage) {
if (processMessage(signedPayload, message)) {
gossiper.gossipMessage(message);
handleMessage.accept(signedPayload);
}
}
public void handleNewBlockEvent(final NewChainHead newChainHead) { public void handleNewBlockEvent(final NewChainHead newChainHead) {
startNewHeightManager(newChainHead.getNewChainHeadHeader()); startNewHeightManager(newChainHead.getNewChainHeadHeader());
} }
@ -151,20 +164,19 @@ public class IbftController {
currentHeightManager = ibftBlockHeightManagerFactory.create(parentHeader); currentHeightManager = ibftBlockHeightManagerFactory.create(parentHeader);
currentHeightManager.start(); currentHeightManager.start();
final long newChainHeight = currentHeightManager.getChainHeight(); final long newChainHeight = currentHeightManager.getChainHeight();
List<MessageData> orDefault = futureMessages.getOrDefault(newChainHeight, emptyList()); final List<Message> orDefault = futureMessages.getOrDefault(newChainHeight, emptyList());
orDefault.forEach(this::handleMessage); orDefault.forEach(this::handleMessage);
futureMessages.remove(newChainHeight); futureMessages.remove(newChainHeight);
} }
private boolean processMessage( private boolean processMessage(final SignedData<? extends Payload> msg, final Message rawMsg) {
final SignedData<? extends Payload> msg, final MessageData rawMsg) {
final ConsensusRoundIdentifier msgRoundIdentifier = msg.getPayload().getRoundIdentifier(); final ConsensusRoundIdentifier msgRoundIdentifier = msg.getPayload().getRoundIdentifier();
if (isMsgForCurrentHeight(msgRoundIdentifier)) { if (isMsgForCurrentHeight(msgRoundIdentifier)) {
return isMsgFromKnownValidator(msg); return isMsgFromKnownValidator(msg);
} else if (isMsgForFutureChainHeight(msgRoundIdentifier)) { } else if (isMsgForFutureChainHeight(msgRoundIdentifier)) {
addMessageToFutureMessageBuffer(msgRoundIdentifier.getSequenceNumber(), rawMsg); addMessageToFutureMessageBuffer(msgRoundIdentifier.getSequenceNumber(), rawMsg);
} else { } else {
LOG.info("IBFT message discarded as it is not for the current block height"); LOG.info("IBFT message discarded as it is from a previous block height");
} }
return false; return false;
} }
@ -181,7 +193,7 @@ public class IbftController {
return roundIdentifier.getSequenceNumber() > currentHeightManager.getChainHeight(); return roundIdentifier.getSequenceNumber() > currentHeightManager.getChainHeight();
} }
private void addMessageToFutureMessageBuffer(final long chainHeight, final MessageData rawMsg) { private void addMessageToFutureMessageBuffer(final long chainHeight, final Message rawMsg) {
if (!futureMessages.containsKey(chainHeight)) { if (!futureMessages.containsKey(chainHeight)) {
futureMessages.put(chainHeight, Lists.newArrayList()); futureMessages.put(chainHeight, Lists.newArrayList());
} }

@ -13,11 +13,11 @@
package tech.pegasys.pantheon.consensus.ibft.statemachine; package tech.pegasys.pantheon.consensus.ibft.statemachine;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.MessageFactory;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload;
@ -49,7 +49,7 @@ public class IbftMessageTransmitter {
final SignedData<ProposalPayload> signedPayload = final SignedData<ProposalPayload> signedPayload =
messageFactory.createSignedProposalPayload(roundIdentifier, block); messageFactory.createSignedProposalPayload(roundIdentifier, block);
final ProposalMessage message = ProposalMessage.create(signedPayload); final ProposalMessageData message = ProposalMessageData.create(signedPayload);
multicaster.multicastToValidators(message); multicaster.multicastToValidators(message);
} }
@ -58,7 +58,7 @@ public class IbftMessageTransmitter {
final SignedData<PreparePayload> signedPayload = final SignedData<PreparePayload> signedPayload =
messageFactory.createSignedPreparePayload(roundIdentifier, digest); messageFactory.createSignedPreparePayload(roundIdentifier, digest);
final PrepareMessage message = PrepareMessage.create(signedPayload); final PrepareMessageData message = PrepareMessageData.create(signedPayload);
multicaster.multicastToValidators(message); multicaster.multicastToValidators(message);
} }
@ -70,7 +70,7 @@ public class IbftMessageTransmitter {
final SignedData<CommitPayload> signedPayload = final SignedData<CommitPayload> signedPayload =
messageFactory.createSignedCommitPayload(roundIdentifier, digest, commitSeal); messageFactory.createSignedCommitPayload(roundIdentifier, digest, commitSeal);
final CommitMessage message = CommitMessage.create(signedPayload); final CommitMessageData message = CommitMessageData.create(signedPayload);
multicaster.multicastToValidators(message); multicaster.multicastToValidators(message);
} }
@ -82,7 +82,7 @@ public class IbftMessageTransmitter {
final SignedData<RoundChangePayload> signedPayload = final SignedData<RoundChangePayload> signedPayload =
messageFactory.createSignedRoundChangePayload(roundIdentifier, preparedCertificate); messageFactory.createSignedRoundChangePayload(roundIdentifier, preparedCertificate);
final RoundChangeMessage message = RoundChangeMessage.create(signedPayload); final RoundChangeMessageData message = RoundChangeMessageData.create(signedPayload);
multicaster.multicastToValidators(message); multicaster.multicastToValidators(message);
} }
@ -96,7 +96,7 @@ public class IbftMessageTransmitter {
messageFactory.createSignedNewRoundPayload( messageFactory.createSignedNewRoundPayload(
roundIdentifier, roundChangeCertificate, proposalPayload); roundIdentifier, roundChangeCertificate, proposalPayload);
final NewRoundMessage message = NewRoundMessage.create(signedPayload); final NewRoundMessageData message = NewRoundMessageData.create(signedPayload);
multicaster.multicastToValidators(message); multicaster.multicastToValidators(message);
} }

@ -0,0 +1,194 @@
/*
* 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;
import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.Payload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.ProposalPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.consensus.ibft.network.IbftNetworkPeers;
import tech.pegasys.pantheon.consensus.ibft.network.MockPeerFactory;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.AddressHelpers;
import tech.pegasys.pantheon.ethereum.p2p.api.Message;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection;
import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage;
import java.util.function.Function;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class IbftGossipTest {
private IbftGossip ibftGossip;
@Mock private IbftNetworkPeers ibftNetworkPeers;
private PeerConnection peerConnection;
private static final Address senderAddress = AddressHelpers.ofValue(9);
@Before
public void setup() {
ibftGossip = new IbftGossip(ibftNetworkPeers, 10);
peerConnection = MockPeerFactory.create(senderAddress);
}
private <P extends Payload> void assertRebroadcastToAllExceptSignerAndSender(
final Function<KeyPair, SignedData<P>> createPayload,
final Function<SignedData<P>, MessageData> createMessageData) {
final KeyPair keypair = KeyPair.generate();
final SignedData<P> payload = createPayload.apply(keypair);
final MessageData messageData = createMessageData.apply(payload);
final Message message = new DefaultMessage(peerConnection, messageData);
final boolean gossipResult = ibftGossip.gossipMessage(message);
assertThat(gossipResult).isTrue();
verify(ibftNetworkPeers)
.multicastToValidatorsExcept(messageData, newArrayList(senderAddress, payload.getSender()));
}
private <P extends Payload> void assertRebroadcastOnlyOnce(
final Function<KeyPair, SignedData<P>> createPayload,
final Function<SignedData<P>, MessageData> createMessageData) {
final KeyPair keypair = KeyPair.generate();
final SignedData<P> payload = createPayload.apply(keypair);
final MessageData messageData = createMessageData.apply(payload);
final Message message = new DefaultMessage(peerConnection, messageData);
final boolean gossip1Result = ibftGossip.gossipMessage(message);
final boolean gossip2Result = ibftGossip.gossipMessage(message);
assertThat(gossip1Result).isTrue();
assertThat(gossip2Result).isFalse();
verify(ibftNetworkPeers, times(1))
.multicastToValidatorsExcept(messageData, newArrayList(senderAddress, payload.getSender()));
}
@Test
public void assertRebroadcastsProposalToAllExceptSignerAndSender() {
assertRebroadcastToAllExceptSignerAndSender(
TestHelpers::createSignedProposalPayload, ProposalMessageData::create);
}
@Test
public void assertRebroadcastsProposalOnlyOnce() {
assertRebroadcastOnlyOnce(
TestHelpers::createSignedProposalPayload, ProposalMessageData::create);
}
@Test
public void assertRebroadcastsPrepareToAllExceptSignerAndSender() {
assertRebroadcastToAllExceptSignerAndSender(
TestHelpers::createSignedPreparePayload, PrepareMessageData::create);
}
@Test
public void assertRebroadcastsPrepareOnlyOnce() {
assertRebroadcastOnlyOnce(TestHelpers::createSignedPreparePayload, PrepareMessageData::create);
}
@Test
public void assertRebroadcastsCommitToAllExceptSignerAndSender() {
assertRebroadcastToAllExceptSignerAndSender(
TestHelpers::createSignedCommitPayload, CommitMessageData::create);
}
@Test
public void assertRebroadcastsCommitOnlyOnce() {
assertRebroadcastOnlyOnce(TestHelpers::createSignedCommitPayload, CommitMessageData::create);
}
@Test
public void assertRebroadcastsRoundChangeToAllExceptSignerAndSender() {
assertRebroadcastToAllExceptSignerAndSender(
TestHelpers::createSignedRoundChangePayload, RoundChangeMessageData::create);
}
@Test
public void assertRebroadcastsRoundChangeOnlyOnce() {
assertRebroadcastOnlyOnce(
TestHelpers::createSignedRoundChangePayload, RoundChangeMessageData::create);
}
@Test
public void assertRebroadcastsNewRoundToAllExceptSignerAndSender() {
assertRebroadcastToAllExceptSignerAndSender(
TestHelpers::createSignedNewRoundPayload, NewRoundMessageData::create);
}
@Test
public void assertRebroadcastsNewRoundOnlyOnce() {
assertRebroadcastOnlyOnce(
TestHelpers::createSignedNewRoundPayload, NewRoundMessageData::create);
}
@Test
public void evictMessageRecordAtCapacity() {
final KeyPair keypair = KeyPair.generate();
final SignedData<ProposalPayload> payload =
TestHelpers.createSignedProposalPayloadWithRound(keypair, 0);
final MessageData messageData = ProposalMessageData.create(payload);
final Message message = new DefaultMessage(peerConnection, messageData);
final boolean gossip1Result = ibftGossip.gossipMessage(message);
final boolean gossip2Result = ibftGossip.gossipMessage(message);
assertThat(gossip1Result).isTrue();
assertThat(gossip2Result).isFalse();
verify(ibftNetworkPeers, times(1))
.multicastToValidatorsExcept(messageData, newArrayList(senderAddress, payload.getSender()));
for (int i = 1; i <= 9; i++) {
final SignedData<ProposalPayload> nextPayload =
TestHelpers.createSignedProposalPayloadWithRound(keypair, i);
final MessageData nextMessageData = ProposalMessageData.create(nextPayload);
final Message nextMessage = new DefaultMessage(peerConnection, nextMessageData);
final boolean nextGossipResult = ibftGossip.gossipMessage(nextMessage);
assertThat(nextGossipResult).isTrue();
}
final boolean gossip3Result = ibftGossip.gossipMessage(message);
assertThat(gossip3Result).isFalse();
verify(ibftNetworkPeers, times(1))
.multicastToValidatorsExcept(messageData, newArrayList(senderAddress, payload.getSender()));
{
final SignedData<ProposalPayload> nextPayload =
TestHelpers.createSignedProposalPayloadWithRound(keypair, 10);
final MessageData nextMessageData = ProposalMessageData.create(nextPayload);
final Message nextMessage = new DefaultMessage(peerConnection, nextMessageData);
final boolean nextGossipResult = ibftGossip.gossipMessage(nextMessage);
assertThat(nextGossipResult).isTrue();
}
final boolean gossip4Result = ibftGossip.gossipMessage(message);
assertThat(gossip4Result).isTrue();
verify(ibftNetworkPeers, times(2))
.multicastToValidatorsExcept(messageData, newArrayList(senderAddress, payload.getSender()));
final boolean gossip5Result = ibftGossip.gossipMessage(message);
assertThat(gossip5Result).isFalse();
verify(ibftNetworkPeers, times(2))
.multicastToValidatorsExcept(messageData, newArrayList(senderAddress, payload.getSender()));
}
}

@ -12,12 +12,28 @@
*/ */
package tech.pegasys.pantheon.consensus.ibft; package tech.pegasys.pantheon.consensus.ibft;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.singletonList;
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.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.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Address; 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.Block;
import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator; import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator;
import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator.BlockOptions; import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator.BlockOptions;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.math.BigInteger;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -45,4 +61,53 @@ public class TestHelpers {
.setBlockHashFunction(IbftBlockHashing::calculateDataHashForCommittedSeal); .setBlockHashFunction(IbftBlockHashing::calculateDataHashForCommittedSeal);
return new BlockDataGenerator().block(blockOptions); return new BlockDataGenerator().block(blockOptions);
} }
public static SignedData<ProposalPayload> createSignedProposalPayload(final KeyPair signerKeys) {
return createSignedProposalPayloadWithRound(signerKeys, 0xFEDCBA98);
}
public static SignedData<ProposalPayload> createSignedProposalPayloadWithRound(
final KeyPair signerKeys, final int round) {
final MessageFactory messageFactory = new MessageFactory(signerKeys);
final ConsensusRoundIdentifier roundIdentifier =
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, round);
final Block block =
TestHelpers.createProposalBlock(singletonList(AddressHelpers.ofValue(1)), 0);
return messageFactory.createSignedProposalPayload(roundIdentifier, block);
}
public static SignedData<PreparePayload> createSignedPreparePayload(final KeyPair signerKeys) {
final MessageFactory messageFactory = new MessageFactory(signerKeys);
final ConsensusRoundIdentifier roundIdentifier =
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98);
return messageFactory.createSignedPreparePayload(
roundIdentifier, Hash.fromHexStringLenient("0"));
}
public static SignedData<CommitPayload> createSignedCommitPayload(final KeyPair signerKeys) {
final MessageFactory messageFactory = new MessageFactory(signerKeys);
final ConsensusRoundIdentifier roundIdentifier =
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98);
return messageFactory.createSignedCommitPayload(
roundIdentifier,
Hash.fromHexStringLenient("0"),
Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0));
}
public static SignedData<RoundChangePayload> createSignedRoundChangePayload(
final KeyPair signerKeys) {
final MessageFactory messageFactory = new MessageFactory(signerKeys);
final ConsensusRoundIdentifier roundIdentifier =
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98);
return messageFactory.createSignedRoundChangePayload(roundIdentifier, Optional.empty());
}
public static SignedData<NewRoundPayload> createSignedNewRoundPayload(final KeyPair signerKeys) {
final MessageFactory messageFactory = new MessageFactory(signerKeys);
final ConsensusRoundIdentifier roundIdentifier =
new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98);
final SignedData<ProposalPayload> proposalPayload = createSignedProposalPayload(signerKeys);
return messageFactory.createSignedNewRoundPayload(
roundIdentifier, new RoundChangeCertificate(newArrayList()), proposalPayload);
}
} }

@ -32,12 +32,12 @@ public class CommitMessageTest {
@Mock private SignedData<CommitPayload> commitPayload; @Mock private SignedData<CommitPayload> commitPayload;
@Mock private BytesValue messageBytes; @Mock private BytesValue messageBytes;
@Mock private MessageData messageData; @Mock private MessageData messageData;
@Mock private CommitMessage commitMessage; @Mock private CommitMessageData commitMessage;
@Test @Test
public void createMessageFromCommitMessageData() { public void createMessageFromCommitMessageData() {
when(commitPayload.encode()).thenReturn(messageBytes); when(commitPayload.encode()).thenReturn(messageBytes);
CommitMessage commitMessage = CommitMessage.create(commitPayload); CommitMessageData commitMessage = CommitMessageData.create(commitPayload);
assertThat(commitMessage.getData()).isEqualTo(messageBytes); assertThat(commitMessage.getData()).isEqualTo(messageBytes);
assertThat(commitMessage.getCode()).isEqualTo(IbftV2.COMMIT); assertThat(commitMessage.getCode()).isEqualTo(IbftV2.COMMIT);
@ -46,7 +46,7 @@ public class CommitMessageTest {
@Test @Test
public void createMessageFromCommitMessage() { public void createMessageFromCommitMessage() {
CommitMessage message = CommitMessage.fromMessage(commitMessage); CommitMessageData message = CommitMessageData.fromMessageData(commitMessage);
assertThat(message).isSameAs(commitMessage); assertThat(message).isSameAs(commitMessage);
} }
@ -54,7 +54,7 @@ public class CommitMessageTest {
public void createMessageFromGenericMessageData() { public void createMessageFromGenericMessageData() {
when(messageData.getData()).thenReturn(messageBytes); when(messageData.getData()).thenReturn(messageBytes);
when(messageData.getCode()).thenReturn(IbftV2.COMMIT); when(messageData.getCode()).thenReturn(IbftV2.COMMIT);
CommitMessage commitMessage = CommitMessage.fromMessage(messageData); CommitMessageData commitMessage = CommitMessageData.fromMessageData(messageData);
assertThat(commitMessage.getData()).isEqualTo(messageData.getData()); assertThat(commitMessage.getData()).isEqualTo(messageData.getData());
assertThat(commitMessage.getCode()).isEqualTo(IbftV2.COMMIT); assertThat(commitMessage.getCode()).isEqualTo(IbftV2.COMMIT);
@ -63,8 +63,8 @@ public class CommitMessageTest {
@Test @Test
public void createMessageFailsWhenIncorrectMessageCode() { public void createMessageFailsWhenIncorrectMessageCode() {
when(messageData.getCode()).thenReturn(42); when(messageData.getCode()).thenReturn(42);
assertThatThrownBy(() -> CommitMessage.fromMessage(messageData)) assertThatThrownBy(() -> CommitMessageData.fromMessageData(messageData))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Message has code 42 and thus is not a CommitMessage"); .hasMessageContaining("MessageData has code 42 and thus is not a CommitMessageData");
} }
} }

@ -32,12 +32,12 @@ public class NewRoundMessageTest {
@Mock private SignedData<NewRoundPayload> newRoundPayload; @Mock private SignedData<NewRoundPayload> newRoundPayload;
@Mock private BytesValue messageBytes; @Mock private BytesValue messageBytes;
@Mock private MessageData messageData; @Mock private MessageData messageData;
@Mock private NewRoundMessage newRoundMessage; @Mock private NewRoundMessageData newRoundMessage;
@Test @Test
public void createMessageFromNewRoundChangeMessageData() { public void createMessageFromNewRoundChangeMessageData() {
when(newRoundPayload.encode()).thenReturn(messageBytes); when(newRoundPayload.encode()).thenReturn(messageBytes);
NewRoundMessage prepareMessage = NewRoundMessage.create(newRoundPayload); NewRoundMessageData prepareMessage = NewRoundMessageData.create(newRoundPayload);
assertThat(prepareMessage.getData()).isEqualTo(messageBytes); assertThat(prepareMessage.getData()).isEqualTo(messageBytes);
assertThat(prepareMessage.getCode()).isEqualTo(IbftV2.NEW_ROUND); assertThat(prepareMessage.getCode()).isEqualTo(IbftV2.NEW_ROUND);
@ -46,7 +46,7 @@ public class NewRoundMessageTest {
@Test @Test
public void createMessageFromNewRoundMessage() { public void createMessageFromNewRoundMessage() {
NewRoundMessage message = NewRoundMessage.fromMessage(newRoundMessage); NewRoundMessageData message = NewRoundMessageData.fromMessageData(newRoundMessage);
assertThat(message).isSameAs(newRoundMessage); assertThat(message).isSameAs(newRoundMessage);
} }
@ -54,7 +54,7 @@ public class NewRoundMessageTest {
public void createMessageFromGenericMessageData() { public void createMessageFromGenericMessageData() {
when(messageData.getData()).thenReturn(messageBytes); when(messageData.getData()).thenReturn(messageBytes);
when(messageData.getCode()).thenReturn(IbftV2.NEW_ROUND); when(messageData.getCode()).thenReturn(IbftV2.NEW_ROUND);
NewRoundMessage newRoundMessage = NewRoundMessage.fromMessage(messageData); NewRoundMessageData newRoundMessage = NewRoundMessageData.fromMessageData(messageData);
assertThat(newRoundMessage.getData()).isEqualTo(messageData.getData()); assertThat(newRoundMessage.getData()).isEqualTo(messageData.getData());
assertThat(newRoundMessage.getCode()).isEqualTo(IbftV2.NEW_ROUND); assertThat(newRoundMessage.getCode()).isEqualTo(IbftV2.NEW_ROUND);
@ -63,8 +63,8 @@ public class NewRoundMessageTest {
@Test @Test
public void createMessageFailsWhenIncorrectMessageCode() { public void createMessageFailsWhenIncorrectMessageCode() {
when(messageData.getCode()).thenReturn(42); when(messageData.getCode()).thenReturn(42);
assertThatThrownBy(() -> NewRoundMessage.fromMessage(messageData)) assertThatThrownBy(() -> NewRoundMessageData.fromMessageData(messageData))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Message has code 42 and thus is not a NewRoundMessage"); .hasMessageContaining("MessageData has code 42 and thus is not a NewRoundMessageData");
} }
} }

@ -32,12 +32,12 @@ public class PrepareMessageTest {
@Mock private SignedData<PreparePayload> preparePayload; @Mock private SignedData<PreparePayload> preparePayload;
@Mock private BytesValue messageBytes; @Mock private BytesValue messageBytes;
@Mock private MessageData messageData; @Mock private MessageData messageData;
@Mock private PrepareMessage prepareMessage; @Mock private PrepareMessageData prepareMessage;
@Test @Test
public void createMessageFromPrepareMessageData() { public void createMessageFromPrepareMessageData() {
when(preparePayload.encode()).thenReturn(messageBytes); when(preparePayload.encode()).thenReturn(messageBytes);
PrepareMessage prepareMessage = PrepareMessage.create(preparePayload); PrepareMessageData prepareMessage = PrepareMessageData.create(preparePayload);
assertThat(prepareMessage.getData()).isEqualTo(messageBytes); assertThat(prepareMessage.getData()).isEqualTo(messageBytes);
assertThat(prepareMessage.getCode()).isEqualTo(IbftV2.PREPARE); assertThat(prepareMessage.getCode()).isEqualTo(IbftV2.PREPARE);
@ -46,7 +46,7 @@ public class PrepareMessageTest {
@Test @Test
public void createMessageFromPrepareMessage() { public void createMessageFromPrepareMessage() {
PrepareMessage message = PrepareMessage.fromMessage(prepareMessage); PrepareMessageData message = PrepareMessageData.fromMessageData(prepareMessage);
assertThat(message).isSameAs(prepareMessage); assertThat(message).isSameAs(prepareMessage);
} }
@ -54,7 +54,7 @@ public class PrepareMessageTest {
public void createMessageFromGenericMessageData() { public void createMessageFromGenericMessageData() {
when(messageData.getData()).thenReturn(messageBytes); when(messageData.getData()).thenReturn(messageBytes);
when(messageData.getCode()).thenReturn(IbftV2.PREPARE); when(messageData.getCode()).thenReturn(IbftV2.PREPARE);
PrepareMessage prepareMessage = PrepareMessage.fromMessage(messageData); PrepareMessageData prepareMessage = PrepareMessageData.fromMessageData(messageData);
assertThat(prepareMessage.getData()).isEqualTo(messageData.getData()); assertThat(prepareMessage.getData()).isEqualTo(messageData.getData());
assertThat(prepareMessage.getCode()).isEqualTo(IbftV2.PREPARE); assertThat(prepareMessage.getCode()).isEqualTo(IbftV2.PREPARE);
@ -63,8 +63,8 @@ public class PrepareMessageTest {
@Test @Test
public void createMessageFailsWhenIncorrectMessageCode() { public void createMessageFailsWhenIncorrectMessageCode() {
when(messageData.getCode()).thenReturn(42); when(messageData.getCode()).thenReturn(42);
assertThatThrownBy(() -> PrepareMessage.fromMessage(messageData)) assertThatThrownBy(() -> PrepareMessageData.fromMessageData(messageData))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Message has code 42 and thus is not a PrepareMessage"); .hasMessageContaining("MessageData has code 42 and thus is not a PrepareMessageData");
} }
} }

@ -28,25 +28,25 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class PrePrepareMessageTest { public class ProposalMessageTest {
@Mock private SignedData<ProposalPayload> prePrepareMessageData; @Mock private SignedData<ProposalPayload> proposalMessageData;
@Mock private BytesValue messageBytes; @Mock private BytesValue messageBytes;
@Mock private MessageData messageData; @Mock private MessageData messageData;
@Mock private ProposalMessage proposalMessage; @Mock private ProposalMessageData proposalMessage;
@Test @Test
public void createMessageFromPrePrepareMessageData() { public void createMessageFromPrePrepareMessageData() {
when(prePrepareMessageData.encode()).thenReturn(messageBytes); when(proposalMessageData.encode()).thenReturn(messageBytes);
ProposalMessage proposalMessage = ProposalMessage.create(prePrepareMessageData); final ProposalMessageData proposalMessage = ProposalMessageData.create(proposalMessageData);
assertThat(proposalMessage.getData()).isEqualTo(messageBytes); assertThat(proposalMessage.getData()).isEqualTo(messageBytes);
assertThat(proposalMessage.getCode()).isEqualTo(IbftV2.PROPOSAL); assertThat(proposalMessage.getCode()).isEqualTo(IbftV2.PROPOSAL);
verify(prePrepareMessageData).encode(); verify(proposalMessageData).encode();
} }
@Test @Test
public void createMessageFromPrePrepareMessage() { public void createMessageFromPrePrepareMessage() {
ProposalMessage message = ProposalMessage.fromMessage(proposalMessage); final ProposalMessageData message = ProposalMessageData.fromMessageData(proposalMessage);
assertThat(message).isSameAs(proposalMessage); assertThat(message).isSameAs(proposalMessage);
} }
@ -54,7 +54,7 @@ public class PrePrepareMessageTest {
public void createMessageFromGenericMessageData() { public void createMessageFromGenericMessageData() {
when(messageData.getCode()).thenReturn(IbftV2.PROPOSAL); when(messageData.getCode()).thenReturn(IbftV2.PROPOSAL);
when(messageData.getData()).thenReturn(messageBytes); when(messageData.getData()).thenReturn(messageBytes);
ProposalMessage proposalMessage = ProposalMessage.fromMessage(messageData); final ProposalMessageData proposalMessage = ProposalMessageData.fromMessageData(messageData);
assertThat(proposalMessage.getData()).isEqualTo(messageData.getData()); assertThat(proposalMessage.getData()).isEqualTo(messageData.getData());
assertThat(proposalMessage.getCode()).isEqualTo(IbftV2.PROPOSAL); assertThat(proposalMessage.getCode()).isEqualTo(IbftV2.PROPOSAL);
@ -63,8 +63,8 @@ public class PrePrepareMessageTest {
@Test @Test
public void createMessageFailsWhenIncorrectMessageCode() { public void createMessageFailsWhenIncorrectMessageCode() {
when(messageData.getCode()).thenReturn(42); when(messageData.getCode()).thenReturn(42);
assertThatThrownBy(() -> ProposalMessage.fromMessage(messageData)) assertThatThrownBy(() -> ProposalMessageData.fromMessageData(messageData))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Message has code 42 and thus is not a ProposalMessage"); .hasMessageContaining("MessageData has code 42 and thus is not a ProposalMessageData");
} }
} }

@ -32,12 +32,12 @@ public class RoundChangeMessageTest {
@Mock private SignedData<RoundChangePayload> roundChangePayload; @Mock private SignedData<RoundChangePayload> roundChangePayload;
@Mock private BytesValue messageBytes; @Mock private BytesValue messageBytes;
@Mock private MessageData messageData; @Mock private MessageData messageData;
@Mock private RoundChangeMessage roundChangeMessage; @Mock private RoundChangeMessageData roundChangeMessage;
@Test @Test
public void createMessageFromRoundChangeMessageData() { public void createMessageFromRoundChangeMessageData() {
when(roundChangePayload.encode()).thenReturn(messageBytes); when(roundChangePayload.encode()).thenReturn(messageBytes);
RoundChangeMessage roundChangeMessage = RoundChangeMessage.create(roundChangePayload); RoundChangeMessageData roundChangeMessage = RoundChangeMessageData.create(roundChangePayload);
assertThat(roundChangeMessage.getData()).isEqualTo(messageBytes); assertThat(roundChangeMessage.getData()).isEqualTo(messageBytes);
assertThat(roundChangeMessage.getCode()).isEqualTo(IbftV2.ROUND_CHANGE); assertThat(roundChangeMessage.getCode()).isEqualTo(IbftV2.ROUND_CHANGE);
@ -46,7 +46,7 @@ public class RoundChangeMessageTest {
@Test @Test
public void createMessageFromRoundChangeMessage() { public void createMessageFromRoundChangeMessage() {
RoundChangeMessage message = RoundChangeMessage.fromMessage(roundChangeMessage); RoundChangeMessageData message = RoundChangeMessageData.fromMessageData(roundChangeMessage);
assertThat(message).isSameAs(roundChangeMessage); assertThat(message).isSameAs(roundChangeMessage);
} }
@ -54,7 +54,7 @@ public class RoundChangeMessageTest {
public void createMessageFromGenericMessageData() { public void createMessageFromGenericMessageData() {
when(messageData.getData()).thenReturn(messageBytes); when(messageData.getData()).thenReturn(messageBytes);
when(messageData.getCode()).thenReturn(IbftV2.ROUND_CHANGE); when(messageData.getCode()).thenReturn(IbftV2.ROUND_CHANGE);
RoundChangeMessage roundChangeMessage = RoundChangeMessage.fromMessage(messageData); RoundChangeMessageData roundChangeMessage = RoundChangeMessageData.fromMessageData(messageData);
assertThat(roundChangeMessage.getData()).isEqualTo(messageData.getData()); assertThat(roundChangeMessage.getData()).isEqualTo(messageData.getData());
assertThat(roundChangeMessage.getCode()).isEqualTo(IbftV2.ROUND_CHANGE); assertThat(roundChangeMessage.getCode()).isEqualTo(IbftV2.ROUND_CHANGE);
@ -63,8 +63,8 @@ public class RoundChangeMessageTest {
@Test @Test
public void createMessageFailsWhenIncorrectMessageCode() { public void createMessageFailsWhenIncorrectMessageCode() {
when(messageData.getCode()).thenReturn(42); when(messageData.getCode()).thenReturn(42);
assertThatThrownBy(() -> RoundChangeMessage.fromMessage(messageData)) assertThatThrownBy(() -> RoundChangeMessageData.fromMessageData(messageData))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Message has code 42 and thus is not a RoundChangeMessage"); .hasMessageContaining("MessageData has code 42 and thus is not a RoundChangeMessageData");
} }
} }

@ -12,6 +12,7 @@
*/ */
package tech.pegasys.pantheon.consensus.ibft.network; package tech.pegasys.pantheon.consensus.ibft.network;
import static com.google.common.collect.Lists.newArrayList;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
@ -33,7 +34,6 @@ import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.List; import java.util.List;
import com.google.common.collect.Lists;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -42,21 +42,22 @@ import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class IbftNetworkPeersTest { public class IbftNetworkPeersTest {
private final List<Address> validators = Lists.newArrayList(); private final List<Address> validators = newArrayList();
private final List<PublicKey> publicKeys = Lists.newArrayList(); private final List<PublicKey> publicKeys = newArrayList();
private final List<PeerConnection> peerConnections = Lists.newArrayList(); private final List<PeerConnection> peerConnections = newArrayList();
@Before @Before
public void setup() { public void setup() {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
final PublicKey pubKey = PublicKey.create(BigInteger.valueOf(i)); final PublicKey pubKey = PublicKey.create(BigInteger.valueOf(i));
publicKeys.add(pubKey); publicKeys.add(pubKey);
final Address address = Util.publicKeyToAddress(pubKey);
final PeerInfo peerInfo = mock(PeerInfo.class); final PeerInfo peerInfo = mock(PeerInfo.class);
final PeerConnection peerConnection = mock(PeerConnection.class); final PeerConnection peerConnection = mock(PeerConnection.class);
when(peerConnection.getPeer()).thenReturn(peerInfo); when(peerConnection.getPeer()).thenReturn(peerInfo);
when(peerInfo.getNodeId()).thenReturn(pubKey.getEncodedBytes()); when(peerInfo.getAddress()).thenReturn(address);
peerConnections.add(peerConnection); peerConnections.add(peerConnection);
} }
@ -93,7 +94,7 @@ public class IbftNetworkPeersTest {
final IbftNetworkPeers peers = new IbftNetworkPeers(validatorProvider); final IbftNetworkPeers peers = new IbftNetworkPeers(validatorProvider);
// only add peer connections 1, 2 & 3, none of which should be invoked. // only add peer connections 1, 2 & 3, none of which should be invoked.
Lists.newArrayList(1, 2, 3).forEach(i -> peers.peerAdded(peerConnections.get(i))); newArrayList(1, 2, 3).forEach(i -> peers.peerAdded(peerConnections.get(i)));
final MessageData messageToSend = new RawMessage(1, BytesValue.EMPTY); final MessageData messageToSend = new RawMessage(1, BytesValue.EMPTY);
peers.multicastToValidators(messageToSend); peers.multicastToValidators(messageToSend);
@ -103,4 +104,27 @@ public class IbftNetworkPeersTest {
verify(peerConnections.get(2), never()).sendForProtocol(any(), any()); verify(peerConnections.get(2), never()).sendForProtocol(any(), any());
verify(peerConnections.get(3), never()).sendForProtocol(any(), any()); verify(peerConnections.get(3), never()).sendForProtocol(any(), any());
} }
@Test
public void onlyValidatorsAreSentAMessageNotInExcludes() throws PeerNotConnected {
// Only add the first Peer's address to the validators.
final Address validatorAddress = Util.publicKeyToAddress(publicKeys.get(0));
validators.add(validatorAddress);
validators.add(Util.publicKeyToAddress(publicKeys.get(1)));
final ValidatorProvider validatorProvider = mock(ValidatorProvider.class);
when(validatorProvider.getValidators()).thenReturn(validators);
final IbftNetworkPeers peers = new IbftNetworkPeers(validatorProvider);
for (final PeerConnection peer : peerConnections) {
peers.peerAdded(peer);
}
final MessageData messageToSend = new RawMessage(1, BytesValue.EMPTY);
peers.multicastToValidatorsExcept(messageToSend, newArrayList(validatorAddress));
verify(peerConnections.get(0), never()).sendForProtocol(any(), any());
verify(peerConnections.get(1), times(1)).sendForProtocol(any(), any());
verify(peerConnections.get(2), never()).sendForProtocol(any(), any());
verify(peerConnections.get(3), never()).sendForProtocol(any(), any());
}
} }

@ -0,0 +1,40 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.consensus.ibft.network;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.AddressHelpers;
import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection;
import tech.pegasys.pantheon.ethereum.p2p.wire.PeerInfo;
public class MockPeerFactory {
public static PeerConnection create() {
return create(AddressHelpers.ofValue(9));
}
public static PeerConnection create(final Address address) {
final PeerConnection peerConnection = mock(PeerConnection.class);
final PeerInfo peerInfo = createPeerInfo(address);
when(peerConnection.getPeer()).thenReturn(peerInfo);
return peerConnection;
}
public static PeerInfo createPeerInfo(final Address address) {
final PeerInfo peerInfo = mock(PeerInfo.class);
when(peerInfo.getAddress()).thenReturn(address);
return peerInfo;
}
}

@ -233,7 +233,7 @@ public class IbftBlockHeightManagerTest {
manager.start(); manager.start();
verify(roundFactory).createNewRound(any(), eq(0)); verify(roundFactory).createNewRound(any(), eq(0));
manager.handleRoundChangeMessage(roundChangePayload); manager.handleRoundChangePayload(roundChangePayload);
verify(roundChangeManager, times(1)).appendRoundChangeMessage(roundChangePayload); verify(roundChangeManager, times(1)).appendRoundChangeMessage(roundChangePayload);
verify(roundFactory, times(1)) verify(roundFactory, times(1))
@ -279,7 +279,7 @@ public class IbftBlockHeightManagerTest {
messageValidatorFactory); messageValidatorFactory);
manager.start(); manager.start();
manager.handleRoundChangeMessage(roundChangePayload); manager.handleRoundChangePayload(roundChangePayload);
verify(messageTransmitter, times(1)) verify(messageTransmitter, times(1))
.multicastNewRound(eq(futureRoundIdentifier), eq(roundChangCert), any()); .multicastNewRound(eq(futureRoundIdentifier), eq(roundChangCert), any());
@ -311,8 +311,8 @@ public class IbftBlockHeightManagerTest {
Hash.fromHexStringLenient("0"), Hash.fromHexStringLenient("0"),
Signature.create(BigInteger.ONE, BigInteger.ONE, (byte) 1)); Signature.create(BigInteger.ONE, BigInteger.ONE, (byte) 1));
manager.handlePrepareMessage(preparePayload); manager.handlePreparePayload(preparePayload);
manager.handleCommitMessage(commitPayload); manager.handleCommitPayload(commitPayload);
// Force a new round to be started at new round number. // Force a new round to be started at new round number.
final SignedData<NewRoundPayload> newRound = final SignedData<NewRoundPayload> newRound =
@ -321,7 +321,7 @@ public class IbftBlockHeightManagerTest {
new RoundChangeCertificate(Collections.emptyList()), new RoundChangeCertificate(Collections.emptyList()),
messageFactory.createSignedProposalPayload(futureRoundIdentifier, createdBlock)); messageFactory.createSignedProposalPayload(futureRoundIdentifier, createdBlock));
manager.handleNewRoundMessage(newRound); manager.handleNewRoundPayload(newRound);
// Final state sets the Quorum Size to 3, so should send a Prepare and also a commit // Final state sets the Quorum Size to 3, so should send a Prepare and also a commit
verify(messageTransmitter, times(1)).multicastPrepare(eq(futureRoundIdentifier), any()); verify(messageTransmitter, times(1)).multicastPrepare(eq(futureRoundIdentifier), any());
@ -349,8 +349,8 @@ public class IbftBlockHeightManagerTest {
validatorMessageFactory validatorMessageFactory
.get(1) .get(1)
.createSignedPreparePayload(roundIdentifier, Hash.fromHexStringLenient("0")); .createSignedPreparePayload(roundIdentifier, Hash.fromHexStringLenient("0"));
manager.handlePrepareMessage(preparePayload); manager.handlePreparePayload(preparePayload);
manager.handlePrepareMessage(secondPreparePayload); manager.handlePreparePayload(secondPreparePayload);
manager.roundExpired(new RoundExpiry(roundIdentifier)); manager.roundExpired(new RoundExpiry(roundIdentifier));

@ -12,7 +12,7 @@
*/ */
package tech.pegasys.pantheon.consensus.ibft.statemachine; package tech.pegasys.pantheon.consensus.ibft.statemachine;
import static org.assertj.core.api.Java6Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Lists.newArrayList; import static org.assertj.core.util.Lists.newArrayList;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
@ -22,16 +22,17 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier;
import tech.pegasys.pantheon.consensus.ibft.IbftGossip;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.BlockTimerExpiry; import tech.pegasys.pantheon.consensus.ibft.ibftevent.BlockTimerExpiry;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftReceivedMessageEvent;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead; import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead;
import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.CommitMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.NewRoundMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.PrepareMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.ProposalMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessage; import tech.pegasys.pantheon.consensus.ibft.ibftmessage.RoundChangeMessageData;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.CommitPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.NewRoundPayload;
import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload; import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.PreparePayload;
@ -41,7 +42,8 @@ import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.SignedData;
import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.Message;
import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -64,39 +66,46 @@ public class IbftControllerTest {
@Mock private IbftBlockHeightManager blockHeightManager; @Mock private IbftBlockHeightManager blockHeightManager;
@Mock private SignedData<ProposalPayload> signedProposal; @Mock private SignedData<ProposalPayload> signedProposal;
@Mock private ProposalMessage proposalMessage; private Message proposalMessage;
@Mock private ProposalMessageData proposalMessageData;
@Mock private ProposalPayload proposalPayload; @Mock private ProposalPayload proposalPayload;
@Mock private SignedData<PreparePayload> signedPrepare; @Mock private SignedData<PreparePayload> signedPrepare;
@Mock private PrepareMessage prepareMessage; private Message prepareMessage;
@Mock private PrepareMessageData prepareMessageData;
@Mock private PreparePayload preparePayload; @Mock private PreparePayload preparePayload;
@Mock private SignedData<CommitPayload> signedCommit; @Mock private SignedData<CommitPayload> signedCommit;
@Mock private CommitMessage commitMessage; private Message commitMessage;
@Mock private CommitMessageData commitMessageData;
@Mock private CommitPayload commitPayload; @Mock private CommitPayload commitPayload;
@Mock private SignedData<NewRoundPayload> signedNewRound; @Mock private SignedData<NewRoundPayload> signedNewRound;
@Mock private NewRoundMessage newRoundMessage; private Message newRoundMessage;
@Mock private NewRoundMessageData newRoundMessageData;
@Mock private NewRoundPayload newRoundPayload; @Mock private NewRoundPayload newRoundPayload;
@Mock private SignedData<RoundChangePayload> signedRoundChange; @Mock private SignedData<RoundChangePayload> signedRoundChange;
@Mock private RoundChangeMessage roundChangeMessage; private Message roundChangeMessage;
@Mock private RoundChangeMessageData roundChangeMessageData;
@Mock private RoundChangePayload roundChangePayload; @Mock private RoundChangePayload roundChangePayload;
private final Map<Long, List<MessageData>> futureMessages = new HashMap<>(); private final Map<Long, List<Message>> futureMessages = new HashMap<>();
private final Address validator = Address.fromHexString("0x0"); private final Address validator = Address.fromHexString("0x0");
private final Address unknownValidator = Address.fromHexString("0x2"); private final Address unknownValidator = Address.fromHexString("0x2");
private final ConsensusRoundIdentifier futureRoundIdentifier = new ConsensusRoundIdentifier(2, 0); private final ConsensusRoundIdentifier futureRoundIdentifier = new ConsensusRoundIdentifier(2, 0);
private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(0, 0); private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(0, 0);
@Mock private IbftGossip ibftGossip;
private IbftController ibftController; private IbftController ibftController;
@Before @Before
public void setup() { public void setup() {
ibftController =
new IbftController(blockChain, ibftFinalState, blockHeightManagerFactory, futureMessages);
when(blockChain.getChainHeadHeader()).thenReturn(blockHeader); when(blockChain.getChainHeadHeader()).thenReturn(blockHeader);
when(blockHeightManagerFactory.create(blockHeader)).thenReturn(blockHeightManager); when(blockHeightManagerFactory.create(blockHeader)).thenReturn(blockHeightManager);
when(ibftFinalState.getValidators()).thenReturn(ImmutableList.of(validator)); when(ibftFinalState.getValidators()).thenReturn(ImmutableList.of(validator));
ibftController =
new IbftController(
blockChain, ibftFinalState, blockHeightManagerFactory, futureMessages, ibftGossip);
} }
@Test @Test
@ -115,9 +124,9 @@ public class IbftControllerTest {
setupRoundChange(futureRoundIdentifier, validator); setupRoundChange(futureRoundIdentifier, validator);
setupNewRound(roundIdentifierHeight3, validator); setupNewRound(roundIdentifierHeight3, validator);
final List<MessageData> height2Msgs = final List<Message> height2Msgs =
newArrayList(prepareMessage, commitMessage, roundChangeMessage); newArrayList(prepareMessage, commitMessage, roundChangeMessage);
final List<MessageData> height3Msgs = newArrayList(proposalMessage, newRoundMessage); final List<Message> height3Msgs = newArrayList(proposalMessage, newRoundMessage);
futureMessages.put(2L, height2Msgs); futureMessages.put(2L, height2Msgs);
futureMessages.put(3L, height3Msgs); futureMessages.put(3L, height3Msgs);
when(blockHeightManager.getChainHeight()).thenReturn(2L); when(blockHeightManager.getChainHeight()).thenReturn(2L);
@ -128,11 +137,14 @@ public class IbftControllerTest {
verify(blockHeightManagerFactory).create(blockHeader); verify(blockHeightManagerFactory).create(blockHeader);
verify(blockHeightManager, atLeastOnce()).getChainHeight(); verify(blockHeightManager, atLeastOnce()).getChainHeight();
verify(blockHeightManager).start(); verify(blockHeightManager).start();
verify(blockHeightManager, never()).handleProposalMessage(signedProposal); verify(blockHeightManager, never()).handleProposalPayload(signedProposal);
verify(blockHeightManager).handlePrepareMessage(signedPrepare); verify(blockHeightManager).handlePreparePayload(signedPrepare);
verify(blockHeightManager).handleCommitMessage(signedCommit); verify(ibftGossip).gossipMessage(prepareMessage);
verify(blockHeightManager).handleRoundChangeMessage(signedRoundChange); verify(blockHeightManager).handleCommitPayload(signedCommit);
verify(blockHeightManager, never()).handleNewRoundMessage(signedNewRound); verify(ibftGossip).gossipMessage(commitMessage);
verify(blockHeightManager).handleRoundChangePayload(signedRoundChange);
verify(ibftGossip).gossipMessage(roundChangeMessage);
verify(blockHeightManager, never()).handleNewRoundPayload(signedNewRound);
} }
@Test @Test
@ -155,11 +167,16 @@ public class IbftControllerTest {
verify(blockHeightManagerFactory).create(blockHeader); verify(blockHeightManagerFactory).create(blockHeader);
verify(blockHeightManager, atLeastOnce()).getChainHeight(); verify(blockHeightManager, atLeastOnce()).getChainHeight();
verify(blockHeightManager).start(); verify(blockHeightManager).start();
verify(blockHeightManager).handleProposalMessage(signedProposal); verify(blockHeightManager).handleProposalPayload(signedProposal);
verify(blockHeightManager).handlePrepareMessage(signedPrepare); verify(ibftGossip).gossipMessage(proposalMessage);
verify(blockHeightManager).handleCommitMessage(signedCommit); verify(blockHeightManager).handlePreparePayload(signedPrepare);
verify(blockHeightManager).handleRoundChangeMessage(signedRoundChange); verify(ibftGossip).gossipMessage(prepareMessage);
verify(blockHeightManager).handleNewRoundMessage(signedNewRound); verify(blockHeightManager).handleCommitPayload(signedCommit);
verify(ibftGossip).gossipMessage(commitMessage);
verify(blockHeightManager).handleRoundChangePayload(signedRoundChange);
verify(ibftGossip).gossipMessage(roundChangeMessage);
verify(blockHeightManager).handleNewRoundPayload(signedNewRound);
verify(ibftGossip).gossipMessage(newRoundMessage);
} }
@Test @Test
@ -189,7 +206,8 @@ public class IbftControllerTest {
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(proposalMessage)); ibftController.handleMessageEvent(new IbftReceivedMessageEvent(proposalMessage));
assertThat(futureMessages).isEmpty(); assertThat(futureMessages).isEmpty();
verify(blockHeightManager).handleProposalMessage(signedProposal); verify(blockHeightManager).handleProposalPayload(signedProposal);
verify(ibftGossip).gossipMessage(proposalMessage);
verify(blockHeightManager, atLeastOnce()).getChainHeight(); verify(blockHeightManager, atLeastOnce()).getChainHeight();
verify(blockHeightManager).start(); verify(blockHeightManager).start();
verifyNoMoreInteractions(blockHeightManager); verifyNoMoreInteractions(blockHeightManager);
@ -202,7 +220,8 @@ public class IbftControllerTest {
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(prepareMessage)); ibftController.handleMessageEvent(new IbftReceivedMessageEvent(prepareMessage));
assertThat(futureMessages).isEmpty(); assertThat(futureMessages).isEmpty();
verify(blockHeightManager).handlePrepareMessage(signedPrepare); verify(blockHeightManager).handlePreparePayload(signedPrepare);
verify(ibftGossip).gossipMessage(prepareMessage);
verify(blockHeightManager, atLeastOnce()).getChainHeight(); verify(blockHeightManager, atLeastOnce()).getChainHeight();
verify(blockHeightManager).start(); verify(blockHeightManager).start();
verifyNoMoreInteractions(blockHeightManager); verifyNoMoreInteractions(blockHeightManager);
@ -215,7 +234,8 @@ public class IbftControllerTest {
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(commitMessage)); ibftController.handleMessageEvent(new IbftReceivedMessageEvent(commitMessage));
assertThat(futureMessages).isEmpty(); assertThat(futureMessages).isEmpty();
verify(blockHeightManager).handleCommitMessage(signedCommit); verify(blockHeightManager).handleCommitPayload(signedCommit);
verify(ibftGossip).gossipMessage(commitMessage);
verify(blockHeightManager, atLeastOnce()).getChainHeight(); verify(blockHeightManager, atLeastOnce()).getChainHeight();
verify(blockHeightManager).start(); verify(blockHeightManager).start();
verifyNoMoreInteractions(blockHeightManager); verifyNoMoreInteractions(blockHeightManager);
@ -229,7 +249,8 @@ public class IbftControllerTest {
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(newRoundMessage)); ibftController.handleMessageEvent(new IbftReceivedMessageEvent(newRoundMessage));
assertThat(futureMessages).isEmpty(); assertThat(futureMessages).isEmpty();
verify(blockHeightManager).handleNewRoundMessage(signedNewRound); verify(blockHeightManager).handleNewRoundPayload(signedNewRound);
verify(ibftGossip).gossipMessage(newRoundMessage);
verify(blockHeightManager, atLeastOnce()).getChainHeight(); verify(blockHeightManager, atLeastOnce()).getChainHeight();
verify(blockHeightManager).start(); verify(blockHeightManager).start();
verifyNoMoreInteractions(blockHeightManager); verifyNoMoreInteractions(blockHeightManager);
@ -243,7 +264,8 @@ public class IbftControllerTest {
ibftController.handleMessageEvent(new IbftReceivedMessageEvent(roundChangeMessage)); ibftController.handleMessageEvent(new IbftReceivedMessageEvent(roundChangeMessage));
assertThat(futureMessages).isEmpty(); assertThat(futureMessages).isEmpty();
verify(blockHeightManager).handleRoundChangeMessage(signedRoundChange); verify(blockHeightManager).handleRoundChangePayload(signedRoundChange);
verify(ibftGossip).gossipMessage(roundChangeMessage);
verify(blockHeightManager, atLeastOnce()).getChainHeight(); verify(blockHeightManager, atLeastOnce()).getChainHeight();
verify(blockHeightManager).start(); verify(blockHeightManager).start();
verifyNoMoreInteractions(blockHeightManager); verifyNoMoreInteractions(blockHeightManager);
@ -337,7 +359,7 @@ public class IbftControllerTest {
@Test @Test
public void proposalForFutureHeightIsBuffered() { public void proposalForFutureHeightIsBuffered() {
setupProposal(futureRoundIdentifier, validator); setupProposal(futureRoundIdentifier, validator);
final Map<Long, List<MessageData>> expectedFutureMsgs = final Map<Long, List<Message>> expectedFutureMsgs =
ImmutableMap.of(2L, ImmutableList.of(proposalMessage)); ImmutableMap.of(2L, ImmutableList.of(proposalMessage));
verifyHasFutureMessages(new IbftReceivedMessageEvent(proposalMessage), expectedFutureMsgs); verifyHasFutureMessages(new IbftReceivedMessageEvent(proposalMessage), expectedFutureMsgs);
} }
@ -345,7 +367,7 @@ public class IbftControllerTest {
@Test @Test
public void prepareForFutureHeightIsBuffered() { public void prepareForFutureHeightIsBuffered() {
setupPrepare(futureRoundIdentifier, validator); setupPrepare(futureRoundIdentifier, validator);
final Map<Long, List<MessageData>> expectedFutureMsgs = final Map<Long, List<Message>> expectedFutureMsgs =
ImmutableMap.of(2L, ImmutableList.of(prepareMessage)); ImmutableMap.of(2L, ImmutableList.of(prepareMessage));
verifyHasFutureMessages(new IbftReceivedMessageEvent(prepareMessage), expectedFutureMsgs); verifyHasFutureMessages(new IbftReceivedMessageEvent(prepareMessage), expectedFutureMsgs);
} }
@ -353,7 +375,7 @@ public class IbftControllerTest {
@Test @Test
public void commitForFutureHeightIsBuffered() { public void commitForFutureHeightIsBuffered() {
setupCommit(futureRoundIdentifier, validator); setupCommit(futureRoundIdentifier, validator);
final Map<Long, List<MessageData>> expectedFutureMsgs = final Map<Long, List<Message>> expectedFutureMsgs =
ImmutableMap.of(2L, ImmutableList.of(commitMessage)); ImmutableMap.of(2L, ImmutableList.of(commitMessage));
verifyHasFutureMessages(new IbftReceivedMessageEvent(commitMessage), expectedFutureMsgs); verifyHasFutureMessages(new IbftReceivedMessageEvent(commitMessage), expectedFutureMsgs);
} }
@ -361,7 +383,7 @@ public class IbftControllerTest {
@Test @Test
public void newRoundForFutureHeightIsBuffered() { public void newRoundForFutureHeightIsBuffered() {
setupNewRound(futureRoundIdentifier, validator); setupNewRound(futureRoundIdentifier, validator);
final Map<Long, List<MessageData>> expectedFutureMsgs = final Map<Long, List<Message>> expectedFutureMsgs =
ImmutableMap.of(2L, ImmutableList.of(newRoundMessage)); ImmutableMap.of(2L, ImmutableList.of(newRoundMessage));
verifyHasFutureMessages(new IbftReceivedMessageEvent(newRoundMessage), expectedFutureMsgs); verifyHasFutureMessages(new IbftReceivedMessageEvent(newRoundMessage), expectedFutureMsgs);
} }
@ -369,7 +391,7 @@ public class IbftControllerTest {
@Test @Test
public void roundChangeForFutureHeightIsBuffered() { public void roundChangeForFutureHeightIsBuffered() {
setupRoundChange(futureRoundIdentifier, validator); setupRoundChange(futureRoundIdentifier, validator);
final Map<Long, List<MessageData>> expectedFutureMsgs = final Map<Long, List<Message>> expectedFutureMsgs =
ImmutableMap.of(2L, ImmutableList.of(roundChangeMessage)); ImmutableMap.of(2L, ImmutableList.of(roundChangeMessage));
verifyHasFutureMessages(new IbftReceivedMessageEvent(roundChangeMessage), expectedFutureMsgs); verifyHasFutureMessages(new IbftReceivedMessageEvent(roundChangeMessage), expectedFutureMsgs);
} }
@ -385,7 +407,7 @@ public class IbftControllerTest {
} }
private void verifyHasFutureMessages( private void verifyHasFutureMessages(
final IbftReceivedMessageEvent msg, final Map<Long, List<MessageData>> expectedFutureMsgs) { final IbftReceivedMessageEvent msg, final Map<Long, List<Message>> expectedFutureMsgs) {
ibftController.start(); ibftController.start();
ibftController.handleMessageEvent(msg); ibftController.handleMessageEvent(msg);
@ -401,8 +423,9 @@ public class IbftControllerTest {
when(signedProposal.getPayload()).thenReturn(proposalPayload); when(signedProposal.getPayload()).thenReturn(proposalPayload);
when(signedProposal.getSender()).thenReturn(validator); when(signedProposal.getSender()).thenReturn(validator);
when(proposalPayload.getRoundIdentifier()).thenReturn(roundIdentifier); when(proposalPayload.getRoundIdentifier()).thenReturn(roundIdentifier);
when(proposalMessage.getCode()).thenReturn(IbftV2.PROPOSAL); when(proposalMessageData.getCode()).thenReturn(IbftV2.PROPOSAL);
when(proposalMessage.decode()).thenReturn(signedProposal); when(proposalMessageData.decode()).thenReturn(signedProposal);
proposalMessage = new DefaultMessage(null, proposalMessageData);
} }
private void setupPrepare( private void setupPrepare(
@ -410,8 +433,9 @@ public class IbftControllerTest {
when(signedPrepare.getPayload()).thenReturn(preparePayload); when(signedPrepare.getPayload()).thenReturn(preparePayload);
when(signedPrepare.getSender()).thenReturn(validator); when(signedPrepare.getSender()).thenReturn(validator);
when(preparePayload.getRoundIdentifier()).thenReturn(roundIdentifier); when(preparePayload.getRoundIdentifier()).thenReturn(roundIdentifier);
when(prepareMessage.getCode()).thenReturn(IbftV2.PREPARE); when(prepareMessageData.getCode()).thenReturn(IbftV2.PREPARE);
when(prepareMessage.decode()).thenReturn(signedPrepare); when(prepareMessageData.decode()).thenReturn(signedPrepare);
prepareMessage = new DefaultMessage(null, prepareMessageData);
} }
private void setupCommit( private void setupCommit(
@ -419,8 +443,9 @@ public class IbftControllerTest {
when(signedCommit.getPayload()).thenReturn(commitPayload); when(signedCommit.getPayload()).thenReturn(commitPayload);
when(signedCommit.getSender()).thenReturn(validator); when(signedCommit.getSender()).thenReturn(validator);
when(commitPayload.getRoundIdentifier()).thenReturn(roundIdentifier); when(commitPayload.getRoundIdentifier()).thenReturn(roundIdentifier);
when(commitMessage.getCode()).thenReturn(IbftV2.COMMIT); when(commitMessageData.getCode()).thenReturn(IbftV2.COMMIT);
when(commitMessage.decode()).thenReturn(signedCommit); when(commitMessageData.decode()).thenReturn(signedCommit);
commitMessage = new DefaultMessage(null, commitMessageData);
} }
private void setupNewRound( private void setupNewRound(
@ -428,8 +453,9 @@ public class IbftControllerTest {
when(signedNewRound.getPayload()).thenReturn(newRoundPayload); when(signedNewRound.getPayload()).thenReturn(newRoundPayload);
when(signedNewRound.getSender()).thenReturn(validator); when(signedNewRound.getSender()).thenReturn(validator);
when(newRoundPayload.getRoundIdentifier()).thenReturn(roundIdentifier); when(newRoundPayload.getRoundIdentifier()).thenReturn(roundIdentifier);
when(newRoundMessage.getCode()).thenReturn(IbftV2.NEW_ROUND); when(newRoundMessageData.getCode()).thenReturn(IbftV2.NEW_ROUND);
when(newRoundMessage.decode()).thenReturn(signedNewRound); when(newRoundMessageData.decode()).thenReturn(signedNewRound);
newRoundMessage = new DefaultMessage(null, newRoundMessageData);
} }
private void setupRoundChange( private void setupRoundChange(
@ -437,7 +463,8 @@ public class IbftControllerTest {
when(signedRoundChange.getPayload()).thenReturn(roundChangePayload); when(signedRoundChange.getPayload()).thenReturn(roundChangePayload);
when(signedRoundChange.getSender()).thenReturn(validator); when(signedRoundChange.getSender()).thenReturn(validator);
when(roundChangePayload.getRoundIdentifier()).thenReturn(roundIdentifier); when(roundChangePayload.getRoundIdentifier()).thenReturn(roundIdentifier);
when(roundChangeMessage.getCode()).thenReturn(IbftV2.ROUND_CHANGE); when(roundChangeMessageData.getCode()).thenReturn(IbftV2.ROUND_CHANGE);
when(roundChangeMessage.decode()).thenReturn(signedRoundChange); when(roundChangeMessageData.decode()).thenReturn(signedRoundChange);
roundChangeMessage = new DefaultMessage(null, roundChangeMessageData);
} }
} }

@ -15,6 +15,8 @@ package tech.pegasys.pantheon.ethereum.p2p.wire;
import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import com.google.common.base.Objects;
public abstract class AbstractMessageData implements MessageData { public abstract class AbstractMessageData implements MessageData {
protected final BytesValue data; protected final BytesValue data;
@ -32,4 +34,21 @@ public abstract class AbstractMessageData implements MessageData {
public BytesValue getData() { public BytesValue getData() {
return data; return data;
} }
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final AbstractMessageData that = (AbstractMessageData) o;
return Objects.equal(data, that.data);
}
@Override
public int hashCode() {
return Objects.hashCode(data);
}
} }

@ -14,6 +14,9 @@ package tech.pegasys.pantheon.ethereum.p2p.wire;
import static tech.pegasys.pantheon.util.bytes.BytesValue.wrap; import static tech.pegasys.pantheon.util.bytes.BytesValue.wrap;
import tech.pegasys.pantheon.crypto.SECP256K1.PublicKey;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.rlp.RLPInput; import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; import tech.pegasys.pantheon.ethereum.rlp.RLPOutput;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
@ -82,6 +85,11 @@ public class PeerInfo {
return nodeId; return nodeId;
} }
public Address getAddress() {
final PublicKey remotePublicKey = PublicKey.create(nodeId);
return Util.publicKeyToAddress(remotePublicKey);
}
public void writeTo(final RLPOutput out) { public void writeTo(final RLPOutput out) {
out.startList(); out.startList();
out.writeUnsignedByte(getVersion()); out.writeUnsignedByte(getVersion());

Loading…
Cancel
Save