From 8835c56c8db9e57d72d480c782cca6c390ee4297 Mon Sep 17 00:00:00 2001 From: tmohay Date: Thu, 14 Feb 2019 14:51:47 +1100 Subject: [PATCH 01/21] Remove the NewRound message, and increase proposal --- .../ibft/support/IntegrationTestHelpers.java | 6 +- .../ibft/support/RoundSpecificPeers.java | 4 - .../consensus/ibft/support/ValidatorPeer.java | 27 +-- .../consensus/ibft/tests/FutureRoundTest.java | 13 +- .../consensus/ibft/tests/GossipTest.java | 15 +- .../ibft/tests/LocalNodeIsProposerTest.java | 4 +- .../ibft/tests/ReceivedNewRoundTest.java | 42 ++-- .../consensus/ibft/tests/RoundChangeTest.java | 80 ++++---- .../ibft/tests/SpuriousBehaviourTest.java | 2 +- .../pantheon/consensus/ibft/IbftGossip.java | 4 - .../consensus/ibft/messagedata/IbftV2.java | 3 +- .../ibft/messagedata/NewRoundMessageData.java | 44 ----- .../ibft/messagewrappers/NewRound.java | 66 ------- .../ibft/messagewrappers/Proposal.java | 30 ++- .../ibft/network/IbftMessageTransmitter.java | 27 +-- .../ibft/payload/MessageFactory.java | 19 +- .../ibft/payload/NewRoundPayload.java | 145 -------------- .../consensus/ibft/payload/SignedData.java | 11 +- .../ibft/protocol/IbftSubProtocol.java | 3 - .../ibft/statemachine/BlockHeightManager.java | 3 - .../statemachine/IbftBlockHeightManager.java | 45 ++--- .../ibft/statemachine/IbftController.java | 8 - .../ibft/statemachine/IbftRound.java | 67 +++---- .../statemachine/NoOpBlockHeightManager.java | 4 - .../FutureRoundProposalMessageValidator.java | 48 +++++ .../ibft/validation/MessageValidator.java | 51 ++++- .../validation/MessageValidatorFactory.java | 29 +-- .../validation/NewRoundMessageValidator.java | 84 -------- .../validation/NewRoundPayloadValidator.java | 84 -------- .../consensus/ibft/IbftGossipTest.java | 7 - .../consensus/ibft/IbftHelpersTest.java | 5 +- .../pantheon/consensus/ibft/TestHelpers.java | 17 +- .../ibft/messagedata/NewRoundMessageTest.java | 69 ------- .../ibft/payload/NewRoundPayloadTest.java | 98 ---------- .../ibft/protocol/IbftSubProtocolTest.java | 5 +- .../IbftBlockHeightManagerTest.java | 60 ++++-- .../ibft/statemachine/IbftControllerTest.java | 62 +----- .../ibft/statemachine/IbftRoundTest.java | 39 ++-- .../RoundChangeArtifactsTest.java | 2 +- .../statemachine/RoundChangeManagerTest.java | 2 +- .../ibft/statemachine/RoundStateTest.java | 16 +- ...tureRoundProposalMessageValidatorTest.java | 86 ++++++++ .../ibft/validation/MessageValidatorTest.java | 79 +++++++- .../NewRoundMessageValidatorTest.java | 182 ----------------- .../NewRoundSignedDataValidatorTest.java | 185 ------------------ ...ProposalBlockConsistencyValidatorTest.java | 10 +- .../RoundChangeCertificateValidatorTest.java | 10 +- .../RoundChangeSignedDataValidatorTest.java | 12 +- .../validation/SignedDataValidatorTest.java | 37 ++-- 49 files changed, 561 insertions(+), 1390 deletions(-) delete mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageData.java delete mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/NewRound.java delete mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayload.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java delete mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java delete mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundPayloadValidator.java delete mode 100644 consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageTest.java delete mode 100644 consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayloadTest.java create mode 100644 consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java delete mode 100644 consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java delete mode 100644 consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/IntegrationTestHelpers.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/IntegrationTestHelpers.java index 336df86b08..63c25af914 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/IntegrationTestHelpers.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/IntegrationTestHelpers.java @@ -25,6 +25,7 @@ import tech.pegasys.pantheon.crypto.SECP256K1.Signature; import tech.pegasys.pantheon.ethereum.core.Block; +import java.util.Optional; import java.util.stream.Collectors; public class IntegrationTestHelpers { @@ -49,7 +50,10 @@ public static PreparedRoundArtifacts createValidPreparedRoundArtifacts( final RoundSpecificPeers peers = context.roundSpecificPeers(preparedRound); return new PreparedRoundArtifacts( - peers.getProposer().getMessageFactory().createProposal(preparedRound, block), + peers + .getProposer() + .getMessageFactory() + .createProposal(preparedRound, block, Optional.empty()), peers.createSignedPreparePayloadOfNonProposing(preparedRound, block.getHash()).stream() .map(Prepare::new) .collect(Collectors.toList())); diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificPeers.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificPeers.java index f904e72956..671160d98d 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificPeers.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/RoundSpecificPeers.java @@ -19,7 +19,6 @@ import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2; -import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData; @@ -197,9 +196,6 @@ private void verifyMessage(final MessageData actual, final IbftMessage expect case IbftV2.COMMIT: actualSignedPayload = CommitMessageData.fromMessageData(actual).decode(); break; - case IbftV2.NEW_ROUND: - actualSignedPayload = NewRoundMessageData.fromMessageData(actual).decode(); - break; case IbftV2.ROUND_CHANGE: actualSignedPayload = RoundChangeMessageData.fromMessageData(actual).decode(); break; diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java index 343edd95b8..03d1e29c8b 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/support/ValidatorPeer.java @@ -16,19 +16,15 @@ import tech.pegasys.pantheon.consensus.ibft.EventMultiplexer; import tech.pegasys.pantheon.consensus.ibft.ibftevent.IbftEvents; import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData; -import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; -import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangePayload; -import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; import tech.pegasys.pantheon.consensus.ibft.statemachine.PreparedRoundArtifacts; import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; @@ -79,7 +75,7 @@ public KeyPair getNodeKeys() { } public Proposal injectProposal(final ConsensusRoundIdentifier rId, final Block block) { - final Proposal payload = messageFactory.createProposal(rId, block); + final Proposal payload = messageFactory.createProposal(rId, block, Optional.empty()); injectMessage(ProposalMessageData.create(payload)); return payload; @@ -108,26 +104,15 @@ public Commit injectCommit( return payload; } - public NewRound injectNewRound( + public Proposal injectProposalForFutureRound( final ConsensusRoundIdentifier rId, final RoundChangeCertificate roundChangeCertificate, - final Proposal proposal) { - - final NewRound payload = - messageFactory.createNewRound( - rId, roundChangeCertificate, proposal.getSignedPayload(), proposal.getBlock()); - injectMessage(NewRoundMessageData.create(payload)); - return payload; - } - - public NewRound injectEmptyNewRound( - final ConsensusRoundIdentifier targetRoundId, - final List> roundChangePayloads, final Block blockToPropose) { - final Proposal proposal = messageFactory.createProposal(targetRoundId, blockToPropose); - - return injectNewRound(targetRoundId, new RoundChangeCertificate(roundChangePayloads), proposal); + final Proposal payload = + messageFactory.createProposal(rId, blockToPropose, Optional.of(roundChangeCertificate)); + injectMessage(ProposalMessageData.create(payload)); + return payload; } public RoundChange injectRoundChange( diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/FutureRoundTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/FutureRoundTest.java index f1ab08d5c3..bab80ec658 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/FutureRoundTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/FutureRoundTest.java @@ -19,6 +19,7 @@ import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; +import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; import tech.pegasys.pantheon.consensus.ibft.support.RoundSpecificPeers; import tech.pegasys.pantheon.consensus.ibft.support.TestContext; import tech.pegasys.pantheon.consensus.ibft.support.TestContextBuilder; @@ -93,8 +94,10 @@ public void messagesForFutureRoundAreNotActionedUntilRoundIsActive() { // and updates blockchain futurePeers .getProposer() - .injectEmptyNewRound( - futureRoundId, futurePeers.createSignedRoundChangePayload(futureRoundId), futureBlock); + .injectProposalForFutureRound( + futureRoundId, + new RoundChangeCertificate(futurePeers.createSignedRoundChangePayload(futureRoundId)), + futureBlock); final Prepare expectedPrepare = localNodeMessageFactory.createPrepare(futureRoundId, futureBlock.getHash()); @@ -136,8 +139,10 @@ public void priorRoundsCannotBeCompletedAfterReceptionOfNewRound() { futurePeers .getProposer() - .injectEmptyNewRound( - futureRoundId, futurePeers.createSignedRoundChangePayload(futureRoundId), futureBlock); + .injectProposalForFutureRound( + futureRoundId, + new RoundChangeCertificate(futurePeers.createSignedRoundChangePayload(futureRoundId)), + futureBlock); final Prepare expectedFuturePrepare = localNodeMessageFactory.createPrepare(futureRoundId, futureBlock.getHash()); diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/GossipTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/GossipTest.java index 9753aba637..fd3dff3149 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/GossipTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/GossipTest.java @@ -20,7 +20,6 @@ import tech.pegasys.pantheon.consensus.ibft.ibftevent.NewChainHead; import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; @@ -93,8 +92,10 @@ public void gossipMessagesToPeers() { final RoundChange roundChange = msgFactory.createRoundChange(roundId, Optional.empty()); final RoundChangeCertificate roundChangeCert = new RoundChangeCertificate(singleton(roundChange.getSignedPayload())); - final NewRound newRound = sender.injectNewRound(roundId, roundChangeCert, proposal); - peers.verifyMessagesReceivedNonPropsing(newRound); + + final Proposal nextRoundProposal = + sender.injectProposalForFutureRound(roundId, roundChangeCert, proposal.getBlock()); + peers.verifyMessagesReceivedNonPropsing(nextRoundProposal); peers.verifyNoMessagesReceivedProposer(); sender.injectRoundChange(roundId, Optional.empty()); @@ -118,7 +119,8 @@ public void onlyGossipOnce() { public void messageWithUnknownValidatorIsNotGossiped() { final KeyPair unknownKeyPair = KeyPair.generate(); final MessageFactory unknownMsgFactory = new MessageFactory(unknownKeyPair); - final Proposal unknownProposal = unknownMsgFactory.createProposal(roundId, block); + final Proposal unknownProposal = + unknownMsgFactory.createProposal(roundId, block, Optional.empty()); sender.injectMessage(ProposalMessageData.create(unknownProposal)); peers.verifyNoMessagesReceived(); @@ -128,7 +130,8 @@ public void messageWithUnknownValidatorIsNotGossiped() { public void messageIsNotGossipedToSenderOrCreator() { final ValidatorPeer msgCreator = peers.getFirstNonProposer(); final MessageFactory peerMsgFactory = msgCreator.getMessageFactory(); - final Proposal proposalFromPeer = peerMsgFactory.createProposal(roundId, block); + final Proposal proposalFromPeer = + peerMsgFactory.createProposal(roundId, block, Optional.empty()); sender.injectMessage(ProposalMessageData.create(proposalFromPeer)); @@ -139,7 +142,7 @@ public void messageIsNotGossipedToSenderOrCreator() { @Test public void futureMessageIsNotGossipedImmediately() { ConsensusRoundIdentifier futureRoundId = new ConsensusRoundIdentifier(2, 0); - msgFactory.createProposal(futureRoundId, block); + msgFactory.createProposal(futureRoundId, block, Optional.empty()); sender.injectProposal(futureRoundId, block); peers.verifyNoMessagesReceived(); diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeIsProposerTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeIsProposerTest.java index 51573d81ac..24bb0d9014 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeIsProposerTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/LocalNodeIsProposerTest.java @@ -28,6 +28,7 @@ import java.time.Clock; import java.time.Instant; import java.time.ZoneId; +import java.util.Optional; import org.junit.Before; import org.junit.Test; @@ -63,7 +64,8 @@ public class LocalNodeIsProposerTest { @Before public void setup() { expectedProposedBlock = context.createBlockForProposalFromChainHead(0, blockTimeStamp); - expectedTxProposal = localNodeMessageFactory.createProposal(roundId, expectedProposedBlock); + expectedTxProposal = + localNodeMessageFactory.createProposal(roundId, expectedProposedBlock, Optional.empty()); expectedTxCommit = new Commit( diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedNewRoundTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedNewRoundTest.java index 0301fe9969..16b6b6c8ee 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedNewRoundTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedNewRoundTest.java @@ -17,7 +17,6 @@ import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangePayload; @@ -68,10 +67,8 @@ public void newRoundMessageWithEmptyPrepareCertificatesOfferNewBlock() { final ValidatorPeer nextProposer = context.roundSpecificPeers(nextRoundId).getProposer(); - nextProposer.injectNewRound( - targetRound, - new RoundChangeCertificate(roundChanges), - nextProposer.getMessageFactory().createProposal(targetRound, blockToPropose)); + nextProposer.injectProposalForFutureRound( + targetRound, new RoundChangeCertificate(roundChanges), blockToPropose); final Prepare expectedPrepare = localNodeMessageFactory.createPrepare(targetRound, blockToPropose.getHash()); @@ -91,10 +88,8 @@ public void newRoundMessageFromIllegalSenderIsDiscardedAndNoPrepareForNewRoundIs final ValidatorPeer illegalProposer = context.roundSpecificPeers(nextRoundId).getNonProposing(0); - illegalProposer.injectNewRound( - nextRoundId, - new RoundChangeCertificate(roundChanges), - illegalProposer.getMessageFactory().createProposal(nextRoundId, blockToPropose)); + illegalProposer.injectProposalForFutureRound( + nextRoundId, new RoundChangeCertificate(roundChanges), blockToPropose); peers.verifyNoMessagesReceived(); } @@ -113,10 +108,8 @@ public void newRoundWithPrepareCertificateResultsInNewRoundStartingWithExpectedB final ValidatorPeer nextProposer = context.roundSpecificPeers(nextRoundId).getProposer(); - nextProposer.injectNewRound( - nextRoundId, - new RoundChangeCertificate(roundChanges), - peers.getNonProposing(0).getMessageFactory().createProposal(nextRoundId, reproposedBlock)); + nextProposer.injectProposalForFutureRound( + nextRoundId, new RoundChangeCertificate(roundChanges), reproposedBlock); peers.verifyMessagesReceived( localNodeMessageFactory.createPrepare(nextRoundId, reproposedBlock.getHash())); @@ -136,13 +129,10 @@ public void newRoundMessageForPriorRoundIsNotActioned() { final ValidatorPeer interimRoundProposer = context.roundSpecificPeers(interimRound).getProposer(); - final Proposal proposal = - interimRoundProposer - .getMessageFactory() - .createProposal(interimRound, context.createBlockForProposalFromChainHead(1, 30)); - - interimRoundProposer.injectNewRound( - interimRound, new RoundChangeCertificate(roundChangePayloads), proposal); + interimRoundProposer.injectProposalForFutureRound( + interimRound, + new RoundChangeCertificate(roundChangePayloads), + context.createBlockForProposalFromChainHead(1, 30)); peers.verifyNoMessagesReceived(); } @@ -162,10 +152,8 @@ public void receiveRoundStateIsNotLostIfASecondNewRoundMessageIsReceivedForCurre final RoundSpecificPeers nextRoles = context.roundSpecificPeers(nextRoundId); final ValidatorPeer nextProposer = nextRoles.getProposer(); - nextProposer.injectNewRound( - nextRoundId, - new RoundChangeCertificate(roundChanges), - peers.getNonProposing(0).getMessageFactory().createProposal(nextRoundId, reproposedBlock)); + nextProposer.injectProposalForFutureRound( + nextRoundId, new RoundChangeCertificate(roundChanges), reproposedBlock); peers.verifyMessagesReceived( localNodeMessageFactory.createPrepare(nextRoundId, reproposedBlock.getHash())); @@ -174,10 +162,8 @@ public void receiveRoundStateIsNotLostIfASecondNewRoundMessageIsReceivedForCurre // to trigger a Commit transmission from the local node nextRoles.getNonProposing(0).injectPrepare(nextRoundId, reproposedBlock.getHash()); - nextProposer.injectNewRound( - nextRoundId, - new RoundChangeCertificate(roundChanges), - peers.getNonProposing(0).getMessageFactory().createProposal(nextRoundId, reproposedBlock)); + nextProposer.injectProposalForFutureRound( + nextRoundId, new RoundChangeCertificate(roundChanges), reproposedBlock); peers.verifyNoMessagesReceived(); diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java index 80c24bf060..47e8287181 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java @@ -19,7 +19,6 @@ import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.IbftHelpers; import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; @@ -149,19 +148,17 @@ public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCrea final RoundChange rc3 = peers.getNonProposing(2).injectRoundChange(targetRound, empty()); final RoundChange rc4 = peers.getProposer().injectRoundChange(targetRound, empty()); - final NewRound expectedNewRound = - localNodeMessageFactory.createNewRound( + final Proposal expectedNewRound = + localNodeMessageFactory.createProposal( targetRound, - new RoundChangeCertificate( - Lists.newArrayList( - rc1.getSignedPayload(), - rc2.getSignedPayload(), - rc3.getSignedPayload(), - rc4.getSignedPayload())), - localNodeMessageFactory - .createProposal(targetRound, locallyProposedBlock) - .getSignedPayload(), - locallyProposedBlock); + locallyProposedBlock, + Optional.of( + new RoundChangeCertificate( + Lists.newArrayList( + rc1.getSignedPayload(), + rc2.getSignedPayload(), + rc3.getSignedPayload(), + rc4.getSignedPayload())))); peers.verifyMessagesReceived(expectedNewRound); } @@ -204,21 +201,19 @@ public void newRoundMessageContainsBlockOnWhichPeerPrepared() { context.createBlockForProposalFromChainHead( targetRound.getRoundNumber(), ARBITRARY_BLOCKTIME); - final NewRound expectedNewRound = - localNodeMessageFactory.createNewRound( + final Proposal expectedProposal = + localNodeMessageFactory.createProposal( targetRound, - new RoundChangeCertificate( - Lists.newArrayList( - rc1.getSignedPayload(), - rc2.getSignedPayload(), - rc3.getSignedPayload(), - rc4.getSignedPayload())), - localNodeMessageFactory - .createProposal(targetRound, expectedBlockToPropose) - .getSignedPayload(), - expectedBlockToPropose); - - peers.verifyMessagesReceived(expectedNewRound); + expectedBlockToPropose, + Optional.of( + new RoundChangeCertificate( + Lists.newArrayList( + rc1.getSignedPayload(), + rc2.getSignedPayload(), + rc3.getSignedPayload(), + rc4.getSignedPayload())))); + + peers.verifyMessagesReceived(expectedProposal); } @Test @@ -234,16 +229,13 @@ public void cannotRoundChangeToAnEarlierRound() { final Block locallyProposedBlock = context.createBlockForProposalFromChainHead(futureRound.getRoundNumber(), blockTimeStamp); - final NewRound expectedNewRound = - localNodeMessageFactory.createNewRound( + final Proposal expectedProposal = + localNodeMessageFactory.createProposal( futureRound, - new RoundChangeCertificate(roundChangeMessages), - localNodeMessageFactory - .createProposal(futureRound, locallyProposedBlock) - .getSignedPayload(), - locallyProposedBlock); + locallyProposedBlock, + Optional.of(new RoundChangeCertificate(roundChangeMessages))); - peers.verifyMessagesReceived(expectedNewRound); + peers.verifyMessagesReceived(expectedProposal); } @Test @@ -289,16 +281,13 @@ public void subsequentRoundChangeMessagesFromPeerDoNotOverwritePriorMessage() { context.createBlockForProposalFromChainHead( targetRound.getRoundNumber(), ARBITRARY_BLOCKTIME); - final NewRound expectedNewRound = - localNodeMessageFactory.createNewRound( + final Proposal expectedProposal = + localNodeMessageFactory.createProposal( targetRound, - new RoundChangeCertificate(Lists.newArrayList(roundChangeMessages)), - localNodeMessageFactory - .createProposal(targetRound, expectedBlockToPropose) - .getSignedPayload(), - expectedBlockToPropose); + expectedBlockToPropose, + Optional.of(new RoundChangeCertificate(Lists.newArrayList(roundChangeMessages)))); - peers.verifyMessagesReceived(expectedNewRound); + peers.verifyMessagesReceived(expectedProposal); } @Test @@ -335,7 +324,10 @@ public void illegallyConstructedRoundChangeMessageIsDiscarded() { // create illegal RoundChangeMessage final PreparedRoundArtifacts illegalPreparedRoundArtifacts = new PreparedRoundArtifacts( - peers.getNonProposing(0).getMessageFactory().createProposal(roundId, blockToPropose), + peers + .getNonProposing(0) + .getMessageFactory() + .createProposal(roundId, blockToPropose, Optional.empty()), emptyList()); peers diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/SpuriousBehaviourTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/SpuriousBehaviourTest.java index 03b10c2ff5..d70743d8a3 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/SpuriousBehaviourTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/SpuriousBehaviourTest.java @@ -96,7 +96,7 @@ public void messageWithIllegalMessageCodeAreDiscardedAndDoNotPreventOngoingIbftO } @Test - public void nonValidatorsCannotTriggerReponses() { + public void nonValidatorsCannotTriggerResponses() { final KeyPair nonValidatorKeys = KeyPair.generate(); final NodeParams nonValidatorParams = new NodeParams(Util.publicKeyToAddress(nonValidatorKeys.getPublicKey()), nonValidatorKeys); diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftGossip.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftGossip.java index be11883764..8ce4de51e0 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftGossip.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftGossip.java @@ -14,7 +14,6 @@ import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2; -import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData; @@ -64,9 +63,6 @@ public void send(final Message message) { case IbftV2.ROUND_CHANGE: decodedMessage = RoundChangeMessageData.fromMessageData(messageData).decode(); break; - case IbftV2.NEW_ROUND: - decodedMessage = NewRoundMessageData.fromMessageData(messageData).decode(); - break; default: throw new IllegalArgumentException( "Received message does not conform to any recognised IBFT message structure."); diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/IbftV2.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/IbftV2.java index 4fb75ca798..d3ab022daa 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/IbftV2.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/IbftV2.java @@ -18,7 +18,6 @@ public class IbftV2 { public static final int PREPARE = 1; public static final int COMMIT = 2; public static final int ROUND_CHANGE = 3; - public static final int NEW_ROUND = 4; - public static final int MESSAGE_SPACE = 5; + public static final int MESSAGE_SPACE = 4; } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageData.java deleted file mode 100644 index fc7cd12bb4..0000000000 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageData.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.messagedata; - -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; -import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; -import tech.pegasys.pantheon.util.bytes.BytesValue; - -public class NewRoundMessageData extends AbstractIbftMessageData { - - private static final int MESSAGE_CODE = IbftV2.NEW_ROUND; - - private NewRoundMessageData(final BytesValue data) { - super(data); - } - - public static NewRoundMessageData fromMessageData(final MessageData messageData) { - return fromMessageData( - messageData, MESSAGE_CODE, NewRoundMessageData.class, NewRoundMessageData::new); - } - - public NewRound decode() { - return NewRound.decode(data); - } - - public static NewRoundMessageData create(final NewRound newRound) { - return new NewRoundMessageData(newRound.encode()); - } - - @Override - public int getCode() { - return MESSAGE_CODE; - } -} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/NewRound.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/NewRound.java deleted file mode 100644 index b93dc7173e..0000000000 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/NewRound.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2019 ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package tech.pegasys.pantheon.consensus.ibft.messagewrappers; - -import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing; -import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload; -import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload; -import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; -import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; -import tech.pegasys.pantheon.ethereum.core.Block; -import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; -import tech.pegasys.pantheon.ethereum.rlp.RLP; -import tech.pegasys.pantheon.ethereum.rlp.RLPInput; -import tech.pegasys.pantheon.util.bytes.BytesValue; - -public class NewRound extends IbftMessage { - - private final Block proposedBlock; - - public NewRound(final SignedData payload, final Block proposedBlock) { - super(payload); - this.proposedBlock = proposedBlock; - } - - public RoundChangeCertificate getRoundChangeCertificate() { - return getPayload().getRoundChangeCertificate(); - } - - public SignedData getProposalPayload() { - return getPayload().getProposalPayload(); - } - - public Block getBlock() { - return proposedBlock; - } - - @Override - public BytesValue encode() { - final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); - rlpOut.startList(); - getSignedPayload().writeTo(rlpOut); - proposedBlock.writeTo(rlpOut); - rlpOut.endList(); - return rlpOut.encoded(); - } - - public static NewRound decode(final BytesValue data) { - RLPInput rlpIn = RLP.input(data); - rlpIn.enterList(); - final SignedData payload = SignedData.readSignedNewRoundPayloadFrom(rlpIn); - final Block proposedBlock = - Block.readFrom(rlpIn, IbftBlockHashing::calculateDataHashForCommittedSeal); - rlpIn.leaveList(); - return new NewRound(payload, proposedBlock); - } -} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java index a3466c8827..de46c37c91 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java @@ -14,6 +14,7 @@ import tech.pegasys.pantheon.consensus.ibft.IbftBlockHashing; import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload; +import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Hash; @@ -22,13 +23,21 @@ import tech.pegasys.pantheon.ethereum.rlp.RLPInput; import tech.pegasys.pantheon.util.bytes.BytesValue; +import java.util.Optional; + public class Proposal extends IbftMessage { private final Block proposedBlock; - public Proposal(final SignedData payload, final Block proposedBlock) { + private final Optional roundChangeCertificate; + + public Proposal( + final SignedData payload, + final Block proposedBlock, + final Optional certificate) { super(payload); this.proposedBlock = proposedBlock; + this.roundChangeCertificate = certificate; } public Block getBlock() { @@ -39,12 +48,21 @@ public Hash getDigest() { return getPayload().getDigest(); } + public Optional getRoundChangeCertificate() { + return roundChangeCertificate; + } + @Override public BytesValue encode() { final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); rlpOut.startList(); getSignedPayload().writeTo(rlpOut); proposedBlock.writeTo(rlpOut); + if (roundChangeCertificate.isPresent()) { + roundChangeCertificate.get().writeTo(rlpOut); + } else { + rlpOut.writeNull(); + } rlpOut.endList(); return rlpOut.encoded(); } @@ -55,7 +73,15 @@ public static Proposal decode(final BytesValue data) { final SignedData payload = SignedData.readSignedProposalPayloadFrom(rlpIn); final Block proposedBlock = Block.readFrom(rlpIn, IbftBlockHashing::calculateDataHashForCommittedSeal); + + RoundChangeCertificate roundChangeCertificate = null; + if (!rlpIn.nextIsNull()) { + roundChangeCertificate = RoundChangeCertificate.readFrom(rlpIn); + } else { + rlpIn.skipNext(); + } + rlpIn.leaveList(); - return new Proposal(payload, proposedBlock); + return new Proposal(payload, proposedBlock, Optional.ofNullable(roundChangeCertificate)); } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMessageTransmitter.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMessageTransmitter.java index d219b3d4a0..61871b4042 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMessageTransmitter.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/network/IbftMessageTransmitter.java @@ -14,19 +14,15 @@ import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData; -import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; -import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload; import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; -import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; import tech.pegasys.pantheon.consensus.ibft.statemachine.PreparedRoundArtifacts; import tech.pegasys.pantheon.crypto.SECP256K1.Signature; import tech.pegasys.pantheon.ethereum.core.Block; @@ -45,8 +41,12 @@ public IbftMessageTransmitter( this.multicaster = multicaster; } - public void multicastProposal(final ConsensusRoundIdentifier roundIdentifier, final Block block) { - final Proposal data = messageFactory.createProposal(roundIdentifier, block); + public void multicastProposal( + final ConsensusRoundIdentifier roundIdentifier, + final Block block, + final Optional roundChangeCertificate) { + final Proposal data = + messageFactory.createProposal(roundIdentifier, block, roundChangeCertificate); final ProposalMessageData message = ProposalMessageData.create(data); @@ -83,19 +83,4 @@ public void multicastRoundChange( multicaster.send(message); } - - public void multicastNewRound( - final ConsensusRoundIdentifier roundIdentifier, - final RoundChangeCertificate roundChangeCertificate, - final SignedData proposalPayload, - final Block block) { - - final NewRound signedPayload = - messageFactory.createNewRound( - roundIdentifier, roundChangeCertificate, proposalPayload, block); - - final NewRoundMessageData message = NewRoundMessageData.create(signedPayload); - - multicaster.send(message); - } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/MessageFactory.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/MessageFactory.java index a6ce818b9a..3204b744f0 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/MessageFactory.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/MessageFactory.java @@ -14,7 +14,6 @@ import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; @@ -38,11 +37,13 @@ public MessageFactory(final KeyPair validatorKeyPair) { } public Proposal createProposal( - final ConsensusRoundIdentifier roundIdentifier, final Block block) { + final ConsensusRoundIdentifier roundIdentifier, + final Block block, + final Optional roundChangeCertificate) { final ProposalPayload payload = new ProposalPayload(roundIdentifier, block.getHash()); - return new Proposal(createSignedMessage(payload), block); + return new Proposal(createSignedMessage(payload), block, roundChangeCertificate); } public Prepare createPrepare(final ConsensusRoundIdentifier roundIdentifier, final Hash digest) { @@ -74,18 +75,6 @@ public RoundChange createRoundChange( createSignedMessage(payload), preparedRoundArtifacts.map(PreparedRoundArtifacts::getBlock)); } - public NewRound createNewRound( - final ConsensusRoundIdentifier roundIdentifier, - final RoundChangeCertificate roundChangeCertificate, - final SignedData proposalPayload, - final Block block) { - - final NewRoundPayload payload = - new NewRoundPayload(roundIdentifier, roundChangeCertificate, proposalPayload); - - return new NewRound(createSignedMessage(payload), block); - } - private SignedData createSignedMessage(final M payload) { final Signature signature = sign(payload, validatorKeyPair); diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayload.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayload.java deleted file mode 100644 index 8c46bb15ec..0000000000 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayload.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.payload; - -import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; -import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; -import tech.pegasys.pantheon.ethereum.rlp.RLPInput; -import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; - -import java.util.Collections; -import java.util.Objects; -import java.util.StringJoiner; - -public class NewRoundPayload implements Payload { - private static final int TYPE = IbftV2.NEW_ROUND; - private final ConsensusRoundIdentifier roundChangeIdentifier; - private final RoundChangeCertificate roundChangeCertificate; - private final SignedData proposalPayload; - - public NewRoundPayload( - final ConsensusRoundIdentifier roundIdentifier, - final RoundChangeCertificate roundChangeCertificate, - final SignedData proposalPayload) { - this.roundChangeIdentifier = roundIdentifier; - this.roundChangeCertificate = roundChangeCertificate; - this.proposalPayload = proposalPayload; - } - - @Override - public ConsensusRoundIdentifier getRoundIdentifier() { - return roundChangeIdentifier; - } - - public RoundChangeCertificate getRoundChangeCertificate() { - return roundChangeCertificate; - } - - public SignedData getProposalPayload() { - return proposalPayload; - } - - @Override - public void writeTo(final RLPOutput rlpOutput) { - // RLP encode of the message data content (round identifier and prepared certificate) - rlpOutput.startList(); - roundChangeIdentifier.writeTo(rlpOutput); - roundChangeCertificate.writeTo(rlpOutput); - proposalPayload.writeTo(rlpOutput); - rlpOutput.endList(); - } - - public static NewRoundPayload readFrom(final RLPInput rlpInput) { - - rlpInput.enterList(); - final ConsensusRoundIdentifier roundIdentifier = ConsensusRoundIdentifier.readFrom(rlpInput); - final RoundChangeCertificate roundChangeCertificate = RoundChangeCertificate.readFrom(rlpInput); - final SignedData proposalPayload = - SignedData.readSignedProposalPayloadFrom(rlpInput); - rlpInput.leaveList(); - - return new NewRoundPayload(roundIdentifier, roundChangeCertificate, proposalPayload); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final NewRoundPayload that = (NewRoundPayload) o; - return Objects.equals(roundChangeIdentifier, that.roundChangeIdentifier) - && Objects.equals(roundChangeCertificate, that.roundChangeCertificate) - && Objects.equals(proposalPayload, that.proposalPayload); - } - - @Override - public int hashCode() { - return Objects.hash(roundChangeIdentifier, roundChangeCertificate, proposalPayload); - } - - @Override - public String toString() { - return new StringJoiner(", ", NewRoundPayload.class.getSimpleName() + "[", "]") - .add("roundChangeIdentifier=" + roundChangeIdentifier) - .add("roundChangeCertificate=" + roundChangeCertificate) - .add("proposalPayload=" + proposalPayload) - .toString(); - } - - @Override - public int getMessageType() { - return TYPE; - } - - public static class Builder { - - private ConsensusRoundIdentifier roundChangeIdentifier = new ConsensusRoundIdentifier(1, 0); - - private RoundChangeCertificate roundChangeCertificate = - new RoundChangeCertificate(Collections.emptyList()); - - private SignedData proposalPayload = null; - - public Builder( - final ConsensusRoundIdentifier roundChangeIdentifier, - final RoundChangeCertificate roundChangeCertificate, - final SignedData proposalPayload) { - this.roundChangeIdentifier = roundChangeIdentifier; - this.roundChangeCertificate = roundChangeCertificate; - this.proposalPayload = proposalPayload; - } - - public static Builder fromExisting(final NewRound payload) { - return new Builder( - payload.getRoundIdentifier(), - payload.getRoundChangeCertificate(), - payload.getProposalPayload()); - } - - public void setRoundChangeIdentifier(final ConsensusRoundIdentifier roundChangeIdentifier) { - this.roundChangeIdentifier = roundChangeIdentifier; - } - - public void setRoundChangeCertificate(final RoundChangeCertificate roundChangeCertificate) { - this.roundChangeCertificate = roundChangeCertificate; - } - - public NewRoundPayload build() { - return new NewRoundPayload(roundChangeIdentifier, roundChangeCertificate, proposalPayload); - } - } -} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/SignedData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/SignedData.java index fd89f8c1bf..ec68e399d5 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/SignedData.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/payload/SignedData.java @@ -24,6 +24,7 @@ import java.util.StringJoiner; public class SignedData implements Authored { + private final Address sender; private final Signature signature; private final M unsignedPayload; @@ -98,16 +99,6 @@ public static SignedData readSignedRoundChangePayloadFrom( return from(unsignedMessageData, signature); } - public static SignedData readSignedNewRoundPayloadFrom(final RLPInput rlpInput) { - - rlpInput.enterList(); - final NewRoundPayload unsignedMessageData = NewRoundPayload.readFrom(rlpInput); - final Signature signature = readSignature(rlpInput); - rlpInput.leaveList(); - - return from(unsignedMessageData, signature); - } - protected static SignedData from( final M unsignedMessageData, final Signature signature) { diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java index c6b546ce4f..3bb74e72a9 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java @@ -44,7 +44,6 @@ public boolean isValidMessageCode(final int protocolVersion, final int code) { case IbftV2.PREPARE: case IbftV2.COMMIT: case IbftV2.ROUND_CHANGE: - case IbftV2.NEW_ROUND: return true; default: @@ -63,8 +62,6 @@ public String messageName(final int protocolVersion, final int code) { return "Commit"; case IbftV2.ROUND_CHANGE: return "RoundChange"; - case IbftV2.NEW_ROUND: - return "NewRound"; default: return INVALID_MESSAGE_NAME; } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java index 018b0d5979..ff0d78343e 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/BlockHeightManager.java @@ -15,7 +15,6 @@ import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; @@ -37,8 +36,6 @@ public interface BlockHeightManager { void handleRoundChangePayload(RoundChange roundChange); - void handleNewRoundPayload(NewRound newRound); - long getChainHeight(); BlockHeader getParentBlockHeader(); diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java index 9b4be95fbf..b1aef175f1 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java @@ -22,15 +22,14 @@ import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.IbftMessage; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; import tech.pegasys.pantheon.consensus.ibft.network.IbftMessageTransmitter; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.payload.Payload; +import tech.pegasys.pantheon.consensus.ibft.validation.FutureRoundProposalMessageValidator; import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory; -import tech.pegasys.pantheon.consensus.ibft.validation.NewRoundMessageValidator; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import java.time.Clock; @@ -64,7 +63,7 @@ public class IbftBlockHeightManager implements BlockHeightManager { private final IbftMessageTransmitter transmitter; private final MessageFactory messageFactory; private final Map futureRoundStateBuffer = Maps.newHashMap(); - private final NewRoundMessageValidator newRoundMessageValidator; + private final FutureRoundProposalMessageValidator futureRoundProposalMessageValidator; private final Clock clock; private final Function roundStateCreator; private final IbftFinalState finalState; @@ -90,7 +89,8 @@ public IbftBlockHeightManager( this.roundChangeManager = roundChangeManager; this.finalState = finalState; - newRoundMessageValidator = messageValidatorFactory.createNewRoundValidator(getChainHeight()); + futureRoundProposalMessageValidator = + messageValidatorFactory.createFutureRoundProposalMessageValidator(getChainHeight()); roundStateCreator = (roundIdentifier) -> @@ -156,8 +156,21 @@ public void roundExpired(final RoundExpiry expire) { @Override public void handleProposalPayload(final Proposal proposal) { LOG.trace("Received a Proposal Payload."); - actionOrBufferMessage( - proposal, currentRound::handleProposalMessage, RoundState::setProposedBlock); + final MessageAge messageAge = + determineAgeOfPayload(proposal.getRoundIdentifier().getRoundNumber()); + + if (messageAge == PRIOR_ROUND) { + LOG.trace("Received Proposal Payload for a prior round={}", proposal.getRoundIdentifier()); + } else { + if (messageAge == FUTURE_ROUND) { + if (!futureRoundProposalMessageValidator.validateProposalMessage(proposal)) { + LOG.debug("Received future Proposal which is illegal, no round change triggered."); + return; + } + startNewRound(proposal.getRoundIdentifier().getRoundNumber()); + } + currentRound.handleProposalMessage(proposal); + } } @Override @@ -236,26 +249,6 @@ private void startNewRound(final int roundNumber) { roundTimer.startTimer(currentRound.getRoundIdentifier()); } - @Override - public void handleNewRoundPayload(final NewRound newRound) { - // final NewRoundPayload payload = newRound.getSignedPayload().getPayload(); - final MessageAge messageAge = - determineAgeOfPayload(newRound.getRoundIdentifier().getRoundNumber()); - - if (messageAge == PRIOR_ROUND) { - LOG.trace("Received NewRound Payload for a prior round={}", newRound.getRoundIdentifier()); - return; - } - LOG.trace("Received NewRound Payload for {}", newRound.getRoundIdentifier()); - - if (newRoundMessageValidator.validateNewRoundMessage(newRound)) { - if (messageAge == FUTURE_ROUND) { - startNewRound(newRound.getRoundIdentifier().getRoundNumber()); - } - currentRound.handleProposalFromNewRound(newRound); - } - } - @Override public long getChainHeight() { return parentHeader.getNumber() + 1; diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java index c8cb5d6223..6433ce38fd 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftController.java @@ -23,7 +23,6 @@ import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2; -import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData; @@ -128,13 +127,6 @@ private void handleMessage(final Message message) { currentHeightManager::handleRoundChangePayload); break; - case IbftV2.NEW_ROUND: - consumeMessage( - message, - NewRoundMessageData.fromMessageData(messageData).decode(), - currentHeightManager::handleNewRoundPayload); - break; - default: throw new IllegalArgumentException( String.format( diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java index e7a3efb817..cc44577086 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java @@ -20,11 +20,11 @@ import tech.pegasys.pantheon.consensus.ibft.IbftHelpers; import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.network.IbftMessageTransmitter; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; +import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.Signature; @@ -86,72 +86,53 @@ public void createAndSendProposalMessage(final long headerTimeStampSeconds) { roundState.getRoundIdentifier(), extraData, block.getHeader()); - transmitter.multicastProposal(roundState.getRoundIdentifier(), block); - updateStateWithProposedBlock( - messageFactory.createProposal(roundState.getRoundIdentifier(), block)); + updateStateWithProposalAndTransmit(block, Optional.empty()); } public void startRoundWith( final RoundChangeArtifacts roundChangeArtifacts, final long headerTimestamp) { final Optional bestBlockFromRoundChange = roundChangeArtifacts.getBlock(); - Proposal proposal; + final RoundChangeCertificate roundChangeCertificate = + roundChangeArtifacts.getRoundChangeCertificate(); + Block blockToPublish; if (!bestBlockFromRoundChange.isPresent()) { - LOG.debug("Multicasting NewRound with new block. round={}", roundState.getRoundIdentifier()); - final Block block = blockCreator.createBlock(headerTimestamp); - proposal = messageFactory.createProposal(getRoundIdentifier(), block); + LOG.debug("Multicasting Proposal with new block. round={}", roundState.getRoundIdentifier()); + blockToPublish = blockCreator.createBlock(headerTimestamp); } else { LOG.debug( - "Multicasting NewRound from PreparedCertificate. round={}", + "Multicasting Proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier()); - proposal = createProposalAroundBlock(bestBlockFromRoundChange.get()); + blockToPublish = + IbftBlockInterface.replaceRoundInBlock( + bestBlockFromRoundChange.get(), + getRoundIdentifier().getRoundNumber(), + IbftBlockHashing::calculateDataHashForCommittedSeal); } - transmitter.multicastNewRound( - getRoundIdentifier(), - roundChangeArtifacts.getRoundChangeCertificate(), - proposal.getSignedPayload(), - proposal.getBlock()); - updateStateWithProposedBlock(proposal); - } - private Proposal createProposalAroundBlock(final Block block) { - final Block blockToPublish = - IbftBlockInterface.replaceRoundInBlock( - block, - getRoundIdentifier().getRoundNumber(), - IbftBlockHashing::calculateDataHashForCommittedSeal); - return messageFactory.createProposal(getRoundIdentifier(), blockToPublish); + updateStateWithProposalAndTransmit(blockToPublish, Optional.of(roundChangeCertificate)); } - public void handleProposalMessage(final Proposal msg) { - LOG.debug("Handling a Proposal message."); + private void updateStateWithProposalAndTransmit( + final Block block, final Optional roundChangeCertificate) { + final Proposal proposal = + messageFactory.createProposal(getRoundIdentifier(), block, roundChangeCertificate); - if (getRoundIdentifier().getRoundNumber() != 0) { - LOG.error("Illegally received a Proposal message when not in Round 0."); - return; - } - actionReceivedProposal(msg); - } - - public void handleProposalFromNewRound(final NewRound msg) { - LOG.debug("Handling a New Round Proposal."); - - if (getRoundIdentifier().getRoundNumber() == 0) { - LOG.error("Illegally received a NewRound message when in Round 0."); - return; - } - actionReceivedProposal(new Proposal(msg.getProposalPayload(), msg.getBlock())); + transmitter.multicastProposal( + proposal.getRoundIdentifier(), proposal.getBlock(), proposal.getRoundChangeCertificate()); + updateStateWithProposedBlock(proposal); } - private void actionReceivedProposal(final Proposal msg) { + public void handleProposalMessage(final Proposal msg) { + LOG.debug("Handling a Proposal message."); final Block block = msg.getBlock(); if (updateStateWithProposedBlock(msg)) { LOG.debug("Sending prepare message."); transmitter.multicastPrepare(getRoundIdentifier(), block.getHash()); final Prepare localPrepareMessage = - messageFactory.createPrepare(roundState.getRoundIdentifier(), block.getHash()); + messageFactory.createPrepare(getRoundIdentifier(), block.getHash()); peerIsPrepared(localPrepareMessage); } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java index e49f0e84f2..5714079d47 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/NoOpBlockHeightManager.java @@ -15,7 +15,6 @@ import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; @@ -50,9 +49,6 @@ public void handleCommitPayload(final Commit commit) {} @Override public void handleRoundChangePayload(final RoundChange roundChange) {} - @Override - public void handleNewRoundPayload(final NewRound newRound) {} - @Override public long getChainHeight() { return parentHeader.getNumber() + 1; diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java new file mode 100644 index 0000000000..4fed271ca7 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java @@ -0,0 +1,48 @@ +/* + * 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.validation; + +import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/* One of these will be created by the IbftBlockHeightManager and will exist for the life of the +chainheight, and used to ensure supplied Proposals are suitable for starting a new round. + */ +public class FutureRoundProposalMessageValidator { + + private static final Logger LOG = LogManager.getLogger(); + + private MessageValidatorFactory messageValidatorFactory; + private long chainHeight; + + public FutureRoundProposalMessageValidator( + final MessageValidatorFactory messageValidatorFactory, final long chainHeight) { + this.messageValidatorFactory = messageValidatorFactory; + this.chainHeight = chainHeight; + } + + public boolean validateProposalMessage(final Proposal msg) { + + if (msg.getRoundIdentifier().getSequenceNumber() != chainHeight) { + LOG.debug("Illegal Proposal message, does not target the correct round height."); + return false; + } + + final MessageValidator messageValidator = + messageValidatorFactory.createMessageValidator(msg.getRoundIdentifier()); + + return messageValidator.validateProposal(msg); + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java index 529fe29b33..8b5dc9cf23 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java @@ -12,10 +12,12 @@ */ package tech.pegasys.pantheon.consensus.ibft.validation; +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.IbftContext; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; +import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; import tech.pegasys.pantheon.ethereum.BlockValidator; import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs; import tech.pegasys.pantheon.ethereum.ProtocolContext; @@ -35,19 +37,23 @@ public class MessageValidator { private final ProposalBlockConsistencyValidator proposalConsistencyValidator; private final BlockValidator blockValidator; private final ProtocolContext protocolContext; + private RoundChangeCertificateValidator roundChangeCertificateValidator; public MessageValidator( final SignedDataValidator signedDataValidator, final ProposalBlockConsistencyValidator proposalConsistencyValidator, final BlockValidator blockValidator, - final ProtocolContext protocolContext) { + final ProtocolContext protocolContext, + final RoundChangeCertificateValidator roundChangeCertificateValidator) { this.signedDataValidator = signedDataValidator; this.proposalConsistencyValidator = proposalConsistencyValidator; this.blockValidator = blockValidator; this.protocolContext = protocolContext; + this.roundChangeCertificateValidator = roundChangeCertificateValidator; } public boolean validateProposal(final Proposal msg) { + if (!signedDataValidator.validateProposal(msg.getSignedPayload())) { LOG.debug("Illegal Proposal message, embedded signed data failed validation"); return false; @@ -57,6 +63,11 @@ public boolean validateProposal(final Proposal msg) { return false; } + if (!validateRoundChangeCertificate(msg, msg.getRoundChangeCertificate())) { + LOG.debug("Illegal Proposal message, embedded roundChange does not match proposal."); + return false; + } + return proposalConsistencyValidator.validateProposalMatchesBlock( msg.getSignedPayload(), msg.getBlock()); } @@ -74,6 +85,44 @@ private boolean validateBlock(final Block block) { return true; } + private boolean validateRoundChangeCertificate( + final Proposal proposal, final Optional roundChangeCertificate) { + final ConsensusRoundIdentifier proposalRoundIdentifier = proposal.getRoundIdentifier(); + + if (proposalRoundIdentifier.getRoundNumber() == 0) { + if (roundChangeCertificate.isPresent()) { + LOG.debug( + "Illegal Proposal message, round-0 proposal must not contain a round change certificate."); + return false; + } + } else { + if (!roundChangeCertificate.isPresent()) { + LOG.debug( + "Illegal Proposal message, rounds other than 0 must contain a round change certificate."); + return false; + } + + final RoundChangeCertificate roundChangeCert = roundChangeCertificate.get(); + + if (!roundChangeCertificateValidator + .validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( + proposal.getRoundIdentifier(), roundChangeCert)) { + LOG.debug( + "Illegal Proposal message, embedded RoundChangeCertificate is not self-consistent"); + return false; + } + + if (!roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( + roundChangeCert, proposal.getBlock())) { + LOG.debug( + "Illegal Proposal message, piggybacked block does not match latest PrepareCertificate"); + return false; + } + } + + return true; + } + public boolean validatePrepare(final Prepare msg) { return signedDataValidator.validatePrepare(msg.getSignedPayload()); } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java index 1e41c038ea..2d5b5cce26 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java @@ -52,12 +52,16 @@ private SignedDataValidator createSignedDataValidator( public MessageValidator createMessageValidator(final ConsensusRoundIdentifier roundIdentifier) { final BlockValidator blockValidator = protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockValidator(); + final Collection
validators = + protocolContext.getConsensusState().getVoteTally().getValidators(); return new MessageValidator( createSignedDataValidator(roundIdentifier), new ProposalBlockConsistencyValidator(), blockValidator, - protocolContext); + protocolContext, + new RoundChangeCertificateValidator( + validators, this::createSignedDataValidator, roundIdentifier.getSequenceNumber())); } public RoundChangeMessageValidator createRoundChangeMessageValidator(final long chainHeight) { @@ -74,26 +78,9 @@ public RoundChangeMessageValidator createRoundChangeMessageValidator(final long new ProposalBlockConsistencyValidator()); } - public NewRoundMessageValidator createNewRoundValidator(final long chainHeight) { - final Collection
validators = - protocolContext.getConsensusState().getVoteTally().getValidators(); - - final BlockValidator blockValidator = - protocolSchedule.getByBlockNumber(chainHeight).getBlockValidator(); - - final RoundChangeCertificateValidator roundChangeCertificateValidator = - new RoundChangeCertificateValidator( - validators, this::createSignedDataValidator, chainHeight); + public FutureRoundProposalMessageValidator createFutureRoundProposalMessageValidator( + final long chainHeight) { - return new NewRoundMessageValidator( - new NewRoundPayloadValidator( - proposerSelector, - this::createSignedDataValidator, - chainHeight, - roundChangeCertificateValidator), - new ProposalBlockConsistencyValidator(), - blockValidator, - protocolContext, - roundChangeCertificateValidator); + return new FutureRoundProposalMessageValidator(this, chainHeight); } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java deleted file mode 100644 index dbdedb6aa1..0000000000 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidator.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2019 ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package tech.pegasys.pantheon.consensus.ibft.validation; - -import tech.pegasys.pantheon.consensus.ibft.IbftContext; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; -import tech.pegasys.pantheon.ethereum.BlockValidator; -import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs; -import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.core.Block; -import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; - -import java.util.Optional; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class NewRoundMessageValidator { - - private static final Logger LOG = LogManager.getLogger(); - - private final NewRoundPayloadValidator payloadValidator; - private final ProposalBlockConsistencyValidator proposalConsistencyValidator; - private final BlockValidator blockValidator; - private final ProtocolContext protocolContext; - private final RoundChangeCertificateValidator roundChangeCertificateValidator; - - public NewRoundMessageValidator( - final NewRoundPayloadValidator payloadValidator, - final ProposalBlockConsistencyValidator proposalConsistencyValidator, - final BlockValidator blockValidator, - final ProtocolContext protocolContext, - final RoundChangeCertificateValidator roundChangeCertificateValidator) { - this.payloadValidator = payloadValidator; - this.proposalConsistencyValidator = proposalConsistencyValidator; - this.blockValidator = blockValidator; - this.protocolContext = protocolContext; - this.roundChangeCertificateValidator = roundChangeCertificateValidator; - } - - public boolean validateNewRoundMessage(final NewRound msg) { - if (!payloadValidator.validateNewRoundMessage(msg.getSignedPayload())) { - LOG.debug("Illegal NewRound message, embedded signed data failed validation."); - return false; - } - - if (!roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( - msg.getRoundChangeCertificate(), msg.getBlock())) { - LOG.debug( - "Illegal NewRound message, piggybacked block does not match latest PrepareCertificate"); - return false; - } - - if (!validateBlock(msg.getBlock())) { - return false; - } - - return proposalConsistencyValidator.validateProposalMatchesBlock( - msg.getProposalPayload(), msg.getBlock()); - } - - private boolean validateBlock(final Block block) { - final Optional validationResult = - blockValidator.validateAndProcessBlock( - protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL); - - if (!validationResult.isPresent()) { - LOG.info("Invalid Proposal message, block did not pass validation."); - return false; - } - - return true; - } -} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundPayloadValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundPayloadValidator.java deleted file mode 100644 index 60993a6414..0000000000 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundPayloadValidator.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.validation; - -import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; -import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector; -import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload; -import tech.pegasys.pantheon.consensus.ibft.payload.ProposalPayload; -import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; -import tech.pegasys.pantheon.consensus.ibft.payload.SignedData; -import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangePayloadValidator.MessageValidatorForHeightFactory; -import tech.pegasys.pantheon.ethereum.core.Address; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class NewRoundPayloadValidator { - - private static final Logger LOG = LogManager.getLogger(); - - private final ProposerSelector proposerSelector; - private final MessageValidatorForHeightFactory messageValidatorFactory; - private final long chainHeight; - private final RoundChangeCertificateValidator roundChangeCertificateValidator; - - public NewRoundPayloadValidator( - final ProposerSelector proposerSelector, - final MessageValidatorForHeightFactory messageValidatorFactory, - final long chainHeight, - final RoundChangeCertificateValidator roundChangeCertificateValidator) { - this.proposerSelector = proposerSelector; - this.messageValidatorFactory = messageValidatorFactory; - this.chainHeight = chainHeight; - this.roundChangeCertificateValidator = roundChangeCertificateValidator; - } - - public boolean validateNewRoundMessage(final SignedData msg) { - - final NewRoundPayload payload = msg.getPayload(); - final ConsensusRoundIdentifier rootRoundIdentifier = payload.getRoundIdentifier(); - final Address expectedProposer = proposerSelector.selectProposerForRound(rootRoundIdentifier); - final RoundChangeCertificate roundChangeCert = payload.getRoundChangeCertificate(); - - if (!expectedProposer.equals(msg.getAuthor())) { - LOG.info("Invalid NewRound message, did not originate from expected proposer."); - return false; - } - - if (msg.getPayload().getRoundIdentifier().getSequenceNumber() != chainHeight) { - LOG.info("Invalid NewRound message, not valid for local chain height."); - return false; - } - - if (msg.getPayload().getRoundIdentifier().getRoundNumber() == 0) { - LOG.info("Invalid NewRound message, illegally targets a new round of 0."); - return false; - } - - final SignedData proposalPayload = payload.getProposalPayload(); - final SignedDataValidator proposalValidator = - messageValidatorFactory.createAt(rootRoundIdentifier); - if (!proposalValidator.validateProposal(proposalPayload)) { - LOG.info("Invalid NewRound message, embedded proposal failed validation"); - return false; - } - - if (!roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( - rootRoundIdentifier, roundChangeCert)) { - return false; - } - - return true; - } -} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftGossipTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftGossipTest.java index 425488c0b7..a96ea6a837 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftGossipTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftGossipTest.java @@ -15,7 +15,6 @@ import static com.google.common.collect.Lists.newArrayList; import static org.mockito.Mockito.verify; -import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.IbftMessage; @@ -73,10 +72,4 @@ public void assertRebroadcastsRoundChangeToAllExceptSignerAndSender() { assertRebroadcastToAllExceptSignerAndSender( TestHelpers::createSignedRoundChangePayload, RoundChangeMessageData::create); } - - @Test - public void assertRebroadcastsNewRoundToAllExceptSignerAndSender() { - assertRebroadcastToAllExceptSignerAndSender( - TestHelpers::createSignedNewRoundPayload, NewRoundMessageData::create); - } } diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftHelpersTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftHelpersTest.java index 332c972526..9f43f38e85 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftHelpersTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/IbftHelpersTest.java @@ -91,7 +91,7 @@ public void latestPreparedCertificateIsExtractedFromRoundChangeCertificate() { final ConsensusRoundIdentifier preparedRound = TestHelpers.createFrom(roundIdentifier, 0, -1); final Proposal differentProposal = - proposerMessageFactory.createProposal(preparedRound, proposedBlock); + proposerMessageFactory.createProposal(preparedRound, proposedBlock, Optional.empty()); final Optional latterPreparedRoundArtifacts = Optional.of( @@ -107,7 +107,8 @@ public void latestPreparedCertificateIsExtractedFromRoundChangeCertificate() { final ConsensusRoundIdentifier earlierPreparedRound = TestHelpers.createFrom(roundIdentifier, 0, -2); final Proposal earlierProposal = - proposerMessageFactory.createProposal(earlierPreparedRound, proposedBlock); + proposerMessageFactory.createProposal( + earlierPreparedRound, proposedBlock, Optional.empty()); final Optional earlierPreparedRoundArtifacts = Optional.of( new PreparedRoundArtifacts( diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/TestHelpers.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/TestHelpers.java index 53d884e8f8..812e9d27a3 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/TestHelpers.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/TestHelpers.java @@ -12,16 +12,13 @@ */ 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.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; -import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.Signature; import tech.pegasys.pantheon.ethereum.core.Address; @@ -74,7 +71,7 @@ public static Proposal createSignedProposalPayloadWithRound( new ConsensusRoundIdentifier(0x1234567890ABCDEFL, round); final Block block = TestHelpers.createProposalBlock(singletonList(AddressHelpers.ofValue(1)), roundIdentifier); - return messageFactory.createProposal(roundIdentifier, block); + return messageFactory.createProposal(roundIdentifier, block, Optional.empty()); } public static Prepare createSignedPreparePayload(final KeyPair signerKeys) { @@ -100,16 +97,4 @@ public static RoundChange createSignedRoundChangePayload(final KeyPair signerKey new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98); return messageFactory.createRoundChange(roundIdentifier, Optional.empty()); } - - public static NewRound createSignedNewRoundPayload(final KeyPair signerKeys) { - final MessageFactory messageFactory = new MessageFactory(signerKeys); - final ConsensusRoundIdentifier roundIdentifier = - new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98); - final Proposal proposal = createSignedProposalPayload(signerKeys); - return messageFactory.createNewRound( - roundIdentifier, - new RoundChangeCertificate(newArrayList()), - proposal.getSignedPayload(), - proposal.getBlock()); - } } diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageTest.java deleted file mode 100644 index 7d2d1c47d9..0000000000 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/messagedata/NewRoundMessageTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.messagedata; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; -import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; -import tech.pegasys.pantheon.util.bytes.BytesValue; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class NewRoundMessageTest { - @Mock private NewRound newRoundPayload; - @Mock private BytesValue messageBytes; - @Mock private MessageData messageData; - @Mock private NewRoundMessageData newRoundMessage; - - @Test - public void createMessageFromNewRoundChangeMessageData() { - when(newRoundPayload.encode()).thenReturn(messageBytes); - NewRoundMessageData prepareMessage = NewRoundMessageData.create(newRoundPayload); - - assertThat(prepareMessage.getData()).isEqualTo(messageBytes); - assertThat(prepareMessage.getCode()).isEqualTo(IbftV2.NEW_ROUND); - verify(newRoundPayload).encode(); - } - - @Test - public void createMessageFromNewRoundMessage() { - NewRoundMessageData message = NewRoundMessageData.fromMessageData(newRoundMessage); - assertThat(message).isSameAs(newRoundMessage); - } - - @Test - public void createMessageFromGenericMessageData() { - when(messageData.getData()).thenReturn(messageBytes); - when(messageData.getCode()).thenReturn(IbftV2.NEW_ROUND); - NewRoundMessageData newRoundMessage = NewRoundMessageData.fromMessageData(messageData); - - assertThat(newRoundMessage.getData()).isEqualTo(messageData.getData()); - assertThat(newRoundMessage.getCode()).isEqualTo(IbftV2.NEW_ROUND); - } - - @Test - public void createMessageFailsWhenIncorrectMessageCode() { - when(messageData.getCode()).thenReturn(42); - assertThatThrownBy(() -> NewRoundMessageData.fromMessageData(messageData)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("MessageData has code 42 and thus is not a NewRoundMessageData"); - } -} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayloadTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayloadTest.java deleted file mode 100644 index 0b8fc4ff5c..0000000000 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/payload/NewRoundPayloadTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.payload; - -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; - -import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; -import tech.pegasys.pantheon.consensus.ibft.TestHelpers; -import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2; -import tech.pegasys.pantheon.crypto.SECP256K1.Signature; -import tech.pegasys.pantheon.ethereum.core.AddressHelpers; -import tech.pegasys.pantheon.ethereum.core.Block; -import tech.pegasys.pantheon.ethereum.core.Hash; -import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; -import tech.pegasys.pantheon.ethereum.rlp.RLP; -import tech.pegasys.pantheon.ethereum.rlp.RLPInput; - -import java.math.BigInteger; -import java.util.Collections; -import java.util.Optional; - -import org.assertj.core.util.Lists; -import org.junit.Test; - -public class NewRoundPayloadTest { - - private static final ConsensusRoundIdentifier ROUND_IDENTIFIER = - new ConsensusRoundIdentifier(0x1234567890ABCDEFL, 0xFEDCBA98); - - @Test - public void roundTripRlpWithNoRoundChangePayloads() { - final Block block = - TestHelpers.createProposalBlock(singletonList(AddressHelpers.ofValue(1)), ROUND_IDENTIFIER); - final ProposalPayload proposalPayload = new ProposalPayload(ROUND_IDENTIFIER, block.getHash()); - final Signature signature = Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0); - final SignedData proposalPayloadSignedData = - SignedData.from(proposalPayload, signature); - - final RoundChangeCertificate roundChangeCertificate = - new RoundChangeCertificate(Collections.emptyList()); - final NewRoundPayload expectedNewRoundPayload = - new NewRoundPayload(ROUND_IDENTIFIER, roundChangeCertificate, proposalPayloadSignedData); - final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); - expectedNewRoundPayload.writeTo(rlpOut); - - final RLPInput rlpInput = RLP.input(rlpOut.encoded()); - final NewRoundPayload newRoundPayload = NewRoundPayload.readFrom(rlpInput); - assertThat(newRoundPayload.getProposalPayload()).isEqualTo(proposalPayloadSignedData); - assertThat(newRoundPayload.getRoundChangeCertificate()).isEqualTo(roundChangeCertificate); - assertThat(newRoundPayload.getRoundIdentifier()).isEqualTo(ROUND_IDENTIFIER); - assertThat(newRoundPayload.getMessageType()).isEqualTo(IbftV2.NEW_ROUND); - } - - @Test - public void roundTripRlpWithRoundChangePayloads() { - final Block block = - TestHelpers.createProposalBlock(singletonList(AddressHelpers.ofValue(1)), ROUND_IDENTIFIER); - final ProposalPayload proposalPayload = new ProposalPayload(ROUND_IDENTIFIER, block.getHash()); - final Signature signature = Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 0); - final SignedData signedProposal = SignedData.from(proposalPayload, signature); - - final PreparePayload preparePayload = - new PreparePayload(ROUND_IDENTIFIER, Hash.fromHexStringLenient("0x8523ba6e7c5f59ae87")); - final SignedData signedPrepare = SignedData.from(preparePayload, signature); - final PreparedCertificate preparedCert = - new PreparedCertificate(signedProposal, Lists.newArrayList(signedPrepare)); - - final RoundChangePayload roundChangePayload = - new RoundChangePayload(ROUND_IDENTIFIER, Optional.of(preparedCert)); - SignedData signedRoundChange = - SignedData.from(roundChangePayload, signature); - - final RoundChangeCertificate roundChangeCertificate = - new RoundChangeCertificate(Lists.list(signedRoundChange)); - final NewRoundPayload expectedNewRoundPayload = - new NewRoundPayload(ROUND_IDENTIFIER, roundChangeCertificate, signedProposal); - final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); - expectedNewRoundPayload.writeTo(rlpOut); - - final RLPInput rlpInput = RLP.input(rlpOut.encoded()); - final NewRoundPayload newRoundPayload = NewRoundPayload.readFrom(rlpInput); - assertThat(newRoundPayload.getProposalPayload()).isEqualTo(signedProposal); - assertThat(newRoundPayload.getRoundChangeCertificate()).isEqualTo(roundChangeCertificate); - assertThat(newRoundPayload.getRoundIdentifier()).isEqualTo(ROUND_IDENTIFIER); - assertThat(newRoundPayload.getMessageType()).isEqualTo(IbftV2.NEW_ROUND); - } -} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocolTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocolTest.java index 3e1ee3baf8..b969560aef 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocolTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocolTest.java @@ -22,7 +22,7 @@ public class IbftSubProtocolTest { public void messageSpaceReportsCorrectly() { final IbftSubProtocol subProt = new IbftSubProtocol(); - assertThat(subProt.messageSpace(1)).isEqualTo(5); + assertThat(subProt.messageSpace(1)).isEqualTo(4); } @Test @@ -33,13 +33,12 @@ public void allIbftMessageTypesAreRecognisedAsValidByTheSubProtocol() { assertThat(subProt.isValidMessageCode(1, 1)).isTrue(); assertThat(subProt.isValidMessageCode(1, 2)).isTrue(); assertThat(subProt.isValidMessageCode(1, 3)).isTrue(); - assertThat(subProt.isValidMessageCode(1, 4)).isTrue(); } @Test public void invalidMessageTypesAreNotAcceptedByTheSubprotocol() { final IbftSubProtocol subProt = new IbftSubProtocol(); - assertThat(subProt.isValidMessageCode(1, 5)).isFalse(); + assertThat(subProt.isValidMessageCode(1, 4)).isFalse(); } } diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java index 4a8eb63bc3..fe124692ca 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,15 +36,15 @@ import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator; import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; +import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; import tech.pegasys.pantheon.consensus.ibft.network.IbftMessageTransmitter; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; +import tech.pegasys.pantheon.consensus.ibft.validation.FutureRoundProposalMessageValidator; import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidator; import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory; -import tech.pegasys.pantheon.consensus.ibft.validation.NewRoundMessageValidator; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.Signature; import tech.pegasys.pantheon.ethereum.ProtocolContext; @@ -90,7 +91,7 @@ public class IbftBlockHeightManagerTest { @Mock private BlockImporter blockImporter; @Mock private BlockTimer blockTimer; @Mock private RoundTimer roundTimer; - @Mock private NewRoundMessageValidator newRoundPayloadValidator; + @Mock private FutureRoundProposalMessageValidator futureRoundProposalMessageValidator; @Captor private ArgumentCaptor> preparedRoundArtifactsCaptor; @@ -134,9 +135,10 @@ public void setup() { when(finalState.getQuorum()).thenReturn(3); when(finalState.getMessageFactory()).thenReturn(messageFactory); when(blockCreator.createBlock(anyLong())).thenReturn(createdBlock); - when(newRoundPayloadValidator.validateNewRoundMessage(any())).thenReturn(true); - when(messageValidatorFactory.createNewRoundValidator(anyLong())) - .thenReturn(newRoundPayloadValidator); + + when(futureRoundProposalMessageValidator.validateProposalMessage(any())).thenReturn(true); + when(messageValidatorFactory.createFutureRoundProposalMessageValidator(anyLong())) + .thenReturn(futureRoundProposalMessageValidator); when(messageValidatorFactory.createMessageValidator(any())).thenReturn(messageValidator); protocolContext = @@ -207,7 +209,7 @@ public void onBlockTimerExpiryProposalMessageIsTransmitted() { manager.start(); manager.handleBlockTimerExpiry(roundIdentifier); - verify(messageTransmitter, times(1)).multicastProposal(eq(roundIdentifier), any()); + verify(messageTransmitter, times(1)).multicastProposal(eq(roundIdentifier), any(), any()); verify(messageTransmitter, never()).multicastPrepare(any(), any()); verify(messageTransmitter, never()).multicastPrepare(any(), any()); } @@ -257,7 +259,7 @@ public void onRoundTimerExpiryANewRoundIsCreatedWithAnIncrementedRoundNumber() { } @Test - public void whenSufficientRoundChangesAreReceivedANewRoundMessageIsTransmitted() { + public void whenSufficientRoundChangesAreReceivedAProposalMessageIsTransmitted() { final ConsensusRoundIdentifier futureRoundIdentifier = createFrom(roundIdentifier, 0, +2); final RoundChange roundChange = messageFactory.createRoundChange(futureRoundIdentifier, Optional.empty()); @@ -277,11 +279,12 @@ public void whenSufficientRoundChangesAreReceivedANewRoundMessageIsTransmitted() clock, messageValidatorFactory); manager.start(); + reset(messageTransmitter); manager.handleRoundChangePayload(roundChange); verify(messageTransmitter, times(1)) - .multicastNewRound(eq(futureRoundIdentifier), eq(roundChangCert), any(), any()); + .multicastProposal(eq(futureRoundIdentifier), any(), eq(Optional.of(roundChangCert))); } @Test @@ -314,14 +317,13 @@ public void messagesForFutureRoundsAreBufferedAndUsedToPreloadNewRoundWhenItIsSt manager.handleCommitPayload(commit); // Force a new round to be started at new round number. - final NewRound newRound = - messageFactory.createNewRound( + final Proposal futureRoundProposal = + messageFactory.createProposal( futureRoundIdentifier, - new RoundChangeCertificate(Collections.emptyList()), - messageFactory.createProposal(futureRoundIdentifier, createdBlock).getSignedPayload(), - createdBlock); + createdBlock, + Optional.of(new RoundChangeCertificate(Collections.emptyList()))); - manager.handleNewRoundPayload(newRound); + manager.handleProposalPayload(futureRoundProposal); // 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()); @@ -365,4 +367,32 @@ public void preparedCertificateIncludedInRoundChangeMessageOnRoundTimeoutExpired assertThat(preparedCert.get().getPreparedCertificate().getPreparePayloads()) .containsOnly(firstPrepare.getSignedPayload(), secondPrepare.getSignedPayload()); } + + @Test + public void illegalFutureRoundProposalDoesNotTriggerNewRound() { + when(futureRoundProposalMessageValidator.validateProposalMessage(any())).thenReturn(false); + + final ConsensusRoundIdentifier futureRoundIdentifier = createFrom(roundIdentifier, 0, +2); + + final IbftBlockHeightManager manager = + new IbftBlockHeightManager( + headerTestFixture.buildHeader(), + finalState, + roundChangeManager, + roundFactory, + clock, + messageValidatorFactory); + + // Force a new round to be started at new round number. + final Proposal futureRoundProposal = + messageFactory.createProposal( + futureRoundIdentifier, + createdBlock, + Optional.of(new RoundChangeCertificate(Collections.emptyList()))); + manager.start(); + reset(roundFactory); // Discard the existing createNewRound invocation. + + manager.handleProposalPayload(futureRoundProposal); + verify(roundFactory, never()).createNewRound(any(), anyInt()); + } } diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java index 2e6a5f1446..470d95da81 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftControllerTest.java @@ -31,12 +31,10 @@ import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.messagedata.CommitMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.IbftV2; -import tech.pegasys.pantheon.consensus.ibft.messagedata.NewRoundMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.PrepareMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.ProposalMessageData; import tech.pegasys.pantheon.consensus.ibft.messagedata.RoundChangeMessageData; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; @@ -80,10 +78,6 @@ public class IbftControllerTest { private Message commitMessage; @Mock private CommitMessageData commitMessageData; - @Mock private NewRound newRound; - private Message newRoundMessage; - @Mock private NewRoundMessageData newRoundMessageData; - @Mock private RoundChange roundChange; private Message roundChangeMessage; @Mock private RoundChangeMessageData roundChangeMessageData; @@ -136,11 +130,10 @@ public void startsNewBlockHeightManagerAndReplaysFutureMessages() { setupProposal(roundIdentifierHeight3, validator); setupCommit(futureRoundIdentifier, validator); setupRoundChange(futureRoundIdentifier, validator); - setupNewRound(roundIdentifierHeight3, validator); final List height2Msgs = newArrayList(prepareMessage, commitMessage, roundChangeMessage); - final List height3Msgs = newArrayList(proposalMessage, newRoundMessage); + final List height3Msgs = newArrayList(proposalMessage); futureMessages.put(2L, height2Msgs); futureMessages.put(3L, height3Msgs); when(blockHeightManager.getChainHeight()).thenReturn(2L); @@ -158,7 +151,6 @@ public void startsNewBlockHeightManagerAndReplaysFutureMessages() { verify(ibftGossip).send(commitMessage); verify(blockHeightManager).handleRoundChangePayload(roundChange); verify(ibftGossip).send(roundChangeMessage); - verify(blockHeightManager, never()).handleNewRoundPayload(newRound); } @Test @@ -167,12 +159,9 @@ public void createsNewBlockHeightManagerAndReplaysFutureMessagesOnNewChainHeadEv setupProposal(futureRoundIdentifier, validator); setupCommit(futureRoundIdentifier, validator); setupRoundChange(futureRoundIdentifier, validator); - setupNewRound(futureRoundIdentifier, validator); futureMessages.put( - 2L, - ImmutableList.of( - prepareMessage, proposalMessage, commitMessage, roundChangeMessage, newRoundMessage)); + 2L, ImmutableList.of(prepareMessage, proposalMessage, commitMessage, roundChangeMessage)); when(blockHeightManager.getChainHeight()).thenReturn(2L); ibftController.start(); @@ -190,8 +179,6 @@ public void createsNewBlockHeightManagerAndReplaysFutureMessagesOnNewChainHeadEv verify(ibftGossip).send(commitMessage); verify(blockHeightManager).handleRoundChangePayload(roundChange); verify(ibftGossip).send(roundChangeMessage); - verify(blockHeightManager).handleNewRoundPayload(newRound); - verify(ibftGossip).send(newRoundMessage); } @Test @@ -275,21 +262,6 @@ public void commitForCurrentHeightIsPassedToBlockHeightManager() { verifyNoMoreInteractions(blockHeightManager); } - @Test - public void newRoundForCurrentHeightIsPassedToBlockHeightManager() { - roundIdentifier = new ConsensusRoundIdentifier(0, 1); - setupNewRound(roundIdentifier, validator); - ibftController.start(); - ibftController.handleMessageEvent(new IbftReceivedMessageEvent(newRoundMessage)); - - assertThat(futureMessages).isEmpty(); - verify(blockHeightManager).handleNewRoundPayload(newRound); - verify(ibftGossip).send(newRoundMessage); - verify(blockHeightManager, atLeastOnce()).getChainHeight(); - verify(blockHeightManager).start(); - verifyNoMoreInteractions(blockHeightManager); - } - @Test public void roundChangeForCurrentHeightIsPassedToBlockHeightManager() { roundIdentifier = new ConsensusRoundIdentifier(0, 1); @@ -326,13 +298,6 @@ public void commitForPastHeightIsDiscarded() { verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(commitMessage)); } - @Test - public void newRoundForPastHeightIsDiscarded() { - setupNewRound(roundIdentifier, validator); - when(blockHeightManager.getChainHeight()).thenReturn(1L); - verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(newRoundMessage)); - } - @Test public void roundChangeForPastHeightIsDiscarded() { setupRoundChange(roundIdentifier, validator); @@ -378,12 +343,6 @@ public void commitForUnknownValidatorIsDiscarded() { verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(commitMessage)); } - @Test - public void newRoundForUnknownValidatorIsDiscarded() { - setupNewRound(roundIdentifier, unknownValidator); - verifyNotHandledAndNoFutureMsgs(new IbftReceivedMessageEvent(newRoundMessage)); - } - @Test public void roundChangeForUnknownValidatorIsDiscarded() { setupRoundChange(roundIdentifier, unknownValidator); @@ -414,14 +373,6 @@ public void commitForFutureHeightIsBuffered() { verifyHasFutureMessages(new IbftReceivedMessageEvent(commitMessage), expectedFutureMsgs); } - @Test - public void newRoundForFutureHeightIsBuffered() { - setupNewRound(futureRoundIdentifier, validator); - final Map> expectedFutureMsgs = - ImmutableMap.of(2L, ImmutableList.of(newRoundMessage)); - verifyHasFutureMessages(new IbftReceivedMessageEvent(newRoundMessage), expectedFutureMsgs); - } - @Test public void roundChangeForFutureHeightIsBuffered() { setupRoundChange(futureRoundIdentifier, validator); @@ -497,15 +448,6 @@ private void setupCommit( commitMessage = new DefaultMessage(null, commitMessageData); } - private void setupNewRound( - final ConsensusRoundIdentifier roundIdentifier, final Address validator) { - when(newRound.getAuthor()).thenReturn(validator); - when(newRound.getRoundIdentifier()).thenReturn(roundIdentifier); - when(newRoundMessageData.getCode()).thenReturn(IbftV2.NEW_ROUND); - when(newRoundMessageData.decode()).thenReturn(newRound); - newRoundMessage = new DefaultMessage(null, newRoundMessageData); - } - private void setupRoundChange( final ConsensusRoundIdentifier roundIdentifier, final Address validator) { when(roundChange.getAuthor()).thenReturn(validator); diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java index c54f6ca2ae..e2fbee78be 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java @@ -132,7 +132,8 @@ public void onReceptionOfValidProposalSendsAPrepareToNetworkPeers() { messageFactory, transmitter); - round.handleProposalMessage(messageFactory.createProposal(roundIdentifier, proposedBlock)); + round.handleProposalMessage( + messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty())); verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash()); verify(transmitter, never()).multicastCommit(any(), any(), any()); } @@ -152,7 +153,8 @@ public void sendsAProposalWhenRequested() { transmitter); round.createAndSendProposalMessage(15); - verify(transmitter, times(1)).multicastProposal(roundIdentifier, proposedBlock); + verify(transmitter, times(1)) + .multicastProposal(roundIdentifier, proposedBlock, Optional.empty()); verify(transmitter, never()).multicastPrepare(any(), any()); verify(transmitter, never()).multicastCommit(any(), any(), any()); } @@ -171,7 +173,8 @@ public void singleValidatorImportBlocksImmediatelyOnProposalCreation() { messageFactory, transmitter); round.createAndSendProposalMessage(15); - verify(transmitter, times(1)).multicastProposal(roundIdentifier, proposedBlock); + verify(transmitter, times(1)) + .multicastProposal(roundIdentifier, proposedBlock, Optional.empty()); verify(transmitter, never()).multicastPrepare(any(), any()); verify(transmitter, times(1)).multicastCommit(any(), any(), any()); verify(blockImporter, times(1)).importBlock(any(), any(), any()); @@ -197,7 +200,8 @@ public void twoValidatorNetworkSendsPrepareOnProposalReceptionThenSendsCommitOnC final Signature localCommitSeal = SECP256K1.sign(commitSealHash, localNodeKeys); // Receive Proposal Message - round.handleProposalMessage(messageFactory.createProposal(roundIdentifier, proposedBlock)); + round.handleProposalMessage( + messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty())); verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash()); verify(transmitter, times(1)) .multicastCommit(roundIdentifier, proposedBlock.getHash(), localCommitSeal); @@ -254,7 +258,7 @@ public void localNodeProposesToNetworkOfTwoValidatorsImportsOnReceptionOfCommitF } @Test - public void aNewRoundMessageWithAnewBlockIsSentUponReceptionOfARoundChangeWithNoCertificate() { + public void aProposalWithAnewBlockIsSentUponReceptionOfARoundChangeWithNoCertificate() { final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); final IbftRound round = new IbftRound( @@ -271,7 +275,7 @@ public void aNewRoundMessageWithAnewBlockIsSentUponReceptionOfARoundChangeWithNo round.startRoundWith(new RoundChangeArtifacts(empty(), emptyList()), 15); verify(transmitter, times(1)) - .multicastNewRound(eq(roundIdentifier), eq(roundChangeCertificate), any(), any()); + .multicastProposal(eq(roundIdentifier), any(), eq(Optional.of(roundChangeCertificate))); } @Test @@ -296,18 +300,18 @@ public void aNewRoundMessageWithTheSameBlockIsSentUponReceptionOfARoundChangeWit roundIdentifier, Optional.of( new PreparedRoundArtifacts( - messageFactory.createProposal(priorRoundChange, proposedBlock), + messageFactory.createProposal( + priorRoundChange, proposedBlock, Optional.empty()), emptyList()))))); // NOTE: IbftRound assumes the prepare's are valid round.startRoundWith(roundChangeArtifacts, 15); verify(transmitter, times(1)) - .multicastNewRound( + .multicastProposal( eq(roundIdentifier), - eq(roundChangeArtifacts.getRoundChangeCertificate()), - any(), - blockCaptor.capture()); + blockCaptor.capture(), + eq(Optional.of(roundChangeArtifacts.getRoundChangeCertificate()))); final IbftExtraData proposedExtraData = IbftExtraData.decode(blockCaptor.getValue().getHeader().getExtraData()); @@ -340,11 +344,10 @@ public void creatingNewBlockFromEmptyPreparedCertificateUpdatesInternalState() { round.startRoundWith(roundChangeArtifacts, 15); verify(transmitter, times(1)) - .multicastNewRound( + .multicastProposal( eq(roundIdentifier), - eq(roundChangeArtifacts.getRoundChangeCertificate()), - payloadArgCaptor.capture(), - blockCaptor.capture()); + blockCaptor.capture(), + eq(Optional.of(roundChangeArtifacts.getRoundChangeCertificate()))); // Inject a single Prepare message, and confirm the roundState has gone to Prepared (which // indicates the block has entered the roundState (note: all msgs are deemed valid due to mocks) @@ -389,7 +392,8 @@ public void blockIsOnlyImportedOnceWhenCommitsAreReceivedBeforeProposal() { round.handleCommitMessage( messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); - round.handleProposalMessage(messageFactory.createProposal(roundIdentifier, proposedBlock)); + round.handleProposalMessage( + messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty())); verify(blockImporter, times(1)).importBlock(any(), any(), any()); } @@ -412,7 +416,8 @@ public void blockIsImportedOnlyOnceIfQuorumCommitsAreReceivedPriorToProposal() { round.handleCommitMessage( messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); - round.handleProposalMessage(messageFactory.createProposal(roundIdentifier, proposedBlock)); + round.handleProposalMessage( + messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty())); verify(blockImporter, times(1)).importBlock(any(), any(), any()); } diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeArtifactsTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeArtifactsTest.java index 7f853627d2..873e7c3999 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeArtifactsTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeArtifactsTest.java @@ -54,7 +54,7 @@ private PreparedRoundArtifacts createPreparedRoundArtefacts(final int fromRound) final Block block = TestHelpers.createProposalBlock(emptyList(), preparedRound); return new PreparedRoundArtifacts( - messageFactories.get(0).createProposal(preparedRound, block), + messageFactories.get(0).createProposal(preparedRound, block, Optional.empty()), messageFactories.stream() .map(factory -> factory.createPrepare(preparedRound, block.getHash())) .collect(Collectors.toList())); diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java index 8d359c4633..f27874b266 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManagerTest.java @@ -134,7 +134,7 @@ private RoundChange makeRoundChangeMessageWithPreparedCert( final ConsensusRoundIdentifier proposalRound = TestHelpers.createFrom(round, 0, -1); final Block block = TestHelpers.createProposalBlock(validators, proposalRound); // Proposal must come from an earlier round. - final Proposal proposal = messageFactory.createProposal(proposalRound, block); + final Proposal proposal = messageFactory.createProposal(proposalRound, block, Optional.empty()); final List preparePayloads = prepareProviders.stream() diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java index c0ccab9f13..0dcb580370 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundStateTest.java @@ -33,6 +33,7 @@ import java.math.BigInteger; import java.util.List; +import java.util.Optional; import com.google.common.collect.Lists; import org.junit.Before; @@ -80,7 +81,7 @@ public void ifProposalMessageFailsValidationMethodReturnsFalse() { final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator); final Proposal proposal = - validatorMessageFactories.get(0).createProposal(roundIdentifier, block); + validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty()); assertThat(roundState.setProposedBlock(proposal)).isFalse(); assertThat(roundState.isPrepared()).isFalse(); @@ -94,8 +95,7 @@ public void singleValidatorIsPreparedWithJustProposal() { final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator); final Proposal proposal = - validatorMessageFactories.get(0).createProposal(roundIdentifier, block); - + validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty()); assertThat(roundState.setProposedBlock(proposal)).isTrue(); assertThat(roundState.isPrepared()).isTrue(); assertThat(roundState.isCommitted()).isFalse(); @@ -117,7 +117,7 @@ public void singleValidatorRequiresCommitMessageToBeCommitted() { final RoundState roundState = new RoundState(roundIdentifier, 1, messageValidator); final Proposal proposal = - validatorMessageFactories.get(0).createProposal(roundIdentifier, block); + validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty()); assertThat(roundState.setProposedBlock(proposal)).isTrue(); assertThat(roundState.isPrepared()).isTrue(); @@ -161,7 +161,7 @@ public void prepareMessagesCanBeReceivedPriorToProposal() { assertThat(roundState.constructPreparedRoundArtifacts()).isEmpty(); final Proposal proposal = - validatorMessageFactories.get(0).createProposal(roundIdentifier, block); + validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty()); assertThat(roundState.setProposedBlock(proposal)).isTrue(); assertThat(roundState.isPrepared()).isTrue(); assertThat(roundState.isCommitted()).isFalse(); @@ -189,7 +189,7 @@ public void invalidPriorPrepareMessagesAreDiscardedUponSubsequentProposal() { verify(messageValidator, never()).validatePrepare(any()); final Proposal proposal = - validatorMessageFactories.get(0).createProposal(roundIdentifier, block); + validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty()); assertThat(roundState.setProposedBlock(proposal)).isTrue(); assertThat(roundState.isPrepared()).isFalse(); @@ -208,7 +208,7 @@ public void prepareMessageIsValidatedAgainstExitingProposal() { validatorMessageFactories.get(2).createPrepare(roundIdentifier, block.getHash()); final Proposal proposal = - validatorMessageFactories.get(0).createProposal(roundIdentifier, block); + validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty()); when(messageValidator.validateProposal(any())).thenReturn(true); when(messageValidator.validatePrepare(firstPrepare)).thenReturn(false); @@ -248,7 +248,7 @@ public void commitSealsAreExtractedFromReceivedMessages() { Signature.create(BigInteger.TEN, BigInteger.TEN, (byte) 1)); final Proposal proposal = - validatorMessageFactories.get(0).createProposal(roundIdentifier, block); + validatorMessageFactories.get(0).createProposal(roundIdentifier, block, Optional.empty()); roundState.setProposedBlock(proposal); roundState.addCommitMessage(firstCommit); diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java new file mode 100644 index 0000000000..c5f213e0c1 --- /dev/null +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java @@ -0,0 +1,86 @@ +/* + * 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.validation; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; +import tech.pegasys.pantheon.consensus.ibft.TestHelpers; +import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; +import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; +import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; +import tech.pegasys.pantheon.ethereum.core.Block; + +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class FutureRoundProposalMessageValidatorTest { + + private final KeyPair proposerKey = KeyPair.generate(); + private final MessageFactory messageFactoy = new MessageFactory(proposerKey); + private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1); + private final Block proposedBlock = TestHelpers.createProposalBlock(emptyList(), roundIdentifier); + + private FutureRoundProposalMessageValidator validator; + + private final MessageValidatorFactory messageValidatorFactory = + mock(MessageValidatorFactory.class); + private final MessageValidator messageValidator = mock(MessageValidator.class); + + @Before + public void setup() { + + when(messageValidatorFactory.createMessageValidator(any())).thenReturn(messageValidator); + when(messageValidator.validateProposal(any())).thenReturn(true); + + validator = + new FutureRoundProposalMessageValidator( + messageValidatorFactory, roundIdentifier.getSequenceNumber()); + } + + @Test + public void validProposalMatchingCurrentChainHeightPassesValidation() { + final Proposal proposal = + messageFactoy.createProposal(roundIdentifier, proposedBlock, Optional.empty()); + + assertThat(validator.validateProposalMessage(proposal)).isTrue(); + } + + @Test + public void proposalTargettingDifferentChainHeightFailsValidation() { + final ConsensusRoundIdentifier futureChainIdentifier = + TestHelpers.createFrom(roundIdentifier, 1, 0); + final Proposal proposal = + messageFactoy.createProposal(futureChainIdentifier, proposedBlock, Optional.empty()); + + assertThat(validator.validateProposalMessage(proposal)).isFalse(); + } + + @Test + public void proposalWhichFailsMessageValidationFailsFutureRoundValidation() { + final Proposal proposal = + messageFactoy.createProposal(roundIdentifier, proposedBlock, Optional.empty()); + when(messageValidator.validateProposal(any())).thenReturn(false); + + assertThat(validator.validateProposalMessage(proposal)).isFalse(); + } +} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java index 9f34f43c5d..5b9ff80df8 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java @@ -12,9 +12,11 @@ */ package tech.pegasys.pantheon.consensus.ibft.validation; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -26,6 +28,7 @@ import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; +import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.BlockValidator; @@ -52,7 +55,7 @@ public class MessageValidatorTest { private KeyPair keyPair = KeyPair.generate(); private MessageFactory messageFactory = new MessageFactory(keyPair); - private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1); + private ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 0); private SignedDataValidator signedDataValidator = mock(SignedDataValidator.class); private ProposalBlockConsistencyValidator proposalBlockConsistencyValidator = @@ -60,6 +63,8 @@ public class MessageValidatorTest { @Mock private BlockValidator blockValidator; private ProtocolContext protocolContext; + private final RoundChangeCertificateValidator roundChangeCertificateValidator = + mock(RoundChangeCertificateValidator.class); private MessageValidator messageValidator; @@ -88,17 +93,26 @@ public void setup() { when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) .thenReturn(Optional.of(new BlockProcessingOutputs(null, null))); + when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( + any(), any())) + .thenReturn(true); + when(roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( + any(), any())) + .thenReturn(true); + messageValidator = new MessageValidator( signedDataValidator, proposalBlockConsistencyValidator, blockValidator, - protocolContext); + protocolContext, + roundChangeCertificateValidator); } @Test public void messageValidatorDefersToUnderlyingSignedDataValidator() { - final Proposal proposal = messageFactory.createProposal(roundIdentifier, block); + final Proposal proposal = + messageFactory.createProposal(roundIdentifier, block, Optional.empty()); final Prepare prepare = messageFactory.createPrepare(roundIdentifier, block.getHash()); @@ -118,7 +132,8 @@ public void messageValidatorDefersToUnderlyingSignedDataValidator() { @Test public void ifProposalConsistencyChecksFailProposalIsIllegal() { - final Proposal proposal = messageFactory.createProposal(roundIdentifier, block); + final Proposal proposal = + messageFactory.createProposal(roundIdentifier, block, Optional.empty()); when(proposalBlockConsistencyValidator.validateProposalMatchesBlock(any(), any())) .thenReturn(false); @@ -132,8 +147,62 @@ public void blockValidationFailureFailsValidation() { when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) .thenReturn(Optional.empty()); - final Proposal proposalMsg = messageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + messageFactory.createProposal(roundIdentifier, block, Optional.empty()); assertThat(messageValidator.validateProposal(proposalMsg)).isFalse(); } + + @Test + public void proposalFailsValidationIfRoundChangeCertificateDoeNotMatchBlock() { + final ConsensusRoundIdentifier nonZeroRound = new ConsensusRoundIdentifier(1, 1); + when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( + any(), any())) + .thenReturn(false); + + final Proposal proposal = + messageFactory.createProposal( + nonZeroRound, block, Optional.of(new RoundChangeCertificate(emptyList()))); + + assertThat(messageValidator.validateProposal(proposal)).isFalse(); + } + + @Test + public void proposalFailsValidationIfRoundChangeIsNotSelfConsistent() { + final ConsensusRoundIdentifier nonZeroRound = new ConsensusRoundIdentifier(1, 1); + when(roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( + any(), any())) + .thenReturn(false); + + final Proposal proposal = + messageFactory.createProposal( + nonZeroRound, block, Optional.of(new RoundChangeCertificate(emptyList()))); + + assertThat(messageValidator.validateProposal(proposal)).isFalse(); + } + + @Test + public void proposalForRoundZeroFailsIfItContainsARoundChangeCertificate() { + final Proposal proposal = + messageFactory.createProposal( + roundIdentifier, block, Optional.of(new RoundChangeCertificate(emptyList()))); + + assertThat(messageValidator.validateProposal(proposal)).isFalse(); + verify(roundChangeCertificateValidator, never()) + .validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(any(), any()); + verify(roundChangeCertificateValidator, never()) + .validateProposalMessageMatchesLatestPrepareCertificate(any(), any()); + } + + @Test + public void proposalForRoundsGreaterThanZeroFailIfNoRoundCHangeCertificateAvailable() { + final ConsensusRoundIdentifier nonZeroRound = new ConsensusRoundIdentifier(1, 1); + final Proposal proposal = messageFactory.createProposal(nonZeroRound, block, Optional.empty()); + + assertThat(messageValidator.validateProposal(proposal)).isFalse(); + verify(roundChangeCertificateValidator, never()) + .validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot(any(), any()); + verify(roundChangeCertificateValidator, never()) + .validateProposalMessageMatchesLatestPrepareCertificate(any(), any()); + } } diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java deleted file mode 100644 index 2ae60932ef..0000000000 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundMessageValidatorTest.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2019 ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package tech.pegasys.pantheon.consensus.ibft.validation; - -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; -import tech.pegasys.pantheon.consensus.ibft.IbftContext; -import tech.pegasys.pantheon.consensus.ibft.TestHelpers; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; -import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; -import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; -import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; -import tech.pegasys.pantheon.ethereum.BlockValidator; -import tech.pegasys.pantheon.ethereum.BlockValidator.BlockProcessingOutputs; -import tech.pegasys.pantheon.ethereum.ProtocolContext; -import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; -import tech.pegasys.pantheon.ethereum.core.Address; -import tech.pegasys.pantheon.ethereum.core.Block; -import tech.pegasys.pantheon.ethereum.core.Util; -import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; - -import java.util.List; -import java.util.Optional; - -import org.assertj.core.util.Lists; -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 NewRoundMessageValidatorTest { - - private final NewRoundPayloadValidator payloadValidator = mock(NewRoundPayloadValidator.class); - - private final KeyPair proposerKey = KeyPair.generate(); - private final KeyPair validatorKey = KeyPair.generate(); - private final MessageFactory proposerMessageFactory = new MessageFactory(proposerKey); - private final MessageFactory validatorMessageFactory = new MessageFactory(validatorKey); - private final Address proposerAddress = Util.publicKeyToAddress(proposerKey.getPublicKey()); - private final Address validatorAddress = Util.publicKeyToAddress(validatorKey.getPublicKey()); - private final List
validators = Lists.newArrayList(proposerAddress, validatorAddress); - - private final ConsensusRoundIdentifier roundIdentifier = new ConsensusRoundIdentifier(1, 1); - private final Block proposedBlock = TestHelpers.createProposalBlock(validators, roundIdentifier); - - private ProposalBlockConsistencyValidator proposalBlockConsistencyValidator = - mock(ProposalBlockConsistencyValidator.class); - private final RoundChangeCertificateValidator roundChangeCertificateValidator = - mock(RoundChangeCertificateValidator.class); - - @Mock private BlockValidator blockValidator; - private ProtocolContext protocolContext; - - private NewRoundMessageValidator validator; - - @Before - public void setup() { - - when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) - .thenReturn(Optional.of(new BlockProcessingOutputs(null, null))); - - when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( - any(), any())) - .thenReturn(true); - - protocolContext = - new ProtocolContext<>( - mock(MutableBlockchain.class), mock(WorldStateArchive.class), mock(IbftContext.class)); - - validator = - new NewRoundMessageValidator( - payloadValidator, - proposalBlockConsistencyValidator, - blockValidator, - protocolContext, - roundChangeCertificateValidator); - - when(proposalBlockConsistencyValidator.validateProposalMatchesBlock(any(), any())) - .thenReturn(true); - - when(payloadValidator.validateNewRoundMessage(any())).thenReturn(true); - } - - @Test - public void underlyingPayloadValidatorIsInvokedWithCorrectParameters() { - final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); - final NewRound message = - proposerMessageFactory.createNewRound( - roundIdentifier, - new RoundChangeCertificate(emptyList()), - proposal.getSignedPayload(), - proposal.getBlock()); - - assertThat(validator.validateNewRoundMessage(message)).isTrue(); - verify(payloadValidator, times(1)).validateNewRoundMessage(message.getSignedPayload()); - } - - @Test - public void failedBlockValidationFailsMessageValidation() { - final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); - final NewRound message = - proposerMessageFactory.createNewRound( - roundIdentifier, - new RoundChangeCertificate(emptyList()), - proposal.getSignedPayload(), - proposal.getBlock()); - - when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) - .thenReturn(Optional.empty()); - assertThat(validator.validateNewRoundMessage(message)).isFalse(); - } - - @Test - public void ifProposalConsistencyChecksFailsProposalIsIllegal() { - final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); - final NewRound message = - proposerMessageFactory.createNewRound( - roundIdentifier, - new RoundChangeCertificate(emptyList()), - proposal.getSignedPayload(), - proposal.getBlock()); - - when(proposalBlockConsistencyValidator.validateProposalMatchesBlock(any(), any())) - .thenReturn(false); - when(payloadValidator.validateNewRoundMessage(any())).thenReturn(true); - - assertThat(validator.validateNewRoundMessage(message)).isFalse(); - verify(proposalBlockConsistencyValidator, times(1)) - .validateProposalMatchesBlock(proposal.getSignedPayload(), proposal.getBlock()); - } - - @Test - public void validationFailsIfUnderlyingSignedDataValidatorFails() { - when(payloadValidator.validateNewRoundMessage(any())).thenReturn(false); - final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); - final NewRound message = - proposerMessageFactory.createNewRound( - roundIdentifier, - new RoundChangeCertificate(emptyList()), - proposal.getSignedPayload(), - proposal.getBlock()); - - assertThat(validator.validateNewRoundMessage(message)).isFalse(); - } - - @Test - public void roundChangeCertificateDoesntContainSuppliedBlockFails() { - when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( - any(), any())) - .thenReturn(false); - - final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); - final NewRound message = - proposerMessageFactory.createNewRound( - roundIdentifier, - new RoundChangeCertificate(emptyList()), - proposal.getSignedPayload(), - proposal.getBlock()); - - assertThat(validator.validateNewRoundMessage(message)).isFalse(); - } -} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java deleted file mode 100644 index fe8839c54e..0000000000 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/NewRoundSignedDataValidatorTest.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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.validation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; -import tech.pegasys.pantheon.consensus.ibft.TestHelpers; -import tech.pegasys.pantheon.consensus.ibft.blockcreation.ProposerSelector; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; -import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; -import tech.pegasys.pantheon.consensus.ibft.payload.NewRoundPayload; -import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; -import tech.pegasys.pantheon.consensus.ibft.validation.RoundChangePayloadValidator.MessageValidatorForHeightFactory; -import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; -import tech.pegasys.pantheon.ethereum.core.Address; -import tech.pegasys.pantheon.ethereum.core.Block; -import tech.pegasys.pantheon.ethereum.core.Util; - -import java.util.List; -import java.util.Optional; - -import com.google.common.collect.Lists; -import org.junit.Before; -import org.junit.Test; - -public class NewRoundSignedDataValidatorTest { - - private final KeyPair proposerKey = KeyPair.generate(); - private final KeyPair validatorKey = KeyPair.generate(); - private final KeyPair otherValidatorKey = KeyPair.generate(); - private final MessageFactory proposerMessageFactory = new MessageFactory(proposerKey); - private final MessageFactory validatorMessageFactory = new MessageFactory(validatorKey); - private final Address proposerAddress = Util.publicKeyToAddress(proposerKey.getPublicKey()); - private final List
validators = Lists.newArrayList(); - private final long chainHeight = 2; - private final ConsensusRoundIdentifier roundIdentifier = - new ConsensusRoundIdentifier(chainHeight, 4); - private NewRoundPayloadValidator validator; - - private final ProposerSelector proposerSelector = mock(ProposerSelector.class); - private final MessageValidatorForHeightFactory validatorFactory = - mock(MessageValidatorForHeightFactory.class); - private final SignedDataValidator signedDataValidator = mock(SignedDataValidator.class); - private final RoundChangeCertificateValidator roundChangeCertificateValidator = - mock(RoundChangeCertificateValidator.class); - - private Block proposedBlock; - private NewRound validMsg; - private NewRoundPayload validPayload; - private NewRoundPayload.Builder msgBuilder; - - @Before - public void setup() { - validators.add(Util.publicKeyToAddress(proposerKey.getPublicKey())); - validators.add(Util.publicKeyToAddress(validatorKey.getPublicKey())); - validators.add(Util.publicKeyToAddress(otherValidatorKey.getPublicKey())); - - proposedBlock = TestHelpers.createProposalBlock(validators, roundIdentifier); - validMsg = createValidNewRoundMessageSignedBy(proposerKey); - validPayload = validMsg.getSignedPayload().getPayload(); - msgBuilder = NewRoundPayload.Builder.fromExisting(validMsg); - - when(proposerSelector.selectProposerForRound(any())).thenReturn(proposerAddress); - - when(validatorFactory.createAt(any())).thenReturn(signedDataValidator); - when(signedDataValidator.validateProposal(any())).thenReturn(true); - when(signedDataValidator.validatePrepare(any())).thenReturn(true); - - when(roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( - any(), any())) - .thenReturn(true); - - validator = - new NewRoundPayloadValidator( - proposerSelector, validatorFactory, chainHeight, roundChangeCertificateValidator); - } - - /* NOTE: All test herein assume that the Proposer is the expected transmitter of the NewRound - * message. - */ - - private NewRound createValidNewRoundMessageSignedBy(final KeyPair signingKey) { - final MessageFactory messageCreator = new MessageFactory(signingKey); - - final RoundChangeCertificate.Builder builder = new RoundChangeCertificate.Builder(); - builder.appendRoundChangeMessage( - proposerMessageFactory.createRoundChange(roundIdentifier, Optional.empty())); - - return messageCreator.createNewRound( - roundIdentifier, - builder.buildCertificate(), - messageCreator.createProposal(roundIdentifier, proposedBlock).getSignedPayload(), - proposedBlock); - } - - private NewRound signPayload( - final NewRoundPayload payload, final KeyPair signingKey, final Block block) { - - final MessageFactory messageCreator = new MessageFactory(signingKey); - - return messageCreator.createNewRound( - payload.getRoundIdentifier(), - payload.getRoundChangeCertificate(), - payload.getProposalPayload(), - block); - } - - @Test - public void basicNewRoundMessageIsValid() { - assertThat(validator.validateNewRoundMessage(validMsg.getSignedPayload())).isTrue(); - } - - @Test - public void newRoundFromNonProposerFails() { - final NewRound msg = signPayload(validPayload, validatorKey, proposedBlock); - - assertThat(validator.validateNewRoundMessage(msg.getSignedPayload())).isFalse(); - } - - @Test - public void newRoundTargetingRoundZeroFails() { - msgBuilder.setRoundChangeIdentifier( - new ConsensusRoundIdentifier(roundIdentifier.getSequenceNumber(), 0)); - - final NewRound inValidMsg = signPayload(msgBuilder.build(), proposerKey, proposedBlock); - - assertThat(validator.validateNewRoundMessage(inValidMsg.getSignedPayload())).isFalse(); - } - - @Test - public void newRoundTargetingDifferentSequenceNumberFails() { - final ConsensusRoundIdentifier futureRound = TestHelpers.createFrom(roundIdentifier, 1, 0); - msgBuilder.setRoundChangeIdentifier(futureRound); - - final NewRound inValidMsg = signPayload(msgBuilder.build(), proposerKey, proposedBlock); - - assertThat(validator.validateNewRoundMessage(inValidMsg.getSignedPayload())).isFalse(); - } - - @Test - public void roundChangeCertificateFailsValidation() { - when(roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( - any(), any())) - .thenReturn(false); - assertThat(validator.validateNewRoundMessage(validMsg.getSignedPayload())).isFalse(); - } - - @Test - public void embeddedProposalFailsValidation() { - when(signedDataValidator.validateProposal(any())).thenReturn(false, true); - - final Proposal proposal = proposerMessageFactory.createProposal(roundIdentifier, proposedBlock); - - final NewRound msg = - proposerMessageFactory.createNewRound( - roundIdentifier, - new RoundChangeCertificate( - Lists.newArrayList( - proposerMessageFactory - .createRoundChange(roundIdentifier, Optional.empty()) - .getSignedPayload(), - validatorMessageFactory - .createRoundChange(roundIdentifier, Optional.empty()) - .getSignedPayload())), - proposal.getSignedPayload(), - proposedBlock); - - assertThat(validator.validateNewRoundMessage(msg.getSignedPayload())).isFalse(); - } -} diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/ProposalBlockConsistencyValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/ProposalBlockConsistencyValidatorTest.java index 3ba329fca8..7918a80a18 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/ProposalBlockConsistencyValidatorTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/ProposalBlockConsistencyValidatorTest.java @@ -24,6 +24,7 @@ import tech.pegasys.pantheon.ethereum.core.Block; import java.util.Collections; +import java.util.Optional; import org.junit.Before; import org.junit.Test; @@ -51,7 +52,8 @@ public void setup() { @Test public void blockDigestMisMatchWithMessageRoundFails() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); final Block misMatchedBlock = IbftBlockInterface.replaceRoundInBlock( @@ -68,7 +70,8 @@ public void blockDigestMisMatchWithMessageRoundFails() { @Test public void blockDigestMatchesButRoundDiffersFails() { final ConsensusRoundIdentifier futureRound = TestHelpers.createFrom(roundIdentifier, 0, +1); - final Proposal proposalMsg = proposerMessageFactory.createProposal(futureRound, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(futureRound, block, Optional.empty()); assertThat( consistencyChecker.validateProposalMatchesBlock(proposalMsg.getSignedPayload(), block)) @@ -78,7 +81,8 @@ public void blockDigestMatchesButRoundDiffersFails() { @Test public void blockWithMismatchedNumberFails() { final ConsensusRoundIdentifier futureHeight = TestHelpers.createFrom(roundIdentifier, +1, 0); - final Proposal proposalMsg = proposerMessageFactory.createProposal(futureHeight, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(futureHeight, block, Optional.empty()); assertThat( consistencyChecker.validateProposalMatchesBlock(proposalMsg.getSignedPayload(), block)) diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java index a503de8c01..8459b89e04 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java @@ -104,7 +104,8 @@ public void invalidPrepareMessageInOnePrepareCertificateFails() { roundIdentifier, Optional.of( new PreparedRoundArtifacts( - proposerMessageFactory.createProposal(prevRound, proposedBlock), + proposerMessageFactory.createProposal( + prevRound, proposedBlock, Optional.empty()), Lists.newArrayList( validatorMessageFactory.createPrepare( prevRound, proposedBlock.getHash())))))); @@ -129,7 +130,8 @@ public void detectsTheSuppliedBlockIsNotInLatestPrepareCertificate() { final PreparedRoundArtifacts mismatchedRoundArtefacts = new PreparedRoundArtifacts( - proposerMessageFactory.createProposal(preparedRound, prevProposedBlock), + proposerMessageFactory.createProposal( + preparedRound, prevProposedBlock, Optional.empty()), singletonList( validatorMessageFactory.createPrepare(preparedRound, prevProposedBlock.getHash()))); @@ -152,7 +154,7 @@ public void correctlyMatchesBlockAgainstLatestInRoundChangeCertificate() { TestHelpers.createFrom(roundIdentifier, 0, -1); final Block latterBlock = TestHelpers.createProposalBlock(validators, latterPrepareRound); final Proposal latterProposal = - proposerMessageFactory.createProposal(latterPrepareRound, latterBlock); + proposerMessageFactory.createProposal(latterPrepareRound, latterBlock, Optional.empty()); final Optional latterTerminatedRoundArtefacts = Optional.of( new PreparedRoundArtifacts( @@ -169,7 +171,7 @@ public void correctlyMatchesBlockAgainstLatestInRoundChangeCertificate() { final Block earlierBlock = TestHelpers.createProposalBlock(validators.subList(0, 1), earlierPreparedRound); final Proposal earlierProposal = - proposerMessageFactory.createProposal(earlierPreparedRound, earlierBlock); + proposerMessageFactory.createProposal(earlierPreparedRound, earlierBlock, Optional.empty()); final Optional earlierTerminatedRoundArtefacts = Optional.of( new PreparedRoundArtifacts( diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java index 4109e19957..6c7327e906 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeSignedDataValidatorTest.java @@ -97,7 +97,8 @@ public void roundChangeContainingNoCertificateIsSuccessful() { public void roundChangeContainingInvalidProposalFails() { final PreparedRoundArtifacts preparedRoundArtifacts = new PreparedRoundArtifacts( - proposerMessageFactory.createProposal(currentRound, block), Collections.emptyList()); + proposerMessageFactory.createProposal(currentRound, block, Optional.empty()), + Collections.emptyList()); final PreparedCertificate prepareCertificate = preparedRoundArtifacts.getPreparedCertificate(); @@ -118,7 +119,8 @@ public void roundChangeContainingInvalidProposalFails() { public void roundChangeContainingValidProposalButNoPrepareMessagesFails() { final PreparedRoundArtifacts preparedRoundArtifacts = new PreparedRoundArtifacts( - proposerMessageFactory.createProposal(currentRound, block), Collections.emptyList()); + proposerMessageFactory.createProposal(currentRound, block, Optional.empty()), + Collections.emptyList()); final RoundChange msg = proposerMessageFactory.createRoundChange(targetRound, Optional.of(preparedRoundArtifacts)); @@ -132,7 +134,7 @@ public void roundChangeInvalidPrepareMessageFromProposerFails() { final Prepare prepareMsg = validatorMessageFactory.createPrepare(currentRound, block.getHash()); final PreparedRoundArtifacts preparedRoundArtifacts = new PreparedRoundArtifacts( - proposerMessageFactory.createProposal(currentRound, block), + proposerMessageFactory.createProposal(currentRound, block, Optional.empty()), Lists.newArrayList(prepareMsg)); when(basicValidator.validateProposal(any())).thenReturn(true); @@ -168,7 +170,7 @@ public void roundChangeWithProposalFromARoundAheadOfRoundChangeTargetFails() { final Prepare prepareMsg = validatorMessageFactory.createPrepare(futureRound, block.getHash()); final PreparedRoundArtifacts preparedRoundArtifacts = new PreparedRoundArtifacts( - proposerMessageFactory.createProposal(futureRound, block), + proposerMessageFactory.createProposal(futureRound, block, Optional.empty()), Lists.newArrayList(prepareMsg)); final RoundChange msg = @@ -185,7 +187,7 @@ public void roundChangeWithPastProposalForCurrentHeightIsSuccessful() { final Prepare prepareMsg = validatorMessageFactory.createPrepare(currentRound, block.getHash()); final PreparedRoundArtifacts preparedRoundArtifacts = new PreparedRoundArtifacts( - proposerMessageFactory.createProposal(currentRound, block), + proposerMessageFactory.createProposal(currentRound, block, Optional.empty()), Lists.newArrayList(prepareMsg)); final PreparedCertificate prepareCertificate = preparedRoundArtifacts.getPreparedCertificate(); diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java index ae1038425e..bbd2e8d0a7 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/SignedDataValidatorTest.java @@ -31,6 +31,7 @@ import tech.pegasys.pantheon.ethereum.core.Util; import java.util.List; +import java.util.Optional; import com.google.common.collect.Lists; import org.junit.Before; @@ -86,14 +87,16 @@ public void receivingACommitMessageBeforeProposalFails() { @Test public void receivingProposalMessageFromNonProposerFails() { final Block block = TestHelpers.createProposalBlock(emptyList(), roundIdentifier); - final Proposal proposalMsg = validatorMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + validatorMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); assertThat(validator.validateProposal(proposalMsg.getSignedPayload())).isFalse(); } @Test public void receivingPrepareFromProposerFails() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); final Prepare prepareMsg = proposerMessageFactory.createPrepare(roundIdentifier, block.getHash()); @@ -104,7 +107,8 @@ public void receivingPrepareFromProposerFails() { @Test public void receivingPrepareFromNonValidatorFails() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); final Prepare prepareMsg = nonValidatorMessageFactory.createPrepare(roundIdentifier, block.getHash()); @@ -115,7 +119,8 @@ public void receivingPrepareFromNonValidatorFails() { @Test public void receivingMessagesWithDifferentRoundIdFromProposalFails() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); final ConsensusRoundIdentifier invalidRoundIdentifier = new ConsensusRoundIdentifier( @@ -133,7 +138,8 @@ public void receivingMessagesWithDifferentRoundIdFromProposalFails() { @Test public void receivingPrepareNonProposerValidatorWithCorrectRoundIsSuccessful() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); final Prepare prepareMsg = validatorMessageFactory.createPrepare(roundIdentifier, block.getHash()); @@ -143,7 +149,8 @@ public void receivingPrepareNonProposerValidatorWithCorrectRoundIsSuccessful() { @Test public void receivingACommitMessageWithAnInvalidCommitSealFails() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); final Commit commitMsg = proposerMessageFactory.createCommit( @@ -155,7 +162,8 @@ public void receivingACommitMessageWithAnInvalidCommitSealFails() { @Test public void commitMessageContainingValidSealFromValidatorIsSuccessful() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); final Commit proposerCommitMsg = proposerMessageFactory.createCommit( @@ -172,32 +180,35 @@ public void commitMessageContainingValidSealFromValidatorIsSuccessful() { @Test public void subsequentProposalHasDifferentSenderFails() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); assertThat(validator.validateProposal(proposalMsg.getSignedPayload())).isTrue(); final Proposal secondProposalMsg = - validatorMessageFactory.createProposal(roundIdentifier, block); + validatorMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); assertThat(validator.validateProposal(secondProposalMsg.getSignedPayload())).isFalse(); } @Test public void subsequentProposalHasDifferentContentFails() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); assertThat(validator.validateProposal(proposalMsg.getSignedPayload())).isTrue(); final ConsensusRoundIdentifier newRoundIdentifier = new ConsensusRoundIdentifier(3, 0); final Proposal secondProposalMsg = - proposerMessageFactory.createProposal(newRoundIdentifier, block); + proposerMessageFactory.createProposal(newRoundIdentifier, block, Optional.empty()); assertThat(validator.validateProposal(secondProposalMsg.getSignedPayload())).isFalse(); } @Test public void subsequentProposalHasIdenticalSenderAndContentIsSuccessful() { - final Proposal proposalMsg = proposerMessageFactory.createProposal(roundIdentifier, block); + final Proposal proposalMsg = + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); assertThat(validator.validateProposal(proposalMsg.getSignedPayload())).isTrue(); final Proposal secondProposalMsg = - proposerMessageFactory.createProposal(roundIdentifier, block); + proposerMessageFactory.createProposal(roundIdentifier, block, Optional.empty()); assertThat(validator.validateProposal(secondProposalMsg.getSignedPayload())).isTrue(); } } From 0ab30f0c8ac02445ffd2d721070d9f5c6c658795 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Sat, 16 Feb 2019 06:42:54 +1000 Subject: [PATCH 02/21] Introduce FutureUtils to reduce duplicated code around CompletableFuture (#868) --- .../ethereum/eth/manager/AbstractEthTask.java | 10 +- .../ethereum/eth/manager/EthScheduler.java | 29 +-- .../eth/sync/fastsync/FastSyncActions.java | 58 +++--- .../sync/fastsync/FastSyncBlockHandler.java | 5 +- .../eth/sync/tasks/GetBlockFromPeerTask.java | 6 +- .../p2p/discovery/PeerDiscoveryAgent.java | 15 +- .../pegasys/pantheon/util/FutureUtils.java | 87 +++++++++ .../pantheon/util/FutureUtilsTest.java | 169 ++++++++++++++++++ 8 files changed, 291 insertions(+), 88 deletions(-) create mode 100644 util/src/main/java/tech/pegasys/pantheon/util/FutureUtils.java create mode 100644 util/src/test/java/tech/pegasys/pantheon/util/FutureUtilsTest.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTask.java index ad2dfc0003..b886935537 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTask.java @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.eth.manager; +import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; + import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; @@ -110,9 +112,7 @@ protected final CompletableFuture executeSubTask( }); return subTaskFuture; } else { - final CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(new CancellationException()); - return future; + return completedExceptionally(new CancellationException()); } } } @@ -135,9 +135,7 @@ protected final CompletableFuture registerSubTask( }); return subTaskFuture; } else { - final CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(new CancellationException()); - return future; + return completedExceptionally(new CancellationException()); } } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java index 3e2eef700a..9903ece9e2 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.eth.manager; +import static tech.pegasys.pantheon.util.FutureUtils.propagateResult; + import tech.pegasys.pantheon.util.ExceptionUtils; import java.time.Duration; @@ -98,19 +100,7 @@ public CompletableFuture scheduleSyncWorkerTask( final Supplier> future) { final CompletableFuture promise = new CompletableFuture<>(); final Future workerFuture = - syncWorkerExecutor.submit( - () -> { - future - .get() - .whenComplete( - (r, t) -> { - if (t != null) { - promise.completeExceptionally(t); - } else { - promise.complete(r); - } - }); - }); + syncWorkerExecutor.submit(() -> propagateResult(future.get(), promise)); // If returned promise is cancelled, cancel the worker future promise.whenComplete( (r, t) -> { @@ -170,18 +160,7 @@ public CompletableFuture scheduleFutureTask( final CompletableFuture promise = new CompletableFuture<>(); final ScheduledFuture scheduledFuture = scheduler.schedule( - () -> { - future - .get() - .whenComplete( - (r, t) -> { - if (t != null) { - promise.completeExceptionally(t); - } else { - promise.complete(r); - } - }); - }, + () -> propagateResult(future.get(), promise), duration.toMillis(), TimeUnit.MILLISECONDS); // If returned promise is cancelled, cancel scheduled task diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index a6c37951f7..2fa221a961 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -14,6 +14,8 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static tech.pegasys.pantheon.ethereum.eth.sync.fastsync.FastSyncError.CHAIN_TOO_SHORT; +import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; +import static tech.pegasys.pantheon.util.FutureUtils.exceptionallyCompose; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.BlockHeader; @@ -73,59 +75,39 @@ public CompletableFuture waitForSuitablePeers(final FastSyncState ethContext, syncConfig.getFastSyncMinimumPeerCount(), ethTasksTimer); final EthScheduler scheduler = ethContext.getScheduler(); - final CompletableFuture result = new CompletableFuture<>(); - scheduler - .timeout(waitForPeersTask, syncConfig.getFastSyncMaximumPeerWaitTime()) - .handle( - (waitResult, error) -> { + return exceptionallyCompose( + scheduler.timeout(waitForPeersTask, syncConfig.getFastSyncMaximumPeerWaitTime()), + error -> { if (ExceptionUtils.rootCause(error) instanceof TimeoutException) { if (ethContext.getEthPeers().availablePeerCount() > 0) { LOG.warn( "Fast sync timed out before minimum peer count was reached. Continuing with reduced peers."); - result.complete(fastSyncState); + return completedFuture(null); } else { LOG.warn( "Maximum wait time for fast sync reached but no peers available. Continuing to wait for any available peer."); - waitForAnyPeer() - .thenAccept(value -> result.complete(fastSyncState)) - .exceptionally( - taskError -> { - result.completeExceptionally(error); - return null; - }); + return waitForAnyPeer(); } } else if (error != null) { LOG.error("Failed to find peers for fast sync", error); - result.completeExceptionally(error); - } else { - result.complete(fastSyncState); + return completedExceptionally(error); } return null; - }); - - return result; + }) + .thenApply(successfulWaitResult -> fastSyncState); } private CompletableFuture waitForAnyPeer() { - final CompletableFuture result = new CompletableFuture<>(); - waitForAnyPeer(result); - return result; - } - - private void waitForAnyPeer(final CompletableFuture result) { - ethContext - .getScheduler() - .timeout(WaitForPeersTask.create(ethContext, 1, ethTasksTimer)) - .whenComplete( - (waitResult, throwable) -> { - if (ExceptionUtils.rootCause(throwable) instanceof TimeoutException) { - waitForAnyPeer(result); - } else if (throwable != null) { - result.completeExceptionally(throwable); - } else { - result.complete(waitResult); - } - }); + final CompletableFuture waitForPeerResult = + ethContext.getScheduler().timeout(WaitForPeersTask.create(ethContext, 1, ethTasksTimer)); + return exceptionallyCompose( + waitForPeerResult, + throwable -> { + if (ExceptionUtils.rootCause(throwable) instanceof TimeoutException) { + return waitForAnyPeer(); + } + return completedExceptionally(throwable); + }); } public CompletableFuture selectPivotBlock(final FastSyncState fastSyncState) { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandler.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandler.java index decd2040e3..ce9382a6a4 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandler.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandler.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; import static java.util.Collections.emptyList; +import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.Block; @@ -112,11 +113,9 @@ public CompletableFuture> validateAndImportBlocks( } private CompletableFuture> invalidBlockFailure(final Block block) { - final CompletableFuture> result = new CompletableFuture<>(); - result.completeExceptionally( + return completedExceptionally( new InvalidBlockException( "Failed to import block", block.getHeader().getNumber(), block.getHash())); - return result; } private BlockImporter getBlockImporter(final BlockWithReceipts blockWithReceipt) { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTask.java index 13b1eff091..834c203fab 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTask.java @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.tasks; +import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; + import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; @@ -87,9 +89,7 @@ private CompletableFuture>> downloadHeader(fina private CompletableFuture>> completeBlock( final PeerTaskResult> headerResult) { if (headerResult.getResult().isEmpty()) { - final CompletableFuture>> future = new CompletableFuture<>(); - future.completeExceptionally(new IncompleteResultsException()); - return future; + return completedExceptionally(new IncompleteResultsException()); } return executeSubTask( diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java index 32b720c4da..bd18cc0b56 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java @@ -121,13 +121,12 @@ protected abstract CompletableFuture sendOutgoingPacket( public abstract CompletableFuture stop(); public CompletableFuture start() { - final CompletableFuture future = new CompletableFuture<>(); if (config.isActive()) { final String host = config.getBindHost(); final int port = config.getBindPort(); LOG.info("Starting peer discovery agent on host={}, port={}", host, port); - listenForConnections() + return listenForConnections() .thenAccept( (InetSocketAddress localAddress) -> { // Once listener is set up, finish initializing @@ -140,21 +139,11 @@ public CompletableFuture start() { localAddress.getPort()); isActive = true; startController(); - }) - .whenComplete( - (res, err) -> { - // Finalize future - if (err != null) { - future.completeExceptionally(err); - } else { - future.complete(null); - } }); } else { this.isActive = false; - future.complete(null); + return CompletableFuture.completedFuture(null); } - return future; } private void startController() { diff --git a/util/src/main/java/tech/pegasys/pantheon/util/FutureUtils.java b/util/src/main/java/tech/pegasys/pantheon/util/FutureUtils.java new file mode 100644 index 0000000000..fd1aa79621 --- /dev/null +++ b/util/src/main/java/tech/pegasys/pantheon/util/FutureUtils.java @@ -0,0 +1,87 @@ +/* + * 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.util; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +public class FutureUtils { + + /** + * Creates a {@link CompletableFuture} that is exceptionally completed by error. + * + * @param error the error to exceptionally complete the future with + * @param the type of CompletableFuture + * @return a CompletableFuture exceptionally completed by error. + */ + public static CompletableFuture completedExceptionally(final Throwable error) { + final CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(error); + return future; + } + + /** + * Returns a new CompletionStage that, when the provided stage completes exceptionally, is + * executed with the provided stage's exception as the argument to the supplied function. + * Otherwise the returned stage completes successfully with the same value as the provided stage. + * + *

This is the exceptional equivalent to {@link CompletionStage#thenCompose(Function)} + * + * @param future the future to handle results or exceptions from + * @param errorHandler the function returning a new CompletionStage + * @param the type of the CompletionStage's result + * @return the CompletionStage + */ + public static CompletableFuture exceptionallyCompose( + final CompletableFuture future, + final Function> errorHandler) { + final CompletableFuture result = new CompletableFuture<>(); + future.whenComplete( + (value, error) -> { + try { + final CompletionStage nextStep = + error != null ? errorHandler.apply(error) : completedFuture(value); + propagateResult(nextStep, result); + } catch (final Throwable t) { + result.completeExceptionally(t); + } + }); + return result; + } + + /** + * Propagates the result of one {@link CompletionStage} to a different {@link CompletableFuture}. + * + *

When from completes successfully, to will be completed + * successfully with the same value. When from completes exceptionally, to + * will be completed exceptionally with the same exception. + * + * @param from the CompletionStage to take results and exceptions from + * @param to the CompletableFuture to propagate results and exceptions to + * @param the type of the success value + */ + public static void propagateResult( + final CompletionStage from, final CompletableFuture to) { + from.whenComplete( + (value, error) -> { + if (error != null) { + to.completeExceptionally(error); + } else { + to.complete(value); + } + }); + } +} diff --git a/util/src/test/java/tech/pegasys/pantheon/util/FutureUtilsTest.java b/util/src/test/java/tech/pegasys/pantheon/util/FutureUtilsTest.java new file mode 100644 index 0000000000..6b3a564154 --- /dev/null +++ b/util/src/test/java/tech/pegasys/pantheon/util/FutureUtilsTest.java @@ -0,0 +1,169 @@ +/* + * 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.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static tech.pegasys.pantheon.util.FutureUtils.exceptionallyCompose; +import static tech.pegasys.pantheon.util.FutureUtils.propagateResult; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.function.Function; + +import org.junit.Test; + +public class FutureUtilsTest { + + private static final RuntimeException ERROR = new RuntimeException("Oh no!"); + + @Test + public void shouldCreateExceptionallyCompletedFuture() { + final CompletableFuture future = FutureUtils.completedExceptionally(ERROR); + assertCompletedExceptionally(future, ERROR); + } + + @Test + public void shouldPropagateSuccessfulResult() { + final CompletableFuture input = new CompletableFuture<>(); + final CompletableFuture output = new CompletableFuture<>(); + propagateResult(input, output); + assertThat(output).isNotDone(); + + input.complete("Yay"); + + assertThat(output).isCompletedWithValue("Yay"); + } + + @Test + public void shouldPropagateSuccessfulNullResult() { + final CompletableFuture input = new CompletableFuture<>(); + final CompletableFuture output = new CompletableFuture<>(); + propagateResult(input, output); + assertThat(output).isNotDone(); + + input.complete(null); + + assertThat(output).isCompletedWithValue(null); + } + + @Test + public void shouldPropagateExceptionalResult() { + final CompletableFuture input = new CompletableFuture<>(); + final CompletableFuture output = new CompletableFuture<>(); + propagateResult(input, output); + assertThat(output).isNotDone(); + + input.completeExceptionally(ERROR); + + assertCompletedExceptionally(output, ERROR); + } + + @Test + public void shouldComposeExceptionallyWhenErrorOccurs() { + final Function> errorHandler = mockFunction(); + final CompletableFuture input = new CompletableFuture<>(); + final CompletableFuture afterException = new CompletableFuture<>(); + when(errorHandler.apply(ERROR)).thenReturn(afterException); + + final CompletableFuture result = exceptionallyCompose(input, errorHandler); + + verifyZeroInteractions(errorHandler); + assertThat(result).isNotDone(); + + // Completing input should trigger our error handler but not complete the result yet. + input.completeExceptionally(ERROR); + verify(errorHandler).apply(ERROR); + assertThat(result).isNotDone(); + + afterException.complete("Done"); + assertThat(result).isCompletedWithValue("Done"); + } + + @Test + public void shouldComposeExceptionallyWhenErrorOccursAndComposedFutureFails() { + final RuntimeException secondError = new RuntimeException("Again?"); + final Function> errorHandler = mockFunction(); + final CompletableFuture input = new CompletableFuture<>(); + final CompletableFuture afterException = new CompletableFuture<>(); + when(errorHandler.apply(ERROR)).thenReturn(afterException); + + final CompletableFuture result = exceptionallyCompose(input, errorHandler); + + verifyZeroInteractions(errorHandler); + assertThat(result).isNotDone(); + + // Completing input should trigger our error handler but not complete the result yet. + input.completeExceptionally(ERROR); + verify(errorHandler).apply(ERROR); + assertThat(result).isNotDone(); + + afterException.completeExceptionally(secondError); + assertCompletedExceptionally(result, secondError); + } + + @Test + public void shouldComposeExceptionallyWhenErrorOccursAndErrorHandlerThrowsException() { + final Function> errorHandler = mockFunction(); + final CompletableFuture input = new CompletableFuture<>(); + final IllegalStateException thrownException = new IllegalStateException("Oops"); + when(errorHandler.apply(ERROR)).thenThrow(thrownException); + + final CompletableFuture result = exceptionallyCompose(input, errorHandler); + + verifyZeroInteractions(errorHandler); + assertThat(result).isNotDone(); + + // Completing input should trigger our error handler but not complete the result yet. + input.completeExceptionally(ERROR); + verify(errorHandler).apply(ERROR); + + assertCompletedExceptionally(result, thrownException); + } + + @Test + public void shouldNotCallErrorHandlerWhenFutureCompletesSuccessfully() { + final Function> errorHandler = mockFunction(); + final CompletableFuture input = new CompletableFuture<>(); + final CompletableFuture afterException = new CompletableFuture<>(); + when(errorHandler.apply(ERROR)).thenReturn(afterException); + + final CompletableFuture result = exceptionallyCompose(input, errorHandler); + + verifyZeroInteractions(errorHandler); + assertThat(result).isNotDone(); + + input.complete("Done"); + verifyZeroInteractions(errorHandler); + assertThat(result).isCompletedWithValue("Done"); + } + + private void assertCompletedExceptionally( + final CompletableFuture future, final RuntimeException expectedError) { + assertThat(future).isCompletedExceptionally(); + assertThatThrownBy(future::get) + .isInstanceOf(ExecutionException.class) + .extracting(Throwable::getCause) + .isSameAs(expectedError); + } + + @SuppressWarnings("unchecked") + private Function mockFunction() { + return mock(Function.class); + } +} From cdd7c9554293088c7ea57633bc51af61f1cfad55 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Sat, 16 Feb 2019 06:53:44 +1000 Subject: [PATCH 03/21] Select new pivot block when world state becomes unavailable (#869) --- .../ethereum/eth/sync/FastSynchronizer.java | 1 + .../eth/sync/fastsync/FastSyncDownloader.java | 30 ++++++--- .../WorldStateUnavailableException.java | 15 +++++ .../sync/fastsync/FastSyncDownloaderTest.java | 62 +++++++++++++++++++ 4 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateUnavailableException.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FastSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FastSynchronizer.java index 4ff17ca395..5c16ddc791 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FastSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FastSynchronizer.java @@ -131,6 +131,7 @@ public static Optional> create( } public CompletableFuture start() { + LOG.info("Fast sync enabled"); return fastSyncDownloader.start(initialSyncState); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java index a50e3679cf..e2d45cc90f 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -12,7 +12,12 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.fastsync; +import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; +import static tech.pegasys.pantheon.util.FutureUtils.exceptionallyCompose; + import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.WorldStateDownloader; +import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.WorldStateUnavailableException; +import tech.pegasys.pantheon.util.ExceptionUtils; import java.util.concurrent.CompletableFuture; @@ -35,13 +40,24 @@ public FastSyncDownloader( } public CompletableFuture start(final FastSyncState fastSyncState) { - LOG.info("Fast sync enabled"); - return fastSyncActions - .waitForSuitablePeers(fastSyncState) - .thenCompose(fastSyncActions::selectPivotBlock) - .thenCompose(fastSyncActions::downloadPivotBlockHeader) - .thenApply(this::storeState) - .thenCompose(this::downloadChainAndWorldState); + return exceptionallyCompose( + fastSyncActions + .waitForSuitablePeers(fastSyncState) + .thenCompose(fastSyncActions::selectPivotBlock) + .thenCompose(fastSyncActions::downloadPivotBlockHeader) + .thenApply(this::storeState) + .thenCompose(this::downloadChainAndWorldState), + this::handleWorldStateUnavailable); + } + + private CompletableFuture handleWorldStateUnavailable(final Throwable error) { + if (ExceptionUtils.rootCause(error) instanceof WorldStateUnavailableException) { + LOG.warn( + "Fast sync was unable to download the world state. Retrying with a new pivot block."); + return start(FastSyncState.EMPTY_SYNC_STATE); + } else { + return completedExceptionally(error); + } } private FastSyncState storeState(final FastSyncState state) { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateUnavailableException.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateUnavailableException.java new file mode 100644 index 0000000000..b98d94685c --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateUnavailableException.java @@ -0,0 +1,15 @@ +/* + * 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.ethereum.eth.sync.worldstate; + +public class WorldStateUnavailableException extends RuntimeException {} diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index e45d99934b..b674e5093f 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -15,6 +15,7 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -26,6 +27,7 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.WorldStateDownloader; +import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.WorldStateUnavailableException; import java.util.concurrent.CompletableFuture; @@ -247,6 +249,66 @@ public void shouldNotConsiderFastSyncCompleteIfOnlyChainDownloadIsComplete() { assertThat(result).isNotDone(); } + @SuppressWarnings("unchecked") + @Test + public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable() { + final CompletableFuture firstWorldStateFuture = new CompletableFuture<>(); + final CompletableFuture secondWorldStateFuture = new CompletableFuture<>(); + final CompletableFuture chainFuture = new CompletableFuture<>(); + final FastSyncState selectPivotBlockState = new FastSyncState(50); + final FastSyncState secondSelectPivotBlockState = new FastSyncState(90); + final BlockHeader pivotBlockHeader = new BlockHeaderTestFixture().number(50).buildHeader(); + final BlockHeader secondPivotBlockHeader = + new BlockHeaderTestFixture().number(90).buildHeader(); + final FastSyncState downloadPivotBlockHeaderState = new FastSyncState(pivotBlockHeader); + final FastSyncState secondDownloadPivotBlockHeaderState = + new FastSyncState(secondPivotBlockHeader); + // First attempt + when(fastSyncActions.waitForSuitablePeers(EMPTY_SYNC_STATE)).thenReturn(COMPLETE); + when(fastSyncActions.selectPivotBlock(EMPTY_SYNC_STATE)) + .thenReturn( + completedFuture(selectPivotBlockState), completedFuture(secondSelectPivotBlockState)); + when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) + .thenReturn(completedFuture(downloadPivotBlockHeaderState)); + when(fastSyncActions.downloadChain(downloadPivotBlockHeaderState)).thenReturn(chainFuture); + when(worldStateDownloader.run(pivotBlockHeader)).thenReturn(firstWorldStateFuture); + + // Second attempt with new pivot block + when(fastSyncActions.downloadPivotBlockHeader(secondSelectPivotBlockState)) + .thenReturn(completedFuture(secondDownloadPivotBlockHeaderState)); + when(fastSyncActions.downloadChain(secondDownloadPivotBlockHeaderState)) + .thenReturn(completedFuture(secondDownloadPivotBlockHeaderState)); + when(worldStateDownloader.run(secondPivotBlockHeader)).thenReturn(secondWorldStateFuture); + + final CompletableFuture result = downloader.start(EMPTY_SYNC_STATE); + + verify(fastSyncActions).waitForSuitablePeers(EMPTY_SYNC_STATE); + verify(fastSyncActions).selectPivotBlock(EMPTY_SYNC_STATE); + verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); + verify(storage).storeState(downloadPivotBlockHeaderState); + verify(fastSyncActions).downloadChain(downloadPivotBlockHeaderState); + verify(worldStateDownloader).run(pivotBlockHeader); + verifyNoMoreInteractions(fastSyncActions, worldStateDownloader, storage); + + assertThat(result).isNotDone(); + + firstWorldStateFuture.completeExceptionally(new WorldStateUnavailableException()); + assertThat(result).isNotDone(); + assertThat(chainFuture).isCancelled(); + + verify(fastSyncActions, times(2)).waitForSuitablePeers(EMPTY_SYNC_STATE); + verify(fastSyncActions, times(2)).selectPivotBlock(EMPTY_SYNC_STATE); + verify(fastSyncActions).downloadPivotBlockHeader(secondSelectPivotBlockState); + verify(storage).storeState(secondDownloadPivotBlockHeaderState); + verify(fastSyncActions).downloadChain(secondDownloadPivotBlockHeaderState); + verify(worldStateDownloader).run(secondPivotBlockHeader); + verifyNoMoreInteractions(fastSyncActions, worldStateDownloader, storage); + + secondWorldStateFuture.complete(null); + + assertThat(result).isCompletedWithValue(secondDownloadPivotBlockHeaderState); + } + private CompletableFuture completedExceptionally(final Throwable error) { final CompletableFuture result = new CompletableFuture<>(); result.completeExceptionally(error); From 37914866e3d5965c13f8617a9522412442ea88d5 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 15 Feb 2019 16:07:58 -0500 Subject: [PATCH 04/21] [PAN-2196] Implement world state cancel (#867) --- .../sync/worldstate/WorldStateDownloader.java | 119 ++++++++++++------ .../worldstate/WorldStateDownloaderTest.java | 94 ++++++++++++++ .../services/queue/BytesTaskQueueAdapter.java | 5 + .../services/queue/InMemoryTaskQueue.java | 33 +++-- .../services/queue/RocksDbTaskQueue.java | 33 ++++- .../pantheon/services/queue/TaskQueue.java | 3 + .../services/queue/AbstractTaskQueueTest.java | 59 +++++++++ 7 files changed, 293 insertions(+), 53 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java index 2a5dc1695e..f55442e130 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java @@ -14,9 +14,11 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetNodeDataFromPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage; @@ -32,13 +34,15 @@ import java.time.Duration; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -52,14 +56,16 @@ public class WorldStateDownloader { private enum Status { IDLE, RUNNING, - DONE + CANCELLED, + COMPLETED } private final EthContext ethContext; private final TaskQueue pendingRequests; private final int hashCountPerRequest; private final int maxOutstandingRequests; - private final AtomicInteger outstandingRequests = new AtomicInteger(0); + private final Set> outstandingRequests = + Collections.newSetFromMap(new ConcurrentHashMap<>()); private final LabelledMetric ethTasksTimer; private final WorldStateStorage worldStateStorage; private final AtomicBoolean sendingRequests = new AtomicBoolean(false); @@ -105,27 +111,31 @@ public CompletableFuture run(final BlockHeader header) { header.getNumber(), header.getHash()); synchronized (this) { - if (status == Status.DONE || status == Status.RUNNING) { - return future; + if (status == Status.RUNNING) { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally( + new IllegalStateException( + "Cannot run an already running " + this.getClass().getSimpleName())); + return failed; } status = Status.RUNNING; - future = new CompletableFuture<>(); - } + future = createFuture(); - Hash stateRoot = header.getStateRoot(); - if (worldStateStorage.isWorldStateAvailable(stateRoot)) { - // If we're requesting data for an existing world state, we're already done - markDone(); - } else { - pendingRequests.enqueue(NodeDataRequest.createAccountDataRequest(stateRoot)); - requestNodeData(header); + Hash stateRoot = header.getStateRoot(); + if (worldStateStorage.isWorldStateAvailable(stateRoot)) { + // If we're requesting data for an existing world state, we're already done + markDone(); + } else { + pendingRequests.enqueue(NodeDataRequest.createAccountDataRequest(stateRoot)); + } } + requestNodeData(header); return future; } public void cancel() { - // TODO + getFuture().cancel(true); } private void requestNodeData(final BlockHeader header) { @@ -160,12 +170,18 @@ private void requestNodeData(final BlockHeader header) { } // Request and process node data - outstandingRequests.incrementAndGet(); sendAndProcessRequests(peer, toRequest, header) .whenComplete( - (res, error) -> { - if (outstandingRequests.decrementAndGet() == 0 - && pendingRequests.allTasksCompleted()) { + (task, error) -> { + boolean done; + synchronized (this) { + outstandingRequests.remove(task); + done = + status == Status.RUNNING + && outstandingRequests.size() == 0 + && pendingRequests.allTasksCompleted(); + } + if (done) { // We're done final Updater updater = worldStateStorage.updater(); updater.putAccountStateTrieNode(header.getStateRoot(), rootNode); @@ -182,19 +198,9 @@ private void requestNodeData(final BlockHeader header) { } } - private synchronized void markDone() { - LOG.info("Finished downloading world state from peers"); - if (future == null) { - future = CompletableFuture.completedFuture(null); - } else { - future.complete(null); - } - status = Status.DONE; - } - private boolean shouldRequestNodeData() { return !future.isDone() - && outstandingRequests.get() < maxOutstandingRequests + && outstandingRequests.size() < maxOutstandingRequests && !pendingRequests.isEmpty(); } @@ -204,7 +210,7 @@ private CompletableFuture waitForNewPeer() { .timeout(WaitForPeerTask.create(ethContext, ethTasksTimer), Duration.ofSeconds(5)); } - private CompletableFuture sendAndProcessRequests( + private CompletableFuture>> sendAndProcessRequests( final EthPeer peer, final List> requestTasks, final BlockHeader blockHeader) { @@ -214,12 +220,14 @@ private CompletableFuture sendAndProcessRequests( .map(NodeDataRequest::getHash) .distinct() .collect(Collectors.toList()); - return GetNodeDataFromPeerTask.forHashes(ethContext, hashes, ethTasksTimer) - .assignPeer(peer) + AbstractPeerTask> ethTask = + GetNodeDataFromPeerTask.forHashes(ethContext, hashes, ethTasksTimer).assignPeer(peer); + outstandingRequests.add(ethTask); + return ethTask .run() .thenApply(PeerTaskResult::getResult) .thenApply(this::mapNodeDataByHash) - .whenComplete( + .handle( (data, err) -> { boolean requestFailed = err != null; Updater storageUpdater = worldStateStorage.updater(); @@ -244,11 +252,50 @@ private CompletableFuture sendAndProcessRequests( } } storageUpdater.commit(); + return ethTask; }); } - private void queueChildRequests(final NodeDataRequest request) { - request.getChildRequests().forEach(pendingRequests::enqueue); + private synchronized void queueChildRequests(final NodeDataRequest request) { + if (status == Status.RUNNING) { + request.getChildRequests().forEach(pendingRequests::enqueue); + } + } + + private synchronized CompletableFuture getFuture() { + if (future == null) { + future = createFuture(); + } + return future; + } + + private CompletableFuture createFuture() { + CompletableFuture future = new CompletableFuture<>(); + future.whenComplete( + (res, err) -> { + // Handle cancellations + if (future.isCancelled()) { + handleCancellation(); + } + }); + return future; + } + + private synchronized void handleCancellation() { + LOG.info("World state download cancelled"); + status = Status.CANCELLED; + pendingRequests.clear(); + for (EthTask outstandingRequest : outstandingRequests) { + outstandingRequest.cancel(); + } + } + + private synchronized void markDone() { + final boolean completed = getFuture().complete(null); + if (completed) { + LOG.info("Finished downloading world state from peers"); + status = Status.COMPLETED; + } } private boolean isRootState(final BlockHeader blockHeader, final NodeDataRequest request) { diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java index 12e4d5b7a0..9a3a4be6da 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java @@ -13,7 +13,13 @@ package tech.pegasys.pantheon.ethereum.eth.sync.worldstate; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.Account; @@ -324,6 +330,90 @@ public void doesNotRequestKnownCodeFromNetwork() { assertAccountsMatch(localWorldState, accounts); } + @Test + public void cancelDownloader() { + testCancellation(false); + } + + @Test + public void cancelDownloaderFuture() { + testCancellation(true); + } + + @SuppressWarnings("unchecked") + private void testCancellation(final boolean shouldCancelFuture) { + BlockDataGenerator dataGen = new BlockDataGenerator(1); + final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(); + + // Setup "remote" state + final WorldStateStorage remoteStorage = + new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); + final WorldStateArchive remoteWorldStateArchive = new WorldStateArchive(remoteStorage); + final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); + + // Generate accounts and save corresponding state root + dataGen.createRandomContractAccountsWithNonEmptyStorage(remoteWorldState, 20); + final Hash stateRoot = remoteWorldState.rootHash(); + final BlockHeader header = + dataGen.block(BlockOptions.create().setStateRoot(stateRoot).setBlockNumber(10)).getHeader(); + + // Create some peers + List peers = + Stream.generate( + () -> EthProtocolManagerTestUtil.createPeer(ethProtocolManager, header.getNumber())) + .limit(5) + .collect(Collectors.toList()); + + TaskQueue queue = spy(new InMemoryTaskQueue<>()); + WorldStateStorage localStorage = + new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); + + WorldStateDownloader downloader = + new WorldStateDownloader( + ethProtocolManager.ethContext(), + localStorage, + queue, + 10, + 10, + NoOpMetricsSystem.NO_OP_LABELLED_TIMER, + new NoOpMetricsSystem()); + + CompletableFuture result = downloader.run(header); + + // Send a few responses + Responder responder = + RespondingEthPeer.blockchainResponder(mock(Blockchain.class), remoteWorldStateArchive); + + for (int i = 0; i < 3; i++) { + for (RespondingEthPeer peer : peers) { + peer.respond(responder); + } + } + assertThat(result.isDone()).isFalse(); // Sanity check + + // Reset queue so we can track interactions after the cancellation + reset(queue); + if (shouldCancelFuture) { + result.cancel(true); + } else { + downloader.cancel(); + assertThat(result).isCancelled(); + } + + // Send some more responses after cancelling + for (int i = 0; i < 3; i++) { + for (RespondingEthPeer peer : peers) { + peer.respond(responder); + } + } + + verify(queue, times(1)).clear(); + verify(queue, never()).dequeue(); + verify(queue, never()).enqueue(any()); + // Target world state should not be available + assertThat(localStorage.isWorldStateAvailable(header.getStateRoot())).isFalse(); + } + @Test public void doesRequestKnownAccountTrieNodesFromNetwork() { BlockDataGenerator dataGen = new BlockDataGenerator(1); @@ -637,6 +727,10 @@ private void downloadAvailableWorldStateFromPeers( // Start downloader CompletableFuture result = downloader.run(header); + // A second run should return an error without impacting the first result + CompletableFuture secondResult = downloader.run(header); + assertThat(secondResult).isCompletedExceptionally(); + assertThat(result).isNotCompletedExceptionally(); // Respond to node data requests // Send one round of full responses, so that we can get multiple requests queued up diff --git a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/BytesTaskQueueAdapter.java b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/BytesTaskQueueAdapter.java index 55cf408a6a..8eeafc4ce4 100644 --- a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/BytesTaskQueueAdapter.java +++ b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/BytesTaskQueueAdapter.java @@ -58,6 +58,11 @@ public boolean isEmpty() { return queue.isEmpty(); } + @Override + public void clear() { + queue.clear(); + } + @Override public boolean allTasksCompleted() { return queue.allTasksCompleted(); diff --git a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/InMemoryTaskQueue.java b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/InMemoryTaskQueue.java index 9236c1c2f6..351980b4cf 100644 --- a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/InMemoryTaskQueue.java +++ b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/InMemoryTaskQueue.java @@ -13,13 +13,14 @@ package tech.pegasys.pantheon.services.queue; import java.util.ArrayDeque; +import java.util.HashSet; import java.util.Queue; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; public class InMemoryTaskQueue implements TaskQueue { private final Queue internalQueue = new ArrayDeque<>(); - private final AtomicInteger unfinishedOutstandingTasks = new AtomicInteger(0); + private final Set> unfinishedOutstandingTasks = new HashSet<>(); private final AtomicBoolean closed = new AtomicBoolean(false); @Override @@ -35,8 +36,9 @@ public synchronized Task dequeue() { if (data == null) { return null; } - unfinishedOutstandingTasks.incrementAndGet(); - return new InMemoryTask<>(this, data); + InMemoryTask task = new InMemoryTask<>(this, data); + unfinishedOutstandingTasks.add(task); + return task; } @Override @@ -52,9 +54,17 @@ public synchronized boolean isEmpty() { } @Override - public boolean allTasksCompleted() { + public synchronized void clear() { assertNotClosed(); - return isEmpty() && unfinishedOutstandingTasks.get() == 0; + + unfinishedOutstandingTasks.clear(); + internalQueue.clear(); + } + + @Override + public synchronized boolean allTasksCompleted() { + assertNotClosed(); + return isEmpty() && unfinishedOutstandingTasks.size() == 0; } @Override @@ -70,12 +80,13 @@ private void assertNotClosed() { } private synchronized void handleFailedTask(final InMemoryTask task) { - enqueue(task.getData()); - markTaskCompleted(); + if (markTaskCompleted(task)) { + enqueue(task.getData()); + } } - private synchronized void markTaskCompleted() { - unfinishedOutstandingTasks.decrementAndGet(); + private synchronized boolean markTaskCompleted(final InMemoryTask task) { + return unfinishedOutstandingTasks.remove(task); } private static class InMemoryTask implements Task { @@ -96,7 +107,7 @@ public T getData() { @Override public void markCompleted() { if (completed.compareAndSet(false, true)) { - queue.markTaskCompleted(); + queue.markTaskCompleted(this); } } diff --git a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/RocksDbTaskQueue.java b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/RocksDbTaskQueue.java index 770a31e70e..8ed91b8b5a 100644 --- a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/RocksDbTaskQueue.java +++ b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/RocksDbTaskQueue.java @@ -112,9 +112,26 @@ public synchronized long size() { @Override public synchronized boolean isEmpty() { + assertNotClosed(); return size() == 0; } + @Override + public synchronized void clear() { + assertNotClosed(); + outstandingTasks.clear(); + byte[] from = Longs.toByteArray(oldestKey.get()); + byte[] to = Longs.toByteArray(lastEnqueuedKey.get() + 1); + try { + db.deleteRange(from, to); + lastDequeuedKey.set(0); + lastEnqueuedKey.set(0); + oldestKey.set(0); + } catch (RocksDBException e) { + throw new StorageException(e); + } + } + @Override public synchronized boolean allTasksCompleted() { return isEmpty() && outstandingTasks.isEmpty(); @@ -128,7 +145,7 @@ private synchronized void deleteCompletedTasks() { .orElse(lastDequeuedKey.get() + 1); if (oldestKey.get() < oldestOutstandingKey) { - // Delete all contiguous completed task keys + // Delete all contiguous completed tasks byte[] fromKey = Longs.toByteArray(oldestKey.get()); byte[] toKey = Longs.toByteArray(oldestOutstandingKey); try { @@ -154,14 +171,18 @@ private void assertNotClosed() { } } - private synchronized void markTaskCompleted(final RocksDbTask task) { - outstandingTasks.remove(task); - deleteCompletedTasks(); + private synchronized boolean markTaskCompleted(final RocksDbTask task) { + if (outstandingTasks.remove(task)) { + deleteCompletedTasks(); + return true; + } + return false; } private synchronized void handleFailedTask(final RocksDbTask task) { - enqueue(task.getData()); - markTaskCompleted(task); + if (markTaskCompleted(task)) { + enqueue(task.getData()); + } } public static class StorageException extends RuntimeException { diff --git a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/TaskQueue.java b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/TaskQueue.java index c19f204185..20a5482a7b 100644 --- a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/TaskQueue.java +++ b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/TaskQueue.java @@ -42,6 +42,9 @@ public interface TaskQueue extends Closeable { /** @return True if all tasks have been dequeued. */ boolean isEmpty(); + /** Clear all data from the queue. */ + void clear(); + /** @return True if all tasks have been dequeued and processed. */ boolean allTasksCompleted(); diff --git a/services/queue/src/test/java/tech/pegasys/pantheon/services/queue/AbstractTaskQueueTest.java b/services/queue/src/test/java/tech/pegasys/pantheon/services/queue/AbstractTaskQueueTest.java index f2aca7e50e..af4852433f 100644 --- a/services/queue/src/test/java/tech/pegasys/pantheon/services/queue/AbstractTaskQueueTest.java +++ b/services/queue/src/test/java/tech/pegasys/pantheon/services/queue/AbstractTaskQueueTest.java @@ -113,6 +113,65 @@ public void markTaskCompleted() throws Exception { } } + @Test + public void clear() throws Exception { + try (T queue = createQueue()) { + BytesValue one = BytesValue.of(1); + BytesValue two = BytesValue.of(2); + BytesValue three = BytesValue.of(3); + BytesValue four = BytesValue.of(4); + + // Fill queue + queue.enqueue(one); + queue.enqueue(two); + assertThat(queue.size()).isEqualTo(2); + assertThat(queue.isEmpty()).isFalse(); + assertThat(queue.allTasksCompleted()).isFalse(); + + // Clear queue and check state + queue.clear(); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.allTasksCompleted()).isTrue(); + assertThat(queue.dequeue()).isNull(); + + // Subsequent operations should work as expected + queue.enqueue(three); + assertThat(queue.size()).isEqualTo(1); + queue.enqueue(four); + assertThat(queue.size()).isEqualTo(2); + assertThat(queue.dequeue().getData()).isEqualTo(three); + } + } + + @Test + public void clear_emptyQueueWithOutstandingTasks() throws Exception { + try (T queue = createQueue()) { + BytesValue one = BytesValue.of(1); + + // Add and then dequeue task + queue.enqueue(one); + Task task = queue.dequeue(); + assertThat(task.getData()).isEqualTo(one); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.allTasksCompleted()).isFalse(); + + // Clear queue and check state + queue.clear(); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.allTasksCompleted()).isTrue(); + assertThat(queue.dequeue()).isNull(); + + // Marking old task as failed should not requeue task + task.markFailed(); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.allTasksCompleted()).isTrue(); + assertThat(queue.dequeue()).isNull(); + } + } + @Test public void handlesConcurrentQueuing() throws Exception { final int threadCount = 5; From 8fb9345f7ef6a39f3d0aeca56af43ec4b7400311 Mon Sep 17 00:00:00 2001 From: MadelineMurray <43356962+MadelineMurray@users.noreply.github.com> Date: Mon, 18 Feb 2019 11:52:09 +1000 Subject: [PATCH 05/21] Added perm_reloadPermissionsFromFile --- docs/Permissions/Permissioning.md | 22 +++++++++++++------ docs/Reference/JSON-RPC-API-Methods.md | 29 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/docs/Permissions/Permissioning.md b/docs/Permissions/Permissioning.md index e223de10e7..ddb2cef5de 100644 --- a/docs/Permissions/Permissioning.md +++ b/docs/Permissions/Permissioning.md @@ -18,11 +18,16 @@ A node with node whitelisting enabled communicates only with nodes in the nodes Node whitelisting is at the node level. That is, each node in the network has a [`permissions_config.toml`](#permissions-configuration-file) file in the [data directory](../Reference/Pantheon-CLI-Syntax.md#data-path) for the node. -To view or update the nodes whitelist when the node is running, use the JSON-RPC API methods: +To update the nodes whitelist when the node is running, use the JSON-RPC API methods: * [perm_addNodesToWhitelist](../Reference/JSON-RPC-API-Methods.md#perm__addnodestowhitelist) * [perm_removeNodesFromWhitelist](../Reference/JSON-RPC-API-Methods.md#perm_removeNodesFromWhiteList) -* [perm_getNodesWhitelist](../Reference/JSON-RPC-API-Methods.md#perm_getNodesWhiteList) + +Alternatively, update the [`permissions_config.toml`](#permissions-configuration-file) file directly and use the +[`perm_reloadPermissionsFromFile`](../Reference/JSON-RPC-API-Methods.md#perm_reloadpermissionsfromfile) method +to update the whitelists. + +To view the nodes whitelist, use the [perm_getNodesWhitelist](../Reference/JSON-RPC-API-Methods.md#perm_getNodesWhiteList) method. !!! note Each node has a `permissions_config.toml` file which means nodes can have different nodes whitelists. @@ -103,11 +108,16 @@ can synchronise and add blocks containing transactions from accounts that are no On-chain permissioning is under development. On-chain permissioning will use one on-chain nodes whitelist. -To view or update the accounts whitelist when the node is running, use the JSON-RPC API methods: +To update the accounts whitelist when the node is running, use the JSON-RPC API methods: + +* [`perm_addAccountsToWhitelist`](../Reference/JSON-RPC-API-Methods.md#perm_addAccountsToWhitelist) +* [`perm_removeAccountsFromWhitelist`](../Reference/JSON-RPC-API-Methods.md#perm_removeAccountsFromWhitelist) + +Alternatively, update the [`permissions_config.toml`](#permissions-configuration-file) file directly and use the +[`perm_reloadPermissionsFromFile`](../Reference/JSON-RPC-API-Methods.md#perm_reloadpermissionsfromfile) method +to update the whitelists. -* [perm_addAccountsToWhitelist](../Reference/JSON-RPC-API-Methods.md#perm_addAccountsToWhitelist) -* [perm_removeAccountsFromWhitelist](../Reference/JSON-RPC-API-Methods.md#perm_removeAccountsFromWhitelist) -* [perm_getAccountsWhitelist](../Reference/JSON-RPC-API-Methods.md#perm_getAccountsWhitelist) +To view the accounts whitelist, use the [`perm_getAccountsWhitelist`](../Reference/JSON-RPC-API-Methods.md#perm_getAccountsWhitelist) method. ### Enabling Account Whitelisting diff --git a/docs/Reference/JSON-RPC-API-Methods.md b/docs/Reference/JSON-RPC-API-Methods.md index b21844d761..f5b414f00f 100644 --- a/docs/Reference/JSON-RPC-API-Methods.md +++ b/docs/Reference/JSON-RPC-API-Methods.md @@ -2386,6 +2386,35 @@ including invalid enode URLs. {"jsonrpc":"2.0","method":"perm_removeNodesFromWhitelist","params":[["enode://7e4ef30e9ec683f26ad76ffca5b5148fa7a6575f4cfad4eb0f52f9c3d8335f4a9b6f9e66fcc73ef95ed7a2a52784d4f372e7750ac8ae0b544309a5b391a23dd7@127.0.0.1:30303","enode://2feb33b3c6c4a8f77d84a5ce44954e83e5f163e7a65f7f7a7fec499ceb0ddd76a46ef635408c513d64c076470eac86b7f2c8ae4fcd112cb28ce82c0d64ec2c94@127.0.0.1:30304"]], "id":1} ``` + ```json tab="JSON result" + { + "jsonrpc": "2.0", + "id": 1, + "result": "Success" + } + ``` + +### perm_reloadPermissionsFromFile + +Reloads the accounts and nodes whitelists from the [permissions configuration file](../Permissions/Permissioning.md#permissions-configuration-file). + +**Parameters** + +None + +**Returns** + +`result` - `Success` or `error` if the permissions configuration file is not valid. + +!!! example + ```bash tab="curl HTTP request" + $ curl -X POST --data '{"jsonrpc":"2.0","method":"perm_reloadPermissionsFromFile","params":[], "id":1}' + ``` + + ```bash tab="wscat WS request" + {"jsonrpc":"2.0","method":"perm_reloadPermissionsFromFile","params":[], "id":1} + ``` + ```json tab="JSON result" { "jsonrpc": "2.0", From 5c23d619584aedacb6b0aebdf34501b414b584ba Mon Sep 17 00:00:00 2001 From: Lucas Saldanha Date: Mon, 18 Feb 2019 16:10:35 +1300 Subject: [PATCH 06/21] [PAN-2270] Rename password hash command (#879) * PAN-2270: Rename password hash command * Fixing test * PR review --- docs/Reference/Pantheon-CLI-Syntax.md | 10 ++-- .../pantheon/cli/PasswordSubCommand.java | 48 ++++++++++++++----- .../pantheon/cli/PasswordSubCommandTest.java | 44 +++++++++-------- 3 files changed, 67 insertions(+), 35 deletions(-) diff --git a/docs/Reference/Pantheon-CLI-Syntax.md b/docs/Reference/Pantheon-CLI-Syntax.md index 7899aa55b2..b26e198b6b 100644 --- a/docs/Reference/Pantheon-CLI-Syntax.md +++ b/docs/Reference/Pantheon-CLI-Syntax.md @@ -926,14 +926,18 @@ $ pantheon public-key export --to=/home/me/me_project/not_precious_pub_key Exports node public key to the specified file. -### password-hash +### password + +This command provides password related actions. + +#### hash This command generates the hash of a given password. ```bash tab="Syntax" -$ pantheon password-hash +$ pantheon password hash --password= ``` ```bash tab="Example" -$ pantheon password-hash "password123" +$ pantheon password hash --password=myPassword123 ``` \ No newline at end of file diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java index 7bc95ae877..5bc8e93566 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PasswordSubCommand.java @@ -12,45 +12,71 @@ */ package tech.pegasys.pantheon.cli; +import static com.google.common.base.Preconditions.checkNotNull; import static tech.pegasys.pantheon.cli.PasswordSubCommand.COMMAND_NAME; +import tech.pegasys.pantheon.cli.PasswordSubCommand.HashSubCommand; + import java.io.PrintStream; import org.springframework.security.crypto.bcrypt.BCrypt; import picocli.CommandLine.Command; import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Parameters; +import picocli.CommandLine.Option; import picocli.CommandLine.ParentCommand; import picocli.CommandLine.Spec; @Command( name = COMMAND_NAME, - description = "This command generates the hash of a given password.", - mixinStandardHelpOptions = true) + description = "This command provides password related actions.", + mixinStandardHelpOptions = true, + subcommands = {HashSubCommand.class}) class PasswordSubCommand implements Runnable { - static final String COMMAND_NAME = "password-hash"; + static final String COMMAND_NAME = "password"; @SuppressWarnings("unused") @ParentCommand - private PantheonCommand parentCommand; // Picocli injects reference to parent command + private PantheonCommand parentCommand; @SuppressWarnings("unused") @Spec - private CommandSpec spec; // Picocli injects reference to command spec + private CommandSpec spec; final PrintStream out; - @SuppressWarnings("FieldMustBeFinal") - @Parameters(arity = "1..1", description = "The password input") - private String password = null; - PasswordSubCommand(final PrintStream out) { this.out = out; } @Override public void run() { - out.print(BCrypt.hashpw(password, BCrypt.gensalt())); + spec.commandLine().usage(out); + } + + @Command( + name = "hash", + description = "This command generates the hash of a given password.", + mixinStandardHelpOptions = true) + static class HashSubCommand implements Runnable { + + @SuppressWarnings("FieldMustBeFinal") + @Option( + names = "--password", + arity = "1..1", + required = true, + description = "The password input") + private String password = null; + + @SuppressWarnings("unused") + @ParentCommand + private PasswordSubCommand parentCommand; + + @Override + public void run() { + checkNotNull(parentCommand); + + parentCommand.out.print(BCrypt.hashpw(password, BCrypt.gensalt())); + } } } diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PasswordSubCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PasswordSubCommandTest.java index 1bcaa3d916..e389e3527f 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PasswordSubCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PasswordSubCommandTest.java @@ -20,37 +20,39 @@ public class PasswordSubCommandTest extends CommandTestAbstract { @Test - public void passwordHashSubCommandExists() { + public void passwordSubCommandExistAnbHaveSubCommands() { CommandSpec spec = parseCommand(); - assertThat(spec.subcommands()).containsKeys("password-hash"); + assertThat(spec.subcommands()).containsKeys("password"); + assertThat(spec.subcommands().get("password").getSubcommands()).containsKeys("hash"); assertThat(commandOutput.toString()).isEmpty(); assertThat(commandErrorOutput.toString()).isEmpty(); } @Test - public void callingPasswordHashWithoutPasswordParameterMustDisplayUsage() { - final String expectedUsage = - "Missing required parameter: " - + System.lineSeparator() - + "Usage: pantheon password-hash [-hV] " - + System.lineSeparator() - + "This command generates the hash of a given password." - + System.lineSeparator() - + " The password input" - + System.lineSeparator() - + " -h, --help Show this help message and exit." - + System.lineSeparator() - + " -V, --version Print version information and exit." - + System.lineSeparator(); - - parseCommand("password-hash"); + public void passwordSubCommandExists() { + CommandSpec spec = parseCommand(); + parseCommand("password"); + + assertThat(commandOutput.toString()).contains("This command provides password related actions"); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void passwordHashSubCommandExist() { + parseCommand("password", "hash"); + assertThat(commandOutput.toString()).isEmpty(); - assertThat(commandErrorOutput.toString()).startsWith(expectedUsage); + assertThat(commandErrorOutput.toString()) + .contains("Missing required option '--password='"); + assertThat(commandErrorOutput.toString()) + .contains("Usage: pantheon password hash [-hV] --password="); + assertThat(commandErrorOutput.toString()) + .contains("This command generates the hash of a given password"); } @Test - public void publicKeySubCommandExistAnbHaveSubCommands() { - parseCommand("password-hash", "foo"); + public void passwordHashSubCommandHashesPassword() { + parseCommand("password", "hash", "--password", "foo"); // we can't predict the final value so we are only checking if it starts with the hash marker assertThat(commandOutput.toString()).startsWith("$2"); From 157bead97b0f533d77a9193ec55e5ae3995a18a4 Mon Sep 17 00:00:00 2001 From: Lucas Saldanha Date: Mon, 18 Feb 2019 16:34:50 +1300 Subject: [PATCH 07/21] [PAN-2239] Disconnect peer removed from node whitelist (#877) --- .../NodesWhitelistAcceptanceTest.java | 13 ++ .../p2p/discovery/PeerDiscoveryAgent.java | 30 ++++- .../p2p/discovery/internal/Bucket.java | 3 +- .../internal/DiscoveryProtocolLogger.java | 55 +++++++++ .../internal/PeerDiscoveryController.java | 61 +++++++--- .../p2p/discovery/internal/PeerTable.java | 68 +++++++++-- .../ethereum/p2p/netty/NettyP2PNetwork.java | 43 +++++-- .../PeerDiscoveryTimestampsTest.java | 1 + .../internal/PeerDiscoveryControllerTest.java | 101 +++++++++++++++- .../PeerDiscoveryTableRefreshTest.java | 1 + .../p2p/discovery/internal/PeerTableTest.java | 52 +++++++- .../NodeWhitelistController.java | 40 +++++++ .../node/NodeWhitelistUpdatedEvent.java | 58 +++++++++ .../NodeWhitelistControllerTest.java | 113 ++++++++++++++++++ 14 files changed, 589 insertions(+), 50 deletions(-) create mode 100644 ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/DiscoveryProtocolLogger.java create mode 100644 ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/node/NodeWhitelistUpdatedEvent.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/permissioning/NodesWhitelistAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/permissioning/NodesWhitelistAcceptanceTest.java index de347cdce2..f30e831abf 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/permissioning/NodesWhitelistAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/permissioning/NodesWhitelistAcceptanceTest.java @@ -22,6 +22,7 @@ import java.net.URI; import java.util.Collections; +import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Test; @@ -53,6 +54,18 @@ public void permissionedNodeShouldDiscoverOnlyAllowedNode() { permissionedNode.verify(net.awaitPeerCount(1)); } + @Test + public void permissionedNodeShouldDisconnectFromNodeRemovedFromWhitelist() { + permissionedNode.verify(net.awaitPeerCount(1)); + + // remove allowed node from the whitelist + permissionedNode.verify( + perm.removeNodesFromWhitelist(Lists.newArrayList(((PantheonNode) allowedNode).enodeUrl()))); + + // node should not be connected to any peers + permissionedNode.verify(net.awaitPeerCount(0)); + } + private URI getEnodeURI(final Node node) { return URI.create(((PantheonNode) node).getConfiguration().enodeUrl()); } diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java index bd18cc0b56..6731f9d971 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgent.java @@ -23,6 +23,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerBondedEvent; +import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerDroppedEvent; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerDiscoveryController; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerRequirement; @@ -86,6 +87,7 @@ public abstract class PeerDiscoveryAgent implements DisconnectCallback { /* Is discovery enabled? */ private boolean isActive = false; private final Subscribers> peerBondedObservers = new Subscribers<>(); + private final Subscribers> peerDroppedObservers = new Subscribers<>(); public PeerDiscoveryAgent( final SECP256K1.KeyPair keyPair, @@ -164,7 +166,8 @@ private PeerDiscoveryController createController() { peerRequirement, peerBlacklist, nodeWhitelistController, - peerBondedObservers); + peerBondedObservers, + peerDroppedObservers); } protected boolean validatePacketSize(final int packetSize) { @@ -261,6 +264,29 @@ public boolean removePeerBondedObserver(final long observerId) { return peerBondedObservers.unsubscribe(observerId); } + /** + * Adds an observer that will get called when a peer is dropped from the peer table. + * + *

No guarantees are made about the order in which observers are invoked. + * + * @param observer The observer to call. + * @return A unique ID identifying this observer, to that it can be removed later. + */ + public long observePeerDroppedEvents(final Consumer observer) { + checkNotNull(observer); + return peerDroppedObservers.subscribe(observer); + } + + /** + * Removes an previously added peer dropped observer. + * + * @param observerId The unique ID identifying the observer to remove. + * @return Whether the observer was located and removed. + */ + public boolean removePeerDroppedObserver(final long observerId) { + return peerDroppedObservers.unsubscribe(observerId); + } + /** * Returns the count of observers that are registered on this controller. * @@ -292,7 +318,7 @@ public void onDisconnect( final DisconnectMessage.DisconnectReason reason, final boolean initiatedByPeer) { final BytesValue nodeId = connection.getPeer().getNodeId(); - peerTable.evict(new DefaultPeerId(nodeId)); + peerTable.tryEvict(new DefaultPeerId(nodeId)); } /** diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/Bucket.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/Bucket.java index 7870f42feb..ca67ddd351 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/Bucket.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/Bucket.java @@ -118,7 +118,8 @@ synchronized boolean evict(final PeerId peer) { } // If found, shift all subsequent elements to the left, and decrement tailIndex. for (int i = 0; i <= tailIndex; i++) { - if (peer.equals(kBucket[i])) { + // Peer comparison here must be done by peer id + if (peer.getId().equals(kBucket[i].getId())) { arraycopy(kBucket, i + 1, kBucket, i, tailIndex - i); kBucket[tailIndex--] = null; return true; diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/DiscoveryProtocolLogger.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/DiscoveryProtocolLogger.java new file mode 100644 index 0000000000..0a57c6bda9 --- /dev/null +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/DiscoveryProtocolLogger.java @@ -0,0 +1,55 @@ +/* + * 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.ethereum.p2p.discovery.internal; + +import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class DiscoveryProtocolLogger { + + private static final Logger LOG = LogManager.getLogger(); + + static void logSendingPacket(final Peer peer, final Packet packet) { + LOG.trace( + "<<< Sending {} packet from peer {} ({}): {}", + shortenPacketType(packet), + peer.getId().slice(0, 16), + peer.getEndpoint(), + packet); + } + + static void logReceivedPacket(final Peer peer, final Packet packet) { + LOG.trace( + ">>> Received {} packet from peer {} ({}): {}", + shortenPacketType(packet), + peer.getId().slice(0, 16), + peer.getEndpoint(), + packet); + } + + private static String shortenPacketType(final Packet packet) { + switch (packet.getType()) { + case PING: + return "PING "; + case PONG: + return "PONG "; + case FIND_NEIGHBORS: + return "FINDN"; + case NEIGHBORS: + return "NEIGH"; + } + return null; + } +} diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryController.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryController.java index 768fead1c3..7b53f389fc 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryController.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryController.java @@ -14,17 +14,23 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.AddResult.Outcome; +import static tech.pegasys.pantheon.ethereum.p2p.discovery.internal.DiscoveryProtocolLogger.logReceivedPacket; +import static tech.pegasys.pantheon.ethereum.p2p.discovery.internal.DiscoveryProtocolLogger.logSendingPacket; +import static tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.AddResult.AddOutcome; import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerBondedEvent; +import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerDroppedEvent; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryStatus; +import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.EvictResult; +import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.EvictResult.EvictOutcome; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.permissioning.NodeWhitelistController; +import tech.pegasys.pantheon.ethereum.permissioning.node.NodeWhitelistUpdatedEvent; import tech.pegasys.pantheon.util.Subscribers; import tech.pegasys.pantheon.util.bytes.BytesValue; @@ -123,6 +129,7 @@ public class PeerDiscoveryController { // Observers for "peer bonded" discovery events. private final Subscribers> peerBondedObservers; + private final Subscribers> peerDroppedObservers; private RecursivePeerRefreshState recursivePeerRefreshState; @@ -137,7 +144,8 @@ public PeerDiscoveryController( final PeerRequirement peerRequirement, final PeerBlacklist peerBlacklist, final Optional nodeWhitelistController, - final Subscribers> peerBondedObservers) { + final Subscribers> peerBondedObservers, + final Subscribers> peerDroppedObservers) { this.timerUtil = timerUtil; this.keypair = keypair; this.localPeer = localPeer; @@ -149,6 +157,7 @@ public PeerDiscoveryController( this.nodeWhitelistController = nodeWhitelistController; this.outboundMessageHandler = outboundMessageHandler; this.peerBondedObservers = peerBondedObservers; + this.peerDroppedObservers = peerDroppedObservers; } public CompletableFuture start() { @@ -184,6 +193,9 @@ public CompletableFuture start() { this::refreshTableIfRequired); tableRefreshTimerId = OptionalLong.of(timerId); + nodeWhitelistController.ifPresent( + c -> c.subscribeToListUpdatedEvent(this::handleNodeWhitelistUpdatedEvent)); + return CompletableFuture.completedFuture(null); } @@ -217,12 +229,7 @@ private boolean whitelistIfPresentIsNodePermitted(final DiscoveryPeer sender) { * @param sender The sender. */ public void onMessage(final Packet packet, final DiscoveryPeer sender) { - LOG.trace( - "<<< Received {} discovery packet from {} ({}): {}", - packet.getType(), - sender.getEndpoint(), - sender.getId().slice(0, 16), - packet); + logReceivedPacket(sender, packet); // Message from self. This should not happen. if (sender.getId().equals(localPeer.getId())) { @@ -242,14 +249,12 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) { switch (packet.getType()) { case PING: - LOG.trace("Received PING packet from {}", sender.getEnodeURI()); if (!peerBlacklisted && addToPeerTable(peer)) { final PingPacketData ping = packet.getPacketData(PingPacketData.class).get(); respondToPing(ping, packet.getHash(), peer); } break; case PONG: - LOG.trace("Received PONG packet from {}", sender.getEnodeURI()); matchInteraction(packet) .ifPresent( interaction -> { @@ -261,7 +266,6 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) { }); break; case NEIGHBORS: - LOG.trace("Received NEIGHBORS packet from {}", sender.getEnodeURI()); matchInteraction(packet) .ifPresent( interaction -> @@ -269,7 +273,6 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) { peer, packet.getPacketData(NeighborsPacketData.class).orElse(null))); break; case FIND_NEIGHBORS: - LOG.trace("Received FIND_NEIGHBORS packet from {}", sender.getEnodeURI()); if (!peerKnown || peerBlacklisted) { break; } @@ -282,7 +285,7 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) { private boolean addToPeerTable(final DiscoveryPeer peer) { final PeerTable.AddResult result = peerTable.tryAdd(peer); - if (result.getOutcome() == Outcome.SELF) { + if (result.getOutcome() == AddOutcome.SELF) { return false; } @@ -298,23 +301,45 @@ private boolean addToPeerTable(final DiscoveryPeer peer) { notifyPeerBonded(peer, now); } - if (result.getOutcome() == Outcome.ALREADY_EXISTED) { + if (result.getOutcome() == AddOutcome.ALREADY_EXISTED) { // Bump peer. - peerTable.evict(peer); + peerTable.tryEvict(peer); peerTable.tryAdd(peer); - } else if (result.getOutcome() == Outcome.BUCKET_FULL) { - peerTable.evict(result.getEvictionCandidate()); + } else if (result.getOutcome() == AddOutcome.BUCKET_FULL) { + peerTable.tryEvict(result.getEvictionCandidate()); peerTable.tryAdd(peer); } return true; } + private void handleNodeWhitelistUpdatedEvent(final NodeWhitelistUpdatedEvent event) { + event.getRemovedNodes().stream() + .map(e -> new DiscoveryPeer(DiscoveryPeer.fromURI(e.toURI()))) + .forEach(this::dropFromPeerTable); + } + + @VisibleForTesting + boolean dropFromPeerTable(final DiscoveryPeer peer) { + final EvictResult evictResult = peerTable.tryEvict(peer); + if (evictResult.getOutcome() == EvictOutcome.EVICTED) { + notifyPeerDropped(peer, System.currentTimeMillis()); + return true; + } else { + return false; + } + } + private void notifyPeerBonded(final DiscoveryPeer peer, final long now) { final PeerBondedEvent event = new PeerBondedEvent(peer, now); dispatchEvent(peerBondedObservers, event); } + private void notifyPeerDropped(final DiscoveryPeer peer, final long now) { + final PeerDroppedEvent event = new PeerDroppedEvent(peer, now); + dispatchEvent(peerDroppedObservers, event); + } + private Optional matchInteraction(final Packet packet) { final PeerInteractionState interaction = inflightInteractions.get(packet.getNodeId()); if (interaction == null || !interaction.test(packet)) { @@ -389,10 +414,12 @@ void bond(final DiscoveryPeer peer) { private void sendPacket(final DiscoveryPeer peer, final PacketType type, final PacketData data) { Packet packet = createPacket(type, data); + logSendingPacket(peer, packet); outboundMessageHandler.send(peer, packet); } private void sendPacket(final DiscoveryPeer peer, final Packet packet) { + logSendingPacket(peer, packet); outboundMessageHandler.send(peer, packet); } diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTable.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTable.java index 384f2a6004..daf346185c 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTable.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTable.java @@ -20,6 +20,7 @@ import tech.pegasys.pantheon.crypto.Hash; import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryStatus; +import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.AddResult.AddOutcome; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerId; import tech.pegasys.pantheon.util.bytes.BytesValue; @@ -103,7 +104,7 @@ public Optional get(final PeerId peer) { *

  • the operation failed because the peer already existed. * * - * @see AddResult.Outcome + * @see AddOutcome * @param peer The peer to add. * @return An object indicating the outcome of the operation. */ @@ -145,20 +146,33 @@ public AddResult tryAdd(final DiscoveryPeer peer) { * @param peer The peer to evict. * @return Whether the peer existed, and hence the eviction took place. */ - public boolean evict(final PeerId peer) { + public EvictResult tryEvict(final PeerId peer) { final BytesValue id = peer.getId(); final int distance = distanceFrom(peer); + + if (distance == 0) { + return EvictResult.self(); + } + distanceCache.remove(id); + if (table[distance].peers().isEmpty()) { + return EvictResult.absent(); + } + final boolean evicted = table[distance].evict(peer); - evictionCnt += evicted ? 1 : 0; + if (evicted) { + evictionCnt++; + } else { + return EvictResult.absent(); + } // Trigger the bloom filter regeneration if needed. if (evictionCnt >= BLOOM_FILTER_REGENERATION_THRESHOLD) { ForkJoinPool.commonPool().execute(this::buildBloomFilter); } - return evicted; + return EvictResult.evicted(); } private void buildBloomFilter() { @@ -206,7 +220,7 @@ private int distanceFrom(final PeerId peer) { /** A class that encapsulates the result of a peer addition to the table. */ public static class AddResult { /** The outcome of the operation. */ - public enum Outcome { + public enum AddOutcome { /** The peer was added successfully to its corresponding k-bucket. */ ADDED, @@ -221,31 +235,31 @@ public enum Outcome { SELF } - private final Outcome outcome; + private final AddOutcome outcome; private final Peer evictionCandidate; - private AddResult(final Outcome outcome, final Peer evictionCandidate) { + private AddResult(final AddOutcome outcome, final Peer evictionCandidate) { this.outcome = outcome; this.evictionCandidate = evictionCandidate; } static AddResult added() { - return new AddResult(Outcome.ADDED, null); + return new AddResult(AddOutcome.ADDED, null); } static AddResult bucketFull(final Peer evictionCandidate) { - return new AddResult(Outcome.BUCKET_FULL, evictionCandidate); + return new AddResult(AddOutcome.BUCKET_FULL, evictionCandidate); } static AddResult existed() { - return new AddResult(Outcome.ALREADY_EXISTED, null); + return new AddResult(AddOutcome.ALREADY_EXISTED, null); } static AddResult self() { - return new AddResult(Outcome.SELF, null); + return new AddResult(AddOutcome.SELF, null); } - public Outcome getOutcome() { + public AddOutcome getOutcome() { return outcome; } @@ -253,4 +267,34 @@ public Peer getEvictionCandidate() { return evictionCandidate; } } + + static class EvictResult { + public enum EvictOutcome { + EVICTED, + ABSENT, + SELF + } + + private final EvictOutcome outcome; + + private EvictResult(final EvictOutcome outcome) { + this.outcome = outcome; + } + + static EvictResult evicted() { + return new EvictResult(EvictOutcome.EVICTED); + } + + static EvictResult absent() { + return new EvictResult(EvictOutcome.ABSENT); + } + + static EvictResult self() { + return new EvictResult(EvictOutcome.SELF); + } + + EvictOutcome getOutcome() { + return outcome; + } + } } diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java index 266b5be8e3..67276106ea 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/NettyP2PNetwork.java @@ -22,6 +22,8 @@ import tech.pegasys.pantheon.ethereum.p2p.config.NetworkingConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryAgent; +import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerBondedEvent; +import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerDroppedEvent; import tech.pegasys.pantheon.ethereum.p2p.discovery.VertxPeerDiscoveryAgent; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerRequirement; import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer; @@ -125,6 +127,7 @@ public class NettyP2PNetwork implements P2PNetwork { private final PeerDiscoveryAgent peerDiscoveryAgent; private final PeerBlacklist peerBlacklist; private OptionalLong peerBondedObserverId = OptionalLong.empty(); + private OptionalLong peerDroppedObserverId = OptionalLong.empty(); @VisibleForTesting public final Collection peerMaintainConnectionList; @@ -432,23 +435,37 @@ public void subscribeDisconnect(final DisconnectCallback callback) { public void run() { try { peerDiscoveryAgent.start().join(); - final long observerId = - peerDiscoveryAgent.observePeerBondedEvents( - peerBondedEvent -> { - final Peer peer = peerBondedEvent.getPeer(); - if (connectionCount() < maxPeers - && peer.getEndpoint().getTcpPort().isPresent() - && !isConnecting(peer) - && !isConnected(peer)) { - connect(peer); - } - }); - peerBondedObserverId = OptionalLong.of(observerId); + peerBondedObserverId = + OptionalLong.of(peerDiscoveryAgent.observePeerBondedEvents(handlePeerBondedEvent())); + peerDroppedObserverId = + OptionalLong.of(peerDiscoveryAgent.observePeerDroppedEvents(handlePeerDroppedEvents())); } catch (final Exception ex) { throw new IllegalStateException(ex); } } + private Consumer handlePeerBondedEvent() { + return event -> { + final Peer peer = event.getPeer(); + if (connectionCount() < maxPeers + && peer.getEndpoint().getTcpPort().isPresent() + && !isConnecting(peer) + && !isConnected(peer)) { + connect(peer); + } + }; + } + + private Consumer handlePeerDroppedEvents() { + return event -> { + final Peer peer = event.getPeer(); + getPeers().stream() + .filter(p -> p.getPeer().getNodeId().equals(peer.getId())) + .findFirst() + .ifPresent(p -> p.disconnect(DisconnectReason.REQUESTED)); + }; + } + private boolean isConnecting(final Peer peer) { return pendingConnections.containsKey(peer); } @@ -463,6 +480,8 @@ public void stop() { peerDiscoveryAgent.stop().join(); peerBondedObserverId.ifPresent(peerDiscoveryAgent::removePeerBondedObserver); peerBondedObserverId = OptionalLong.empty(); + peerDroppedObserverId.ifPresent(peerDiscoveryAgent::removePeerDroppedObserver); + peerDroppedObserverId = OptionalLong.empty(); peerDiscoveryAgent.stop().join(); workers.shutdownGracefully(); boss.shutdownGracefully(); diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java index b805784890..fd65d3e84f 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java @@ -62,6 +62,7 @@ public void lastSeenAndFirstDiscoveredTimestampsUpdatedOnMessage() { () -> true, new PeerBlacklist(), Optional.empty(), + new Subscribers<>(), new Subscribers<>()); controller.start(); diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java index 94e23aad50..e024d28551 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java @@ -25,13 +25,17 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer; +import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerBondedEvent; +import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerDroppedEvent; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryStatus; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper; +import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.EvictResult; import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; @@ -45,6 +49,7 @@ import tech.pegasys.pantheon.util.uint.UInt256Value; import java.io.IOException; +import java.net.URI; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; @@ -57,8 +62,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.stream.Collectors; +import com.google.common.collect.Lists; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -1027,6 +1034,84 @@ public void shouldNotRespondToPingFromNonWhitelistedDiscoveryPeer() throws IOExc assertThat(controller.getPeers()).doesNotContain(peers.get(0)); } + @Test + public void whenObservingNodeWhitelistAndNodeIsRemovedShouldEvictPeerFromPeerTable() + throws IOException { + final PeerTable peerTableSpy = spy(peerTable); + final List peers = createPeersInLastBucket(localPeer, 1); + final DiscoveryPeer peer = peers.get(0); + peerTableSpy.tryAdd(peer); + + final PermissioningConfiguration config = permissioningConfigurationWithTempFile(); + final URI peerURI = URI.create(peer.getEnodeURI()); + config.setNodeWhitelist(Lists.newArrayList(peerURI)); + final NodeWhitelistController nodeWhitelistController = new NodeWhitelistController(config); + + controller = + getControllerBuilder().whitelist(nodeWhitelistController).peerTable(peerTableSpy).build(); + + controller.start(); + nodeWhitelistController.removeNodes(Lists.newArrayList(peerURI.toString())); + + verify(peerTableSpy).tryEvict(eq(DiscoveryPeer.fromURI(peerURI))); + } + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + public void whenObservingNodeWhitelistAndNodeIsRemovedShouldNotifyPeerDroppedObservers() + throws IOException { + final PeerTable peerTableSpy = spy(peerTable); + final List peers = createPeersInLastBucket(localPeer, 1); + final DiscoveryPeer peer = peers.get(0); + peerTableSpy.tryAdd(peer); + + final PermissioningConfiguration config = permissioningConfigurationWithTempFile(); + final URI peerURI = URI.create(peer.getEnodeURI()); + config.setNodeWhitelist(Lists.newArrayList(peerURI)); + final NodeWhitelistController nodeWhitelistController = new NodeWhitelistController(config); + + final Consumer peerDroppedEventConsumer = mock(Consumer.class); + final Subscribers> peerDroppedSubscribers = new Subscribers(); + peerDroppedSubscribers.subscribe(peerDroppedEventConsumer); + + doReturn(EvictResult.evicted()).when(peerTableSpy).tryEvict(any()); + + controller = + getControllerBuilder() + .whitelist(nodeWhitelistController) + .peerTable(peerTableSpy) + .peerDroppedObservers(peerDroppedSubscribers) + .build(); + + controller.start(); + nodeWhitelistController.removeNodes(Lists.newArrayList(peerURI.toString())); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PeerDroppedEvent.class); + verify(peerDroppedEventConsumer).accept(captor.capture()); + assertThat(captor.getValue().getPeer()).isEqualTo(DiscoveryPeer.fromURI(peer.getEnodeURI())); + } + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + public void whenPeerIsNotEvictedDropFromTableShouldReturnFalseAndNotifyZeroObservers() { + final List peers = createPeersInLastBucket(localPeer, 1); + final DiscoveryPeer peer = peers.get(0); + final PeerTable peerTableSpy = spy(peerTable); + final Consumer peerDroppedEventConsumer = mock(Consumer.class); + final Subscribers> peerDroppedSubscribers = new Subscribers(); + peerDroppedSubscribers.subscribe(peerDroppedEventConsumer); + + doReturn(EvictResult.absent()).when(peerTableSpy).tryEvict(any()); + + controller = getControllerBuilder().peerDroppedObservers(peerDroppedSubscribers).build(); + + controller.start(); + boolean dropped = controller.dropFromPeerTable(peer); + + assertThat(dropped).isFalse(); + verifyZeroInteractions(peerDroppedEventConsumer); + } + private static Packet mockPingPacket(final Peer from, final Peer to) { final Packet packet = mock(Packet.class); @@ -1103,6 +1188,8 @@ static class ControllerBuilder { private PeerTable peerTable; private OutboundMessageHandler outboundMessageHandler = OutboundMessageHandler.NOOP; private static final PeerDiscoveryTestHelper helper = new PeerDiscoveryTestHelper(); + private Subscribers> peerBondedObservers = new Subscribers<>(); + private Subscribers> peerDroppedObservers = new Subscribers<>(); public static ControllerBuilder create() { return new ControllerBuilder(); @@ -1153,6 +1240,17 @@ ControllerBuilder outboundMessageHandler(final OutboundMessageHandler outboundMe return this; } + ControllerBuilder peerBondedObservers(final Subscribers> observers) { + this.peerBondedObservers = observers; + return this; + } + + ControllerBuilder peerDroppedObservers( + final Subscribers> observers) { + this.peerDroppedObservers = observers; + return this; + } + PeerDiscoveryController build() { checkNotNull(keypair); if (localPeer == null) { @@ -1173,7 +1271,8 @@ PeerDiscoveryController build() { PEER_REQUIREMENT, blacklist, whitelist, - new Subscribers<>())); + peerBondedObservers, + peerDroppedObservers)); } } } diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java index 18e71e6599..c5c9794dab 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java @@ -64,6 +64,7 @@ public void tableRefreshSingleNode() { () -> true, new PeerBlacklist(), Optional.empty(), + new Subscribers<>(), new Subscribers<>())); controller.start(); diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTableTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTableTest.java index fbd9dd13b1..f76c17c812 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTableTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTableTest.java @@ -16,7 +16,9 @@ import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper; -import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.AddResult.Outcome; +import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.AddResult.AddOutcome; +import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.EvictResult; +import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable.EvictResult.EvictOutcome; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; import java.util.List; @@ -33,7 +35,7 @@ public void addPeer() { for (final DiscoveryPeer peer : peers) { final PeerTable.AddResult result = table.tryAdd(peer); - assertThat(result.getOutcome()).isEqualTo(Outcome.ADDED); + assertThat(result.getOutcome()).isEqualTo(AddOutcome.ADDED); } assertThat(table.getAllPeers()).hasSize(5); @@ -45,7 +47,7 @@ public void addSelf() { final PeerTable table = new PeerTable(localPeer.getId(), 16); final PeerTable.AddResult result = table.tryAdd(localPeer); - assertThat(result.getOutcome()).isEqualTo(Outcome.SELF); + assertThat(result.getOutcome()).isEqualTo(AddOutcome.SELF); assertThat(table.getAllPeers()).hasSize(0); } @@ -54,13 +56,53 @@ public void peerExists() { final PeerTable table = new PeerTable(Peer.randomId(), 16); final DiscoveryPeer peer = helper.createDiscoveryPeer(); - assertThat(table.tryAdd(peer).getOutcome()).isEqualTo(Outcome.ADDED); + assertThat(table.tryAdd(peer).getOutcome()).isEqualTo(AddOutcome.ADDED); assertThat(table.tryAdd(peer)) .satisfies( result -> { - assertThat(result.getOutcome()).isEqualTo(Outcome.ALREADY_EXISTED); + assertThat(result.getOutcome()).isEqualTo(AddOutcome.ALREADY_EXISTED); assertThat(result.getEvictionCandidate()).isNull(); }); } + + @Test + public void evictExistingPeerShouldEvict() { + final PeerTable table = new PeerTable(Peer.randomId(), 16); + final DiscoveryPeer peer = helper.createDiscoveryPeer(); + + table.tryAdd(peer); + + EvictResult evictResult = table.tryEvict(peer); + assertThat(evictResult.getOutcome()).isEqualTo(EvictOutcome.EVICTED); + } + + @Test + public void evictPeerFromEmptyTableShouldNotEvict() { + final PeerTable table = new PeerTable(Peer.randomId(), 16); + final DiscoveryPeer peer = helper.createDiscoveryPeer(); + + EvictResult evictResult = table.tryEvict(peer); + assertThat(evictResult.getOutcome()).isEqualTo(EvictOutcome.ABSENT); + } + + @Test + public void evictAbsentPeerShouldNotEvict() { + final PeerTable table = new PeerTable(Peer.randomId(), 16); + final DiscoveryPeer peer = helper.createDiscoveryPeer(); + final List otherPeers = helper.createDiscoveryPeers(5); + otherPeers.forEach(table::tryAdd); + + EvictResult evictResult = table.tryEvict(peer); + assertThat(evictResult.getOutcome()).isEqualTo(EvictOutcome.ABSENT); + } + + @Test + public void evictSelfPeerShouldReturnSelfOutcome() { + final DiscoveryPeer peer = helper.createDiscoveryPeer(); + final PeerTable table = new PeerTable(peer.getId(), 16); + + EvictResult evictResult = table.tryEvict(peer); + assertThat(evictResult.getOutcome()).isEqualTo(EvictOutcome.SELF); + } } diff --git a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeWhitelistController.java b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeWhitelistController.java index 7c515f4ccd..63604e9de5 100644 --- a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeWhitelistController.java +++ b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeWhitelistController.java @@ -12,15 +12,19 @@ */ package tech.pegasys.pantheon.ethereum.permissioning; +import tech.pegasys.pantheon.ethereum.permissioning.node.NodeWhitelistUpdatedEvent; +import tech.pegasys.pantheon.util.Subscribers; import tech.pegasys.pantheon.util.enode.EnodeURL; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Collectors; import com.google.common.annotations.VisibleForTesting; @@ -34,6 +38,8 @@ public class NodeWhitelistController { private PermissioningConfiguration configuration; private List nodesWhitelist = new ArrayList<>(); private final WhitelistPersistor whitelistPersistor; + private final Subscribers> nodeWhitelistUpdatedObservers = + new Subscribers<>(); public NodeWhitelistController(final PermissioningConfiguration permissioningConfiguration) { this( @@ -73,6 +79,7 @@ public NodesWhitelistResult addNodes(final List enodeURLs) { final List oldWhitelist = new ArrayList<>(this.nodesWhitelist); peers.forEach(this::addNode); + notifyListUpdatedSubscribers(new NodeWhitelistUpdatedEvent(peers, Collections.emptyList())); final NodesWhitelistResult updateConfigFileResult = updateWhitelistInConfigFile(oldWhitelist); if (updateConfigFileResult.result() != WhitelistOperationResult.SUCCESS) { @@ -103,6 +110,7 @@ public NodesWhitelistResult removeNodes(final List enodeURLs) { final List oldWhitelist = new ArrayList<>(this.nodesWhitelist); peers.forEach(this::removeNode); + notifyListUpdatedSubscribers(new NodeWhitelistUpdatedEvent(Collections.emptyList(), peers)); final NodesWhitelistResult updateConfigFileResult = updateWhitelistInConfigFile(oldWhitelist); if (updateConfigFileResult.result() != WhitelistOperationResult.SUCCESS) { @@ -206,6 +214,8 @@ public synchronized void reload() throws RuntimeException { readNodesFromConfig(updatedConfig); configuration = updatedConfig; + + createNodeWhitelistModifiedEventAfterReload(currentAccountsList, nodesWhitelist); } catch (Exception e) { LOG.warn( "Error reloading permissions file. In-memory whitelisted nodes will be reverted to previous valid configuration. " @@ -217,6 +227,36 @@ public synchronized void reload() throws RuntimeException { } } + private void createNodeWhitelistModifiedEventAfterReload( + final List previousNodeWhitelist, final List currentNodesList) { + final List removedNodes = + previousNodeWhitelist.stream() + .filter(n -> !currentNodesList.contains(n)) + .collect(Collectors.toList()); + + final List addedNodes = + currentNodesList.stream() + .filter(n -> !previousNodeWhitelist.contains(n)) + .collect(Collectors.toList()); + + if (!removedNodes.isEmpty() || !addedNodes.isEmpty()) { + notifyListUpdatedSubscribers(new NodeWhitelistUpdatedEvent(addedNodes, removedNodes)); + } + } + + public long subscribeToListUpdatedEvent(final Consumer subscriber) { + return nodeWhitelistUpdatedObservers.subscribe(subscriber); + } + + private void notifyListUpdatedSubscribers(final NodeWhitelistUpdatedEvent event) { + LOG.trace( + "Sending NodeWhitelistUpdatedEvent (added: {}, removed {})", + event.getAddedNodes().size(), + event.getRemovedNodes().size()); + + nodeWhitelistUpdatedObservers.forEach(c -> c.accept(event)); + } + public static class NodesWhitelistResult { private final WhitelistOperationResult result; private final Optional message; diff --git a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/node/NodeWhitelistUpdatedEvent.java b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/node/NodeWhitelistUpdatedEvent.java new file mode 100644 index 0000000000..4654c5a189 --- /dev/null +++ b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/node/NodeWhitelistUpdatedEvent.java @@ -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.ethereum.permissioning.node; + +import tech.pegasys.pantheon.util.enode.EnodeURL; + +import java.util.Collections; +import java.util.List; + +import com.google.common.base.Objects; + +public class NodeWhitelistUpdatedEvent { + + private final List addedNodes; + private final List removedNodes; + + public NodeWhitelistUpdatedEvent( + final List addedNodes, final List removedNodes) { + this.addedNodes = addedNodes != null ? addedNodes : Collections.emptyList(); + this.removedNodes = removedNodes != null ? removedNodes : Collections.emptyList(); + } + + public List getAddedNodes() { + return addedNodes; + } + + public List getRemovedNodes() { + return removedNodes; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NodeWhitelistUpdatedEvent that = (NodeWhitelistUpdatedEvent) o; + return Objects.equal(addedNodes, that.addedNodes) + && Objects.equal(removedNodes, that.removedNodes); + } + + @Override + public int hashCode() { + return Objects.hashCode(addedNodes, removedNodes); + } +} diff --git a/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeWhitelistControllerTest.java b/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeWhitelistControllerTest.java index c02dfdf5cb..ee3599d3b2 100644 --- a/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeWhitelistControllerTest.java +++ b/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeWhitelistControllerTest.java @@ -16,14 +16,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static tech.pegasys.pantheon.ethereum.permissioning.NodeWhitelistController.NodesWhitelistResult; +import tech.pegasys.pantheon.ethereum.permissioning.node.NodeWhitelistUpdatedEvent; import tech.pegasys.pantheon.util.enode.EnodeURL; import java.io.IOException; @@ -33,7 +36,9 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import com.google.common.collect.Lists; import org.junit.Before; @@ -59,6 +64,15 @@ public void setUp() { new NodeWhitelistController(PermissioningConfiguration.createDefault(), whitelistPersistor); } + @Test + public void whenAddNodesWithValidInputShouldReturnSuccess() { + NodesWhitelistResult expected = new NodesWhitelistResult(WhitelistOperationResult.SUCCESS); + NodesWhitelistResult actualResult = controller.addNodes(Lists.newArrayList(enode1)); + + assertThat(actualResult).isEqualToComparingOnlyGivenFields(expected, "result"); + assertThat(controller.getNodesWhitelist()).containsExactly(enode1); + } + @Test public void whenAddNodesInputHasExistingNodeShouldReturnAddErrorExistingEntry() { controller.addNodes(Arrays.asList(enode1)); @@ -256,6 +270,105 @@ public void reloadNodeWhitelistWithErrorReadingConfigFileShouldKeepOldWhitelist( assertThat(controller.getNodesWhitelist()).containsExactly(expectedEnodeURI); } + @Test + @SuppressWarnings("unchecked") + public void whenAddingNodeShouldNotifyWhitelistModifiedSubscribers() { + final Consumer consumer = mock(Consumer.class); + final NodeWhitelistUpdatedEvent expectedEvent = + new NodeWhitelistUpdatedEvent( + Lists.newArrayList(new EnodeURL(enode1)), Collections.emptyList()); + + controller.subscribeToListUpdatedEvent(consumer); + controller.addNodes(Lists.newArrayList(enode1)); + + verify(consumer).accept(eq(expectedEvent)); + } + + @Test + @SuppressWarnings("unchecked") + public void whenAddingNodeDoesNotAddShouldNotNotifyWhitelistModifiedSubscribers() { + // adding node before subscribing to whitelist modified events + controller.addNodes(Lists.newArrayList(enode1)); + final Consumer consumer = mock(Consumer.class); + + controller.subscribeToListUpdatedEvent(consumer); + // won't add duplicate node + controller.addNodes(Lists.newArrayList(enode1)); + + verifyZeroInteractions(consumer); + } + + @Test + @SuppressWarnings("unchecked") + public void whenRemovingNodeShouldNotifyWhitelistModifiedSubscribers() { + // adding node before subscribing to whitelist modified events + controller.addNodes(Lists.newArrayList(enode1)); + + final Consumer consumer = mock(Consumer.class); + final NodeWhitelistUpdatedEvent expectedEvent = + new NodeWhitelistUpdatedEvent( + Collections.emptyList(), Lists.newArrayList(new EnodeURL(enode1))); + + controller.subscribeToListUpdatedEvent(consumer); + controller.removeNodes(Lists.newArrayList(enode1)); + + verify(consumer).accept(eq(expectedEvent)); + } + + @Test + @SuppressWarnings("unchecked") + public void whenRemovingNodeDoesNotRemoveShouldNotifyWhitelistModifiedSubscribers() { + final Consumer consumer = mock(Consumer.class); + + controller.subscribeToListUpdatedEvent(consumer); + // won't remove absent node + controller.removeNodes(Lists.newArrayList(enode1)); + + verifyZeroInteractions(consumer); + } + + @Test + @SuppressWarnings("unchecked") + public void whenReloadingWhitelistShouldNotifyWhitelistModifiedSubscribers() throws Exception { + final Path permissionsFile = createPermissionsFileWithNode(enode2); + final PermissioningConfiguration permissioningConfig = mock(PermissioningConfiguration.class); + final Consumer consumer = mock(Consumer.class); + final NodeWhitelistUpdatedEvent expectedEvent = + new NodeWhitelistUpdatedEvent( + Lists.newArrayList(new EnodeURL(enode2)), Lists.newArrayList(new EnodeURL(enode1))); + + when(permissioningConfig.getConfigurationFilePath()) + .thenReturn(permissionsFile.toAbsolutePath().toString()); + when(permissioningConfig.isNodeWhitelistEnabled()).thenReturn(true); + when(permissioningConfig.getNodeWhitelist()).thenReturn(Arrays.asList(URI.create(enode1))); + controller = new NodeWhitelistController(permissioningConfig); + controller.subscribeToListUpdatedEvent(consumer); + + controller.reload(); + + verify(consumer).accept(eq(expectedEvent)); + } + + @Test + @SuppressWarnings("unchecked") + public void whenReloadingWhitelistAndNothingChangesShouldNotNotifyWhitelistModifiedSubscribers() + throws Exception { + final Path permissionsFile = createPermissionsFileWithNode(enode1); + final PermissioningConfiguration permissioningConfig = mock(PermissioningConfiguration.class); + final Consumer consumer = mock(Consumer.class); + + when(permissioningConfig.getConfigurationFilePath()) + .thenReturn(permissionsFile.toAbsolutePath().toString()); + when(permissioningConfig.isNodeWhitelistEnabled()).thenReturn(true); + when(permissioningConfig.getNodeWhitelist()).thenReturn(Arrays.asList(URI.create(enode1))); + controller = new NodeWhitelistController(permissioningConfig); + controller.subscribeToListUpdatedEvent(consumer); + + controller.reload(); + + verifyZeroInteractions(consumer); + } + private Path createPermissionsFileWithNode(final String node) throws IOException { final String nodePermissionsFileContent = "nodes-whitelist=[\"" + node + "\"]"; final Path permissionsFile = Files.createTempFile("node_permissions", ""); From 48a0e4555cab6303792d6169385a4e359ffd65d0 Mon Sep 17 00:00:00 2001 From: Chris Mckay Date: Mon, 18 Feb 2019 15:21:15 +1000 Subject: [PATCH 08/21] More bootnodes for goerli (#880) --- .../pantheon/ethereum/p2p/config/DiscoveryConfiguration.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/DiscoveryConfiguration.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/DiscoveryConfiguration.java index e031e2ac3f..fe93be20b9 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/DiscoveryConfiguration.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/DiscoveryConfiguration.java @@ -57,7 +57,10 @@ public class DiscoveryConfiguration { Collections.unmodifiableList( Stream.of( "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", - "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303") + "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", + "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", + "enode://c1f8b7c2ac4453271fa07d8e9ecf9a2e8285aa0bd0c07df0131f47153306b0736fd3db8924e7a9bf0bed6b1d8d4f87362a71b033dc7c64547728d953e43e59b2@52.64.155.147:30303", + "enode://f4a9c6ee28586009fb5a96c8af13a58ed6d8315a9eee4772212c1d4d9cebe5a8b8a78ea4434f318726317d04a3f531a1ef0420cf9752605a562cfe858c46e263@213.186.16.82:30303") .map(DefaultPeer::fromURI) .collect(toList())); From e60255ff32be27b2081f1073d2cc00ccbbf04726 Mon Sep 17 00:00:00 2001 From: Chris Mckay Date: Mon, 18 Feb 2019 21:15:14 +1000 Subject: [PATCH 09/21] [MINOR] Fixing file locations under docker (#885) --- .../pantheon/cli/DefaultCommandValues.java | 5 + .../pegasys/pantheon/cli/PantheonCommand.java | 102 ++++++++++++------ .../pantheon/cli/StandaloneCommand.java | 25 +++++ .../pantheon/cli/PantheonCommandTest.java | 41 ++++++- 4 files changed, 139 insertions(+), 34 deletions(-) diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java index f90081cb5c..f9f9cb2881 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java @@ -43,6 +43,11 @@ interface DefaultCommandValues { long DEFAULT_MIN_REFRESH_DELAY = 1; String DOCKER_GENESIS_LOCATION = "/etc/pantheon/genesis.json"; String DOCKER_DATADIR_LOCATION = "/var/lib/pantheon"; + String DOCKER_RPC_HTTP_AUTHENTICATION_CREDENTIALS_FILE_LOCATION = + "/etc/pantheon/rpc_http_auth_config.toml"; + String DOCKER_RPC_WS_AUTHENTICATION_CREDENTIALS_FILE_LOCATION = + "/etc/pantheon/rpc_ws_auth_config.toml"; + String DOCKER_PRIVACY_PUBLIC_KEY_FILE = "/etc/pantheon/privacy_public_key"; String PERMISSIONING_CONFIG_LOCATION = "permissions_config.toml"; String MANDATORY_HOST_FORMAT_HELP = ""; String MANDATORY_PORT_FORMAT_HELP = ""; diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index 21bd6d2a39..525a31642a 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -276,15 +276,6 @@ public static class RpcApisConversionException extends Exception { "Set if the JSON-RPC service should require authentication (default: ${DEFAULT-VALUE})") private final Boolean isRpcHttpAuthenticationEnabled = false; - @Option( - names = {"--rpc-http-authentication-credentials-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "Storage file for rpc http authentication credentials (default: ${DEFAULT-VALUE})", - arity = "1", - converter = RpcAuthConverter.class) - private String rpcHttpAuthenticationCredentialsFile = null; - @Option( names = {"--rpc-ws-enabled"}, description = @@ -341,19 +332,9 @@ private Long configureRefreshDelay(final Long refreshDelay) { @Option( names = {"--rpc-ws-authentication-enabled"}, description = - "Set if the websocket JSON-RPC service should require authentication (default: ${DEFAULT-VALUE})", - hidden = true) + "Set if the websocket JSON-RPC service should require authentication (default: ${DEFAULT-VALUE})") private final Boolean isRpcWsAuthenticationEnabled = false; - @Option( - names = {"--rpc-ws-authentication-credentials-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "Storage file for rpc websocket authentication credentials (default: ${DEFAULT-VALUE})", - arity = "1", - hidden = true) - private String rpcWsAuthenticationCredentialsFile = null; - @Option( names = {"--metrics-enabled"}, description = "Set if the metrics exporter should be started (default: ${DEFAULT-VALUE})") @@ -478,11 +459,6 @@ private Long configureRefreshDelay(final Long refreshDelay) { description = "The URL on which enclave is running ") private final URI privacyUrl = PrivacyParameters.DEFAULT_ENCLAVE_URL; - @Option( - names = {"--privacy-public-key-file"}, - description = "the path to the enclave's public key ") - private final File privacyPublicKeyFile = null; - @Option( names = {"--privacy-precompiled-address"}, description = @@ -579,6 +555,8 @@ public void run() { final EthNetworkConfig ethNetworkConfig = updateNetworkConfig(getNetwork()); try { + final JsonRpcConfiguration jsonRpcConfiguration = jsonRpcConfiguration(); + final WebSocketConfiguration webSocketConfiguration = webSocketConfiguration(); final Optional permissioningConfiguration = permissioningConfiguration(); permissioningConfiguration.ifPresent( @@ -592,8 +570,8 @@ public void run() { maxPeers, p2pHost, p2pPort, - jsonRpcConfiguration(), - webSocketConfiguration(), + jsonRpcConfiguration, + webSocketConfiguration, metricsConfiguration(), permissioningConfiguration); } catch (Exception e) { @@ -647,7 +625,7 @@ private String getPermissionsConfigFile() { + DefaultCommandValues.PERMISSIONING_CONFIG_LOCATION; } - private JsonRpcConfiguration jsonRpcConfiguration() { + private JsonRpcConfiguration jsonRpcConfiguration() throws Exception { CommandLineUtils.checkOptionDependencies( logger, @@ -678,7 +656,7 @@ private JsonRpcConfiguration jsonRpcConfiguration() { jsonRpcConfiguration.setRpcApis(rpcHttpApis); jsonRpcConfiguration.setHostsWhitelist(hostsWhitelist); jsonRpcConfiguration.setAuthenticationEnabled(isRpcHttpAuthenticationEnabled); - jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile); + jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile()); return jsonRpcConfiguration; } @@ -712,7 +690,7 @@ private WebSocketConfiguration webSocketConfiguration() { webSocketConfiguration.setRpcApis(rpcWsApis); webSocketConfiguration.setRefreshDelay(rpcWsRefreshDelay); webSocketConfiguration.setAuthenticationEnabled(isRpcWsAuthenticationEnabled); - webSocketConfiguration.setAuthenticationCredentialsFile(rpcWsAuthenticationCredentialsFile); + webSocketConfiguration.setAuthenticationCredentialsFile(rpcWsAuthenticationCredentialsFile()); return webSocketConfiguration; } @@ -781,10 +759,11 @@ private PrivacyParameters privacyParameters() throws IOException { if (privacyEnabled) { privacyParameters.setEnabled(privacyEnabled); privacyParameters.setUrl(privacyUrl.toString()); - if (privacyPublicKeyFile != null) { - privacyParameters.setPublicKeyUsingFile(privacyPublicKeyFile); + if (privacyPublicKeyFile() != null) { + privacyParameters.setPublicKeyUsingFile(privacyPublicKeyFile()); } else { - throw new IOException("Please specify Enclave public Key file path to Enable Privacy"); + throw new ParameterException( + commandLine, "Please specify Enclave public Key file path to Enable Privacy"); } privacyParameters.setPrivacyAddress(privacyPrecompiledAddress); } @@ -979,6 +958,63 @@ private File nodePrivateKeyFile() { : KeyPairUtil.getDefaultKeyFile(dataDir()); } + private File privacyPublicKeyFile() { + if (isDocker) { + final File keyFile = new File(DOCKER_PRIVACY_PUBLIC_KEY_FILE); + if (keyFile.exists()) { + return keyFile; + } else { + return null; + } + } else { + return standaloneCommands.privacyPublicKeyFile; + } + } + + private String rpcHttpAuthenticationCredentialsFile() throws Exception { + if (isFullInstantiation()) { + return standaloneCommands.rpcHttpAuthenticationCredentialsFile; + } else if (isDocker) { + final File authFile = new File(DOCKER_RPC_HTTP_AUTHENTICATION_CREDENTIALS_FILE_LOCATION); + if (authFile.exists()) { + final String path = authFile.getAbsolutePath(); + try { + new RpcAuthConverter().convert(path); + } catch (Exception e) { + throw new ParameterException( + commandLine, "Invalid RPC HTTP authentication credentials file: " + e.getMessage()); + } + return path; + } else { + return null; + } + } else { + return null; + } + } + + private String rpcWsAuthenticationCredentialsFile() { + if (isFullInstantiation()) { + return standaloneCommands.rpcWsAuthenticationCredentialsFile; + } else if (isDocker) { + final File authFile = new File(DOCKER_RPC_WS_AUTHENTICATION_CREDENTIALS_FILE_LOCATION); + if (authFile.exists()) { + final String path = authFile.getAbsolutePath(); + try { + new RpcAuthConverter().convert(path); + } catch (Exception e) { + throw new ParameterException( + commandLine, "Invalid RPC WS authentication credentials file: " + e.getMessage()); + } + return path; + } else { + return null; + } + } else { + return null; + } + } + private boolean isFullInstantiation() { return !isDocker; } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/StandaloneCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/StandaloneCommand.java index 8fd0c35184..c1dfcf4583 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/StandaloneCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/StandaloneCommand.java @@ -14,6 +14,8 @@ import static tech.pegasys.pantheon.cli.DefaultCommandValues.getDefaultPantheonDataPath; +import tech.pegasys.pantheon.cli.custom.RpcAuthConverter; + import java.io.File; import java.nio.file.Path; @@ -51,4 +53,27 @@ class StandaloneCommand implements DefaultCommandValues { description = "the path to the node's private key file (default: a file named \"key\" in the Pantheon data folder)") final File nodePrivateKeyFile = null; + + @CommandLine.Option( + names = {"--rpc-http-authentication-credentials-file"}, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = + "Storage file for rpc http authentication credentials (default: ${DEFAULT-VALUE})", + arity = "1", + converter = RpcAuthConverter.class) + String rpcHttpAuthenticationCredentialsFile = null; + + @CommandLine.Option( + names = {"--rpc-ws-authentication-credentials-file"}, + paramLabel = MANDATORY_FILE_FORMAT_HELP, + description = + "Storage file for rpc websocket authentication credentials (default: ${DEFAULT-VALUE})", + arity = "1", + converter = RpcAuthConverter.class) + String rpcWsAuthenticationCredentialsFile = null; + + @CommandLine.Option( + names = {"--privacy-public-key-file"}, + description = "the path to the enclave's public key ") + final File privacyPublicKeyFile = null; } diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java index e6b1de75bf..7800eb7898 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java @@ -1864,7 +1864,7 @@ public void privacyOptionsRequiresServiceToBeEnabled() { String.valueOf(Byte.MAX_VALUE - 1)); verifyOptionsConstraintLoggerCall( - "--privacy-url, --privacy-public-key-file and --privacy-precompiled-address", + "--privacy-url, --privacy-precompiled-address and --privacy-public-key-file", "--privacy-enabled"); assertThat(commandOutput.toString()).isEmpty(); @@ -1925,4 +1925,43 @@ private void verifyOptionsConstraintLoggerCall( assertThat(stringArgumentCaptor.getAllValues().get(1)).isEqualTo(dependentOptions); assertThat(stringArgumentCaptor.getAllValues().get(2)).isEqualTo(mainOption); } + + @Test + public void privacyPublicKeyFileOptionDisabledUnderDocker() { + System.setProperty("pantheon.docker", "true"); + + assumeFalse(isFullInstantiation()); + + final Path path = Paths.get("."); + parseCommand("--privacy-public-key-file", path.toString()); + assertThat(commandErrorOutput.toString()) + .startsWith("Unknown options: --privacy-public-key-file, ."); + assertThat(commandOutput.toString()).isEmpty(); + } + + @Test + public void rpcHttpAuthCredentialsFileOptionDisabledUnderDocker() { + System.setProperty("pantheon.docker", "true"); + + assumeFalse(isFullInstantiation()); + + final Path path = Paths.get("."); + parseCommand("--rpc-http-authentication-credentials-file", path.toString()); + assertThat(commandErrorOutput.toString()) + .startsWith("Unknown options: --rpc-http-authentication-credentials-file, ."); + assertThat(commandOutput.toString()).isEmpty(); + } + + @Test + public void rpcWsAuthCredentialsFileOptionDisabledUnderDocker() { + System.setProperty("pantheon.docker", "true"); + + assumeFalse(isFullInstantiation()); + + final Path path = Paths.get("."); + parseCommand("--rpc-ws-authentication-credentials-file", path.toString()); + assertThat(commandErrorOutput.toString()) + .startsWith("Unknown options: --rpc-ws-authentication-credentials-file, ."); + assertThat(commandOutput.toString()).isEmpty(); + } } From 0bd22464b19461937bb4c9c74b7a9976f1bd438b Mon Sep 17 00:00:00 2001 From: mbaxter Date: Mon, 18 Feb 2019 11:40:20 -0500 Subject: [PATCH 10/21] [PAN-2305] Detect stalled world state downloads (#875) --- .../ethereum/eth/sync/FastSynchronizer.java | 1 + .../eth/sync/SynchronizerConfiguration.java | 37 +++- .../eth/sync/fastsync/FastSyncDownloader.java | 4 +- .../eth/sync/worldstate/NodeDataRequest.java | 25 ++- .../worldstate/StalledDownloadException.java | 20 +++ .../sync/worldstate/WorldStateDownloader.java | 25 ++- ...ava => WorldStateDownloaderException.java} | 7 +- .../sync/fastsync/FastSyncDownloaderTest.java | 4 +- .../worldstate/WorldStateDownloaderTest.java | 167 +++++++++++------- 9 files changed, 210 insertions(+), 80 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/StalledDownloadException.java rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/{WorldStateUnavailableException.java => WorldStateDownloaderException.java} (80%) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FastSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FastSynchronizer.java index 5c16ddc791..470e5f10c7 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FastSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/FastSynchronizer.java @@ -103,6 +103,7 @@ public static Optional> create( stateQueue, syncConfig.getWorldStateHashCountPerRequest(), syncConfig.getWorldStateRequestParallelism(), + syncConfig.getWorldStateRequestMaxRetries(), ethTasksTimer, metricsSystem); final FastSyncDownloader fastSyncDownloader = diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java index 4d74ac2459..4abb3f3da3 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SynchronizerConfiguration.java @@ -23,12 +23,13 @@ public class SynchronizerConfiguration { // TODO: Determine reasonable defaults here - public static final int DEFAULT_PIVOT_DISTANCE_FROM_HEAD = 50; - public static final float DEFAULT_FULL_VALIDATION_RATE = .1f; - public static final int DEFAULT_FAST_SYNC_MINIMUM_PEERS = 5; + private static final int DEFAULT_PIVOT_DISTANCE_FROM_HEAD = 50; + private static final float DEFAULT_FULL_VALIDATION_RATE = .1f; + private static final int DEFAULT_FAST_SYNC_MINIMUM_PEERS = 5; private static final Duration DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME = Duration.ofMinutes(5); private static final int DEFAULT_WORLD_STATE_HASH_COUNT_PER_REQUEST = 384; private static final int DEFAULT_WORLD_STATE_REQUEST_PARALLELISM = 10; + private static final int DEFAULT_WORLD_STATE_REQUEST_MAX_RETRIES = 25; // Fast sync config private final int fastSyncPivotDistance; @@ -37,6 +38,7 @@ public class SynchronizerConfiguration { private final Duration fastSyncMaximumPeerWaitTime; private final int worldStateHashCountPerRequest; private final int worldStateRequestParallelism; + private final int worldStateRequestMaxRetries; // Block propagation config private final Range blockPropagationRange; @@ -64,6 +66,7 @@ private SynchronizerConfiguration( final Duration fastSyncMaximumPeerWaitTime, final int worldStateHashCountPerRequest, final int worldStateRequestParallelism, + final int worldStateRequestMaxRetries, final Range blockPropagationRange, final SyncMode syncMode, final long downloaderChangeTargetThresholdByHeight, @@ -83,6 +86,7 @@ private SynchronizerConfiguration( this.fastSyncMaximumPeerWaitTime = fastSyncMaximumPeerWaitTime; this.worldStateHashCountPerRequest = worldStateHashCountPerRequest; this.worldStateRequestParallelism = worldStateRequestParallelism; + this.worldStateRequestMaxRetries = worldStateRequestMaxRetries; this.blockPropagationRange = blockPropagationRange; this.syncMode = syncMode; this.downloaderChangeTargetThresholdByHeight = downloaderChangeTargetThresholdByHeight; @@ -207,6 +211,10 @@ public int getWorldStateRequestParallelism() { return worldStateRequestParallelism; } + public int getWorldStateRequestMaxRetries() { + return worldStateRequestMaxRetries; + } + public static class Builder { private SyncMode syncMode = SyncMode.FULL; private Range blockPropagationRange = Range.closed(-10L, 30L); @@ -224,6 +232,9 @@ public static class Builder { private int fastSyncPivotDistance = DEFAULT_PIVOT_DISTANCE_FROM_HEAD; private float fastSyncFullValidationRate = DEFAULT_FULL_VALIDATION_RATE; private int fastSyncMinimumPeerCount = DEFAULT_FAST_SYNC_MINIMUM_PEERS; + private int worldStateHashCountPerRequest = DEFAULT_WORLD_STATE_HASH_COUNT_PER_REQUEST; + private int worldStateRequestParallelism = DEFAULT_WORLD_STATE_REQUEST_PARALLELISM; + private int worldStateRequestMaxRetries = DEFAULT_WORLD_STATE_REQUEST_MAX_RETRIES; private Duration fastSyncMaximumPeerWaitTime = DEFAULT_FAST_SYNC_MAXIMUM_PEER_WAIT_TIME; public Builder fastSyncPivotDistance(final int distance) { @@ -311,6 +322,21 @@ public Builder fastSyncMinimumPeerCount(final int fastSyncMinimumPeerCount) { return this; } + public Builder worldStateHashCountPerRequest(final int worldStateHashCountPerRequest) { + this.worldStateHashCountPerRequest = worldStateHashCountPerRequest; + return this; + } + + public Builder worldStateRequestParallelism(final int worldStateRequestParallelism) { + this.worldStateRequestParallelism = worldStateRequestParallelism; + return this; + } + + public Builder worldStateRequestMaxRetries(final int worldStateRequestMaxRetries) { + this.worldStateRequestMaxRetries = worldStateRequestMaxRetries; + return this; + } + public Builder fastSyncMaximumPeerWaitTime(final Duration fastSyncMaximumPeerWaitTime) { this.fastSyncMaximumPeerWaitTime = fastSyncMaximumPeerWaitTime; return this; @@ -322,8 +348,9 @@ public SynchronizerConfiguration build() { fastSyncFullValidationRate, fastSyncMinimumPeerCount, fastSyncMaximumPeerWaitTime, - DEFAULT_WORLD_STATE_HASH_COUNT_PER_REQUEST, - DEFAULT_WORLD_STATE_REQUEST_PARALLELISM, + worldStateHashCountPerRequest, + worldStateRequestParallelism, + worldStateRequestMaxRetries, blockPropagationRange, syncMode, downloaderChangeTargetThresholdByHeight, diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java index e2d45cc90f..a23dd06fc7 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -15,8 +15,8 @@ import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; import static tech.pegasys.pantheon.util.FutureUtils.exceptionallyCompose; +import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.StalledDownloadException; import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.WorldStateDownloader; -import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.WorldStateUnavailableException; import tech.pegasys.pantheon.util.ExceptionUtils; import java.util.concurrent.CompletableFuture; @@ -51,7 +51,7 @@ public CompletableFuture start(final FastSyncState fastSyncState) } private CompletableFuture handleWorldStateUnavailable(final Throwable error) { - if (ExceptionUtils.rootCause(error) instanceof WorldStateUnavailableException) { + if (ExceptionUtils.rootCause(error) instanceof StalledDownloadException) { LOG.warn( "Fast sync was unable to download the world state. Retrying with a new pivot block."); return start(FastSyncState.EMPTY_SYNC_STATE); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/NodeDataRequest.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/NodeDataRequest.java index 29f4b68d57..776665bc2b 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/NodeDataRequest.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/NodeDataRequest.java @@ -20,6 +20,7 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; public abstract class NodeDataRequest { @@ -27,6 +28,7 @@ public abstract class NodeDataRequest { private final RequestType requestType; private final Hash hash; private BytesValue data; + private final AtomicInteger failedRequestCount = new AtomicInteger(0); protected NodeDataRequest(final RequestType requestType, final Hash hash) { this.requestType = requestType; @@ -54,26 +56,35 @@ public static NodeDataRequest deserialize(final BytesValue encoded) { in.enterList(); RequestType requestType = RequestType.fromValue(in.readByte()); Hash hash = Hash.wrap(in.readBytes32()); + int failureCount = in.readIntScalar(); in.leaveList(); + NodeDataRequest deserialized; switch (requestType) { case ACCOUNT_TRIE_NODE: - return createAccountDataRequest(hash); + deserialized = createAccountDataRequest(hash); + break; case STORAGE_TRIE_NODE: - return createStorageDataRequest(hash); + deserialized = createStorageDataRequest(hash); + break; case CODE: - return createCodeRequest(hash); + deserialized = createCodeRequest(hash); + break; default: throw new IllegalArgumentException( "Unable to deserialize provided data into a valid " + NodeDataRequest.class.getSimpleName()); } + + deserialized.setFailureCount(failureCount); + return deserialized; } private void writeTo(final RLPOutput out) { out.startList(); out.writeByte(requestType.getValue()); out.writeBytesValue(hash); + out.writeIntScalar(failedRequestCount.get()); out.endList(); } @@ -94,6 +105,14 @@ public NodeDataRequest setData(final BytesValue data) { return this; } + public int trackFailure() { + return failedRequestCount.incrementAndGet(); + } + + private void setFailureCount(final int failures) { + failedRequestCount.set(failures); + } + public abstract void persist(final WorldStateStorage.Updater updater); public abstract Stream getChildRequests(); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/StalledDownloadException.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/StalledDownloadException.java new file mode 100644 index 0000000000..20a15564cc --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/StalledDownloadException.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.eth.sync.worldstate; + +public class StalledDownloadException extends WorldStateDownloaderException { + + public StalledDownloadException(final String message) { + super(message); + } +} diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java index f55442e130..308ad1daaa 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java @@ -64,6 +64,7 @@ private enum Status { private final TaskQueue pendingRequests; private final int hashCountPerRequest; private final int maxOutstandingRequests; + private final int maxNodeRequestRetries; private final Set> outstandingRequests = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final LabelledMetric ethTasksTimer; @@ -79,6 +80,7 @@ public WorldStateDownloader( final TaskQueue pendingRequests, final int hashCountPerRequest, final int maxOutstandingRequests, + final int maxNodeRequestRetries, final LabelledMetric ethTasksTimer, final MetricsSystem metricsSystem) { this.ethContext = ethContext; @@ -86,6 +88,7 @@ public WorldStateDownloader( this.pendingRequests = pendingRequests; this.hashCountPerRequest = hashCountPerRequest; this.maxOutstandingRequests = maxOutstandingRequests; + this.maxNodeRequestRetries = maxNodeRequestRetries; this.ethTasksTimer = ethTasksTimer; metricsSystem.createGauge( MetricCategory.SYNCHRONIZER, @@ -236,6 +239,10 @@ private CompletableFuture>> sendAndProcessRequ BytesValue matchingData = requestFailed ? null : data.get(request.getHash()); if (matchingData == null) { retriedRequestsTotal.inc(); + int requestFailures = request.trackFailure(); + if (requestFailures > maxNodeRequestRetries) { + handleStalledDownload(); + } task.markFailed(); } else { completedRequestsCounter.inc(); @@ -275,14 +282,26 @@ private CompletableFuture createFuture() { (res, err) -> { // Handle cancellations if (future.isCancelled()) { - handleCancellation(); + LOG.info("World state download cancelled"); + doCancelDownload(); + } else if (err != null) { + LOG.info("World state download failed. ", err); + doCancelDownload(); } }); return future; } - private synchronized void handleCancellation() { - LOG.info("World state download cancelled"); + private synchronized void handleStalledDownload() { + final String message = + "Download stalled due to too many failures to retrieve node data (>" + + maxNodeRequestRetries + + " failures)"; + WorldStateDownloaderException e = new StalledDownloadException(message); + future.completeExceptionally(e); + } + + private synchronized void doCancelDownload() { status = Status.CANCELLED; pendingRequests.clear(); for (EthTask outstandingRequest : outstandingRequests) { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateUnavailableException.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderException.java similarity index 80% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateUnavailableException.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderException.java index b98d94685c..97fa35ae10 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateUnavailableException.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderException.java @@ -12,4 +12,9 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.worldstate; -public class WorldStateUnavailableException extends RuntimeException {} +public class WorldStateDownloaderException extends RuntimeException { + + public WorldStateDownloaderException(final String message) { + super(message); + } +} diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index b674e5093f..2bf0a31850 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -26,8 +26,8 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; +import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.StalledDownloadException; import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.WorldStateDownloader; -import tech.pegasys.pantheon.ethereum.eth.sync.worldstate.WorldStateUnavailableException; import java.util.concurrent.CompletableFuture; @@ -292,7 +292,7 @@ public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable() assertThat(result).isNotDone(); - firstWorldStateFuture.completeExceptionally(new WorldStateUnavailableException()); + firstWorldStateFuture.completeExceptionally(new StalledDownloadException("test")); assertThat(result).isNotDone(); assertThat(chainFuture).isCancelled(); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java index 9a3a4be6da..102f955f56 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloaderTest.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.eth.sync.worldstate; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -30,12 +31,14 @@ import tech.pegasys.pantheon.ethereum.core.MutableWorldState; import tech.pegasys.pantheon.ethereum.core.WorldState; import tech.pegasys.pantheon.ethereum.eth.manager.DeterministicEthScheduler.TimeoutPolicy; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; import tech.pegasys.pantheon.ethereum.eth.messages.GetNodeDataMessage; +import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.rlp.RLP; @@ -127,14 +130,7 @@ public void downloadEmptyWorldState() { WorldStateStorage localStorage = new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); WorldStateDownloader downloader = - new WorldStateDownloader( - ethProtocolManager.ethContext(), - localStorage, - queue, - 10, - 10, - NoOpMetricsSystem.NO_OP_LABELLED_TIMER, - new NoOpMetricsSystem()); + createDownloader(ethProtocolManager.ethContext(), localStorage, queue); CompletableFuture future = downloader.run(header); assertThat(future).isDone(); @@ -172,14 +168,7 @@ public void downloadAlreadyAvailableWorldState() { TaskQueue queue = new InMemoryTaskQueue<>(); WorldStateDownloader downloader = - new WorldStateDownloader( - ethProtocolManager.ethContext(), - storage, - queue, - 10, - 10, - NoOpMetricsSystem.NO_OP_LABELLED_TIMER, - new NoOpMetricsSystem()); + createDownloader(ethProtocolManager.ethContext(), storage, queue); CompletableFuture future = downloader.run(header); assertThat(future).isDone(); @@ -220,14 +209,7 @@ public void canRecoverFromTimeouts() { WorldStateStorage localStorage = new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); WorldStateDownloader downloader = - new WorldStateDownloader( - ethProtocolManager.ethContext(), - localStorage, - queue, - 10, - 10, - NoOpMetricsSystem.NO_OP_LABELLED_TIMER, - new NoOpMetricsSystem()); + createDownloader(ethProtocolManager.ethContext(), localStorage, queue); CompletableFuture result = downloader.run(header); @@ -289,14 +271,7 @@ public void doesNotRequestKnownCodeFromNetwork() { localStorageUpdater.commit(); WorldStateDownloader downloader = - new WorldStateDownloader( - ethProtocolManager.ethContext(), - localStorage, - queue, - 10, - 10, - NoOpMetricsSystem.NO_OP_LABELLED_TIMER, - new NoOpMetricsSystem()); + createDownloader(ethProtocolManager.ethContext(), localStorage, queue); CompletableFuture result = downloader.run(header); @@ -369,14 +344,7 @@ private void testCancellation(final boolean shouldCancelFuture) { new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); WorldStateDownloader downloader = - new WorldStateDownloader( - ethProtocolManager.ethContext(), - localStorage, - queue, - 10, - 10, - NoOpMetricsSystem.NO_OP_LABELLED_TIMER, - new NoOpMetricsSystem()); + createDownloader(ethProtocolManager.ethContext(), localStorage, queue); CompletableFuture result = downloader.run(header); @@ -464,14 +432,7 @@ public void doesRequestKnownAccountTrieNodesFromNetwork() { localStorageUpdater.commit(); WorldStateDownloader downloader = - new WorldStateDownloader( - ethProtocolManager.ethContext(), - localStorage, - queue, - 10, - 10, - NoOpMetricsSystem.NO_OP_LABELLED_TIMER, - new NoOpMetricsSystem()); + createDownloader(ethProtocolManager.ethContext(), localStorage, queue); CompletableFuture result = downloader.run(header); @@ -570,14 +531,7 @@ public void doesRequestKnownStorageTrieNodesFromNetwork() { localStorageUpdater.commit(); WorldStateDownloader downloader = - new WorldStateDownloader( - ethProtocolManager.ethContext(), - localStorage, - queue, - 10, - 10, - NoOpMetricsSystem.NO_OP_LABELLED_TIMER, - new NoOpMetricsSystem()); + createDownloader(ethProtocolManager.ethContext(), localStorage, queue); CompletableFuture result = downloader.run(header); @@ -616,6 +570,70 @@ public void doesRequestKnownStorageTrieNodesFromNetwork() { assertAccountsMatch(localWorldState, accounts); } + @Test + public void stalledDownloader() { + simulateStalledDownload(10); + } + + @Test + public void stalledDownloaderWithOneRetry() { + simulateStalledDownload(1); + } + + @Test + public void stalledDownloaderWithNoRetries() { + simulateStalledDownload(0); + } + + private void simulateStalledDownload(final int maxRetries) { + final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(); + BlockDataGenerator dataGen = new BlockDataGenerator(1); + + // Setup "remote" state + final WorldStateStorage remoteStorage = + new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); + final WorldStateArchive remoteWorldStateArchive = new WorldStateArchive(remoteStorage); + final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); + + // Generate accounts and save corresponding state root + dataGen.createRandomAccounts(remoteWorldState, 10); + final Hash stateRoot = remoteWorldState.rootHash(); + assertThat(stateRoot).isNotEqualTo(EMPTY_TRIE_ROOT); // Sanity check + final BlockHeader header = + dataGen.block(BlockOptions.create().setStateRoot(stateRoot).setBlockNumber(10)).getHeader(); + + TaskQueue queue = new InMemoryTaskQueue<>(); + WorldStateStorage localStorage = + new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); + SynchronizerConfiguration syncConfig = + SynchronizerConfiguration.builder().worldStateRequestMaxRetries(maxRetries).build(); + WorldStateDownloader downloader = + createDownloader(syncConfig, ethProtocolManager.ethContext(), localStorage, queue); + + // Create a peer that can respond + RespondingEthPeer peer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, header.getNumber()); + + // Start downloader + CompletableFuture result = downloader.run(header); + // A second run should return an error without impacting the first result + CompletableFuture secondResult = downloader.run(header); + assertThat(secondResult).isCompletedExceptionally(); + assertThat(result).isNotCompletedExceptionally(); + + Responder emptyResponder = RespondingEthPeer.emptyResponder(); + for (int i = 0; i < maxRetries; i++) { + peer.respond(emptyResponder); + } + // Downloader should not be done yet + assertThat(result).isNotDone(); + + // One more empty response should trigger a failure + peer.respond(emptyResponder); + assertThat(result).isCompletedExceptionally(); + assertThatThrownBy(result::get).hasCauseInstanceOf(StalledDownloadException.class); + } + /** * Walks through trie represented by the given rootHash and returns hash-node pairs that would * need to be requested from the network in order to reconstruct this trie. @@ -700,15 +718,13 @@ private void downloadAvailableWorldStateFromPeers( WorldStateStorage localStorage = new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); WorldStateArchive localWorldStateArchive = new WorldStateArchive(localStorage); + SynchronizerConfiguration syncConfig = + SynchronizerConfiguration.builder() + .worldStateHashCountPerRequest(hashesPerRequest) + .worldStateRequestParallelism(maxOutstandingRequests) + .build(); WorldStateDownloader downloader = - new WorldStateDownloader( - ethProtocolManager.ethContext(), - localStorage, - queue, - hashesPerRequest, - maxOutstandingRequests, - NoOpMetricsSystem.NO_OP_LABELLED_TIMER, - new NoOpMetricsSystem()); + createDownloader(syncConfig, ethProtocolManager.ethContext(), localStorage, queue); // Create some peers that can respond List usefulPeers = @@ -828,6 +844,29 @@ private void assertAccountsMatch( } } + private WorldStateDownloader createDownloader( + final EthContext context, + final WorldStateStorage storage, + final TaskQueue queue) { + return createDownloader(SynchronizerConfiguration.builder().build(), context, storage, queue); + } + + private WorldStateDownloader createDownloader( + final SynchronizerConfiguration config, + final EthContext context, + final WorldStateStorage storage, + final TaskQueue queue) { + return new WorldStateDownloader( + context, + storage, + queue, + config.getWorldStateHashCountPerRequest(), + config.getWorldStateRequestParallelism(), + config.getWorldStateRequestMaxRetries(), + NoOpMetricsSystem.NO_OP_LABELLED_TIMER, + new NoOpMetricsSystem()); + } + @FunctionalInterface private interface NetworkResponder { void respond( From bfbdd94d6aa3555d4b50ff7ade6adb104266a22f Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 19 Feb 2019 06:03:44 +1000 Subject: [PATCH 11/21] Ensure exceptions in suppliers passed to EthScheduler are propagated to the returned CompletableFuture. (#884) --- .../ethereum/eth/manager/EthScheduler.java | 10 +++----- .../pegasys/pantheon/util/FutureUtils.java | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java index 9903ece9e2..4735c882c6 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java @@ -100,7 +100,7 @@ public CompletableFuture scheduleSyncWorkerTask( final Supplier> future) { final CompletableFuture promise = new CompletableFuture<>(); final Future workerFuture = - syncWorkerExecutor.submit(() -> propagateResult(future.get(), promise)); + syncWorkerExecutor.submit(() -> propagateResult(future, promise)); // If returned promise is cancelled, cancel the worker future promise.whenComplete( (r, t) -> { @@ -139,8 +139,8 @@ public CompletableFuture scheduleFutureTask( try { command.run(); promise.complete(null); - } catch (final Exception e) { - promise.completeExceptionally(e); + } catch (final Throwable t) { + promise.completeExceptionally(t); } }, duration.toMillis(), @@ -160,9 +160,7 @@ public CompletableFuture scheduleFutureTask( final CompletableFuture promise = new CompletableFuture<>(); final ScheduledFuture scheduledFuture = scheduler.schedule( - () -> propagateResult(future.get(), promise), - duration.toMillis(), - TimeUnit.MILLISECONDS); + () -> propagateResult(future, promise), duration.toMillis(), TimeUnit.MILLISECONDS); // If returned promise is cancelled, cancel scheduled task promise.whenComplete( (r, t) -> { diff --git a/util/src/main/java/tech/pegasys/pantheon/util/FutureUtils.java b/util/src/main/java/tech/pegasys/pantheon/util/FutureUtils.java index fd1aa79621..86c9f63811 100644 --- a/util/src/main/java/tech/pegasys/pantheon/util/FutureUtils.java +++ b/util/src/main/java/tech/pegasys/pantheon/util/FutureUtils.java @@ -17,6 +17,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; +import java.util.function.Supplier; public class FutureUtils { @@ -84,4 +85,28 @@ public static void propagateResult( } }); } + + /** + * Propagates the result of the {@link CompletableFuture} returned from a {@link Supplier} to a + * different {@link CompletableFuture}. + * + *

    When from completes successfully, to will be completed + * successfully with the same value. When from completes exceptionally, to + * will be completed exceptionally with the same exception. + * + *

    If the Supplier throws an exception, the target CompletableFuture will be completed + * exceptionally with the thrown exception. + * + * @param from the Supplier to get the CompletableFuture to take results and exceptions from + * @param to the CompletableFuture to propagate results and exceptions to + * @param the type of the success value + */ + public static void propagateResult( + final Supplier> from, final CompletableFuture to) { + try { + propagateResult(from.get(), to); + } catch (final Throwable t) { + to.completeExceptionally(t); + } + } } From c76ef70b2b7429e86256178906ea45e7cef6ff79 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 19 Feb 2019 06:14:37 +1000 Subject: [PATCH 12/21] Reduce logging noise from invalid peer discovery packets and handshaking (#876) * Reduce logging for invalid peer discovery packets. The message is enough to locate the source of the rejection. * Reduce ECIESHandshaker logging to trace since it documents a normal flow through the handshake process. --- .../p2p/discovery/VertxPeerDiscoveryAgent.java | 2 +- .../p2p/rlpx/handshake/ecies/ECIESHandshaker.java | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java index 5032ba72b6..f9574c6d4d 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java @@ -189,7 +189,7 @@ private void handlePacket(final DatagramPacket datagram) { final Endpoint endpoint = new Endpoint(host, port, OptionalInt.empty()); handleIncomingPacket(endpoint, packet); } catch (final PeerDiscoveryPacketDecodingException e) { - LOG.debug("Discarding invalid peer discovery packet", e); + LOG.debug("Discarding invalid peer discovery packet: {}", e.getMessage()); } catch (final Throwable t) { LOG.error("Encountered error while handling packet", t); } diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/handshake/ecies/ECIESHandshaker.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/handshake/ecies/ECIESHandshaker.java index 06179a3da9..d1271c1c44 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/handshake/ecies/ECIESHandshaker.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/handshake/ecies/ECIESHandshaker.java @@ -94,7 +94,7 @@ public void prepareInitiator(final SECP256K1.KeyPair ourKeypair, final PublicKey this.ephKeyPair = SECP256K1.KeyPair.generate(); this.partyPubKey = theirPubKey; this.initiatorNonce = Bytes32.wrap(random(32), 0); - LOG.debug( + LOG.trace( "Prepared ECIES handshake with node {}... under INITIATOR role", theirPubKey.getEncodedBytes().slice(0, 16)); } @@ -110,7 +110,7 @@ public void prepareResponder(final SECP256K1.KeyPair ourKeypair) { this.identityKeyPair = ourKeypair; this.ephKeyPair = SECP256K1.KeyPair.generate(); this.responderNonce = Bytes32.wrap(random(32), 0); - LOG.debug("Prepared ECIES handshake under RESPONDER role"); + LOG.trace("Prepared ECIES handshake under RESPONDER role"); } @Override @@ -147,7 +147,7 @@ public ByteBuf firstMessage() throws HandshakeException { throw new HandshakeException("Encrypting the first handshake message failed", e); } - LOG.debug("First ECIES handshake message under INITIATOR role: {}", initiatorMsg); + LOG.trace("First ECIES handshake message under INITIATOR role: {}", initiatorMsg); return Unpooled.wrappedBuffer(initiatorMsgEnc.extractArray()); } @@ -230,7 +230,7 @@ public Optional handleMessage(final ByteBuf buf) throws HandshakeExcept responderNonce = responderMsg.getNonce(); partyEphPubKey = responderMsg.getEphPublicKey(); - LOG.debug( + LOG.trace( "Received responder's ECIES handshake message from node {}...: {}", partyPubKey.getEncodedBytes().slice(0, 16), responderMsg); @@ -251,7 +251,7 @@ public Optional handleMessage(final ByteBuf buf) throws HandshakeExcept initiatorMsg = InitiatorHandshakeMessageV1.decode(bytes, identityKeyPair); } - LOG.debug( + LOG.trace( "[{}] Received initiator's ECIES handshake message: {}", identityKeyPair.getPublicKey().getEncodedBytes(), initiatorMsg); @@ -274,7 +274,7 @@ public Optional handleMessage(final ByteBuf buf) throws HandshakeExcept ResponderHandshakeMessageV1.create(ephKeyPair.getPublicKey(), responderNonce, false); } - LOG.debug( + LOG.trace( "Generated responder's ECIES handshake message against peer {}...: {}", partyPubKey.getEncodedBytes().slice(0, 16), responderMsg); @@ -296,7 +296,7 @@ public Optional handleMessage(final ByteBuf buf) throws HandshakeExcept computeSecrets(); status.set(Handshaker.HandshakeStatus.SUCCESS); - LOG.debug("Handshake status set to {}", status.get()); + LOG.trace("Handshake status set to {}", status.get()); return nextMsg.map(bv -> Unpooled.wrappedBuffer(bv.extractArray())); } From 7902ea798fcbd356324f53ababe54c167ab72266 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 19 Feb 2019 06:27:35 +1000 Subject: [PATCH 13/21] Add metrics for EthScheduler executors (#878) --- consensus/ibftlegacy/build.gradle | 1 + .../protocol/Istanbul64ProtocolManager.java | 7 +- .../eth/manager/EthProtocolManager.java | 12 +- .../ethereum/eth/manager/EthScheduler.java | 46 ++--- .../eth/manager/MonitoredExecutors.java | 140 +++++++++++++ .../eth/manager/EthProtocolManagerTest.java | 187 ++++++++++++++++-- .../fastsync/FastSyncChainDownloaderTest.java | 2 +- .../sync/fullsync/FullSyncDownloaderTest.java | 2 +- .../fullsync/FullSyncTargetManagerTest.java | 2 +- .../ethereum/eth/transactions/TestNode.java | 3 +- .../pantheon/metrics/MetricCategory.java | 1 + .../pantheon/metrics/MetricsSystem.java | 16 ++ .../controller/CliquePantheonController.java | 3 +- .../IbftLegacyPantheonController.java | 6 +- .../controller/IbftPantheonController.java | 3 +- .../controller/MainnetPantheonController.java | 3 +- .../tech/pegasys/pantheon/RunnerTest.java | 2 +- 17 files changed, 371 insertions(+), 65 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/MonitoredExecutors.java diff --git a/consensus/ibftlegacy/build.gradle b/consensus/ibftlegacy/build.gradle index b3f572e6c6..e45c0bb85a 100644 --- a/consensus/ibftlegacy/build.gradle +++ b/consensus/ibftlegacy/build.gradle @@ -23,6 +23,7 @@ dependencies { implementation project(':ethereum:jsonrpc') implementation project(':ethereum:rlp') implementation project(':ethereum:p2p') + implementation project(':metrics') implementation project(':services:kvstore') implementation 'com.google.guava:guava' diff --git a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/protocol/Istanbul64ProtocolManager.java b/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/protocol/Istanbul64ProtocolManager.java index 7d0c0b1294..b4aea06729 100644 --- a/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/protocol/Istanbul64ProtocolManager.java +++ b/consensus/ibftlegacy/src/main/java/tech/pegasys/pantheon/consensus/ibftlegacy/protocol/Istanbul64ProtocolManager.java @@ -18,6 +18,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.Message; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; +import tech.pegasys.pantheon.metrics.MetricsSystem; import java.util.List; @@ -33,7 +34,8 @@ public Istanbul64ProtocolManager( final boolean fastSyncEnabled, final int syncWorkers, final int txWorkers, - final int computationWorkers) { + final int computationWorkers, + final MetricsSystem metricsSystem) { super( blockchain, worldStateArchive, @@ -41,7 +43,8 @@ public Istanbul64ProtocolManager( fastSyncEnabled, syncWorkers, txWorkers, - computationWorkers); + computationWorkers, + metricsSystem); } @Override diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManager.java index 53373518f6..86d9ebc1d6 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManager.java @@ -31,6 +31,7 @@ import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; import tech.pegasys.pantheon.ethereum.rlp.RLPException; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; +import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.uint.UInt256; import java.util.Arrays; @@ -96,14 +97,15 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver { final int syncWorkers, final int txWorkers, final int computationWorkers, - final int requestLimit) { + final int requestLimit, + final MetricsSystem metricsSystem) { this( blockchain, worldStateArchive, networkId, fastSyncEnabled, requestLimit, - new EthScheduler(syncWorkers, txWorkers, computationWorkers)); + new EthScheduler(syncWorkers, txWorkers, computationWorkers, metricsSystem)); } public EthProtocolManager( @@ -113,7 +115,8 @@ public EthProtocolManager( final boolean fastSyncEnabled, final int syncWorkers, final int txWorkers, - final int computationWorkers) { + final int computationWorkers, + final MetricsSystem metricsSystem) { this( blockchain, worldStateArchive, @@ -122,7 +125,8 @@ public EthProtocolManager( syncWorkers, txWorkers, computationWorkers, - DEFAULT_REQUEST_LIMIT); + DEFAULT_REQUEST_LIMIT, + metricsSystem); } public EthContext ethContext() { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java index 4735c882c6..c87da2c794 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java @@ -12,8 +12,12 @@ */ package tech.pegasys.pantheon.ethereum.eth.manager; +import static tech.pegasys.pantheon.ethereum.eth.manager.MonitoredExecutors.newCachedThreadPool; +import static tech.pegasys.pantheon.ethereum.eth.manager.MonitoredExecutors.newFixedThreadPool; +import static tech.pegasys.pantheon.ethereum.eth.manager.MonitoredExecutors.newScheduledThreadPool; import static tech.pegasys.pantheon.util.FutureUtils.propagateResult; +import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.ExceptionUtils; import java.time.Duration; @@ -23,7 +27,6 @@ import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -33,7 +36,6 @@ import java.util.function.Function; import java.util.function.Supplier; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -51,36 +53,24 @@ public class EthScheduler { private final ExecutorService servicesExecutor; private final ExecutorService computationExecutor; - private Collection> serviceFutures = new ConcurrentLinkedDeque<>(); + private final Collection> serviceFutures = new ConcurrentLinkedDeque<>(); public EthScheduler( - final int syncWorkerCount, final int txWorkerCount, final int computationWorkerCount) { + final int syncWorkerCount, + final int txWorkerCount, + final int computationWorkerCount, + final MetricsSystem metricsSystem) { this( - Executors.newFixedThreadPool( - syncWorkerCount, - new ThreadFactoryBuilder() - .setNameFormat(EthScheduler.class.getSimpleName() + "-Workers-%d") - .build()), - Executors.newScheduledThreadPool( - 1, - new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat(EthScheduler.class.getSimpleName() + "Timer-%d") - .build()), - Executors.newFixedThreadPool( - txWorkerCount, - new ThreadFactoryBuilder() - .setNameFormat(EthScheduler.class.getSimpleName() + "-Transactions-%d") - .build()), - Executors.newCachedThreadPool( - new ThreadFactoryBuilder() - .setNameFormat(EthScheduler.class.getSimpleName() + "-Services-%d") - .build()), - Executors.newFixedThreadPool( + newFixedThreadPool( + EthScheduler.class.getSimpleName() + "-Workers", syncWorkerCount, metricsSystem), + newScheduledThreadPool(EthScheduler.class.getSimpleName() + "-Timer", 1, metricsSystem), + newFixedThreadPool( + EthScheduler.class.getSimpleName() + "-Transactions", txWorkerCount, metricsSystem), + newCachedThreadPool(EthScheduler.class.getSimpleName() + "-Services", metricsSystem), + newFixedThreadPool( + EthScheduler.class.getSimpleName() + "-Computation", computationWorkerCount, - new ThreadFactoryBuilder() - .setNameFormat(EthScheduler.class.getSimpleName() + "-Computation-%d") - .build())); + metricsSystem)); } protected EthScheduler( diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/MonitoredExecutors.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/MonitoredExecutors.java new file mode 100644 index 0000000000..b13deea9b3 --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/MonitoredExecutors.java @@ -0,0 +1,140 @@ +/* + * 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.ethereum.eth.manager; + +import tech.pegasys.pantheon.metrics.Counter; +import tech.pegasys.pantheon.metrics.MetricCategory; +import tech.pegasys.pantheon.metrics.MetricsSystem; + +import java.util.Locale; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor.AbortPolicy; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +public class MonitoredExecutors { + + public static ExecutorService newFixedThreadPool( + final String name, final int workerCount, final MetricsSystem metricsSystem) { + return newMonitoredExecutor( + name, + metricsSystem, + (rejectedExecutionHandler, threadFactory) -> + new ThreadPoolExecutor( + workerCount, + workerCount, + 0L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), + threadFactory, + rejectedExecutionHandler)); + } + + public static ExecutorService newCachedThreadPool( + final String name, final MetricsSystem metricsSystem) { + return newMonitoredExecutor( + name, + metricsSystem, + (rejectedExecutionHandler, threadFactory) -> + new ThreadPoolExecutor( + 0, + Integer.MAX_VALUE, + 60L, + TimeUnit.SECONDS, + new SynchronousQueue(), + threadFactory, + rejectedExecutionHandler)); + } + + public static ScheduledExecutorService newScheduledThreadPool( + final String name, final int corePoolSize, final MetricsSystem metricsSystem) { + return newMonitoredExecutor( + name, + metricsSystem, + (rejectedExecutionHandler, threadFactory) -> + new ScheduledThreadPoolExecutor(corePoolSize, threadFactory, rejectedExecutionHandler)); + } + + private static T newMonitoredExecutor( + final String name, + final MetricsSystem metricsSystem, + final BiFunction creator) { + + final String metricName = name.toLowerCase(Locale.US).replace('-', '_'); + + final T executor = + creator.apply( + new CountingAbortPolicy(metricName, metricsSystem), + new ThreadFactoryBuilder().setNameFormat(name + "-%d").build()); + + metricsSystem.createIntegerGauge( + MetricCategory.EXECUTORS, + metricName + "_queue_length_current", + "Current number of tasks awaiting execution", + executor.getQueue()::size); + + metricsSystem.createIntegerGauge( + MetricCategory.EXECUTORS, + metricName + "_active_threads_current", + "Current number of threads executing tasks", + executor::getActiveCount); + + metricsSystem.createIntegerGauge( + MetricCategory.EXECUTORS, + metricName + "_pool_size_current", + "Current number of threads in the thread pool", + executor::getPoolSize); + + metricsSystem.createLongGauge( + MetricCategory.EXECUTORS, + metricName + "_completed_tasks_total", + "Total number of tasks executed", + executor::getCompletedTaskCount); + + metricsSystem.createLongGauge( + MetricCategory.EXECUTORS, + metricName + "_submitted_tasks_total", + "Total number of tasks executed", + executor::getTaskCount); + + return executor; + } + + private static class CountingAbortPolicy extends AbortPolicy { + + private final Counter rejectedTaskCounter; + + public CountingAbortPolicy(final String metricName, final MetricsSystem metricsSystem) { + this.rejectedTaskCounter = + metricsSystem.createCounter( + MetricCategory.EXECUTORS, + metricName + "_rejected_tasks_total", + "Total number of tasks rejected by this executor"); + } + + @Override + public void rejectedExecution(final Runnable r, final ThreadPoolExecutor e) { + rejectedTaskCounter.inc(); + super.rejectedExecution(r, e); + } + } +} diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTest.java index e89d79ffa7..25000a994d 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTest.java @@ -57,6 +57,7 @@ import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage; import tech.pegasys.pantheon.ethereum.p2p.wire.RawMessage; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; +import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.uint.UInt256; @@ -104,7 +105,14 @@ public static void setup() { public void disconnectOnUnsolicitedMessage() { try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final MessageData messageData = BlockHeadersMessage.create(Collections.singletonList(blockchain.getBlockHeader(1).get())); final MockPeerConnection peer = setupPeer(ethManager, (cap, msg, conn) -> {}); @@ -117,7 +125,14 @@ public void disconnectOnUnsolicitedMessage() { public void disconnectOnFailureToSendStatusMessage() { try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final MessageData messageData = BlockHeadersMessage.create(Collections.singletonList(blockchain.getBlockHeader(1).get())); final MockPeerConnection peer = @@ -131,7 +146,14 @@ public void disconnectOnFailureToSendStatusMessage() { public void disconnectOnWrongChainId() { try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final MessageData messageData = BlockHeadersMessage.create(Collections.singletonList(blockchain.getBlockHeader(1).get())); final MockPeerConnection peer = @@ -156,7 +178,14 @@ public void disconnectOnWrongChainId() { public void disconnectOnWrongGenesisHash() { try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final MessageData messageData = BlockHeadersMessage.create(Collections.singletonList(blockchain.getBlockHeader(1).get())); final MockPeerConnection peer = @@ -181,7 +210,14 @@ public void disconnectOnWrongGenesisHash() { public void doNotDisconnectOnValidMessage() { try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final MessageData messageData = GetBlockBodiesMessage.create(Collections.singletonList(gen.hash())); final MockPeerConnection peer = setupPeer(ethManager, (cap, msg, conn) -> {}); @@ -198,7 +234,14 @@ public void respondToGetHeaders() throws ExecutionException, InterruptedExceptio final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final long startBlock = 5L; final int blockCount = 5; final MessageData messageData = @@ -231,7 +274,15 @@ public void respondToGetHeadersWithinLimits() throws ExecutionException, Interru final int limit = 5; try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1, limit)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + limit, + new NoOpMetricsSystem())) { final long startBlock = 5L; final int blockCount = 10; final MessageData messageData = @@ -263,7 +314,14 @@ public void respondToGetHeadersReversed() throws ExecutionException, Interrupted final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final long endBlock = 10L; final int blockCount = 5; final MessageData messageData = GetBlockHeadersMessage.create(endBlock, blockCount, 0, true); @@ -294,7 +352,14 @@ public void respondToGetHeadersWithSkip() throws ExecutionException, Interrupted final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final long startBlock = 5L; final int blockCount = 5; final int skip = 1; @@ -328,7 +393,14 @@ public void respondToGetHeadersReversedWithSkip() final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final long endBlock = 10L; final int blockCount = 5; final int skip = 1; @@ -383,7 +455,14 @@ public void respondToGetHeadersPartial() throws ExecutionException, InterruptedE final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final long startBlock = blockchain.getChainHeadBlockNumber() - 1L; final int blockCount = 5; final MessageData messageData = @@ -415,7 +494,14 @@ public void respondToGetHeadersEmpty() throws ExecutionException, InterruptedExc final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final long startBlock = blockchain.getChainHeadBlockNumber() + 1; final int blockCount = 5; final MessageData messageData = @@ -444,7 +530,14 @@ public void respondToGetBodies() throws ExecutionException, InterruptedException final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { // Setup blocks query final long startBlock = blockchain.getChainHeadBlockNumber() - 5; final int blockCount = 2; @@ -489,7 +582,15 @@ public void respondToGetBodiesWithinLimits() throws ExecutionException, Interrup final int limit = 5; try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1, limit)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + limit, + new NoOpMetricsSystem())) { // Setup blocks query final int blockCount = 10; final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; @@ -533,7 +634,14 @@ public void respondToGetBodiesPartial() throws ExecutionException, InterruptedEx final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { // Setup blocks query final long expectedBlockNumber = blockchain.getChainHeadBlockNumber() - 1; final BlockHeader header = blockchain.getBlockHeader(expectedBlockNumber).get(); @@ -571,7 +679,14 @@ public void respondToGetReceipts() throws ExecutionException, InterruptedExcepti final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { // Setup blocks query final long startBlock = blockchain.getChainHeadBlockNumber() - 5; final int blockCount = 2; @@ -615,7 +730,15 @@ public void respondToGetReceiptsWithinLimits() throws ExecutionException, Interr final int limit = 5; try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1, limit)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + limit, + new NoOpMetricsSystem())) { // Setup blocks query final int blockCount = 10; final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; @@ -658,7 +781,14 @@ public void respondToGetReceiptsPartial() throws ExecutionException, Interrupted final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { // Setup blocks query final long blockNumber = blockchain.getChainHeadBlockNumber() - 5; final BlockHeader header = blockchain.getBlockHeader(blockNumber).get(); @@ -697,7 +827,8 @@ public void respondToGetNodeData() throws Exception { final WorldStateArchive worldStateArchive = protocolContext.getWorldStateArchive(); try (final EthProtocolManager ethManager = - new EthProtocolManager(blockchain, worldStateArchive, 1, true, 1, 1, 1)) { + new EthProtocolManager( + blockchain, worldStateArchive, 1, true, 1, 1, 1, new NoOpMetricsSystem())) { // Setup node data query final List expectedResults = new ArrayList<>(); @@ -740,7 +871,14 @@ public void respondToGetNodeData() throws Exception { public void newBlockMinedSendsNewBlockMessageToAllPeers() { final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1); + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem()); // Define handler to validate response final PeerSendHandler onSend = mock(PeerSendHandler.class); @@ -804,7 +942,14 @@ public void shouldSuccessfullyRespondToGetHeadersRequestLessThanZero() final CompletableFuture done = new CompletableFuture<>(); try (final EthProtocolManager ethManager = new EthProtocolManager( - blockchain, protocolContext.getWorldStateArchive(), 1, true, 1, 1, 1)) { + blockchain, + protocolContext.getWorldStateArchive(), + 1, + true, + 1, + 1, + 1, + new NoOpMetricsSystem())) { final long startBlock = 1L; final int requestedBlockCount = 13; final int receivedBlockCount = 2; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java index 1168ca7d6e..4df7085d06 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java @@ -69,7 +69,7 @@ public void setup() { localBlockchain, localBlockchainSetup.getWorldArchive(), DeterministicEthScheduler.TimeoutPolicy.NEVER, - new EthScheduler(1, 1, 1)); + new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); ethContext = ethProtocolManager.ethContext(); syncState = new SyncState(protocolContext.getBlockchain(), ethContext.getEthPeers()); } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java index a7392cf851..175407cc9e 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java @@ -88,7 +88,7 @@ public void setupTest() { localBlockchain, localBlockchainSetup.getWorldArchive(), DeterministicEthScheduler.TimeoutPolicy.NEVER, - new EthScheduler(1, 1, 1)); + new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); ethContext = ethProtocolManager.ethContext(); syncState = new SyncState(protocolContext.getBlockchain(), ethContext.getEthPeers()); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java index 880ae6949a..55b1d4539e 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java @@ -66,7 +66,7 @@ public void setup() { localBlockchain, localWorldState, DeterministicEthScheduler.TimeoutPolicy.NEVER, - new EthScheduler(1, 1, 1)); + new EthScheduler(1, 1, 1, new NoOpMetricsSystem())); final EthContext ethContext = ethProtocolManager.ethContext(); final SyncState syncState = new SyncState(protocolContext.getBlockchain(), ethContext.getEthPeers()); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java index 00cab49578..8cf7433a3e 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java @@ -104,7 +104,8 @@ public TestNode( final ProtocolContext protocolContext = new ProtocolContext<>(blockchain, worldStateArchive, null); final EthProtocolManager ethProtocolManager = - new EthProtocolManager(blockchain, worldStateArchive, 1, false, 1, 1, 1); + new EthProtocolManager( + blockchain, worldStateArchive, 1, false, 1, 1, 1, new NoOpMetricsSystem()); final NetworkRunner networkRunner = NetworkRunner.builder() diff --git a/metrics/src/main/java/tech/pegasys/pantheon/metrics/MetricCategory.java b/metrics/src/main/java/tech/pegasys/pantheon/metrics/MetricCategory.java index 110f25a88d..41a32b3889 100644 --- a/metrics/src/main/java/tech/pegasys/pantheon/metrics/MetricCategory.java +++ b/metrics/src/main/java/tech/pegasys/pantheon/metrics/MetricCategory.java @@ -15,6 +15,7 @@ public enum MetricCategory { BIG_QUEUE("big_queue"), BLOCKCHAIN("blockchain"), + EXECUTORS("executors"), JVM("jvm", false), NETWORK("network"), PEERS("peers"), diff --git a/metrics/src/main/java/tech/pegasys/pantheon/metrics/MetricsSystem.java b/metrics/src/main/java/tech/pegasys/pantheon/metrics/MetricsSystem.java index 2e95b93416..329c639d67 100644 --- a/metrics/src/main/java/tech/pegasys/pantheon/metrics/MetricsSystem.java +++ b/metrics/src/main/java/tech/pegasys/pantheon/metrics/MetricsSystem.java @@ -36,6 +36,22 @@ LabelledMetric createLabelledTimer( void createGauge( MetricCategory category, String name, String help, Supplier valueSupplier); + default void createIntegerGauge( + final MetricCategory category, + final String name, + final String help, + final Supplier valueSupplier) { + createGauge(category, name, help, () -> (double) valueSupplier.get()); + } + + default void createLongGauge( + final MetricCategory category, + final String name, + final String help, + final Supplier valueSupplier) { + createGauge(category, name, help, () -> (double) valueSupplier.get()); + } + Stream getMetrics(MetricCategory category); default Stream getMetrics() { diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java index 2b74d50075..111d1cf852 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/CliquePantheonController.java @@ -143,7 +143,8 @@ public static PantheonController init( fastSyncEnabled, syncConfig.downloaderParallelism(), syncConfig.transactionsParallelism(), - syncConfig.computationParallelism()); + syncConfig.computationParallelism(), + metricsSystem); final SyncState syncState = new SyncState(blockchain, ethProtocolManager.ethContext().getEthPeers()); final Synchronizer synchronizer = diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonController.java index 2c9703c0f7..7c92cd82b0 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonController.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonController.java @@ -135,7 +135,8 @@ public static PantheonController init( fastSyncEnabled, syncConfig.downloaderParallelism(), syncConfig.transactionsParallelism(), - syncConfig.computationParallelism()); + syncConfig.computationParallelism(), + metricsSystem); } else { ethSubProtocol = EthProtocol.get(); ethProtocolManager = @@ -146,7 +147,8 @@ public static PantheonController init( fastSyncEnabled, syncConfig.downloaderParallelism(), syncConfig.transactionsParallelism(), - syncConfig.computationParallelism()); + syncConfig.computationParallelism(), + metricsSystem); } final SyncState syncState = diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java index 533e215a96..9aac539eb5 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftPantheonController.java @@ -161,7 +161,8 @@ public static PantheonController init( fastSyncEnabled, syncConfig.downloaderParallelism(), syncConfig.transactionsParallelism(), - syncConfig.computationParallelism()); + syncConfig.computationParallelism(), + metricsSystem); final SubProtocol ethSubProtocol = EthProtocol.get(); final SyncState syncState = diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java index ee4edd0ec7..add89d5ad2 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/MainnetPantheonController.java @@ -115,7 +115,8 @@ public static PantheonController init( fastSyncEnabled, syncConfig.downloaderParallelism(), syncConfig.transactionsParallelism(), - syncConfig.computationParallelism()); + syncConfig.computationParallelism(), + metricsSystem); final SyncState syncState = new SyncState(blockchain, ethProtocolManager.ethContext().getEthPeers()); final Synchronizer synchronizer = diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java index aff095fe12..7f03c4926c 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java @@ -156,7 +156,7 @@ private void syncFromGenesis(final SyncMode mode) throws Exception { SynchronizerConfiguration.builder() .syncMode(mode) .fastSyncPivotDistance(5) - .fastSyncMaximumPeerWaitTime(Duration.ofSeconds(5)) + .fastSyncMaximumPeerWaitTime(Duration.ofSeconds(1)) .build(); final Path dataDirBehind = temp.newFolder().toPath(); final JsonRpcConfiguration behindJsonRpcConfiguration = jsonRpcConfiguration(); From 8dd15c4427ba56a7039f5b1d99413d5a0ff750bf Mon Sep 17 00:00:00 2001 From: "S. Matthew English" Date: Mon, 18 Feb 2019 15:49:33 -0500 Subject: [PATCH 14/21] [NC-2058] Improve block propagation time (#808) * o * add test * clean up * scaffolding * update * update * comments * add test * update * update ii * format * update ii * fix * verifyBroadcastBlockInvocation * test * update * update to difficulty calculation * remove BlockBroadcasterTest from this pr * update * update * update II --- .../ethereum/eth/manager/EthPeer.java | 1 + .../ethereum/eth/sync/BlockBroadcaster.java | 34 ++++++++++++++ .../eth/sync/BlockPropagationManager.java | 47 ++++++++++++++----- .../eth/sync/DefaultSynchronizer.java | 3 +- .../eth/sync/BlockPropagationManagerTest.java | 32 +++++++++++-- 5 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockBroadcaster.java diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java index 5fc4a56c80..06867e22e1 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java @@ -133,6 +133,7 @@ public ResponseStream send(final MessageData messageData) throws PeerNotConnecte } public void propagateBlock(final Block block, final UInt256 totalDifficulty) { + registerKnownBlock(block.getHash()); final NewBlockMessage newBlockMessage = NewBlockMessage.create(block, totalDifficulty); try { connection.sendForProtocol(protocolName, newBlockMessage); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockBroadcaster.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockBroadcaster.java new file mode 100644 index 0000000000..fecffaaf4c --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockBroadcaster.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.eth.sync; + +import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.util.uint.UInt256; + +class BlockBroadcaster { + + private final EthContext ethContext; + + BlockBroadcaster(final EthContext ethContext) { + this.ethContext = ethContext; + } + + void propagate(final Block block, final UInt256 difficulty) { + ethContext + .getEthPeers() + .availablePeers() + .filter(ethPeer -> !ethPeer.hasSeenBlock(block.getHash())) + .forEach(ethPeer -> ethPeer.propagateBlock(block, difficulty)); + } +} diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java index dca15d6539..a05764cd41 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java @@ -17,6 +17,7 @@ import tech.pegasys.pantheon.ethereum.chain.BlockAddedEvent.EventType; import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; @@ -30,8 +31,10 @@ import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetBlockFromPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.PersistBlockTask; +import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; import tech.pegasys.pantheon.ethereum.rlp.RLPException; import tech.pegasys.pantheon.metrics.LabelledMetric; @@ -63,6 +66,7 @@ public class BlockPropagationManager { private final EthContext ethContext; private final SyncState syncState; private final LabelledMetric ethTasksTimer; + private final BlockBroadcaster blockBroadcaster; private final AtomicBoolean started = new AtomicBoolean(false); @@ -77,13 +81,14 @@ public class BlockPropagationManager { final EthContext ethContext, final SyncState syncState, final PendingBlocks pendingBlocks, - final LabelledMetric ethTasksTimer) { + final LabelledMetric ethTasksTimer, + final BlockBroadcaster blockBroadcaster) { this.config = config; this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; this.ethContext = ethContext; this.ethTasksTimer = ethTasksTimer; - + this.blockBroadcaster = blockBroadcaster; this.syncState = syncState; this.pendingBlocks = pendingBlocks; } @@ -105,6 +110,32 @@ private void setupListeners() { .subscribe(EthPV62.NEW_BLOCK_HASHES, this::handleNewBlockHashesFromNetwork); } + protected void validateAndBroadcastBlock(final Block block) { + final ProtocolSpec protocolSpec = + protocolSchedule.getByBlockNumber(block.getHeader().getNumber()); + final BlockHeaderValidator blockHeaderValidator = protocolSpec.getBlockHeaderValidator(); + final BlockHeader parent = + protocolContext + .getBlockchain() + .getBlockHeader(block.getHeader().getParentHash()) + .orElseThrow( + () -> + new IllegalArgumentException( + "Incapable of retrieving header from non-existent parent of " + + block.getHeader().getNumber() + + ".")); + if (blockHeaderValidator.validateHeader( + block.getHeader(), parent, protocolContext, HeaderValidationMode.FULL)) { + final UInt256 totalDifficulty = + protocolContext + .getBlockchain() + .getTotalDifficultyByHash(parent.getHash()) + .get() + .plus(block.getHeader().getDifficulty()); + blockBroadcaster.propagate(block, totalDifficulty); + } + } + private void onBlockAdded(final BlockAddedEvent blockAddedEvent, final Blockchain blockchain) { // Check to see if any of our pending blocks are now ready for import final Block newBlock = blockAddedEvent.getBlock(); @@ -144,13 +175,6 @@ private void onBlockAdded(final BlockAddedEvent blockAddedEvent, final Blockchai } } - void broadcastBlock(final Block block, final UInt256 difficulty) { - ethContext - .getEthPeers() - .availablePeers() - .forEach(ethPeer -> ethPeer.propagateBlock(block, difficulty)); - } - void handleNewBlockFromNetwork(final EthMessage message) { final Blockchain blockchain = protocolContext.getBlockchain(); final NewBlockMessage newBlockMessage = NewBlockMessage.readFrom(message.getData()); @@ -158,9 +182,6 @@ void handleNewBlockFromNetwork(final EthMessage message) { final Block block = newBlockMessage.block(protocolSchedule); final UInt256 totalDifficulty = newBlockMessage.totalDifficulty(protocolSchedule); - // TODO: Extract broadcast functionality to independent class. - // broadcastBlock(block, totalDifficulty); - message.getPeer().chainState().updateForAnnouncedBlock(block.getHeader(), totalDifficulty); // Return early if we don't care about this block @@ -272,6 +293,8 @@ CompletableFuture importOrSavePendingBlock(final Block block) { return CompletableFuture.completedFuture(block); } + validateAndBroadcastBlock(block); + // Import block final PersistBlockTask importTask = PersistBlockTask.create( diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java index a177da7725..9a50612103 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/DefaultSynchronizer.java @@ -72,7 +72,8 @@ public DefaultSynchronizer( ethContext, syncState, new PendingBlocks(), - ethTasksTimer); + ethTasksTimer, + new BlockBroadcaster(ethContext)); ChainHeadTracker.trackChainHeadForPeers( ethContext, protocolSchedule, protocolContext.getBlockchain(), syncConfig, ethTasksTimer); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManagerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManagerTest.java index 71603e0a02..97f5239776 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManagerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManagerTest.java @@ -62,6 +62,7 @@ public class BlockPropagationManagerTest { private ProtocolSchedule protocolSchedule; private ProtocolContext protocolContext; private MutableBlockchain blockchain; + private BlockBroadcaster blockBroadcaster; private EthProtocolManager ethProtocolManager; private BlockPropagationManager blockPropagationManager; private SynchronizerConfiguration syncConfig; @@ -90,6 +91,7 @@ public void setup() { EthProtocolManagerTestUtil.create(blockchain, blockchainUtil.getWorldArchive()); syncConfig = SynchronizerConfiguration.builder().blockPropagationRange(-3, 5).build(); syncState = new SyncState(blockchain, ethProtocolManager.ethContext().getEthPeers()); + blockBroadcaster = mock(BlockBroadcaster.class); blockPropagationManager = new BlockPropagationManager<>( syncConfig, @@ -98,7 +100,8 @@ public void setup() { ethProtocolManager.ethContext(), syncState, pendingBlocks, - ethTasksTimer); + ethTasksTimer, + blockBroadcaster); } @Test @@ -471,7 +474,8 @@ public void purgesOldBlocks() { ethProtocolManager.ethContext(), syncState, pendingBlocks, - ethTasksTimer); + ethTasksTimer, + blockBroadcaster); final BlockDataGenerator gen = new BlockDataGenerator(); // Import some blocks @@ -551,7 +555,8 @@ public void shouldNotImportBlocksThatAreAlreadyBeingImported() { ethContext, syncState, pendingBlocks, - ethTasksTimer); + ethTasksTimer, + blockBroadcaster); blockchainUtil.importFirstBlocks(2); final Block nextBlock = blockchainUtil.getBlock(2); @@ -561,4 +566,25 @@ public void shouldNotImportBlocksThatAreAlreadyBeingImported() { verify(ethScheduler, times(1)).scheduleSyncWorkerTask(any(Supplier.class)); } + + @Test + public void verifyBroadcastBlockInvocation() { + blockchainUtil.importFirstBlocks(2); + final Block block = blockchainUtil.getBlock(2); + blockPropagationManager.start(); + + // Setup peer and messages + final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 0); + + final UInt256 totalDifficulty = fullBlockchain.getTotalDifficultyByHash(block.getHash()).get(); + final NewBlockMessage newBlockMessage = NewBlockMessage.create(block, totalDifficulty); + + // Broadcast message + EthProtocolManagerTestUtil.broadcastMessage(ethProtocolManager, peer, newBlockMessage); + + final Responder responder = RespondingEthPeer.blockchainResponder(fullBlockchain); + peer.respondWhile(responder, peer::hasOutstandingRequests); + + verify(blockBroadcaster, times(1)).propagate(block, totalDifficulty); + } } From 9e0d4de336175e0a51644e9f17aea1edf4fb19c0 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Tue, 19 Feb 2019 07:09:14 +1000 Subject: [PATCH 15/21] [NC-2046] websocket method permissions (#870) --- ...PermissionJsonRpcUnauthorizedResponse.java | 32 ++++++ .../tests/acceptance/dsl/jsonrpc/Net.java | 5 + .../net/NetVersionTransaction.java | 1 - .../WebsocketServiceLoginAcceptanceTest.java | 7 ++ .../ethereum/jsonrpc/JsonRpcHttpService.java | 74 ++------------ .../authentication/AuthenticationService.java | 1 + .../authentication/AuthenticationUtils.java | 89 +++++++++++++++++ .../websocket/WebSocketRequestHandler.java | 20 +++- .../jsonrpc/websocket/WebSocketService.java | 16 ++- .../jsonrpc/JsonRpcHttpServiceLoginTest.java | 31 ++++-- .../websocket/WebSocketServiceLoginTest.java | 98 ++++++++++++++++++- .../resources/JsonRpcHttpService/auth.toml | 2 +- 12 files changed, 299 insertions(+), 77 deletions(-) create mode 100644 acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionJsonRpcUnauthorizedResponse.java create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtils.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionJsonRpcUnauthorizedResponse.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionJsonRpcUnauthorizedResponse.java new file mode 100644 index 0000000000..faa62f403e --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/net/ExpectNetVersionPermissionJsonRpcUnauthorizedResponse.java @@ -0,0 +1,32 @@ +/* + * 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.tests.acceptance.dsl.condition.net; + +import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; +import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.NetVersionTransaction; + +public class ExpectNetVersionPermissionJsonRpcUnauthorizedResponse implements Condition { + + private final NetVersionTransaction transaction; + + public ExpectNetVersionPermissionJsonRpcUnauthorizedResponse( + final NetVersionTransaction transaction) { + this.transaction = transaction; + } + + @Override + public void verify(final Node node) { + node.execute(transaction); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Net.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Net.java index 001b33aad4..0b8ba3226c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Net.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Net.java @@ -19,6 +19,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.condition.net.ExpectNetVersionConnectionExceptionWithCause; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.net.ExpectNetVersionIsNotBlank; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.net.ExpectNetVersionPermissionException; +import tech.pegasys.pantheon.tests.acceptance.dsl.condition.net.ExpectNetVersionPermissionJsonRpcUnauthorizedResponse; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.NetTransactions; import java.math.BigInteger; @@ -51,6 +52,10 @@ public Condition netVersionUnauthorizedExceptional(final String expectedMessage) return new ExpectNetVersionPermissionException(transactions.netVersion(), expectedMessage); } + public Condition netVersionUnauthorizedResponse() { + return new ExpectNetVersionPermissionJsonRpcUnauthorizedResponse(transactions.netVersion()); + } + public Condition awaitPeerCountExceptional() { return new AwaitNetPeerCountException(transactions.peerCount()); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java index 7b4d7a92b3..de4c177c5e 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java @@ -28,7 +28,6 @@ public String execute(final JsonRequestFactories node) { try { final NetVersion result = node.net().netVersion().send(); assertThat(result).isNotNull(); - assertThat(result.hasError()).isFalse(); return result.getNetVersion(); } catch (final Exception e) { throw new RuntimeException(e); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java index 1e5ed52677..f695be52f2 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/jsonrpc/WebsocketServiceLoginAcceptanceTest.java @@ -40,4 +40,11 @@ public void shouldFailLoginWithWrongCredentials() { public void shouldSucceedLoginWithCorrectCredentials() { node.verify(login.loginSucceeds("user", "pegasys")); } + + @Test + public void jsonRpcMethodShouldSucceedWithAuthenticatedUserAndPermission() { + node.verify(login.loginSucceedsAndSetsAuthenticationToken("user", "pegasys")); + node.verify(net.awaitPeerCount(0)); + node.verify(net.netVersionUnauthorizedResponse()); + } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index c7df34b603..d769886572 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -18,6 +18,7 @@ import static tech.pegasys.pantheon.util.NetworkUtility.urlForSocketAddress; import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationService; +import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationUtils; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequestId; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.exception.InvalidJsonRpcParameters; @@ -43,7 +44,6 @@ import java.util.Optional; import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; @@ -231,68 +231,10 @@ private Handler checkWhitelistHostHeader() { }; } - private boolean requiresAuthentication() { - return authenticationService.isPresent(); - } - - @VisibleForTesting - public boolean isPermitted(final Optional optionalUser, final JsonRpcMethod jsonRpcMethod) { - - AtomicBoolean foundMatchingPermission = new AtomicBoolean(); - - if (requiresAuthentication()) { - if (optionalUser.isPresent()) { - User user = optionalUser.get(); - for (String perm : jsonRpcMethod.getPermissions()) { - user.isAuthorized( - perm, - (authed) -> { - if (authed.result()) { - LOG.trace( - "user {} authorized : {} via permission {}", - user, - jsonRpcMethod.getName(), - perm); - foundMatchingPermission.set(true); - } - }); - } - } - } else { - // no auth provider configured thus anything is permitted - foundMatchingPermission.set(true); - } - - if (!foundMatchingPermission.get()) { - LOG.trace("user NOT authorized : {}", jsonRpcMethod.getName()); - } - return foundMatchingPermission.get(); - } - - private String getToken(final RoutingContext routingContext) { + private String getAuthToken(final RoutingContext routingContext) { return routingContext.request().getHeader("Bearer"); } - private void getUser(final String token, final Handler> handler) { - try { - if (!requiresAuthentication()) { - handler.handle(Optional.empty()); - } else { - authenticationService - .get() - .getJwtAuthProvider() - .authenticate( - new JsonObject().put("jwt", token), - (r) -> { - final User user = r.result(); - handler.handle(Optional.of(user)); - }); - } - } catch (Exception e) { - handler.handle(Optional.empty()); - } - } - private Optional getAndValidateHostHeader(final RoutingContext event) { final Iterable splitHostHeader = Splitter.on(':').split(event.request().host()); final long hostPieces = stream(splitHostHeader).count(); @@ -345,8 +287,8 @@ public String url() { private void handleJsonRPCRequest(final RoutingContext routingContext) { // first check token if authentication is required - String token = getToken(routingContext); - if (requiresAuthentication() && token == null) { + String token = getAuthToken(routingContext); + if (authenticationService.isPresent() && token == null) { // no auth token when auth required handleJsonRpcUnauthorizedError(routingContext, null, JsonRpcError.UNAUTHORIZED); } else { @@ -354,7 +296,8 @@ private void handleJsonRPCRequest(final RoutingContext routingContext) { try { final String json = routingContext.getBodyAsString().trim(); if (!json.isEmpty() && json.charAt(0) == '{') { - getUser( + AuthenticationUtils.getUser( + authenticationService, token, user -> { handleJsonSingleRequest(routingContext, new JsonObject(json), user); @@ -365,7 +308,8 @@ private void handleJsonRPCRequest(final RoutingContext routingContext) { handleJsonRpcError(routingContext, null, JsonRpcError.INVALID_REQUEST); return; } - getUser( + AuthenticationUtils.getUser( + authenticationService, token, user -> { handleJsonBatchRequest(routingContext, array, user); @@ -502,7 +446,7 @@ private JsonRpcResponse process(final JsonObject requestJson, final Optional authenticationService, + final Optional optionalUser, + final JsonRpcMethod jsonRpcMethod) { + + AtomicBoolean foundMatchingPermission = new AtomicBoolean(); + + if (authenticationService.isPresent()) { + if (optionalUser.isPresent()) { + User user = optionalUser.get(); + for (String perm : jsonRpcMethod.getPermissions()) { + user.isAuthorized( + perm, + (authed) -> { + if (authed.result()) { + LOG.trace( + "user {} authorized : {} via permission {}", + user, + jsonRpcMethod.getName(), + perm); + foundMatchingPermission.set(true); + } + }); + } + } + } else { + // no auth provider configured thus anything is permitted + foundMatchingPermission.set(true); + } + + if (!foundMatchingPermission.get()) { + LOG.trace("user NOT authorized : {}", jsonRpcMethod.getName()); + } + return foundMatchingPermission.get(); + } + + public static void getUser( + final Optional authenticationService, + final String token, + final Handler> handler) { + try { + if (!authenticationService.isPresent()) { + handler.handle(Optional.empty()); + } else { + authenticationService + .get() + .getJwtAuthProvider() + .authenticate( + new JsonObject().put("jwt", token), + (r) -> { + final User user = r.result(); + handler.handle(Optional.of(user)); + }); + } + } catch (Exception e) { + handler.handle(Optional.empty()); + } + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketRequestHandler.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketRequestHandler.java index 7ec02c054d..07c8f52ca7 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketRequestHandler.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketRequestHandler.java @@ -12,17 +12,22 @@ */ package tech.pegasys.pantheon.ethereum.jsonrpc.websocket; +import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationService; +import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationUtils; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcUnauthorizedResponse; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.methods.WebSocketRpcRequest; import java.util.Map; +import java.util.Optional; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.DecodeException; import io.vertx.core.json.Json; +import io.vertx.ext.auth.User; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -39,6 +44,14 @@ public WebSocketRequestHandler(final Vertx vertx, final Map authenticationService, + final String id, + final Buffer buffer, + final Optional user) { vertx.executeBlocking( future -> { final WebSocketRpcRequest request; @@ -60,7 +73,12 @@ public void handle(final String id, final Buffer buffer) { try { LOG.debug("WS-RPC request -> {}", request.getMethod()); request.setConnectionId(id); - future.complete(method.response(request)); + if (AuthenticationUtils.isPermitted(authenticationService, user, method)) { + future.complete(method.response(request)); + } else { + future.complete( + new JsonRpcUnauthorizedResponse(request.getId(), JsonRpcError.UNAUTHORIZED)); + } } catch (final Exception e) { LOG.error(JsonRpcError.INTERNAL_ERROR.getMessage(), e); future.complete(new JsonRpcErrorResponse(request.getId(), JsonRpcError.INTERNAL_ERROR)); diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java index b8cfde1e0a..e560ef260c 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.jsonrpc.websocket; import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationService; +import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationUtils; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.SubscriptionManager; import java.net.InetSocketAddress; @@ -94,6 +95,10 @@ private Handler websocketHandler() { return websocket -> { final SocketAddress socketAddress = websocket.remoteAddress(); final String connectionId = websocket.textHandlerID(); + final String token = getAuthToken(websocket); + if (token != null) { + LOG.trace("Websocket authentication token {}", token); + } LOG.debug("Websocket Connected ({})", socketAddressAsString(socketAddress)); @@ -104,7 +109,12 @@ private Handler websocketHandler() { buffer.toString(), socketAddressAsString(socketAddress)); - websocketRequestHandler.handle(connectionId, buffer); + AuthenticationUtils.getUser( + authenticationService, + token, + user -> + websocketRequestHandler.handle( + authenticationService, connectionId, buffer, user)); }); websocket.closeHandler( @@ -197,4 +207,8 @@ public InetSocketAddress socketAddress() { private String socketAddressAsString(final SocketAddress socketAddress) { return String.format("host=%s, port=%d", socketAddress.host(), socketAddress.port()); } + + private String getAuthToken(final ServerWebSocket websocket) { + return websocket.headers().get("Bearer"); + } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java index 4c6ed4f4a1..0d34a5ffd2 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -22,6 +22,7 @@ import tech.pegasys.pantheon.ethereum.core.Synchronizer; import tech.pegasys.pantheon.ethereum.core.TransactionPool; import tech.pegasys.pantheon.ethereum.eth.EthProtocol; +import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationUtils; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthAccounts; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthBlockNumber; @@ -391,14 +392,29 @@ public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() thro assertThat(r.succeeded()).isTrue(); final User user = r.result(); // single eth/blockNumber method permitted - assertThat(service.isPermitted(Optional.of(user), ethBlockNumber)).isTrue(); + assertThat( + AuthenticationUtils.isPermitted( + service.authenticationService, Optional.of(user), ethBlockNumber)) + .isTrue(); // eth/accounts not permitted - assertThat(service.isPermitted(Optional.of(user), ethAccounts)).isFalse(); + assertThat( + AuthenticationUtils.isPermitted( + service.authenticationService, Optional.of(user), ethAccounts)) + .isFalse(); // allowed by web3/* - assertThat(service.isPermitted(Optional.of(user), web3ClientVersion)).isTrue(); - assertThat(service.isPermitted(Optional.of(user), web3Sha3)).isTrue(); + assertThat( + AuthenticationUtils.isPermitted( + service.authenticationService, Optional.of(user), web3ClientVersion)) + .isTrue(); + assertThat( + AuthenticationUtils.isPermitted( + service.authenticationService, Optional.of(user), web3Sha3)) + .isTrue(); // no net permissions - assertThat(service.isPermitted(Optional.of(user), netVersion)).isFalse(); + assertThat( + AuthenticationUtils.isPermitted( + service.authenticationService, Optional.of(user), netVersion)) + .isFalse(); }); } } @@ -407,7 +423,10 @@ public void checkJsonRpcMethodsAvailableWithGoodCredentialsAndPermissions() thro public void checkPermissionsWithEmptyUser() { JsonRpcMethod ethAccounts = new EthAccounts(); - assertThat(service.isPermitted(Optional.empty(), ethAccounts)).isFalse(); + assertThat( + AuthenticationUtils.isPermitted( + service.authenticationService, Optional.empty(), ethAccounts)) + .isFalse(); } @Test diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketServiceLoginTest.java index 9f87fb04d4..acb1870d96 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketServiceLoginTest.java @@ -25,12 +25,21 @@ import java.util.HashMap; import java.util.Map; +import com.google.common.collect.Lists; +import io.vertx.core.MultiMap; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.RequestOptions; +import io.vertx.core.http.impl.headers.VertxHttpHeaders; import io.vertx.core.json.JsonObject; import io.vertx.ext.auth.User; +import io.vertx.ext.auth.jwt.JWTAuth; +import io.vertx.ext.jwt.JWTOptions; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.VertxUnitRunner; import org.junit.After; import org.junit.Before; @@ -46,6 +55,7 @@ public class WebSocketServiceLoginTest { private WebSocketRequestHandler webSocketRequestHandlerSpy; private WebSocketService websocketService; private HttpClient httpClient; + protected static JWTAuth jwtAuth; @Before public void before() throws URISyntaxException { @@ -68,6 +78,7 @@ public void before() throws URISyntaxException { websocketService = new WebSocketService(vertx, websocketConfiguration, webSocketRequestHandlerSpy); websocketService.start().join(); + jwtAuth = websocketService.authenticationService.get().getJwtAuthProvider(); websocketConfiguration.setPort(websocketService.socketAddress().getPort()); @@ -86,7 +97,8 @@ public void after() { } @Test - public void loginWithBadCredentials() { + public void loginWithBadCredentials(final TestContext context) { + final Async async = context.async(); final HttpClientRequest request = httpClient.post( websocketConfiguration.getPort(), @@ -95,13 +107,16 @@ public void loginWithBadCredentials() { response -> { assertThat(response.statusCode()).isEqualTo(401); assertThat(response.statusMessage()).isEqualTo("Unauthorized"); + async.complete(); }); request.putHeader("Content-Type", "application/json; charset=utf-8"); request.end("{\"username\":\"user\",\"password\":\"pass\"}"); + async.awaitSuccess(VERTX_AWAIT_TIMEOUT_MILLIS); } @Test - public void loginWithGoodCredentials() { + public void loginWithGoodCredentials(final TestContext context) { + final Async async = context.async(); final HttpClientRequest request = httpClient.post( websocketConfiguration.getPort(), @@ -142,10 +157,89 @@ public void loginWithGoodCredentials() { assertThat(authed.succeeded()).isTrue(); assertThat(authed.result()).isTrue(); }); + user.isAuthorized( + "eth:subscribe", + (authed) -> { + assertThat(authed.succeeded()).isTrue(); + assertThat(authed.result()).isTrue(); + async.complete(); + }); }); }); }); request.putHeader("Content-Type", "application/json; charset=utf-8"); request.end("{\"username\":\"user\",\"password\":\"pegasys\"}"); + + async.awaitSuccess(VERTX_AWAIT_TIMEOUT_MILLIS); + } + + @Test + public void websocketServiceWithBadHeaderAuthenticationToken(final TestContext context) { + final Async async = context.async(); + + final String request = "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"syncing\"]}"; + final String expectedResponse = + "{\"jsonrpc\":\"2.0\",\"id\":1,\"error\":{\"code\":-40100,\"message\":\"Unauthorized\"}}"; + + RequestOptions options = new RequestOptions(); + options.setURI("/"); + options.setHost(websocketConfiguration.getHost()); + options.setPort(websocketConfiguration.getPort()); + final MultiMap headers = new VertxHttpHeaders(); + String badtoken = "badtoken"; + if (badtoken != null) { + headers.add("Bearer", badtoken); + } + httpClient.websocket( + options, + headers, + webSocket -> { + webSocket.write(Buffer.buffer(request)); + + webSocket.handler( + buffer -> { + context.assertEquals(expectedResponse, buffer.toString()); + async.complete(); + }); + }); + + async.awaitSuccess(VERTX_AWAIT_TIMEOUT_MILLIS); + } + + @Test + public void websocketServiceWithGoodHeaderAuthenticationToken(final TestContext context) { + final Async async = context.async(); + + final JWTOptions jwtOptions = new JWTOptions().setExpiresInMinutes(5).setAlgorithm("RS256"); + final JsonObject jwtContents = + new JsonObject().put("permissions", Lists.newArrayList("eth:*")).put("username", "user"); + final String goodToken = jwtAuth.generateToken(jwtContents, jwtOptions); + + final String requestSub = + "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"syncing\"]}"; + final String expectedResponse = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x1\"}"; + + RequestOptions options = new RequestOptions(); + options.setURI("/"); + options.setHost(websocketConfiguration.getHost()); + options.setPort(websocketConfiguration.getPort()); + final MultiMap headers = new VertxHttpHeaders(); + if (goodToken != null) { + headers.add("Bearer", goodToken); + } + httpClient.websocket( + options, + headers, + webSocket -> { + webSocket.write(Buffer.buffer(requestSub)); + + webSocket.handler( + buffer -> { + context.assertEquals(expectedResponse, buffer.toString()); + async.complete(); + }); + }); + + async.awaitSuccess(VERTX_AWAIT_TIMEOUT_MILLIS); } } diff --git a/ethereum/jsonrpc/src/test/resources/JsonRpcHttpService/auth.toml b/ethereum/jsonrpc/src/test/resources/JsonRpcHttpService/auth.toml index 80f74aab59..13b19700ee 100644 --- a/ethereum/jsonrpc/src/test/resources/JsonRpcHttpService/auth.toml +++ b/ethereum/jsonrpc/src/test/resources/JsonRpcHttpService/auth.toml @@ -1,3 +1,3 @@ [Users.user] password = "$2a$10$l3GA7K8g6rJ/Yv.YFSygCuI9byngpEzxgWS9qEg5emYDZomQW7fGC" -permissions = ["fakePermission","eth:blockNumber", "web3:*"] +permissions = ["fakePermission","eth:blockNumber","eth:subscribe","web3:*"] From 597b511ee59a81182bfdcca39dc6613c44ccbbe2 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Mon, 18 Feb 2019 16:21:25 -0500 Subject: [PATCH 16/21] Reorganize eth tasks (#890) --- .../pantheon/ethereum/eth/manager/EthScheduler.java | 3 ++- .../eth/manager/{ => task}/AbstractEthTask.java | 3 ++- .../task}/AbstractGetHeadersFromPeerTask.java | 3 +-- .../manager/{ => task}/AbstractPeerRequestTask.java | 4 +++- .../eth/manager/{ => task}/AbstractPeerTask.java | 6 ++++-- .../manager/{ => task}/AbstractPipelinedPeerTask.java | 4 +++- .../manager/{ => task}/AbstractRetryingPeerTask.java | 5 +++-- .../ethereum/eth/manager/{ => task}/EthTask.java | 2 +- .../tasks => manager/task}/GetBlockFromPeerTask.java | 3 +-- .../tasks => manager/task}/GetBodiesFromPeerTask.java | 3 +-- .../task}/GetHeadersFromPeerByHashTask.java | 2 +- .../task}/GetHeadersFromPeerByNumberTask.java | 2 +- .../task}/GetNodeDataFromPeerTask.java | 3 +-- .../task}/GetReceiptsFromPeerTask.java | 3 +-- .../{sync/tasks => manager/task}/WaitForPeerTask.java | 3 +-- .../{sync/tasks => manager/task}/WaitForPeersTask.java | 3 +-- .../ethereum/eth/sync/BlockPropagationManager.java | 4 ++-- .../pantheon/ethereum/eth/sync/ChainDownloader.java | 2 +- .../pantheon/ethereum/eth/sync/ChainHeadTracker.java | 2 +- .../ethereum/eth/sync/CheckpointHeaderManager.java | 4 ++-- .../pantheon/ethereum/eth/sync/SyncTargetManager.java | 2 +- .../ethereum/eth/sync/fastsync/FastSyncActions.java | 2 +- .../ethereum/eth/sync/fullsync/FullSyncDownloader.java | 2 +- .../ethereum/eth/sync/tasks/CompleteBlocksTask.java | 5 +++-- .../eth/sync/tasks/DetermineCommonAncestorTask.java | 5 +++-- .../eth/sync/tasks/DownloadHeaderSequenceTask.java | 6 ++++-- .../eth/sync/tasks/GetReceiptsForHeadersTask.java | 5 +++-- .../ethereum/eth/sync/tasks/ImportBlocksTask.java | 3 ++- .../eth/sync/tasks/ParallelDownloadBodiesTask.java | 2 +- .../eth/sync/tasks/ParallelDownloadHeadersTask.java | 2 +- .../eth/sync/tasks/ParallelImportChainSegmentTask.java | 4 ++-- .../tasks/ParallelValidateAndImportBodiesTask.java | 2 +- .../eth/sync/tasks/ParallelValidateHeadersTask.java | 2 +- .../ethereum/eth/sync/tasks/PersistBlockTask.java | 2 +- .../sync/tasks/PipelinedImportChainSegmentTask.java | 2 +- .../tasks/RetryingGetHeaderFromPeerByNumberTask.java | 4 +++- .../eth/sync/worldstate/WorldStateDownloader.java | 10 +++++----- .../pantheon/ethereum/eth/manager/MockEthTask.java | 1 + .../manager/ethtaskutils/AbstractMessageTaskTest.java | 2 +- .../eth/manager/ethtaskutils/PeerMessageTaskTest.java | 4 ++-- .../manager/ethtaskutils/RetryingMessageTaskTest.java | 2 +- .../eth/manager/{ => task}/AbstractEthTaskTest.java | 2 +- .../task}/GetBlockFromPeerTaskTest.java | 5 ++--- .../task}/GetBodiesFromPeerTaskTest.java | 5 ++--- .../task}/GetHeadersFromPeerByHashTaskTest.java | 5 ++--- .../task}/GetHeadersFromPeerByNumberTaskTest.java | 4 +--- .../task}/GetNodeDataFromPeerTaskTest.java | 5 ++--- .../task}/GetReceiptsFromPeerTaskTest.java | 5 ++--- .../tasks => manager/task}/WaitForPeerTaskTest.java | 3 +-- .../tasks => manager/task}/WaitForPeersTaskTest.java | 3 +-- .../eth/sync/tasks/CompleteBlocksTaskTest.java | 2 +- .../DetermineCommonAncestorTaskParameterizedTest.java | 2 +- .../sync/tasks/DetermineCommonAncestorTaskTest.java | 2 +- .../eth/sync/tasks/DownloadHeaderSequenceTaskTest.java | 2 +- .../eth/sync/tasks/GetReceiptsForHeadersTaskTest.java | 2 +- .../ethereum/eth/sync/tasks/ImportBlocksTaskTest.java | 4 ++-- .../tasks/PipelinedImportChainSegmentTaskTest.java | 2 +- .../RetryingGetHeaderFromPeerByNumberTaskTest.java | 2 +- 58 files changed, 95 insertions(+), 93 deletions(-) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/{ => task}/AbstractEthTask.java (98%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/AbstractGetHeadersFromPeerTask.java (97%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/{ => task}/AbstractPeerRequestTask.java (95%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/{ => task}/AbstractPeerTask.java (90%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/{ => task}/AbstractPipelinedPeerTask.java (95%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/{ => task}/AbstractRetryingPeerTask.java (96%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/{ => task}/EthTask.java (93%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetBlockFromPeerTask.java (97%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetBodiesFromPeerTask.java (98%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetHeadersFromPeerByHashTask.java (98%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetHeadersFromPeerByNumberTask.java (98%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetNodeDataFromPeerTask.java (96%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetReceiptsFromPeerTask.java (97%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/WaitForPeerTask.java (95%) rename ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/WaitForPeersTask.java (95%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/{ => task}/AbstractEthTaskTest.java (98%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetBlockFromPeerTaskTest.java (96%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetBodiesFromPeerTaskTest.java (92%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetHeadersFromPeerByHashTaskTest.java (95%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetHeadersFromPeerByNumberTaskTest.java (96%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetNodeDataFromPeerTaskTest.java (92%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/GetReceiptsFromPeerTaskTest.java (92%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/WaitForPeerTaskTest.java (96%) rename ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/{sync/tasks => manager/task}/WaitForPeersTaskTest.java (97%) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java index c87da2c794..3521582f8d 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthScheduler.java @@ -17,6 +17,7 @@ import static tech.pegasys.pantheon.ethereum.eth.manager.MonitoredExecutors.newScheduledThreadPool; import static tech.pegasys.pantheon.util.FutureUtils.propagateResult; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.ExceptionUtils; @@ -233,7 +234,7 @@ private CompletableFuture failAfterTimeout(final Duration timeout) { return promise; } - void failAfterTimeout(final CompletableFuture promise) { + public void failAfterTimeout(final CompletableFuture promise) { failAfterTimeout(promise, defaultTimeout); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractEthTask.java similarity index 98% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractEthTask.java index b886935537..09037950ca 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractEthTask.java @@ -10,10 +10,11 @@ * 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.ethereum.eth.manager; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; +import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/AbstractGetHeadersFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java similarity index 97% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/AbstractGetHeadersFromPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java index 821b59cc16..c3451ef102 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/AbstractGetHeadersFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java @@ -10,12 +10,11 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static com.google.common.base.Preconditions.checkArgument; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerRequestTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.messages.BlockHeadersMessage; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPeerRequestTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java similarity index 95% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPeerRequestTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java index 2347dea444..f725ad3d40 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPeerRequestTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java @@ -10,8 +10,10 @@ * 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.ethereum.eth.manager; +package tech.pegasys.pantheon.ethereum.eth.manager.task; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerTask.java similarity index 90% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerTask.java index 3d908879b3..e8bc41f0e3 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerTask.java @@ -10,11 +10,13 @@ * 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.ethereum.eth.manager; +package tech.pegasys.pantheon.ethereum.eth.manager.task; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPipelinedPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPipelinedPeerTask.java similarity index 95% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPipelinedPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPipelinedPeerTask.java index 7079f00825..7aeadb626e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractPipelinedPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPipelinedPeerTask.java @@ -10,8 +10,10 @@ * 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.ethereum.eth.manager; +package tech.pegasys.pantheon.ethereum.eth.manager.task; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractRetryingPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractRetryingPeerTask.java similarity index 96% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractRetryingPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractRetryingPeerTask.java index 7d874de153..e8995da3f1 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractRetryingPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractRetryingPeerTask.java @@ -10,13 +10,14 @@ * 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.ethereum.eth.manager; +package tech.pegasys.pantheon.ethereum.eth.manager.task; +import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.MaxRetriesReachedException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; import tech.pegasys.pantheon.util.ExceptionUtils; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/EthTask.java similarity index 93% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/EthTask.java index 43cee1cd64..9388aa123a 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/EthTask.java @@ -10,7 +10,7 @@ * 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.ethereum.eth.manager; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTask.java similarity index 97% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTask.java index 834c203fab..1ad74856bc 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTask.java @@ -10,14 +10,13 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.IncompleteResultsException; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBodiesFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java similarity index 98% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBodiesFromPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java index 0c6422356e..ee93d98a3c 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBodiesFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java @@ -10,7 +10,7 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static com.google.common.base.Preconditions.checkArgument; @@ -19,7 +19,6 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.Transaction; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerRequestTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByHashTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java similarity index 98% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByHashTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java index a6cdffd6fb..f6456e603f 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByHashTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java @@ -10,7 +10,7 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByNumberTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java similarity index 98% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByNumberTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java index bd622f2bdd..fadf123a01 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByNumberTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java @@ -10,7 +10,7 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetNodeDataFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java similarity index 96% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetNodeDataFromPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java index cf54138b73..c9582af02e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetNodeDataFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java @@ -10,12 +10,11 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static java.util.Collections.emptyList; import tech.pegasys.pantheon.ethereum.core.Hash; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerRequestTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java similarity index 97% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsFromPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java index d2125a8a52..a03491912e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java @@ -10,7 +10,7 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.toList; @@ -19,7 +19,6 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerRequestTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTask.java similarity index 95% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeerTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTask.java index f4f97b02dc..9f85b34e41 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTask.java @@ -10,9 +10,8 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractEthTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers; import tech.pegasys.pantheon.metrics.LabelledMetric; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeersTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeersTask.java similarity index 95% rename from ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeersTask.java rename to ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeersTask.java index d2359026ae..e76f195edb 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeersTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeersTask.java @@ -10,9 +10,8 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractEthTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers; import tech.pegasys.pantheon.metrics.LabelledMetric; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java index a05764cd41..5f1813c384 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java @@ -19,17 +19,17 @@ import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthMessage; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetBlockFromPeerTask; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV62; import tech.pegasys.pantheon.ethereum.eth.messages.NewBlockHashesMessage; import tech.pegasys.pantheon.ethereum.eth.messages.NewBlockHashesMessage.NewBlockHash; import tech.pegasys.pantheon.ethereum.eth.messages.NewBlockMessage; import tech.pegasys.pantheon.ethereum.eth.sync.state.PendingBlocks; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetBlockFromPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.PersistBlockTask; import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainDownloader.java index 714d8beb7c..673bae2573 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainDownloader.java @@ -19,9 +19,9 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.EthTaskException; +import tech.pegasys.pantheon.ethereum.eth.manager.task.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncTarget; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; import tech.pegasys.pantheon.metrics.LabelledMetric; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTracker.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTracker.java index 4d2a1041e7..50812e77fa 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTracker.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/ChainHeadTracker.java @@ -20,7 +20,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.ConnectCallback; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetHeadersFromPeerByHashTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetHeadersFromPeerByHashTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; import tech.pegasys.pantheon.metrics.LabelledMetric; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/CheckpointHeaderManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/CheckpointHeaderManager.java index 07ab7c0e43..1365083e94 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/CheckpointHeaderManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/CheckpointHeaderManager.java @@ -17,11 +17,11 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetHeadersFromPeerByHashTask; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncTarget; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetHeadersFromPeerByHashTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SyncTargetManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SyncTargetManager.java index 7547109284..91d6b0c40a 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SyncTargetManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/SyncTargetManager.java @@ -17,10 +17,10 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncTarget; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.DetermineCommonAncestorTask; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java index 2fa221a961..6582816584 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -21,9 +21,9 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; +import tech.pegasys.pantheon.ethereum.eth.manager.task.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeersTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.Counter; import tech.pegasys.pantheon.metrics.LabelledMetric; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncDownloader.java index bd4cfffd7e..692b79f6e2 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncDownloader.java @@ -15,8 +15,8 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.sync.ChainDownloader; import tech.pegasys.pantheon.ethereum.eth.sync.CheckpointHeaderManager; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTask.java index d96a4792e7..5f18c3ce19 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTask.java @@ -21,10 +21,11 @@ import tech.pegasys.pantheon.ethereum.core.BlockBody; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractRetryingPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetBodiesFromPeerTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTask.java index d7808ec013..11021e2eb4 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTask.java @@ -14,10 +14,11 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractEthTask; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractEthTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetHeadersFromPeerByNumberTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.util.BlockchainUtil; import tech.pegasys.pantheon.metrics.LabelledMetric; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java index fcad141a26..bfa3270152 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java @@ -18,10 +18,12 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractGetHeadersFromPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractRetryingPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetHeadersFromPeerByHashTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java index 6c2671ce22..7a707bef82 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java @@ -19,10 +19,11 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractRetryingPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetReceiptsFromPeerTask; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTask.java index a14776822a..35b0f574ba 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTask.java @@ -15,9 +15,10 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetHeadersFromPeerByHashTask; import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelDownloadBodiesTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelDownloadBodiesTask.java index 9350c74b73..b2ab6ad3b6 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelDownloadBodiesTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelDownloadBodiesTask.java @@ -13,9 +13,9 @@ package tech.pegasys.pantheon.ethereum.eth.sync.tasks; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPipelinedPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPipelinedPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.BlockHandler; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelDownloadHeadersTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelDownloadHeadersTask.java index c134a54d6e..78cb303a49 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelDownloadHeadersTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelDownloadHeadersTask.java @@ -14,9 +14,9 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPipelinedPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPipelinedPeerTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelImportChainSegmentTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelImportChainSegmentTask.java index d1a67fe887..caa2f6d130 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelImportChainSegmentTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelImportChainSegmentTask.java @@ -14,10 +14,10 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractEthTask; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractEthTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.BlockHandler; import tech.pegasys.pantheon.ethereum.eth.sync.ValidationPolicy; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelValidateAndImportBodiesTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelValidateAndImportBodiesTask.java index ccc79be3c0..a2eafd4c08 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelValidateAndImportBodiesTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelValidateAndImportBodiesTask.java @@ -12,9 +12,9 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.tasks; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPipelinedPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPipelinedPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.BlockHandler; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelValidateHeadersTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelValidateHeadersTask.java index 27d1cc13de..5feeb5947b 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelValidateHeadersTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ParallelValidateHeadersTask.java @@ -14,9 +14,9 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPipelinedPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPipelinedPeerTask; import tech.pegasys.pantheon.ethereum.eth.sync.ValidationPolicy; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import tech.pegasys.pantheon.ethereum.mainnet.BlockHeaderValidator; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PersistBlockTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PersistBlockTask.java index a227c9de5a..74d624c72c 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PersistBlockTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PersistBlockTask.java @@ -17,7 +17,7 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockImporter; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractEthTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractEthTask; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PipelinedImportChainSegmentTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PipelinedImportChainSegmentTask.java index e0f6f5b1af..b648085005 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PipelinedImportChainSegmentTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PipelinedImportChainSegmentTask.java @@ -14,8 +14,8 @@ import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractEthTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractEthTask; import tech.pegasys.pantheon.ethereum.eth.sync.BlockHandler; import tech.pegasys.pantheon.ethereum.eth.sync.ValidationPolicy; import tech.pegasys.pantheon.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTask.java index 298f56b918..68d862d726 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTask.java @@ -13,9 +13,11 @@ package tech.pegasys.pantheon.ethereum.eth.sync.tasks; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractRetryingPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractGetHeadersFromPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractRetryingPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetHeadersFromPeerByNumberTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java index 308ad1daaa..2bb8bc8c57 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java @@ -14,13 +14,13 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetNodeDataFromPeerTask; -import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.GetNodeDataFromPeerTask; +import tech.pegasys.pantheon.ethereum.eth.manager.task.WaitForPeerTask; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage.Updater; import tech.pegasys.pantheon.metrics.Counter; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/MockEthTask.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/MockEthTask.java index 601ce1da2c..5a5437e1a8 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/MockEthTask.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/MockEthTask.java @@ -12,6 +12,7 @@ */ package tech.pegasys.pantheon.ethereum.eth.manager; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractEthTask; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import java.util.concurrent.CountDownLatch; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java index ae6452ad8c..6bc60eb49c 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java @@ -20,9 +20,9 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java index 02dcc979d5..1e5e6be653 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java @@ -14,14 +14,14 @@ import static org.assertj.core.api.Assertions.assertThat; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.EthTaskException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.EthTaskException.FailureReason; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.util.ExceptionUtils; import java.util.concurrent.CompletableFuture; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java index 7cd23e1446..47ce8dc584 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/ethtaskutils/RetryingMessageTaskTest.java @@ -17,10 +17,10 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.MaxRetriesReachedException; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractEthTaskTest.java similarity index 98% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractEthTaskTest.java index 7554810d67..ac93f501bd 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/AbstractEthTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractEthTaskTest.java @@ -10,7 +10,7 @@ * 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.ethereum.eth.manager; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTaskTest.java similarity index 96% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTaskTest.java index 8b6122291c..24060152ba 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBlockFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTaskTest.java @@ -10,22 +10,21 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockBody; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.AbstractMessageTaskTest; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.EthTaskException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.EthTaskException.FailureReason; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.util.ExceptionUtils; import java.util.concurrent.CompletableFuture; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBodiesFromPeerTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java similarity index 92% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBodiesFromPeerTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java index 2642190fc9..1b3fcd85fa 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetBodiesFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java @@ -10,16 +10,15 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockBody; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import java.util.ArrayList; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByHashTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTaskTest.java similarity index 95% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByHashTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTaskTest.java index cd25c884c2..7e604c99e8 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByHashTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTaskTest.java @@ -10,17 +10,16 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import java.util.ArrayList; import java.util.List; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByNumberTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTaskTest.java similarity index 96% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByNumberTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTaskTest.java index cce1a9851b..54c5595e4a 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetHeadersFromPeerByNumberTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTaskTest.java @@ -10,14 +10,12 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetNodeDataFromPeerTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTaskTest.java similarity index 92% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetNodeDataFromPeerTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTaskTest.java index 8388e1e30f..c22988d8a6 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetNodeDataFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTaskTest.java @@ -10,17 +10,16 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.ArrayList; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsFromPeerTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTaskTest.java similarity index 92% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsFromPeerTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTaskTest.java index fde986862a..11c86d694b 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTaskTest.java @@ -10,15 +10,14 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import java.util.HashMap; import java.util.List; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeerTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTaskTest.java similarity index 96% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeerTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTaskTest.java index 3b0d69fcb5..e4b21dcb83 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeerTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTaskTest.java @@ -10,14 +10,13 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeersTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeersTaskTest.java similarity index 97% rename from ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeersTaskTest.java rename to ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeersTaskTest.java index bff593a19f..1d16cb8d4c 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/WaitForPeersTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeersTaskTest.java @@ -10,14 +10,13 @@ * 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.ethereum.eth.sync.tasks; +package tech.pegasys.pantheon.ethereum.eth.manager.task; import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.metrics.LabelledMetric; import tech.pegasys.pantheon.metrics.OperationTimer; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTaskTest.java index bf8d063609..7f30aaa1d8 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/CompleteBlocksTaskTest.java @@ -19,8 +19,8 @@ import tech.pegasys.pantheon.ethereum.core.BlockBody; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import java.util.ArrayList; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java index 07416dbf92..2ccaf3ef06 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java @@ -26,8 +26,8 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHashFunction; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java index e8509f482d..cb4e71f261 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java @@ -33,10 +33,10 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.EthTaskException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.EthTaskException.FailureReason; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHashFunction; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTaskTest.java index 25e3a521b6..416e20a57b 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/DownloadHeaderSequenceTaskTest.java @@ -17,11 +17,11 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.MaxRetriesReachedException; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.ethereum.eth.messages.BlockHeadersMessage; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV62; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsForHeadersTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsForHeadersTaskTest.java index 4e1668dd15..941928d5b8 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsForHeadersTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/GetReceiptsForHeadersTaskTest.java @@ -19,8 +19,8 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import java.util.ArrayList; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTaskTest.java index abdb841f11..39dc044daa 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/ImportBlocksTaskTest.java @@ -21,13 +21,13 @@ import tech.pegasys.pantheon.ethereum.core.BlockBody; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; -import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.AbstractMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.ethereum.eth.messages.BlockHeadersMessage; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV62; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PipelinedImportChainSegmentTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PipelinedImportChainSegmentTaskTest.java index 1faf846574..a0ac9194f8 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PipelinedImportChainSegmentTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/PipelinedImportChainSegmentTaskTest.java @@ -25,10 +25,10 @@ import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.AbstractMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV62; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; import tech.pegasys.pantheon.ethereum.eth.sync.ValidationPolicy; diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTaskTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTaskTest.java index 21e91ad60e..9f18f42119 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTaskTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByNumberTaskTest.java @@ -15,8 +15,8 @@ import static java.util.Collections.singletonList; import tech.pegasys.pantheon.ethereum.core.BlockHeader; -import tech.pegasys.pantheon.ethereum.eth.manager.EthTask; import tech.pegasys.pantheon.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest; +import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; import java.util.List; From 706e3199d079fc0e7dde60805f61e563de3eec8e Mon Sep 17 00:00:00 2001 From: Lucas Saldanha Date: Tue, 19 Feb 2019 12:04:41 +1300 Subject: [PATCH 17/21] [PAN-2313] Fix authentication header (#891) --- .../acceptance/dsl/node/PantheonNode.java | 4 +- .../ethereum/jsonrpc/JsonRpcHttpService.java | 3 +- .../authentication/AuthenticationUtils.java | 10 ++++ .../jsonrpc/authentication/TomlAuth.java | 2 - .../jsonrpc/websocket/WebSocketService.java | 3 +- .../jsonrpc/JsonRpcHttpServiceLoginTest.java | 2 +- .../AuthenticationUtilsTest.java | 56 +++++++++++++++++++ .../websocket/WebSocketServiceLoginTest.java | 4 +- 8 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtilsTest.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index e49b96490d..5e4e4273bc 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -202,7 +202,7 @@ private JsonRequestFactories jsonRequestFactories() { final String url = wsRpcBaseUrl().orElse("ws://" + LOCALHOST + ":" + 8546); final Map headers = new HashMap<>(); if (token != null) { - headers.put("Bearer", token); + headers.put("Authorization", "Bearer " + token); } final WebSocketClient wsClient = new WebSocketClient(URI.create(url), headers); @@ -220,7 +220,7 @@ private JsonRequestFactories jsonRequestFactories() { .map(HttpService::new) .orElse(new HttpService("http://" + LOCALHOST + ":" + 8545)); if (token != null) { - ((HttpService) web3jService).addHeader("Bearer", token); + ((HttpService) web3jService).addHeader("Authorization", "Bearer " + token); } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index d769886572..69ea01cf3e 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -232,7 +232,8 @@ private Handler checkWhitelistHostHeader() { } private String getAuthToken(final RoutingContext routingContext) { - return routingContext.request().getHeader("Bearer"); + return AuthenticationUtils.getJwtTokenFromAuthorizationHeaderValue( + routingContext.request().getHeader("Authorization")); } private Optional getAndValidateHostHeader(final RoutingContext event) { diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtils.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtils.java index cd545aa959..9dcd38af73 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtils.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtils.java @@ -86,4 +86,14 @@ public static void getUser( handler.handle(Optional.empty()); } } + + public static String getJwtTokenFromAuthorizationHeaderValue(final String value) { + if (value != null) { + final String bearerSchemaName = "Bearer "; + if (value.startsWith(bearerSchemaName)) { + return value.substring(bearerSchemaName.length()); + } + } + return null; + } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/TomlAuth.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/TomlAuth.java index 0eaecef7e3..7827260972 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/TomlAuth.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/TomlAuth.java @@ -56,8 +56,6 @@ public void authenticate( return; } - LOG.debug("Authenticating user {} with password {}", username, password); - readUser( username, rs -> { diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java index e560ef260c..62ad3e17af 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java @@ -209,6 +209,7 @@ private String socketAddressAsString(final SocketAddress socketAddress) { } private String getAuthToken(final ServerWebSocket websocket) { - return websocket.headers().get("Bearer"); + return AuthenticationUtils.getJwtTokenFromAuthorizationHeaderValue( + websocket.headers().get("Authorization")); } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java index 0d34a5ffd2..302f5a3ba3 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -511,7 +511,7 @@ private Request buildPostRequest(final RequestBody body, final String token) { private Request buildPostRequest(final RequestBody body, final Optional token) { final Request.Builder request = new Request.Builder().post(body).url(baseUrl); - token.ifPresent(t -> request.addHeader("Bearer", t)); + token.ifPresent(t -> request.addHeader("Authorization", "Bearer " + t)); return request.build(); } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtilsTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtilsTest.java new file mode 100644 index 0000000000..459332cc89 --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/authentication/AuthenticationUtilsTest.java @@ -0,0 +1,56 @@ +/* + * 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.ethereum.jsonrpc.authentication; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class AuthenticationUtilsTest { + + @Test + public void getJwtTokenFromNullStringShouldReturnNull() { + final String headerValue = null; + + final String token = AuthenticationUtils.getJwtTokenFromAuthorizationHeaderValue(headerValue); + + assertThat(token).isNull(); + } + + @Test + public void getJwtTokenFromEmptyStringShouldReturnNull() { + final String headerValue = ""; + + final String token = AuthenticationUtils.getJwtTokenFromAuthorizationHeaderValue(headerValue); + + assertThat(token).isNull(); + } + + @Test + public void getJwtTokenFromInvalidAuthorizationHeaderValueShouldReturnNull() { + final String headerValue = "Foo eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"; + + final String token = AuthenticationUtils.getJwtTokenFromAuthorizationHeaderValue(headerValue); + + assertThat(token).isNull(); + } + + @Test + public void getJwtTokenFromValidAuthorizationHeaderValueShouldReturnToken() { + final String headerValue = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"; + + final String token = AuthenticationUtils.getJwtTokenFromAuthorizationHeaderValue(headerValue); + + assertThat(token).isEqualTo("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"); + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketServiceLoginTest.java index acb1870d96..f1d7ca31bc 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketServiceLoginTest.java @@ -188,7 +188,7 @@ public void websocketServiceWithBadHeaderAuthenticationToken(final TestContext c final MultiMap headers = new VertxHttpHeaders(); String badtoken = "badtoken"; if (badtoken != null) { - headers.add("Bearer", badtoken); + headers.add("Authorization", "Bearer " + badtoken); } httpClient.websocket( options, @@ -225,7 +225,7 @@ public void websocketServiceWithGoodHeaderAuthenticationToken(final TestContext options.setPort(websocketConfiguration.getPort()); final MultiMap headers = new VertxHttpHeaders(); if (goodToken != null) { - headers.add("Bearer", goodToken); + headers.add("Authorization", "Bearer " + goodToken); } httpClient.websocket( options, From 0087932f2df2a3d27ff7f660fdc69d15bbee4dc6 Mon Sep 17 00:00:00 2001 From: tmohay Date: Tue, 19 Feb 2019 10:11:09 +1100 Subject: [PATCH 18/21] post review --- ...t.java => ReceivedFutureProposalTest.java} | 21 ++++--- .../consensus/ibft/tests/RoundChangeTest.java | 10 ++-- .../ibft/messagewrappers/Proposal.java | 11 +++- .../ibft/statemachine/IbftRound.java | 3 +- .../ibft/statemachine/RoundChangeManager.java | 2 +- .../ibft/validation/MessageValidator.java | 57 +++++++++++-------- .../RoundChangeCertificateValidator.java | 4 +- .../ibft/statemachine/IbftRoundTest.java | 2 +- .../RoundChangeCertificateValidatorTest.java | 2 +- 9 files changed, 66 insertions(+), 46 deletions(-) rename consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/{ReceivedNewRoundTest.java => ReceivedFutureProposalTest.java} (92%) diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedNewRoundTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedFutureProposalTest.java similarity index 92% rename from consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedNewRoundTest.java rename to consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedFutureProposalTest.java index 16b6b6c8ee..2b43599e1e 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedNewRoundTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/ReceivedFutureProposalTest.java @@ -34,8 +34,11 @@ import org.junit.Before; import org.junit.Test; -/** Ensure the Ibft component responds appropriately when a NewRound message is received. */ -public class ReceivedNewRoundTest { +/** + * Ensure the Ibft component responds appropriately when a future round Proposal message is + * received. + */ +public class ReceivedFutureProposalTest { private final int NETWORK_SIZE = 5; @@ -56,7 +59,7 @@ public void setup() { } @Test - public void newRoundMessageWithEmptyPrepareCertificatesOfferNewBlock() { + public void proposalWithEmptyPrepareCertificatesOfferNewBlock() { final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1); final Block blockToPropose = context.createBlockForProposalFromChainHead(nextRoundId.getRoundNumber(), 15); @@ -77,7 +80,7 @@ public void newRoundMessageWithEmptyPrepareCertificatesOfferNewBlock() { } @Test - public void newRoundMessageFromIllegalSenderIsDiscardedAndNoPrepareForNewRoundIsSent() { + public void proposalFromIllegalSenderIsDiscardedAndNoPrepareForNewRoundIsSent() { final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1); final Block blockToPropose = context.createBlockForProposalFromChainHead(nextRoundId.getRoundNumber(), 15); @@ -95,7 +98,7 @@ public void newRoundMessageFromIllegalSenderIsDiscardedAndNoPrepareForNewRoundIs } @Test - public void newRoundWithPrepareCertificateResultsInNewRoundStartingWithExpectedBlock() { + public void proposalWithPrepareCertificateResultsInNewRoundStartingWithExpectedBlock() { final Block initialBlock = context.createBlockForProposalFromChainHead(0, 15); final Block reproposedBlock = context.createBlockForProposalFromChainHead(1, 15); final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1); @@ -116,8 +119,8 @@ public void newRoundWithPrepareCertificateResultsInNewRoundStartingWithExpectedB } @Test - public void newRoundMessageForPriorRoundIsNotActioned() { - // first move to a future round, then inject a newRound for a prior round, local node + public void proposalMessageForPriorRoundIsNotActioned() { + // first move to a future round, then inject a proposal for a prior round, local node // should send no messages. final ConsensusRoundIdentifier futureRound = new ConsensusRoundIdentifier(1, 2); peers.roundChange(futureRound); @@ -138,7 +141,7 @@ public void newRoundMessageForPriorRoundIsNotActioned() { } @Test - public void receiveRoundStateIsNotLostIfASecondNewRoundMessageIsReceivedForCurrentRound() { + public void receiveRoundStateIsNotLostIfASecondProposalMessageIsReceivedForCurrentRound() { final Block initialBlock = context.createBlockForProposalFromChainHead(0, 15); final Block reproposedBlock = context.createBlockForProposalFromChainHead(1, 15); final ConsensusRoundIdentifier nextRoundId = new ConsensusRoundIdentifier(1, 1); @@ -158,7 +161,7 @@ public void receiveRoundStateIsNotLostIfASecondNewRoundMessageIsReceivedForCurre peers.verifyMessagesReceived( localNodeMessageFactory.createPrepare(nextRoundId, reproposedBlock.getHash())); - // Inject a prepare, then re-inject the newRound - then ensure only a single prepare is enough + // Inject a prepare, then re-inject the proposal - then ensure only a single prepare is enough // to trigger a Commit transmission from the local node nextRoles.getNonProposing(0).injectPrepare(nextRoundId, reproposedBlock.getHash()); diff --git a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java index 47e8287181..65558ba82c 100644 --- a/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java +++ b/consensus/ibft/src/integration-test/java/tech/pegasys/pantheon/consensus/ibft/tests/RoundChangeTest.java @@ -137,7 +137,7 @@ public void roundChangeHasPopulatedCertificateIfQuorumPrepareMessagesAndProposal } @Test - public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCreatesNewRoundMsg() { + public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCreatesProposalMsg() { // Note: Round-4 is the next round for which the local node is Proposer final ConsensusRoundIdentifier targetRound = new ConsensusRoundIdentifier(1, 4); final Block locallyProposedBlock = @@ -148,7 +148,7 @@ public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCrea final RoundChange rc3 = peers.getNonProposing(2).injectRoundChange(targetRound, empty()); final RoundChange rc4 = peers.getProposer().injectRoundChange(targetRound, empty()); - final Proposal expectedNewRound = + final Proposal expectedProposal = localNodeMessageFactory.createProposal( targetRound, locallyProposedBlock, @@ -160,11 +160,11 @@ public void whenSufficientRoundChangeMessagesAreReceivedForNewRoundLocalNodeCrea rc3.getSignedPayload(), rc4.getSignedPayload())))); - peers.verifyMessagesReceived(expectedNewRound); + peers.verifyMessagesReceived(expectedProposal); } @Test - public void newRoundMessageContainsBlockOnWhichPeerPrepared() { + public void proposalMessageContainsBlockOnWhichPeerPrepared() { final long ARBITRARY_BLOCKTIME = 1500; final PreparedRoundArtifacts earlierPrepCert = @@ -334,7 +334,7 @@ public void illegallyConstructedRoundChangeMessageIsDiscarded() { .getNonProposing(2) .injectRoundChange(targetRound, Optional.of(illegalPreparedRoundArtifacts)); - // Ensure no NewRound message is sent. + // Ensure no Proposal message is sent. peers.verifyNoMessagesReceived(); } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java index de46c37c91..a7765d2ed9 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/messagewrappers/Proposal.java @@ -74,6 +74,14 @@ public static Proposal decode(final BytesValue data) { final Block proposedBlock = Block.readFrom(rlpIn, IbftBlockHashing::calculateDataHashForCommittedSeal); + final Optional roundChangeCertificate = + readRoundChangeCertificate(rlpIn); + + rlpIn.leaveList(); + return new Proposal(payload, proposedBlock, roundChangeCertificate); + } + + private static Optional readRoundChangeCertificate(final RLPInput rlpIn) { RoundChangeCertificate roundChangeCertificate = null; if (!rlpIn.nextIsNull()) { roundChangeCertificate = RoundChangeCertificate.readFrom(rlpIn); @@ -81,7 +89,6 @@ public static Proposal decode(final BytesValue data) { rlpIn.skipNext(); } - rlpIn.leaveList(); - return new Proposal(payload, proposedBlock, Optional.ofNullable(roundChangeCertificate)); + return Optional.ofNullable(roundChangeCertificate); } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java index cc44577086..4b80891083 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java @@ -130,9 +130,10 @@ public void handleProposalMessage(final Proposal msg) { if (updateStateWithProposedBlock(msg)) { LOG.debug("Sending prepare message."); - transmitter.multicastPrepare(getRoundIdentifier(), block.getHash()); final Prepare localPrepareMessage = messageFactory.createPrepare(getRoundIdentifier(), block.getHash()); + transmitter.multicastPrepare(localPrepareMessage.getRoundIdentifier(), + localPrepareMessage.getDigest()); peerIsPrepared(localPrepareMessage); } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManager.java index 3f263604ee..edc9f8f38e 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManager.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/RoundChangeManager.java @@ -32,7 +32,7 @@ * and messages for a future round should have been buffered). * *

    If enough RoundChange messages all targeting a given round are received (and this node is the - * proposer for said round) - a newRound message is sent, and a new round should be started by the + * proposer for said round) - a proposal message is sent, and a new round should be started by the * controlling class. */ public class RoundChangeManager { diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java index 8b5dc9cf23..63c273afe2 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java @@ -63,7 +63,7 @@ public boolean validateProposal(final Proposal msg) { return false; } - if (!validateRoundChangeCertificate(msg, msg.getRoundChangeCertificate())) { + if (!validateProposalAndRoundChangeAreConsistent(msg)) { LOG.debug("Illegal Proposal message, embedded roundChange does not match proposal."); return false; } @@ -85,42 +85,51 @@ private boolean validateBlock(final Block block) { return true; } - private boolean validateRoundChangeCertificate( - final Proposal proposal, final Optional roundChangeCertificate) { + private boolean validateProposalAndRoundChangeAreConsistent(final Proposal proposal) { final ConsensusRoundIdentifier proposalRoundIdentifier = proposal.getRoundIdentifier(); + ; + if (proposalRoundIdentifier.getRoundNumber() == 0) { - if (roundChangeCertificate.isPresent()) { + if (proposal.getRoundChangeCertificate().isPresent()) { LOG.debug( "Illegal Proposal message, round-0 proposal must not contain a round change certificate."); return false; } } else { - if (!roundChangeCertificate.isPresent()) { - LOG.debug( - "Illegal Proposal message, rounds other than 0 must contain a round change certificate."); - return false; - } + validateRoundChangeExistsAndMatchesProposedBlock(proposal); + } - final RoundChangeCertificate roundChangeCert = roundChangeCertificate.get(); + return true; + } - if (!roundChangeCertificateValidator - .validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( - proposal.getRoundIdentifier(), roundChangeCert)) { - LOG.debug( - "Illegal Proposal message, embedded RoundChangeCertificate is not self-consistent"); - return false; - } + private boolean validateRoundChangeExistsAndMatchesProposedBlock(final Proposal proposal) { - if (!roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( - roundChangeCert, proposal.getBlock())) { - LOG.debug( - "Illegal Proposal message, piggybacked block does not match latest PrepareCertificate"); - return false; - } + final Optional roundChangeCertificate = + proposal.getRoundChangeCertificate(); + + if (!roundChangeCertificate.isPresent()) { + LOG.debug( + "Illegal Proposal message, rounds other than 0 must contain a round change certificate."); + return false; } - return true; + final RoundChangeCertificate roundChangeCert = roundChangeCertificate.get(); + + if (!roundChangeCertificateValidator + .validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( + proposal.getRoundIdentifier(), roundChangeCert)) { + LOG.debug( + "Illegal Proposal message, embedded RoundChangeCertificate is not self-consistent"); + return false; + } + + if (!roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( + roundChangeCert, proposal.getBlock())) { + LOG.debug( + "Illegal Proposal message, piggybacked block does not match latest PrepareCertificate"); + return false; + } } public boolean validatePrepare(final Prepare msg) { diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidator.java index e7ea6badff..5e5833bccd 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidator.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidator.java @@ -77,7 +77,7 @@ public boolean validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( if (!roundChangeCert.getRoundChangePayloads().stream() .allMatch(roundChangeValidator::validateRoundChange)) { - LOG.info("Invalid NewRound message, embedded RoundChange message failed validation."); + LOG.info("Invalid RoundChangeCertificate, embedded RoundChange message failed validation."); return false; } @@ -116,7 +116,7 @@ public boolean validateProposalMessageMatchesLatestPrepareCertificate( .getHash() .equals(latestPreparedCertificate.get().getProposalPayload().getPayload().getDigest())) { LOG.info( - "Invalid NewRound message, block in latest RoundChange does not match proposed block."); + "Invalid RoundChangeCertificate, block in latest RoundChange does not match proposed block."); return false; } diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java index e2fbee78be..152b48815b 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRoundTest.java @@ -279,7 +279,7 @@ public void aProposalWithAnewBlockIsSentUponReceptionOfARoundChangeWithNoCertifi } @Test - public void aNewRoundMessageWithTheSameBlockIsSentUponReceptionOfARoundChangeWithCertificate() { + public void aProposalMessageWithTheSameBlockIsSentUponReceptionOfARoundChangeWithCertificate() { final ConsensusRoundIdentifier priorRoundChange = new ConsensusRoundIdentifier(1, 0); final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); final IbftRound round = diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java index 8459b89e04..910f91c508 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/RoundChangeCertificateValidatorTest.java @@ -69,7 +69,7 @@ public void setup() { } @Test - public void newRoundWithEmptyRoundChangeCertificateFails() { + public void proposalWithEmptyRoundChangeCertificateFails() { final RoundChangeCertificate cert = new RoundChangeCertificate(Collections.emptyList()); assertThat( From 59a39c325a71c98a5532971fb9dded2fbf710465 Mon Sep 17 00:00:00 2001 From: tmohay Date: Tue, 19 Feb 2019 10:21:46 +1100 Subject: [PATCH 19/21] repair tests --- .../ibft/statemachine/IbftRound.java | 4 +-- .../ibft/validation/MessageValidator.java | 30 ++++++++++--------- .../ibft/validation/MessageValidatorTest.java | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java index 4b80891083..7fbc158959 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftRound.java @@ -132,8 +132,8 @@ public void handleProposalMessage(final Proposal msg) { LOG.debug("Sending prepare message."); final Prepare localPrepareMessage = messageFactory.createPrepare(getRoundIdentifier(), block.getHash()); - transmitter.multicastPrepare(localPrepareMessage.getRoundIdentifier(), - localPrepareMessage.getDigest()); + transmitter.multicastPrepare( + localPrepareMessage.getRoundIdentifier(), localPrepareMessage.getDigest()); peerIsPrepared(localPrepareMessage); } } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java index 63c273afe2..485ac1e7b1 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidator.java @@ -88,22 +88,24 @@ private boolean validateBlock(final Block block) { private boolean validateProposalAndRoundChangeAreConsistent(final Proposal proposal) { final ConsensusRoundIdentifier proposalRoundIdentifier = proposal.getRoundIdentifier(); - ; - if (proposalRoundIdentifier.getRoundNumber() == 0) { - if (proposal.getRoundChangeCertificate().isPresent()) { - LOG.debug( - "Illegal Proposal message, round-0 proposal must not contain a round change certificate."); - return false; - } + return validateRoundZeroProposalHasNoRoundChangeCertificate(proposal); } else { - validateRoundChangeExistsAndMatchesProposedBlock(proposal); + return validateRoundChangeCertificateIsValidAndMatchesProposedBlock(proposal); } + } + private boolean validateRoundZeroProposalHasNoRoundChangeCertificate(final Proposal proposal) { + if (proposal.getRoundChangeCertificate().isPresent()) { + LOG.debug( + "Illegal Proposal message, round-0 proposal must not contain a round change certificate."); + return false; + } return true; } - private boolean validateRoundChangeExistsAndMatchesProposedBlock(final Proposal proposal) { + private boolean validateRoundChangeCertificateIsValidAndMatchesProposedBlock( + final Proposal proposal) { final Optional roundChangeCertificate = proposal.getRoundChangeCertificate(); @@ -116,11 +118,9 @@ private boolean validateRoundChangeExistsAndMatchesProposedBlock(final Proposal final RoundChangeCertificate roundChangeCert = roundChangeCertificate.get(); - if (!roundChangeCertificateValidator - .validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( - proposal.getRoundIdentifier(), roundChangeCert)) { - LOG.debug( - "Illegal Proposal message, embedded RoundChangeCertificate is not self-consistent"); + if (!roundChangeCertificateValidator.validateRoundChangeMessagesAndEnsureTargetRoundMatchesRoot( + proposal.getRoundIdentifier(), roundChangeCert)) { + LOG.debug("Illegal Proposal message, embedded RoundChangeCertificate is not self-consistent"); return false; } @@ -130,6 +130,8 @@ private boolean validateRoundChangeExistsAndMatchesProposedBlock(final Proposal "Illegal Proposal message, piggybacked block does not match latest PrepareCertificate"); return false; } + + return true; } public boolean validatePrepare(final Prepare msg) { diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java index 5b9ff80df8..30fe9367a4 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorTest.java @@ -195,7 +195,7 @@ public void proposalForRoundZeroFailsIfItContainsARoundChangeCertificate() { } @Test - public void proposalForRoundsGreaterThanZeroFailIfNoRoundCHangeCertificateAvailable() { + public void proposalForRoundsGreaterThanZeroFailIfNoRoundChangeCertificateAvailable() { final ConsensusRoundIdentifier nonZeroRound = new ConsensusRoundIdentifier(1, 1); final Proposal proposal = messageFactory.createProposal(nonZeroRound, block, Optional.empty()); From b4e3decfadc13e62b424631ef46050a70ca5474f Mon Sep 17 00:00:00 2001 From: tmohay Date: Wed, 20 Feb 2019 09:06:06 +1100 Subject: [PATCH 20/21] clean up merge --- docs/Permissions/Permissioning.md | 32 ++-- .../ethereum/eth/manager/EthPeer.java | 12 -- .../ethereum/eth/sync/BlockBroadcaster.java | 23 ++- .../eth/sync/BlockPropagationManager.java | 2 +- .../sync/worldstate/WorldStateDownloader.java | 149 ++++++++++++------ .../jsonrpc/websocket/WebSocketService.java | 56 +++++++ .../pegasys/pantheon/cli/PantheonCommand.java | 101 ++++++++---- .../pantheon/cli/StandaloneCommand.java | 8 +- .../services/queue/RocksDbTaskQueue.java | 73 +++++---- 9 files changed, 309 insertions(+), 147 deletions(-) diff --git a/docs/Permissions/Permissioning.md b/docs/Permissions/Permissioning.md index ddb2cef5de..45c3d7045a 100644 --- a/docs/Permissions/Permissioning.md +++ b/docs/Permissions/Permissioning.md @@ -9,13 +9,13 @@ account permissions enabled, or both. ## Node Whitelisting -Node whitelisting is specified by the nodes whitelist in the [`permissions_config.toml`](#permissions-configuration-file) file. -A node with node whitelisting enabled communicates only with nodes in the nodes whitelist. +Node whitelisting is specified by the nodes whitelist in the [permissions configuration file](#permissions-configuration-file) file. +When node whitelisting is enabled, communication is restricted to only nodes in the whitelist. -!!! example "Nodes Whitelist in `permissons_config.toml`" +!!! example "Nodes Whitelist in Permissions Configuration File" `nodes-whitelist=["enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567","enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.169.0.9:4568"]` -Node whitelisting is at the node level. That is, each node in the network has a [`permissions_config.toml`](#permissions-configuration-file) +Node whitelisting is at the node level. That is, each node in the network has a [permissions configuration file](#permissions-configuration-file) file in the [data directory](../Reference/Pantheon-CLI-Syntax.md#data-path) for the node. To update the nodes whitelist when the node is running, use the JSON-RPC API methods: @@ -30,7 +30,7 @@ to update the whitelists. To view the nodes whitelist, use the [perm_getNodesWhitelist](../Reference/JSON-RPC-API-Methods.md#perm_getNodesWhiteList) method. !!! note - Each node has a `permissions_config.toml` file which means nodes can have different nodes whitelists. + Each node has a [permissions configuration file](#permissions-configuration-file) which means nodes can have different nodes whitelists. This means nodes may be participating in the network that are not on the whitelist of other nodes in the network. We recommend each node in the network has the same nodes whitelist. @@ -50,13 +50,13 @@ To view the nodes whitelist, use the [perm_getNodesWhitelist](../Reference/JSON- The bootnodes must be included in the nodes whitelist or Pantheon does not start when node permissions are enabled. !!! example - If you start Pantheon with: + If you start Pantheon with specified bootnodes and have node permissions enabled: ```bash --bootnodes="enode://7e4ef30e9ec683f26ad76ffca5b5148fa7a6575f4cfad4eb0f52f9c3d8335f4a9b6f9e66fcc73ef95ed7a2a52784d4f372e7750ac8ae0b544309a5b391a23dd7@127.0.0.1:30303","enode://2feb33b3c6c4a8f77d84a5ce44954e83e5f163e7a65f7f7a7fec499ceb0ddd76a46ef635408c513d64c076470eac86b7f2c8ae4fcd112cb28ce82c0d64ec2c94@127.0.0.1:30304","enode://7b61d5ee4b44335873e6912cb5dd3e3877c860ba21417c9b9ef1f7e500a82213737d4b269046d0669fb2299a234ca03443f25fe5f706b693b3669e5c92478ade@127.0.0.1:30305" ``` - The `nodes-whitelist` in [`permissions_config.toml`](#permissions-configuration-file) must contain the specified bootnodes. + The `nodes-whitelist` in the [permissions configuration file](#permissions-configuration-file) must contain the specified bootnodes. ### Enabling Node Whitelisting @@ -68,14 +68,14 @@ or [`--rpc-ws-api`](../Reference/Pantheon-CLI-Syntax.md#rpc-ws-api) options to e ## Account Whitelisting -Account whitelisting is specified by the accounts whitelist in the [`permissions_config.toml`](#permissions-configuration-file) file. +Account whitelisting is specified by the accounts whitelist in the [permissions configuration file](#permissions-configuration-file). A node with account permissions accepts transactions only from accounts in the accounts whitelist. -!!! example "Accounts Whitelist in `permissons_config.toml`" +!!! example "Accounts Whitelist in Permissions Configuration File" `accounts-whitelist=["0x0000000000000000000000000000000000000009"]` -Account whitelisting is at the node level. That is, each node in the network has a [`permissions_config.toml`](#permissions-configuration-file) -file in the [data directory](../Reference/Pantheon-CLI-Syntax.md#data-path) for the node. +Account whitelisting is at the node level. That is, each node in the network has a [permisssions configuration file](#permissions-configuration-file) +in the [data directory](../Reference/Pantheon-CLI-Syntax.md#data-path) for the node. Transactions are validated against the accounts whitelist at the following points: @@ -100,7 +100,7 @@ can synchronise and add blocks containing transactions from accounts that are no Node 2 now has a transaction in the blockchain from Account A which is not on the accounts whitelist for Node 2. !!! note - Each node has a [`permissions_config.toml`](#permissions-configuration-file) file which means nodes in the network can have different accounts whitelists. + Each node has a [permissions configuration file](#permissions-configuration-file) which means nodes in the network can have different accounts whitelists. This means a transaction can be successfully submitted by Node A from an account in the Node A whitelist but rejected by Node B to which it is propagated if the account is not in the Node B whitelist. We recommend each node in the network has the same accounts whitelist. @@ -129,10 +129,14 @@ or [`--rpc-ws-api`](../Reference/Pantheon-CLI-Syntax.md#rpc-ws-api) options to e ## Permissions Configuration File -The `permissions_config.toml` file contains the nodes and accounts whitelists. The `permissions_config.toml` +The permissions configuration file contains the nodes and accounts whitelists. If the [`--permissions-config-file`](../Reference/Pantheon-CLI-Syntax.md#permissions-config-file) +option is not specified, the permissions configuration file must be called `permissions_config.toml` and must be in the [data directory](../Reference/Pantheon-CLI-Syntax.md#data-path) for the node. -!!! example "Example permissions_config.toml" +Use the [`--permissions-config-file`](../Reference/Pantheon-CLI-Syntax.md#permissions-config-file) option to specify a permissions configuration file + in any location. + +!!! example "Example Permissions Configuration File" ```toml accounts-whitelist=["0xb9b81ee349c3807e46bc71aa2632203c5b462032", "0xb9b81ee349c3807e46bc71aa2632203c5b462034"] diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java index 06867e22e1..a21306e4e6 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeer.java @@ -14,7 +14,6 @@ import static com.google.common.base.Preconditions.checkArgument; -import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.eth.manager.ChainState.EstimatedHeightListener; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; @@ -24,7 +23,6 @@ import tech.pegasys.pantheon.ethereum.eth.messages.GetBlockHeadersMessage; import tech.pegasys.pantheon.ethereum.eth.messages.GetNodeDataMessage; import tech.pegasys.pantheon.ethereum.eth.messages.GetReceiptsMessage; -import tech.pegasys.pantheon.ethereum.eth.messages.NewBlockMessage; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; @@ -132,16 +130,6 @@ public ResponseStream send(final MessageData messageData) throws PeerNotConnecte } } - public void propagateBlock(final Block block, final UInt256 totalDifficulty) { - registerKnownBlock(block.getHash()); - final NewBlockMessage newBlockMessage = NewBlockMessage.create(block, totalDifficulty); - try { - connection.sendForProtocol(protocolName, newBlockMessage); - } catch (PeerNotConnected e) { - LOG.trace("Failed to broadcast new block to peer", e); - } - } - public ResponseStream getHeadersByHash( final Hash hash, final int maxHeaders, final int skip, final boolean reverse) throws PeerNotConnected { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockBroadcaster.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockBroadcaster.java index fecffaaf4c..6291489bc3 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockBroadcaster.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockBroadcaster.java @@ -14,21 +14,36 @@ import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; +import tech.pegasys.pantheon.ethereum.eth.messages.NewBlockMessage; +import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.util.uint.UInt256; -class BlockBroadcaster { +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class BlockBroadcaster { + private static final Logger LOG = LogManager.getLogger(); private final EthContext ethContext; - BlockBroadcaster(final EthContext ethContext) { + public BlockBroadcaster(final EthContext ethContext) { this.ethContext = ethContext; } - void propagate(final Block block, final UInt256 difficulty) { + public void propagate(final Block block, final UInt256 totalDifficulty) { + final NewBlockMessage newBlockMessage = NewBlockMessage.create(block, totalDifficulty); ethContext .getEthPeers() .availablePeers() .filter(ethPeer -> !ethPeer.hasSeenBlock(block.getHash())) - .forEach(ethPeer -> ethPeer.propagateBlock(block, difficulty)); + .forEach( + ethPeer -> { + ethPeer.registerKnownBlock(block.getHash()); + try { + ethPeer.send(newBlockMessage); + } catch (PeerConnection.PeerNotConnected e) { + LOG.trace("Failed to broadcast new block to peer", e); + } + }); } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java index 5f1813c384..4695039fab 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/BlockPropagationManager.java @@ -110,7 +110,7 @@ private void setupListeners() { .subscribe(EthPV62.NEW_BLOCK_HASHES, this::handleNewBlockHashesFromNetwork); } - protected void validateAndBroadcastBlock(final Block block) { + private void validateAndBroadcastBlock(final Block block) { final ProtocolSpec protocolSpec = protocolSchedule.getByBlockNumber(block.getHeader().getNumber()); final BlockHeaderValidator blockHeaderValidator = protocolSpec.getBlockHeaderValidator(); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java index 2bb8bc8c57..f4fef8b6ab 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/sync/worldstate/WorldStateDownloader.java @@ -16,6 +16,7 @@ import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.EthTaskException; import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask; import tech.pegasys.pantheon.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import tech.pegasys.pantheon.ethereum.eth.manager.task.EthTask; @@ -30,6 +31,7 @@ import tech.pegasys.pantheon.metrics.OperationTimer; import tech.pegasys.pantheon.services.queue.TaskQueue; import tech.pegasys.pantheon.services.queue.TaskQueue.Task; +import tech.pegasys.pantheon.util.ExceptionUtils; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.time.Duration; @@ -40,9 +42,12 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -73,6 +78,7 @@ private enum Status { private volatile CompletableFuture future; private volatile Status status = Status.IDLE; private volatile BytesValue rootNode; + private final AtomicInteger highestRetryCount = new AtomicInteger(0); public WorldStateDownloader( final EthContext ethContext, @@ -90,11 +96,17 @@ public WorldStateDownloader( this.maxOutstandingRequests = maxOutstandingRequests; this.maxNodeRequestRetries = maxNodeRequestRetries; this.ethTasksTimer = ethTasksTimer; - metricsSystem.createGauge( + metricsSystem.createLongGauge( MetricCategory.SYNCHRONIZER, "world_state_pending_requests_current", "Number of pending requests for fast sync world state download", - () -> (double) pendingRequests.size()); + pendingRequests::size); + + metricsSystem.createIntegerGauge( + MetricCategory.SYNCHRONIZER, + "world_state_inflight_requests_current", + "Number of requests currently in flight for fast sync world state download", + outstandingRequests::size); completedRequestsCounter = metricsSystem.createCounter( @@ -106,6 +118,12 @@ public WorldStateDownloader( MetricCategory.SYNCHRONIZER, "world_state_retried_requests_total", "Total number of node data requests repeated as part of fast sync world state download"); + + metricsSystem.createIntegerGauge( + MetricCategory.SYNCHRONIZER, + "world_state_node_request_failures_max", + "Highest number of times a node data request has been retried in this download", + highestRetryCount::get); } public CompletableFuture run(final BlockHeader header) { @@ -115,7 +133,7 @@ public CompletableFuture run(final BlockHeader header) { header.getHash()); synchronized (this) { if (status == Status.RUNNING) { - CompletableFuture failed = new CompletableFuture<>(); + final CompletableFuture failed = new CompletableFuture<>(); failed.completeExceptionally( new IllegalStateException( "Cannot run an already running " + this.getClass().getSimpleName())); @@ -123,8 +141,9 @@ public CompletableFuture run(final BlockHeader header) { } status = Status.RUNNING; future = createFuture(); + highestRetryCount.set(0); - Hash stateRoot = header.getStateRoot(); + final Hash stateRoot = header.getStateRoot(); if (worldStateStorage.isWorldStateAvailable(stateRoot)) { // If we're requesting data for an existing world state, we're already done markDone(); @@ -132,8 +151,7 @@ public CompletableFuture run(final BlockHeader header) { pendingRequests.enqueue(NodeDataRequest.createAccountDataRequest(stateRoot)); } } - - requestNodeData(header); + ethContext.getScheduler().scheduleSyncWorkerTask(() -> requestNodeData(header)); return future; } @@ -144,23 +162,23 @@ public void cancel() { private void requestNodeData(final BlockHeader header) { if (sendingRequests.compareAndSet(false, true)) { while (shouldRequestNodeData()) { - Optional maybePeer = ethContext.getEthPeers().idlePeer(header.getNumber()); + final Optional maybePeer = ethContext.getEthPeers().idlePeer(header.getNumber()); if (!maybePeer.isPresent()) { // If no peer is available, wait and try again waitForNewPeer().whenComplete((r, t) -> requestNodeData(header)); break; } else { - EthPeer peer = maybePeer.get(); + final EthPeer peer = maybePeer.get(); // Collect data to be requested - List> toRequest = new ArrayList<>(); + final List> toRequest = new ArrayList<>(); while (toRequest.size() < hashCountPerRequest) { - Task pendingRequestTask = pendingRequests.dequeue(); + final Task pendingRequestTask = pendingRequests.dequeue(); if (pendingRequestTask == null) { break; } - NodeDataRequest pendingRequest = pendingRequestTask.getData(); + final NodeDataRequest pendingRequest = pendingRequestTask.getData(); final Optional existingData = pendingRequest.getExistingData(worldStateStorage); if (existingData.isPresent()) { @@ -176,7 +194,7 @@ private void requestNodeData(final BlockHeader header) { sendAndProcessRequests(peer, toRequest, header) .whenComplete( (task, error) -> { - boolean done; + final boolean done; synchronized (this) { outstandingRequests.remove(task); done = @@ -217,50 +235,79 @@ private CompletableFuture>> sendAndProcessRequ final EthPeer peer, final List> requestTasks, final BlockHeader blockHeader) { - List hashes = + final List hashes = requestTasks.stream() .map(Task::getData) .map(NodeDataRequest::getHash) .distinct() .collect(Collectors.toList()); - AbstractPeerTask> ethTask = + final AbstractPeerTask> ethTask = GetNodeDataFromPeerTask.forHashes(ethContext, hashes, ethTasksTimer).assignPeer(peer); outstandingRequests.add(ethTask); return ethTask .run() .thenApply(PeerTaskResult::getResult) .thenApply(this::mapNodeDataByHash) - .handle( - (data, err) -> { - boolean requestFailed = err != null; - Updater storageUpdater = worldStateStorage.updater(); - for (Task task : requestTasks) { - NodeDataRequest request = task.getData(); - BytesValue matchingData = requestFailed ? null : data.get(request.getHash()); - if (matchingData == null) { - retriedRequestsTotal.inc(); - int requestFailures = request.trackFailure(); - if (requestFailures > maxNodeRequestRetries) { - handleStalledDownload(); - } - task.markFailed(); - } else { - completedRequestsCounter.inc(); - // Persist request data - request.setData(matchingData); - if (isRootState(blockHeader, request)) { - rootNode = request.getData(); - } else { - request.persist(storageUpdater); - } - - queueChildRequests(request); - task.markCompleted(); - } + .exceptionally( + error -> { + final Throwable rootCause = ExceptionUtils.rootCause(error); + if (!(rootCause instanceof TimeoutException + || rootCause instanceof InterruptedException + || rootCause instanceof CancellationException + || rootCause instanceof EthTaskException)) { + LOG.debug("GetNodeDataRequest failed", error); } - storageUpdater.commit(); - return ethTask; - }); + return Collections.emptyMap(); + }) + .thenCompose( + data -> + ethContext + .getScheduler() + .scheduleSyncWorkerTask( + () -> storeData(requestTasks, blockHeader, ethTask, data))); + } + + private CompletableFuture>> storeData( + final List> requestTasks, + final BlockHeader blockHeader, + final AbstractPeerTask> ethTask, + final Map data) { + final Updater storageUpdater = worldStateStorage.updater(); + for (final Task task : requestTasks) { + final NodeDataRequest request = task.getData(); + final BytesValue matchingData = data.get(request.getHash()); + if (matchingData == null) { + retriedRequestsTotal.inc(); + final int requestFailures = request.trackFailure(); + updateHighestRetryCount(requestFailures); + if (requestFailures > maxNodeRequestRetries) { + handleStalledDownload(); + } + task.markFailed(); + } else { + completedRequestsCounter.inc(); + // Persist request data + request.setData(matchingData); + if (isRootState(blockHeader, request)) { + rootNode = request.getData(); + } else { + request.persist(storageUpdater); + } + + queueChildRequests(request); + task.markCompleted(); + } + } + storageUpdater.commit(); + return CompletableFuture.completedFuture(ethTask); + } + + private void updateHighestRetryCount(final int requestFailures) { + int previousHighestRetry = highestRetryCount.get(); + while (requestFailures > previousHighestRetry) { + highestRetryCount.compareAndSet(previousHighestRetry, requestFailures); + previousHighestRetry = highestRetryCount.get(); + } } private synchronized void queueChildRequests(final NodeDataRequest request) { @@ -277,7 +324,7 @@ private synchronized CompletableFuture getFuture() { } private CompletableFuture createFuture() { - CompletableFuture future = new CompletableFuture<>(); + final CompletableFuture future = new CompletableFuture<>(); future.whenComplete( (res, err) -> { // Handle cancellations @@ -285,7 +332,9 @@ private CompletableFuture createFuture() { LOG.info("World state download cancelled"); doCancelDownload(); } else if (err != null) { - LOG.info("World state download failed. ", err); + if (!(ExceptionUtils.rootCause(err) instanceof StalledDownloadException)) { + LOG.info("World state download failed. ", err); + } doCancelDownload(); } }); @@ -297,14 +346,14 @@ private synchronized void handleStalledDownload() { "Download stalled due to too many failures to retrieve node data (>" + maxNodeRequestRetries + " failures)"; - WorldStateDownloaderException e = new StalledDownloadException(message); + final WorldStateDownloaderException e = new StalledDownloadException(message); future.completeExceptionally(e); } private synchronized void doCancelDownload() { status = Status.CANCELLED; pendingRequests.clear(); - for (EthTask outstandingRequest : outstandingRequests) { + for (final EthTask outstandingRequest : outstandingRequests) { outstandingRequest.cancel(); } } @@ -323,8 +372,8 @@ private boolean isRootState(final BlockHeader blockHeader, final NodeDataRequest private Map mapNodeDataByHash(final List data) { // Map data by hash - Map dataByHash = new HashMap<>(); - data.stream().forEach(d -> dataByHash.put(Hash.hash(d), d)); + final Map dataByHash = new HashMap<>(); + data.forEach(d -> dataByHash.put(Hash.hash(d), d)); return dataByHash; } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java index 62ad3e17af..8ebf1cb66a 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/WebSocketService.java @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.jsonrpc.websocket; +import static com.google.common.collect.Streams.stream; + import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationService; import tech.pegasys.pantheon.ethereum.jsonrpc.authentication.AuthenticationUtils; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.subscription.SubscriptionManager; @@ -21,6 +23,8 @@ import java.util.concurrent.CompletableFuture; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; @@ -30,6 +34,7 @@ import io.vertx.core.http.ServerWebSocket; import io.vertx.core.net.SocketAddress; import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -100,6 +105,10 @@ private Handler websocketHandler() { LOG.trace("Websocket authentication token {}", token); } + if (!hasWhitelistedHostnameHeader(Optional.ofNullable(websocket.headers().get("Host")))) { + websocket.reject(403); + } + LOG.debug("Websocket Connected ({})", socketAddressAsString(socketAddress)); websocket.handler( @@ -138,6 +147,10 @@ private Handler websocketHandler() { private Handler httpHandler() { final Router router = Router.router(vertx); + + // Verify Host header to avoid rebind attack. + router.route().handler(checkWhitelistHostHeader()); + if (authenticationService.isPresent()) { router.route("/login").handler(BodyHandler.create()); router @@ -212,4 +225,47 @@ private String getAuthToken(final ServerWebSocket websocket) { return AuthenticationUtils.getJwtTokenFromAuthorizationHeaderValue( websocket.headers().get("Authorization")); } + + private Handler checkWhitelistHostHeader() { + return event -> { + if (hasWhitelistedHostnameHeader(Optional.ofNullable(event.request().host()))) { + event.next(); + } else { + event + .response() + .setStatusCode(403) + .putHeader("Content-Type", "application/json; charset=utf-8") + .end("{\"message\":\"Host not authorized.\"}"); + } + }; + } + + @VisibleForTesting + public boolean hasWhitelistedHostnameHeader(final Optional header) { + return configuration.getHostsWhitelist().contains("*") + || header.map(value -> checkHostInWhitelist(validateHostHeader(value))).orElse(false); + } + + private Optional validateHostHeader(final String header) { + final Iterable splitHostHeader = Splitter.on(':').split(header); + final long hostPieces = stream(splitHostHeader).count(); + if (hostPieces > 1) { + // If the host contains a colon, verify the host is correctly formed - host [ ":" port ] + if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { + return Optional.empty(); + } + } + return Optional.ofNullable(Iterables.get(splitHostHeader, 0)); + } + + private boolean checkHostInWhitelist(final Optional hostHeader) { + return hostHeader + .map( + header -> + configuration.getHostsWhitelist().stream() + .anyMatch( + whitelistEntry -> + whitelistEntry.toLowerCase().equals(header.toLowerCase()))) + .orElse(false); + } } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index 45f0749f84..c13a790df5 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -30,7 +30,7 @@ import tech.pegasys.pantheon.cli.custom.CorsAllowedOriginsProperty; import tech.pegasys.pantheon.cli.custom.EnodeToURIPropertyConverter; import tech.pegasys.pantheon.cli.custom.JsonRPCWhitelistHostsProperty; -import tech.pegasys.pantheon.cli.custom.RpcAuthConverter; +import tech.pegasys.pantheon.cli.custom.RpcAuthFileValidator; import tech.pegasys.pantheon.config.GenesisConfigFile; import tech.pegasys.pantheon.consensus.clique.jsonrpc.CliqueRpcApis; import tech.pegasys.pantheon.consensus.ibft.jsonrpc.IbftRpcApis; @@ -65,7 +65,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -319,7 +318,7 @@ public static class RpcApisConversionException extends Exception { private Long configureRefreshDelay(final Long refreshDelay) { if (refreshDelay < DEFAULT_MIN_REFRESH_DELAY || refreshDelay > DEFAULT_MAX_REFRESH_DELAY) { throw new ParameterException( - new CommandLine(this), + this.commandLine, String.format( "Refresh delay must be a positive integer between %s and %s", String.valueOf(DEFAULT_MIN_REFRESH_DELAY), @@ -548,7 +547,7 @@ public void run() { //noinspection ConstantConditions if (isMiningEnabled && coinbase == null) { throw new ParameterException( - new CommandLine(this), + this.commandLine, "Unable to mine without a valid coinbase. Either disable mining (remove --miner-enabled)" + "or specify the beneficiary of mining (via --miner-coinbase

    )"); } @@ -575,7 +574,7 @@ public void run() { metricsConfiguration(), permissioningConfiguration); } catch (Exception e) { - throw new ParameterException(new CommandLine(this), e.getMessage()); + throw new ParameterException(this.commandLine, e.getMessage()); } } @@ -591,7 +590,7 @@ private void ensureAllBootnodesAreInWhitelist( PermissioningConfigurationValidator.areAllBootnodesAreInWhitelist( ethNetworkConfig, permissioningConfiguration); } catch (final Exception e) { - throw new ParameterException(new CommandLine(this), e.getMessage()); + throw new ParameterException(this.commandLine, e.getMessage()); } } @@ -610,9 +609,9 @@ PantheonController buildController() { .privacyParameters(privacyParameters()) .build(); } catch (final InvalidConfigurationException e) { - throw new ExecutionException(new CommandLine(this), e.getMessage()); + throw new ExecutionException(this.commandLine, e.getMessage()); } catch (final IOException e) { - throw new ExecutionException(new CommandLine(this), "Invalid path", e); + throw new ExecutionException(this.commandLine, "Invalid path", e); } } @@ -641,12 +640,11 @@ private JsonRpcConfiguration jsonRpcConfiguration() throws Exception { "--rpc-http-authentication-enabled", "--rpc-http-authentication-credentials-file")); - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--rpc-http-authentication-enabled", - !isRpcHttpAuthenticationEnabled, - Collections.singletonList("--rpc-http-authentication-credentials-file")); + if (isRpcHttpAuthenticationEnabled && rpcHttpAuthenticationCredentialsFile() == null) { + throw new ParameterException( + commandLine, + "Unable to authenticate RPC HTTP endpoint without a supplied credentials file"); + } final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); jsonRpcConfiguration.setEnabled(isRpcHttpEnabled); @@ -676,12 +674,11 @@ private WebSocketConfiguration webSocketConfiguration() { "--rpc-ws-authentication-enabled", "--rpc-ws-authentication-credentials-file")); - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--rpc-ws-authentication-enabled", - !isRpcWsAuthenticationEnabled, - Collections.singletonList("--rpc-ws-authentication-credentials-file")); + if (isRpcWsAuthenticationEnabled && rpcWsAuthenticationCredentialsFile() == null) { + throw new ParameterException( + commandLine, + "Unable to authenticate RPC WS endpoint without a supplied credentials file"); + } final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); webSocketConfiguration.setEnabled(isRpcWsEnabled); @@ -690,14 +687,15 @@ private WebSocketConfiguration webSocketConfiguration() { webSocketConfiguration.setRpcApis(rpcWsApis); webSocketConfiguration.setRefreshDelay(rpcWsRefreshDelay); webSocketConfiguration.setAuthenticationEnabled(isRpcWsAuthenticationEnabled); - webSocketConfiguration.setAuthenticationCredentialsFile(rpcWsAuthenticationCredentialsFile); + webSocketConfiguration.setAuthenticationCredentialsFile(rpcWsAuthenticationCredentialsFile()); + webSocketConfiguration.setHostsWhitelist(hostsWhitelist); return webSocketConfiguration; } MetricsConfiguration metricsConfiguration() { if (isMetricsEnabled && isMetricsPushEnabled) { throw new ParameterException( - new CommandLine(this), + this.commandLine, "--metrics-enabled option and --metrics-push-enabled option can't be used at the same " + "time. Please refer to CLI reference for more details about this constraint."); } @@ -861,7 +859,7 @@ private EthNetworkConfig updateNetworkConfig(final NetworkName network) { // if user provided it and provided the genesis file option at the same time, it raises a // conflict error throw new ParameterException( - new CommandLine(this), + this.commandLine, "--network option and --genesis-file option can't be used at the same time. Please " + "refer to CLI reference for more details about this constraint."); } @@ -885,9 +883,7 @@ private EthNetworkConfig updateNetworkConfig(final NetworkName network) { .orElse(EthNetworkConfig.getNetworkConfig(MAINNET).getNetworkId())); } catch (final DecodeException e) { throw new ParameterException( - new CommandLine(this), - String.format("Unable to parse genesis file %s.", genesisFile), - e); + this.commandLine, String.format("Unable to parse genesis file %s.", genesisFile), e); } } @@ -916,9 +912,7 @@ private String genesisConfig() { return Resources.toString(genesisFile().toURI().toURL(), UTF_8); } catch (final IOException e) { throw new ParameterException( - new CommandLine(this), - String.format("Unable to load genesis file %s.", genesisFile()), - e); + this.commandLine, String.format("Unable to load genesis file %s.", genesisFile()), e); } } @@ -939,7 +933,7 @@ private File genesisFile() { private Path dataDir() { if (isFullInstantiation()) { - return standaloneCommands.dataPath; + return standaloneCommands.dataPath.toAbsolutePath(); } else if (isDocker) { return Paths.get(DOCKER_DATADIR_LOCATION); } else { @@ -958,6 +952,53 @@ private File nodePrivateKeyFile() { : KeyPairUtil.getDefaultKeyFile(dataDir()); } + private File privacyPublicKeyFile() { + if (isDocker) { + final File keyFile = new File(DOCKER_PRIVACY_PUBLIC_KEY_FILE); + if (keyFile.exists()) { + return keyFile; + } else { + return null; + } + } else { + return standaloneCommands.privacyPublicKeyFile; + } + } + + private String rpcHttpAuthenticationCredentialsFile() throws Exception { + String filename = null; + if (isFullInstantiation()) { + filename = standaloneCommands.rpcHttpAuthenticationCredentialsFile; + } else if (isDocker) { + final File authFile = new File(DOCKER_RPC_HTTP_AUTHENTICATION_CREDENTIALS_FILE_LOCATION); + if (authFile.exists()) { + filename = authFile.getAbsolutePath(); + } + } + + if (filename != null) { + RpcAuthFileValidator.validate(commandLine, filename, "HTTP"); + } + return filename; + } + + private String rpcWsAuthenticationCredentialsFile() { + String filename = null; + if (isFullInstantiation()) { + filename = standaloneCommands.rpcWsAuthenticationCredentialsFile; + } else if (isDocker) { + final File authFile = new File(DOCKER_RPC_WS_AUTHENTICATION_CREDENTIALS_FILE_LOCATION); + if (authFile.exists()) { + filename = authFile.getAbsolutePath(); + } + } + + if (filename != null) { + RpcAuthFileValidator.validate(commandLine, filename, "WS"); + } + return filename; + } + private boolean isFullInstantiation() { return !isDocker; } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/StandaloneCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/StandaloneCommand.java index c1dfcf4583..379f541773 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/StandaloneCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/StandaloneCommand.java @@ -14,8 +14,6 @@ import static tech.pegasys.pantheon.cli.DefaultCommandValues.getDefaultPantheonDataPath; -import tech.pegasys.pantheon.cli.custom.RpcAuthConverter; - import java.io.File; import java.nio.file.Path; @@ -59,8 +57,7 @@ class StandaloneCommand implements DefaultCommandValues { paramLabel = MANDATORY_FILE_FORMAT_HELP, description = "Storage file for rpc http authentication credentials (default: ${DEFAULT-VALUE})", - arity = "1", - converter = RpcAuthConverter.class) + arity = "1") String rpcHttpAuthenticationCredentialsFile = null; @CommandLine.Option( @@ -68,8 +65,7 @@ class StandaloneCommand implements DefaultCommandValues { paramLabel = MANDATORY_FILE_FORMAT_HELP, description = "Storage file for rpc websocket authentication credentials (default: ${DEFAULT-VALUE})", - arity = "1", - converter = RpcAuthConverter.class) + arity = "1") String rpcWsAuthenticationCredentialsFile = null; @CommandLine.Option( diff --git a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/RocksDbTaskQueue.java b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/RocksDbTaskQueue.java index 8ed91b8b5a..6a6c21e7a2 100644 --- a/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/RocksDbTaskQueue.java +++ b/services/queue/src/main/java/tech/pegasys/pantheon/services/queue/RocksDbTaskQueue.java @@ -24,13 +24,14 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; import com.google.common.primitives.Longs; import org.rocksdb.Options; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; -public class RocksDbTaskQueue implements BytesTaskQueue { +public class RocksDbTaskQueue implements TaskQueue { private final Options options; private final RocksDB db; @@ -38,14 +39,23 @@ public class RocksDbTaskQueue implements BytesTaskQueue { private final AtomicLong lastEnqueuedKey = new AtomicLong(0); private final AtomicLong lastDequeuedKey = new AtomicLong(0); private final AtomicLong oldestKey = new AtomicLong(0); - private final Set outstandingTasks = new HashSet<>(); + private final Set> outstandingTasks = new HashSet<>(); private final AtomicBoolean closed = new AtomicBoolean(false); + private final Function serializer; + private final Function deserializer; + private final OperationTimer enqueueLatency; private final OperationTimer dequeueLatency; - private RocksDbTaskQueue(final Path storageDirectory, final MetricsSystem metricsSystem) { + private RocksDbTaskQueue( + final Path storageDirectory, + final Function serializer, + final Function deserializer, + final MetricsSystem metricsSystem) { + this.serializer = serializer; + this.deserializer = deserializer; try { RocksDbUtil.loadNativeLibrary(); options = new Options().setCreateIfMissing(true); @@ -66,40 +76,43 @@ private RocksDbTaskQueue(final Path storageDirectory, final MetricsSystem metric } } - public static RocksDbTaskQueue create( - final Path storageDirectory, final MetricsSystem metricsSystem) { - return new RocksDbTaskQueue(storageDirectory, metricsSystem); + public static RocksDbTaskQueue create( + final Path storageDirectory, + final Function serializer, + final Function deserializer, + final MetricsSystem metricsSystem) { + return new RocksDbTaskQueue<>(storageDirectory, serializer, deserializer, metricsSystem); } @Override - public synchronized void enqueue(final BytesValue taskData) { + public synchronized void enqueue(final T taskData) { assertNotClosed(); try (final OperationTimer.TimingContext ignored = enqueueLatency.startTimer()) { - byte[] key = Longs.toByteArray(lastEnqueuedKey.incrementAndGet()); - db.put(key, taskData.getArrayUnsafe()); - } catch (RocksDBException e) { + final byte[] key = Longs.toByteArray(lastEnqueuedKey.incrementAndGet()); + db.put(key, serializer.apply(taskData).getArrayUnsafe()); + } catch (final RocksDBException e) { throw new StorageException(e); } } @Override - public synchronized Task dequeue() { + public synchronized Task dequeue() { assertNotClosed(); if (isEmpty()) { return null; } try (final OperationTimer.TimingContext ignored = dequeueLatency.startTimer()) { - long key = lastDequeuedKey.incrementAndGet(); - byte[] value = db.get(Longs.toByteArray(key)); + final long key = lastDequeuedKey.incrementAndGet(); + final byte[] value = db.get(Longs.toByteArray(key)); if (value == null) { throw new IllegalStateException("Next expected value is missing"); } - BytesValue data = BytesValue.of(value); - RocksDbTask task = new RocksDbTask(this, data, key); + final BytesValue data = BytesValue.of(value); + final RocksDbTask task = new RocksDbTask<>(this, deserializer.apply(data), key); outstandingTasks.add(task); return task; - } catch (RocksDBException e) { + } catch (final RocksDBException e) { throw new StorageException(e); } } @@ -120,14 +133,14 @@ public synchronized boolean isEmpty() { public synchronized void clear() { assertNotClosed(); outstandingTasks.clear(); - byte[] from = Longs.toByteArray(oldestKey.get()); - byte[] to = Longs.toByteArray(lastEnqueuedKey.get() + 1); + final byte[] from = Longs.toByteArray(oldestKey.get()); + final byte[] to = Longs.toByteArray(lastEnqueuedKey.get() + 1); try { db.deleteRange(from, to); lastDequeuedKey.set(0); lastEnqueuedKey.set(0); oldestKey.set(0); - } catch (RocksDBException e) { + } catch (final RocksDBException e) { throw new StorageException(e); } } @@ -138,7 +151,7 @@ public synchronized boolean allTasksCompleted() { } private synchronized void deleteCompletedTasks() { - long oldestOutstandingKey = + final long oldestOutstandingKey = outstandingTasks.stream() .min(Comparator.comparingLong(RocksDbTask::getKey)) .map(RocksDbTask::getKey) @@ -146,12 +159,12 @@ private synchronized void deleteCompletedTasks() { if (oldestKey.get() < oldestOutstandingKey) { // Delete all contiguous completed tasks - byte[] fromKey = Longs.toByteArray(oldestKey.get()); - byte[] toKey = Longs.toByteArray(oldestOutstandingKey); + final byte[] fromKey = Longs.toByteArray(oldestKey.get()); + final byte[] toKey = Longs.toByteArray(oldestOutstandingKey); try { db.deleteRange(fromKey, toKey); oldestKey.set(oldestOutstandingKey); - } catch (RocksDBException e) { + } catch (final RocksDBException e) { throw new StorageException(e); } } @@ -171,7 +184,7 @@ private void assertNotClosed() { } } - private synchronized boolean markTaskCompleted(final RocksDbTask task) { + private synchronized boolean markTaskCompleted(final RocksDbTask task) { if (outstandingTasks.remove(task)) { deleteCompletedTasks(); return true; @@ -179,7 +192,7 @@ private synchronized boolean markTaskCompleted(final RocksDbTask task) { return false; } - private synchronized void handleFailedTask(final RocksDbTask task) { + private synchronized void handleFailedTask(final RocksDbTask task) { if (markTaskCompleted(task)) { enqueue(task.getData()); } @@ -191,13 +204,13 @@ public static class StorageException extends RuntimeException { } } - private static class RocksDbTask implements Task { + private static class RocksDbTask implements Task { private final AtomicBoolean completed = new AtomicBoolean(false); - private final RocksDbTaskQueue parentQueue; - private final BytesValue data; + private final RocksDbTaskQueue parentQueue; + private final T data; private final long key; - private RocksDbTask(final RocksDbTaskQueue parentQueue, final BytesValue data, final long key) { + private RocksDbTask(final RocksDbTaskQueue parentQueue, final T data, final long key) { this.parentQueue = parentQueue; this.data = data; this.key = key; @@ -208,7 +221,7 @@ public long getKey() { } @Override - public BytesValue getData() { + public T getData() { return data; } From e0f08b1e8c6272b9670bbd735460aa78cbaaf36c Mon Sep 17 00:00:00 2001 From: tmohay Date: Wed, 20 Feb 2019 15:40:32 +1100 Subject: [PATCH 21/21] rebased and working --- .../ibft/statemachine/IbftBlockHeightManager.java | 8 ++++---- .../FutureRoundProposalMessageValidator.java | 13 +++++++++---- .../ibft/validation/MessageValidatorFactory.java | 11 +++++++---- .../statemachine/IbftBlockHeightManagerTest.java | 11 ++++++----- .../FutureRoundProposalMessageValidatorTest.java | 7 +++++-- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java index 89ecc7c7fb..aec650b751 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManager.java @@ -22,15 +22,14 @@ import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.IbftMessage; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; import tech.pegasys.pantheon.consensus.ibft.network.IbftMessageTransmitter; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.payload.Payload; +import tech.pegasys.pantheon.consensus.ibft.validation.FutureRoundProposalMessageValidator; import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory; -import tech.pegasys.pantheon.consensus.ibft.validation.NewRoundMessageValidator; import tech.pegasys.pantheon.ethereum.core.BlockHeader; import java.time.Clock; @@ -91,14 +90,15 @@ public IbftBlockHeightManager( this.finalState = finalState; futureRoundProposalMessageValidator = - messageValidatorFactory.createFutureRoundProposalMessageValidator(getChainHeight()); + messageValidatorFactory.createFutureRoundProposalMessageValidator( + getChainHeight(), parentHeader); roundStateCreator = (roundIdentifier) -> new RoundState( roundIdentifier, finalState.getQuorum(), - messageValidatorFactory.createMessageValidator(roundIdentifier)); + messageValidatorFactory.createMessageValidator(roundIdentifier, parentHeader)); } @Override diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java index 4fed271ca7..508bad9155 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidator.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.consensus.ibft.validation; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,13 +25,17 @@ public class FutureRoundProposalMessageValidator { private static final Logger LOG = LogManager.getLogger(); - private MessageValidatorFactory messageValidatorFactory; - private long chainHeight; + private final MessageValidatorFactory messageValidatorFactory; + private final long chainHeight; + private final BlockHeader parentHeader; public FutureRoundProposalMessageValidator( - final MessageValidatorFactory messageValidatorFactory, final long chainHeight) { + final MessageValidatorFactory messageValidatorFactory, + final long chainHeight, + final BlockHeader parentHeader) { this.messageValidatorFactory = messageValidatorFactory; this.chainHeight = chainHeight; + this.parentHeader = parentHeader; } public boolean validateProposalMessage(final Proposal msg) { @@ -41,7 +46,7 @@ public boolean validateProposalMessage(final Proposal msg) { } final MessageValidator messageValidator = - messageValidatorFactory.createMessageValidator(msg.getRoundIdentifier()); + messageValidatorFactory.createMessageValidator(msg.getRoundIdentifier(), parentHeader); return messageValidator.validateProposal(msg); } diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java index 00b6aa5bd3..83fae657e4 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/validation/MessageValidatorFactory.java @@ -21,6 +21,7 @@ import tech.pegasys.pantheon.ethereum.BlockValidator; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import java.util.Collection; @@ -49,7 +50,7 @@ private Collection
    getValidatorsAfterBlock(final BlockHeader parentHead } private SignedDataValidator createSignedDataValidator( - final ConsensusRoundIdentifier roundIdentifier) { + final ConsensusRoundIdentifier roundIdentifier, final BlockHeader parentHeader) { return new SignedDataValidator( getValidatorsAfterBlock(parentHeader), @@ -69,7 +70,9 @@ public MessageValidator createMessageValidator( blockValidator, protocolContext, new RoundChangeCertificateValidator( - validators, this::createSignedDataValidator, roundIdentifier.getSequenceNumber())); + validators, + (ri) -> createSignedDataValidator(ri, parentHeader), + roundIdentifier.getSequenceNumber())); } public RoundChangeMessageValidator createRoundChangeMessageValidator( @@ -87,8 +90,8 @@ public RoundChangeMessageValidator createRoundChangeMessageValidator( } public FutureRoundProposalMessageValidator createFutureRoundProposalMessageValidator( - final long chainHeight) { + final long chainHeight, final BlockHeader parentHeader) { - return new FutureRoundProposalMessageValidator(this, chainHeight); + return new FutureRoundProposalMessageValidator(this, chainHeight, parentHeader); } } diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java index dddc90f295..1c38622239 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java @@ -21,12 +21,13 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static tech.pegasys.pantheon.consensus.ibft.IbftContextBuilder.setupContextWithValidators; import static tech.pegasys.pantheon.consensus.ibft.TestHelpers.createFrom; -import tech.pegasys.pantheon.consensus.common.VoteTally; import tech.pegasys.pantheon.consensus.ibft.BlockTimer; import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; import tech.pegasys.pantheon.consensus.ibft.IbftContext; @@ -35,15 +36,15 @@ import tech.pegasys.pantheon.consensus.ibft.blockcreation.IbftBlockCreator; import tech.pegasys.pantheon.consensus.ibft.ibftevent.RoundExpiry; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Commit; -import tech.pegasys.pantheon.consensus.ibft.messagewrappers.NewRound; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Prepare; +import tech.pegasys.pantheon.consensus.ibft.messagewrappers.Proposal; import tech.pegasys.pantheon.consensus.ibft.messagewrappers.RoundChange; import tech.pegasys.pantheon.consensus.ibft.network.IbftMessageTransmitter; import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; import tech.pegasys.pantheon.consensus.ibft.payload.RoundChangeCertificate; +import tech.pegasys.pantheon.consensus.ibft.validation.FutureRoundProposalMessageValidator; import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidator; import tech.pegasys.pantheon.consensus.ibft.validation.MessageValidatorFactory; -import tech.pegasys.pantheon.consensus.ibft.validation.NewRoundMessageValidator; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.Signature; import tech.pegasys.pantheon.ethereum.ProtocolContext; @@ -136,9 +137,9 @@ public void setup() { when(blockCreator.createBlock(anyLong())).thenReturn(createdBlock); when(futureRoundProposalMessageValidator.validateProposalMessage(any())).thenReturn(true); - when(messageValidatorFactory.createFutureRoundProposalMessageValidator(anyLong())) + when(messageValidatorFactory.createFutureRoundProposalMessageValidator(anyLong(), any())) .thenReturn(futureRoundProposalMessageValidator); - when(messageValidatorFactory.createMessageValidator(any())).thenReturn(messageValidator); + when(messageValidatorFactory.createMessageValidator(any(), any())).thenReturn(messageValidator); protocolContext = new ProtocolContext<>(null, null, setupContextWithValidators(validators)); diff --git a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java index c5f213e0c1..8dc3846b49 100644 --- a/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java +++ b/consensus/ibft/src/test/java/tech/pegasys/pantheon/consensus/ibft/validation/FutureRoundProposalMessageValidatorTest.java @@ -24,6 +24,7 @@ import tech.pegasys.pantheon.consensus.ibft.payload.MessageFactory; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.core.BlockHeader; import java.util.Optional; @@ -49,12 +50,14 @@ public class FutureRoundProposalMessageValidatorTest { @Before public void setup() { - when(messageValidatorFactory.createMessageValidator(any())).thenReturn(messageValidator); + when(messageValidatorFactory.createMessageValidator(any(), any())).thenReturn(messageValidator); when(messageValidator.validateProposal(any())).thenReturn(true); + final BlockHeader parentHeader = mock(BlockHeader.class); + validator = new FutureRoundProposalMessageValidator( - messageValidatorFactory, roundIdentifier.getSequenceNumber()); + messageValidatorFactory, roundIdentifier.getSequenceNumber(), parentHeader); } @Test