mirror of https://github.com/hyperledger/besu
Signed-off-by: Matilda Clerke <matilda.clerke@consensys.net>pull/7628/head
parent
89dfa95860
commit
4b80016587
@ -0,0 +1,26 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
public class InvalidPeerTaskResponseException extends Exception { |
||||||
|
|
||||||
|
public InvalidPeerTaskResponseException() { |
||||||
|
super(); |
||||||
|
} |
||||||
|
|
||||||
|
public InvalidPeerTaskResponseException(final Throwable cause) { |
||||||
|
super(cause); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
public class NoAvailablePeerException extends Exception {} |
@ -0,0 +1,64 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.peers.PeerId; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.function.Predicate; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
/** "Manages" the EthPeers for the PeerTaskExecutor */ |
||||||
|
public class PeerManager { |
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(PeerManager.class); |
||||||
|
|
||||||
|
// use a synchronized map to ensure the map is never modified by multiple threads at once
|
||||||
|
private final Map<PeerId, EthPeer> ethPeersByPeerId = |
||||||
|
Collections.synchronizedMap(new HashMap<>()); |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the highest reputation peer matching the supplies filter |
||||||
|
* |
||||||
|
* @param filter a filter to match prospective peers with |
||||||
|
* @return the highest reputation peer matching the supplies filter |
||||||
|
* @throws NoAvailablePeerException If there are no suitable peers |
||||||
|
*/ |
||||||
|
public EthPeer getPeer(final Predicate<EthPeer> filter) throws NoAvailablePeerException { |
||||||
|
LOG.trace("Getting peer from pool of {} peers", ethPeersByPeerId.size()); |
||||||
|
return ethPeersByPeerId.values().stream() |
||||||
|
.filter(filter) |
||||||
|
.max(Comparator.naturalOrder()) |
||||||
|
.orElseThrow(NoAvailablePeerException::new); |
||||||
|
} |
||||||
|
|
||||||
|
public Optional<EthPeer> getPeerByPeerId(final PeerId peerId) { |
||||||
|
return Optional.ofNullable(ethPeersByPeerId.get(peerId)); |
||||||
|
} |
||||||
|
|
||||||
|
public void addPeer(final EthPeer ethPeer) { |
||||||
|
ethPeersByPeerId.put(ethPeer.getConnection().getPeer(), ethPeer); |
||||||
|
} |
||||||
|
|
||||||
|
public void removePeer(final PeerId peerId) { |
||||||
|
ethPeersByPeerId.remove(peerId); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
/** |
||||||
|
* Represents a task to be executed on an EthPeer by the PeerTaskExecutor |
||||||
|
* |
||||||
|
* @param <T> The type of the result of this PeerTask |
||||||
|
*/ |
||||||
|
public interface PeerTask<T> { |
||||||
|
/** |
||||||
|
* Returns the SubProtocol used for this PeerTask |
||||||
|
* |
||||||
|
* @return the SubProtocol used for this PeerTask |
||||||
|
*/ |
||||||
|
String getSubProtocol(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the minimum required block number for a peer to have to successfully execute this task |
||||||
|
* |
||||||
|
* @return the minimum required block number for a peer to have to successfully execute this task |
||||||
|
*/ |
||||||
|
long getRequiredBlockNumber(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the request data to send to the EthPeer |
||||||
|
* |
||||||
|
* @return the request data to send to the EthPeer |
||||||
|
*/ |
||||||
|
MessageData getRequestMessage(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses the MessageData response from the EthPeer |
||||||
|
* |
||||||
|
* @param messageData the response MessageData to be parsed |
||||||
|
* @return a T built from the response MessageData |
||||||
|
* @throws InvalidPeerTaskResponseException if the response messageData is invalid |
||||||
|
*/ |
||||||
|
T parseResponse(MessageData messageData) throws InvalidPeerTaskResponseException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the Collection of behaviors this task is expected to exhibit in the PeetTaskExecutor |
||||||
|
* |
||||||
|
* @return the Collection of behaviors this task is expected to exhibit in the PeetTaskExecutor |
||||||
|
*/ |
||||||
|
Collection<PeerTaskBehavior> getPeerTaskBehaviors(); |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
public enum PeerTaskBehavior { |
||||||
|
RETRY_WITH_SAME_PEER, |
||||||
|
RETRY_WITH_OTHER_PEERS |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; |
||||||
|
import org.hyperledger.besu.metrics.BesuMetricCategory; |
||||||
|
import org.hyperledger.besu.plugin.services.MetricsSystem; |
||||||
|
import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; |
||||||
|
import org.hyperledger.besu.plugin.services.metrics.OperationTimer; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.concurrent.CompletableFuture; |
||||||
|
import java.util.concurrent.ExecutionException; |
||||||
|
import java.util.concurrent.TimeoutException; |
||||||
|
import java.util.function.Supplier; |
||||||
|
|
||||||
|
/** Manages the execution of PeerTasks, respecting their PeerTaskBehavior */ |
||||||
|
public class PeerTaskExecutor { |
||||||
|
private static final long[] WAIT_TIME_BEFORE_RETRY = {0, 20000, 5000}; |
||||||
|
|
||||||
|
private final PeerManager peerManager; |
||||||
|
private final PeerTaskRequestSender requestSender; |
||||||
|
private final Supplier<ProtocolSpec> protocolSpecSupplier; |
||||||
|
private final LabelledMetric<OperationTimer> requestTimer; |
||||||
|
|
||||||
|
public PeerTaskExecutor( |
||||||
|
final PeerManager peerManager, |
||||||
|
final PeerTaskRequestSender requestSender, |
||||||
|
final Supplier<ProtocolSpec> protocolSpecSupplier, |
||||||
|
final MetricsSystem metricsSystem) { |
||||||
|
this.peerManager = peerManager; |
||||||
|
this.requestSender = requestSender; |
||||||
|
this.protocolSpecSupplier = protocolSpecSupplier; |
||||||
|
requestTimer = |
||||||
|
metricsSystem.createLabelledTimer( |
||||||
|
BesuMetricCategory.PEERS, "Peer Task Executor Request Time", "", "Task Class Name"); |
||||||
|
} |
||||||
|
|
||||||
|
public <T> PeerTaskExecutorResult<T> execute(final PeerTask<T> peerTask) { |
||||||
|
PeerTaskExecutorResult<T> executorResult; |
||||||
|
int triesRemaining = |
||||||
|
peerTask.getPeerTaskBehaviors().contains(PeerTaskBehavior.RETRY_WITH_OTHER_PEERS) ? 3 : 1; |
||||||
|
final Collection<EthPeer> usedEthPeers = new ArrayList<>(); |
||||||
|
do { |
||||||
|
EthPeer peer; |
||||||
|
try { |
||||||
|
peer = |
||||||
|
peerManager.getPeer( |
||||||
|
(candidatePeer) -> |
||||||
|
isPeerUnused(candidatePeer, usedEthPeers) |
||||||
|
&& (protocolSpecSupplier.get().isPoS() |
||||||
|
|| isPeerHeightHighEnough( |
||||||
|
candidatePeer, peerTask.getRequiredBlockNumber())) |
||||||
|
&& isPeerProtocolSuitable(candidatePeer, peerTask.getSubProtocol())); |
||||||
|
usedEthPeers.add(peer); |
||||||
|
executorResult = executeAgainstPeer(peerTask, peer); |
||||||
|
} catch (NoAvailablePeerException e) { |
||||||
|
executorResult = |
||||||
|
new PeerTaskExecutorResult<>(null, PeerTaskExecutorResponseCode.NO_PEER_AVAILABLE); |
||||||
|
} |
||||||
|
} while (--triesRemaining > 0 |
||||||
|
&& executorResult.getResponseCode() != PeerTaskExecutorResponseCode.SUCCESS); |
||||||
|
|
||||||
|
return executorResult; |
||||||
|
} |
||||||
|
|
||||||
|
public <T> CompletableFuture<PeerTaskExecutorResult<T>> executeAsync(final PeerTask<T> peerTask) { |
||||||
|
return CompletableFuture.supplyAsync(() -> execute(peerTask)); |
||||||
|
} |
||||||
|
|
||||||
|
public <T> PeerTaskExecutorResult<T> executeAgainstPeer( |
||||||
|
final PeerTask<T> peerTask, final EthPeer peer) { |
||||||
|
MessageData requestMessageData = peerTask.getRequestMessage(); |
||||||
|
PeerTaskExecutorResult<T> executorResult; |
||||||
|
int triesRemaining = |
||||||
|
peerTask.getPeerTaskBehaviors().contains(PeerTaskBehavior.RETRY_WITH_SAME_PEER) ? 3 : 1; |
||||||
|
do { |
||||||
|
try { |
||||||
|
|
||||||
|
MessageData responseMessageData; |
||||||
|
try (final OperationTimer.TimingContext timingContext = |
||||||
|
requestTimer.labels(peerTask.getClass().getSimpleName()).startTimer()) { |
||||||
|
responseMessageData = |
||||||
|
requestSender.sendRequest(peerTask.getSubProtocol(), requestMessageData, peer); |
||||||
|
} |
||||||
|
T result = peerTask.parseResponse(responseMessageData); |
||||||
|
peer.recordUsefulResponse(); |
||||||
|
executorResult = new PeerTaskExecutorResult<>(result, PeerTaskExecutorResponseCode.SUCCESS); |
||||||
|
|
||||||
|
} catch (PeerConnection.PeerNotConnected e) { |
||||||
|
executorResult = |
||||||
|
new PeerTaskExecutorResult<>(null, PeerTaskExecutorResponseCode.PEER_DISCONNECTED); |
||||||
|
|
||||||
|
} catch (InterruptedException | TimeoutException e) { |
||||||
|
peer.recordRequestTimeout(requestMessageData.getCode()); |
||||||
|
executorResult = new PeerTaskExecutorResult<>(null, PeerTaskExecutorResponseCode.TIMEOUT); |
||||||
|
|
||||||
|
} catch (InvalidPeerTaskResponseException e) { |
||||||
|
peer.recordUselessResponse(e.getMessage()); |
||||||
|
executorResult = |
||||||
|
new PeerTaskExecutorResult<>(null, PeerTaskExecutorResponseCode.INVALID_RESPONSE); |
||||||
|
|
||||||
|
} catch (ExecutionException e) { |
||||||
|
executorResult = |
||||||
|
new PeerTaskExecutorResult<>(null, PeerTaskExecutorResponseCode.INTERNAL_SERVER_ERROR); |
||||||
|
} |
||||||
|
} while (--triesRemaining > 0 |
||||||
|
&& executorResult.getResponseCode() != PeerTaskExecutorResponseCode.SUCCESS |
||||||
|
&& executorResult.getResponseCode() != PeerTaskExecutorResponseCode.PEER_DISCONNECTED |
||||||
|
&& sleepBetweenRetries(WAIT_TIME_BEFORE_RETRY[triesRemaining])); |
||||||
|
|
||||||
|
return executorResult; |
||||||
|
} |
||||||
|
|
||||||
|
public <T> CompletableFuture<PeerTaskExecutorResult<T>> executeAgainstPeerAsync( |
||||||
|
final PeerTask<T> peerTask, final EthPeer peer) { |
||||||
|
return CompletableFuture.supplyAsync(() -> executeAgainstPeer(peerTask, peer)); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean sleepBetweenRetries(final long sleepTime) { |
||||||
|
try { |
||||||
|
Thread.sleep(sleepTime); |
||||||
|
return true; |
||||||
|
} catch (InterruptedException e) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isPeerUnused( |
||||||
|
final EthPeer ethPeer, final Collection<EthPeer> usedEthPeers) { |
||||||
|
return !usedEthPeers.contains(ethPeer); |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isPeerHeightHighEnough(final EthPeer ethPeer, final long requiredHeight) { |
||||||
|
return ethPeer.chainState().getEstimatedHeight() >= requiredHeight; |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isPeerProtocolSuitable(final EthPeer ethPeer, final String protocol) { |
||||||
|
return ethPeer.getProtocolName().equals(protocol); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
public enum PeerTaskExecutorResponseCode { |
||||||
|
SUCCESS, |
||||||
|
NO_PEER_AVAILABLE, |
||||||
|
PEER_DISCONNECTED, |
||||||
|
INTERNAL_SERVER_ERROR, |
||||||
|
TIMEOUT, |
||||||
|
INVALID_RESPONSE |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
public class PeerTaskExecutorResult<T> { |
||||||
|
private final Optional<T> result; |
||||||
|
private final PeerTaskExecutorResponseCode responseCode; |
||||||
|
|
||||||
|
public PeerTaskExecutorResult(final T result, final PeerTaskExecutorResponseCode responseCode) { |
||||||
|
this.result = Optional.ofNullable(result); |
||||||
|
this.responseCode = responseCode; |
||||||
|
} |
||||||
|
|
||||||
|
public Optional<T> getResult() { |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public PeerTaskExecutorResponseCode getResponseCode() { |
||||||
|
return responseCode; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.RequestManager.ResponseStream; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; |
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture; |
||||||
|
import java.util.concurrent.ExecutionException; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
import java.util.concurrent.TimeoutException; |
||||||
|
|
||||||
|
public class PeerTaskRequestSender { |
||||||
|
private static final long DEFAULT_TIMEOUT_MS = 20_000; |
||||||
|
|
||||||
|
private final long timeoutMs; |
||||||
|
|
||||||
|
public PeerTaskRequestSender() { |
||||||
|
this.timeoutMs = DEFAULT_TIMEOUT_MS; |
||||||
|
} |
||||||
|
|
||||||
|
public PeerTaskRequestSender(final long timeoutMs) { |
||||||
|
this.timeoutMs = timeoutMs; |
||||||
|
} |
||||||
|
|
||||||
|
public MessageData sendRequest( |
||||||
|
final String subProtocol, final MessageData requestMessageData, final EthPeer ethPeer) |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException { |
||||||
|
ResponseStream responseStream = |
||||||
|
ethPeer.send(requestMessageData, subProtocol, ethPeer.getConnection()); |
||||||
|
final CompletableFuture<MessageData> responseMessageDataFuture = new CompletableFuture<>(); |
||||||
|
responseStream.then( |
||||||
|
(boolean streamClosed, MessageData message, EthPeer peer) -> { |
||||||
|
responseMessageDataFuture.complete(message); |
||||||
|
}); |
||||||
|
return responseMessageDataFuture.get(timeoutMs, TimeUnit.MILLISECONDS); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.MockPeerConnection; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; |
||||||
|
|
||||||
|
import java.time.Clock; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.junit.jupiter.api.Assertions; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
public class PeerManagerTest { |
||||||
|
|
||||||
|
public PeerManager peerManager; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
public void beforeTest() { |
||||||
|
peerManager = new PeerManager(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testGetPeer() throws NoAvailablePeerException { |
||||||
|
EthPeer protocol1With5ReputationPeer = |
||||||
|
createTestPeer(Set.of(Capability.create("capability1", 1)), "protocol1", 5); |
||||||
|
peerManager.addPeer(protocol1With5ReputationPeer); |
||||||
|
EthPeer protocol1With4ReputationPeer = |
||||||
|
createTestPeer(Set.of(Capability.create("capability1", 1)), "protocol1", 4); |
||||||
|
peerManager.addPeer(protocol1With4ReputationPeer); |
||||||
|
EthPeer protocol2With50ReputationPeer = |
||||||
|
createTestPeer(Set.of(Capability.create("capability1", 1)), "protocol2", 50); |
||||||
|
peerManager.addPeer(protocol2With50ReputationPeer); |
||||||
|
EthPeer protocol2With4ReputationPeer = |
||||||
|
createTestPeer(Set.of(Capability.create("capability1", 1)), "protocol2", 4); |
||||||
|
peerManager.addPeer(protocol2With4ReputationPeer); |
||||||
|
|
||||||
|
EthPeer result = peerManager.getPeer((p) -> p.getProtocolName().equals("protocol1")); |
||||||
|
|
||||||
|
Assertions.assertSame(protocol1With5ReputationPeer, result); |
||||||
|
} |
||||||
|
|
||||||
|
private EthPeer createTestPeer( |
||||||
|
final Set<Capability> connectionCapabilities, |
||||||
|
final String protocolName, |
||||||
|
final int reputationAdjustment) { |
||||||
|
PeerConnection peerConnection = new MockPeerConnection(connectionCapabilities); |
||||||
|
EthPeer peer = |
||||||
|
new EthPeer( |
||||||
|
peerConnection, |
||||||
|
protocolName, |
||||||
|
null, |
||||||
|
Collections.emptyList(), |
||||||
|
1, |
||||||
|
Clock.systemUTC(), |
||||||
|
Collections.emptyList(), |
||||||
|
Bytes.EMPTY); |
||||||
|
for (int i = 0; i < reputationAdjustment; i++) { |
||||||
|
peer.getReputation().recordUsefulResponse(); |
||||||
|
} |
||||||
|
return peer; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,268 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||||
|
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; |
||||||
|
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.ExecutionException; |
||||||
|
import java.util.concurrent.TimeoutException; |
||||||
|
import java.util.function.Predicate; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach; |
||||||
|
import org.junit.jupiter.api.Assertions; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.Mockito; |
||||||
|
import org.mockito.MockitoAnnotations; |
||||||
|
|
||||||
|
public class PeerTaskExecutorTest { |
||||||
|
private @Mock PeerManager peerManager; |
||||||
|
private @Mock PeerTaskRequestSender requestSender; |
||||||
|
private @Mock ProtocolSpec protocolSpec; |
||||||
|
private @Mock PeerTask<Object> peerTask; |
||||||
|
private @Mock MessageData requestMessageData; |
||||||
|
private @Mock MessageData responseMessageData; |
||||||
|
private @Mock EthPeer ethPeer; |
||||||
|
private AutoCloseable mockCloser; |
||||||
|
|
||||||
|
private PeerTaskExecutor peerTaskExecutor; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
public void beforeTest() { |
||||||
|
mockCloser = MockitoAnnotations.openMocks(this); |
||||||
|
peerTaskExecutor = |
||||||
|
new PeerTaskExecutor( |
||||||
|
peerManager, requestSender, () -> protocolSpec, new NoOpMetricsSystem()); |
||||||
|
} |
||||||
|
|
||||||
|
@AfterEach |
||||||
|
public void afterTest() throws Exception { |
||||||
|
mockCloser.close(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testExecuteAgainstPeerWithNoPeerTaskBehaviorsAndSuccessfulFlow() |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException, |
||||||
|
InvalidPeerTaskResponseException { |
||||||
|
String subprotocol = "subprotocol"; |
||||||
|
Object responseObject = new Object(); |
||||||
|
|
||||||
|
Mockito.when(peerTask.getRequestMessage()).thenReturn(requestMessageData); |
||||||
|
Mockito.when(peerTask.getPeerTaskBehaviors()).thenReturn(Collections.emptyList()); |
||||||
|
Mockito.when(peerTask.getSubProtocol()).thenReturn(subprotocol); |
||||||
|
Mockito.when(requestSender.sendRequest(subprotocol, requestMessageData, ethPeer)) |
||||||
|
.thenReturn(responseMessageData); |
||||||
|
Mockito.when(peerTask.parseResponse(responseMessageData)).thenReturn(responseObject); |
||||||
|
|
||||||
|
PeerTaskExecutorResult<Object> result = peerTaskExecutor.executeAgainstPeer(peerTask, ethPeer); |
||||||
|
|
||||||
|
Mockito.verify(ethPeer).recordUsefulResponse(); |
||||||
|
|
||||||
|
Assertions.assertNotNull(result); |
||||||
|
Assertions.assertTrue(result.getResult().isPresent()); |
||||||
|
Assertions.assertSame(responseObject, result.getResult().get()); |
||||||
|
Assertions.assertEquals(PeerTaskExecutorResponseCode.SUCCESS, result.getResponseCode()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testExecuteAgainstPeerWithRetryBehaviorsAndSuccessfulFlowAfterFirstFailure() |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException, |
||||||
|
InvalidPeerTaskResponseException { |
||||||
|
String subprotocol = "subprotocol"; |
||||||
|
Object responseObject = new Object(); |
||||||
|
int requestMessageDataCode = 123; |
||||||
|
|
||||||
|
Mockito.when(peerTask.getRequestMessage()).thenReturn(requestMessageData); |
||||||
|
Mockito.when(peerTask.getPeerTaskBehaviors()) |
||||||
|
.thenReturn(List.of(PeerTaskBehavior.RETRY_WITH_SAME_PEER)); |
||||||
|
|
||||||
|
Mockito.when(peerTask.getSubProtocol()).thenReturn(subprotocol); |
||||||
|
Mockito.when(requestSender.sendRequest(subprotocol, requestMessageData, ethPeer)) |
||||||
|
.thenThrow(new TimeoutException()) |
||||||
|
.thenReturn(responseMessageData); |
||||||
|
Mockito.when(requestMessageData.getCode()).thenReturn(requestMessageDataCode); |
||||||
|
Mockito.when(peerTask.parseResponse(responseMessageData)).thenReturn(responseObject); |
||||||
|
|
||||||
|
PeerTaskExecutorResult<Object> result = peerTaskExecutor.executeAgainstPeer(peerTask, ethPeer); |
||||||
|
|
||||||
|
Mockito.verify(ethPeer).recordRequestTimeout(requestMessageDataCode); |
||||||
|
Mockito.verify(ethPeer).recordUsefulResponse(); |
||||||
|
|
||||||
|
Assertions.assertNotNull(result); |
||||||
|
Assertions.assertTrue(result.getResult().isPresent()); |
||||||
|
Assertions.assertSame(responseObject, result.getResult().get()); |
||||||
|
Assertions.assertEquals(PeerTaskExecutorResponseCode.SUCCESS, result.getResponseCode()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testExecuteAgainstPeerWithNoPeerTaskBehaviorsAndPeerNotConnected() |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException, |
||||||
|
InvalidPeerTaskResponseException { |
||||||
|
String subprotocol = "subprotocol"; |
||||||
|
|
||||||
|
Mockito.when(peerTask.getRequestMessage()).thenReturn(requestMessageData); |
||||||
|
Mockito.when(peerTask.getPeerTaskBehaviors()).thenReturn(Collections.emptyList()); |
||||||
|
Mockito.when(peerTask.getSubProtocol()).thenReturn(subprotocol); |
||||||
|
Mockito.when(requestSender.sendRequest(subprotocol, requestMessageData, ethPeer)) |
||||||
|
.thenThrow(new PeerConnection.PeerNotConnected("")); |
||||||
|
|
||||||
|
PeerTaskExecutorResult<Object> result = peerTaskExecutor.executeAgainstPeer(peerTask, ethPeer); |
||||||
|
|
||||||
|
Assertions.assertNotNull(result); |
||||||
|
Assertions.assertTrue(result.getResult().isEmpty()); |
||||||
|
Assertions.assertEquals( |
||||||
|
PeerTaskExecutorResponseCode.PEER_DISCONNECTED, result.getResponseCode()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testExecuteAgainstPeerWithNoPeerTaskBehaviorsAndTimeoutException() |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException, |
||||||
|
InvalidPeerTaskResponseException { |
||||||
|
String subprotocol = "subprotocol"; |
||||||
|
int requestMessageDataCode = 123; |
||||||
|
|
||||||
|
Mockito.when(peerTask.getRequestMessage()).thenReturn(requestMessageData); |
||||||
|
Mockito.when(peerTask.getPeerTaskBehaviors()).thenReturn(Collections.emptyList()); |
||||||
|
Mockito.when(peerTask.getSubProtocol()).thenReturn(subprotocol); |
||||||
|
Mockito.when(requestSender.sendRequest(subprotocol, requestMessageData, ethPeer)) |
||||||
|
.thenThrow(new TimeoutException()); |
||||||
|
Mockito.when(requestMessageData.getCode()).thenReturn(requestMessageDataCode); |
||||||
|
|
||||||
|
PeerTaskExecutorResult<Object> result = peerTaskExecutor.executeAgainstPeer(peerTask, ethPeer); |
||||||
|
|
||||||
|
Mockito.verify(ethPeer).recordRequestTimeout(requestMessageDataCode); |
||||||
|
|
||||||
|
Assertions.assertNotNull(result); |
||||||
|
Assertions.assertTrue(result.getResult().isEmpty()); |
||||||
|
Assertions.assertEquals(PeerTaskExecutorResponseCode.TIMEOUT, result.getResponseCode()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testExecuteAgainstPeerWithNoPeerTaskBehaviorsAndInvalidResponseMessage() |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException, |
||||||
|
InvalidPeerTaskResponseException { |
||||||
|
String subprotocol = "subprotocol"; |
||||||
|
|
||||||
|
Mockito.when(peerTask.getRequestMessage()).thenReturn(requestMessageData); |
||||||
|
Mockito.when(peerTask.getPeerTaskBehaviors()).thenReturn(Collections.emptyList()); |
||||||
|
Mockito.when(peerTask.getSubProtocol()).thenReturn(subprotocol); |
||||||
|
Mockito.when(requestSender.sendRequest(subprotocol, requestMessageData, ethPeer)) |
||||||
|
.thenReturn(responseMessageData); |
||||||
|
Mockito.when(peerTask.parseResponse(responseMessageData)) |
||||||
|
.thenThrow(new InvalidPeerTaskResponseException()); |
||||||
|
|
||||||
|
PeerTaskExecutorResult<Object> result = peerTaskExecutor.executeAgainstPeer(peerTask, ethPeer); |
||||||
|
|
||||||
|
Mockito.verify(ethPeer).recordUselessResponse(null); |
||||||
|
|
||||||
|
Assertions.assertNotNull(result); |
||||||
|
Assertions.assertTrue(result.getResult().isEmpty()); |
||||||
|
Assertions.assertEquals( |
||||||
|
PeerTaskExecutorResponseCode.INVALID_RESPONSE, result.getResponseCode()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void testExecuteWithNoPeerTaskBehaviorsAndSuccessFlow() |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException, |
||||||
|
InvalidPeerTaskResponseException, |
||||||
|
NoAvailablePeerException { |
||||||
|
String subprotocol = "subprotocol"; |
||||||
|
Object responseObject = new Object(); |
||||||
|
|
||||||
|
Mockito.when(peerManager.getPeer(Mockito.any(Predicate.class))).thenReturn(ethPeer); |
||||||
|
|
||||||
|
Mockito.when(peerTask.getRequestMessage()).thenReturn(requestMessageData); |
||||||
|
Mockito.when(peerTask.getPeerTaskBehaviors()).thenReturn(Collections.emptyList()); |
||||||
|
Mockito.when(peerTask.getSubProtocol()).thenReturn(subprotocol); |
||||||
|
Mockito.when(requestSender.sendRequest(subprotocol, requestMessageData, ethPeer)) |
||||||
|
.thenReturn(responseMessageData); |
||||||
|
Mockito.when(peerTask.parseResponse(responseMessageData)).thenReturn(responseObject); |
||||||
|
|
||||||
|
PeerTaskExecutorResult<Object> result = peerTaskExecutor.executeAgainstPeer(peerTask, ethPeer); |
||||||
|
|
||||||
|
Mockito.verify(ethPeer).recordUsefulResponse(); |
||||||
|
|
||||||
|
Assertions.assertNotNull(result); |
||||||
|
Assertions.assertTrue(result.getResult().isPresent()); |
||||||
|
Assertions.assertSame(responseObject, result.getResult().get()); |
||||||
|
Assertions.assertEquals(PeerTaskExecutorResponseCode.SUCCESS, result.getResponseCode()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void testExecuteWithPeerSwitchingAndSuccessFlow() |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException, |
||||||
|
InvalidPeerTaskResponseException, |
||||||
|
NoAvailablePeerException { |
||||||
|
String subprotocol = "subprotocol"; |
||||||
|
Object responseObject = new Object(); |
||||||
|
int requestMessageDataCode = 123; |
||||||
|
EthPeer peer2 = Mockito.mock(EthPeer.class); |
||||||
|
|
||||||
|
Mockito.when(peerManager.getPeer(Mockito.any(Predicate.class))) |
||||||
|
.thenReturn(ethPeer) |
||||||
|
.thenReturn(peer2); |
||||||
|
|
||||||
|
Mockito.when(peerTask.getRequestMessage()).thenReturn(requestMessageData); |
||||||
|
Mockito.when(peerTask.getPeerTaskBehaviors()) |
||||||
|
.thenReturn(List.of(PeerTaskBehavior.RETRY_WITH_OTHER_PEERS)); |
||||||
|
Mockito.when(peerTask.getSubProtocol()).thenReturn(subprotocol); |
||||||
|
Mockito.when(requestSender.sendRequest(subprotocol, requestMessageData, ethPeer)) |
||||||
|
.thenThrow(new TimeoutException()); |
||||||
|
Mockito.when(requestMessageData.getCode()).thenReturn(requestMessageDataCode); |
||||||
|
Mockito.when(requestSender.sendRequest(subprotocol, requestMessageData, peer2)) |
||||||
|
.thenReturn(responseMessageData); |
||||||
|
Mockito.when(peerTask.parseResponse(responseMessageData)).thenReturn(responseObject); |
||||||
|
|
||||||
|
PeerTaskExecutorResult<Object> result = peerTaskExecutor.execute(peerTask); |
||||||
|
|
||||||
|
Mockito.verify(ethPeer).recordRequestTimeout(requestMessageDataCode); |
||||||
|
Mockito.verify(peer2).recordUsefulResponse(); |
||||||
|
|
||||||
|
Assertions.assertNotNull(result); |
||||||
|
Assertions.assertTrue(result.getResult().isPresent()); |
||||||
|
Assertions.assertSame(responseObject, result.getResult().get()); |
||||||
|
Assertions.assertEquals(PeerTaskExecutorResponseCode.SUCCESS, result.getResponseCode()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
/* |
||||||
|
* Copyright contributors to Hyperledger Besu. |
||||||
|
* |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.eth.manager.peertask; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.EthPeer; |
||||||
|
import org.hyperledger.besu.ethereum.eth.manager.RequestManager; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; |
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture; |
||||||
|
import java.util.concurrent.ExecutionException; |
||||||
|
import java.util.concurrent.TimeoutException; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.mockito.ArgumentCaptor; |
||||||
|
import org.mockito.Mockito; |
||||||
|
|
||||||
|
public class PeerTaskRequestSenderTest { |
||||||
|
|
||||||
|
private PeerTaskRequestSender peerTaskRequestSender; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
public void beforeTest() { |
||||||
|
peerTaskRequestSender = new PeerTaskRequestSender(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testSendRequest() |
||||||
|
throws PeerConnection.PeerNotConnected, |
||||||
|
ExecutionException, |
||||||
|
InterruptedException, |
||||||
|
TimeoutException { |
||||||
|
String subprotocol = "subprotocol"; |
||||||
|
MessageData requestMessageData = Mockito.mock(MessageData.class); |
||||||
|
MessageData responseMessageData = Mockito.mock(MessageData.class); |
||||||
|
EthPeer peer = Mockito.mock(EthPeer.class); |
||||||
|
PeerConnection peerConnection = Mockito.mock(PeerConnection.class); |
||||||
|
RequestManager.ResponseStream responseStream = |
||||||
|
Mockito.mock(RequestManager.ResponseStream.class); |
||||||
|
|
||||||
|
Mockito.when(peer.getConnection()).thenReturn(peerConnection); |
||||||
|
Mockito.when(peer.send(requestMessageData, subprotocol, peerConnection)) |
||||||
|
.thenReturn(responseStream); |
||||||
|
|
||||||
|
CompletableFuture<MessageData> actualResponseMessageDataFuture = |
||||||
|
CompletableFuture.supplyAsync( |
||||||
|
() -> { |
||||||
|
try { |
||||||
|
return peerTaskRequestSender.sendRequest(subprotocol, requestMessageData, peer); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
}); |
||||||
|
Thread.sleep(500); |
||||||
|
ArgumentCaptor<RequestManager.ResponseCallback> responseCallbackArgumentCaptor = |
||||||
|
ArgumentCaptor.forClass(RequestManager.ResponseCallback.class); |
||||||
|
Mockito.verify(responseStream).then(responseCallbackArgumentCaptor.capture()); |
||||||
|
RequestManager.ResponseCallback responseCallback = responseCallbackArgumentCaptor.getValue(); |
||||||
|
responseCallback.exec(false, responseMessageData, peer); |
||||||
|
|
||||||
|
Assertions.assertSame(responseMessageData, actualResponseMessageDataFuture.get()); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue