From f05fb5329f5392f3e86f63f0ba87ae706bd31d75 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Thu, 25 Apr 2019 20:02:20 +1000 Subject: [PATCH 01/25] Queue pending requests when all peers are busy. --- .../ethereum/eth/manager/EthPeers.java | 103 ++++++++++++++---- .../eth/manager/EthProtocolManager.java | 13 ++- .../ethereum/eth/manager/RequestManager.java | 2 +- .../task/AbstractGetHeadersFromPeerTask.java | 8 -- .../manager/task/AbstractPeerRequestTask.java | 63 +++++++++-- .../eth/manager/task/AbstractPeerTask.java | 29 ----- .../manager/task/GetBlockFromPeerTask.java | 39 +++---- .../manager/task/GetBodiesFromPeerTask.java | 19 ++-- .../task/GetHeadersFromPeerByHashTask.java | 28 ++--- .../task/GetHeadersFromPeerByNumberTask.java | 16 ++- .../manager/task/GetNodeDataFromPeerTask.java | 19 ++-- .../manager/task/GetReceiptsFromPeerTask.java | 28 ++--- .../eth/manager/task/WaitForPeerTask.java | 4 +- .../ethereum/eth/sync/ChainHeadTracker.java | 1 + .../eth/sync/tasks/ImportBlocksTask.java | 53 ++++----- .../eth/manager/EthContextTestUtil.java | 3 +- .../eth/manager/EthProtocolManagerTest.java | 3 +- .../manager/EthProtocolManagerTestUtil.java | 4 +- .../eth/sync/BlockPropagationManagerTest.java | 2 +- .../fastsync/FastSyncBlockHandlerTest.java | 4 +- 20 files changed, 255 insertions(+), 186 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java index 3b48745013..6c32ed635c 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java @@ -13,14 +13,21 @@ package tech.pegasys.pantheon.ethereum.eth.manager; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer.DisconnectCallback; +import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; +import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; +import tech.pegasys.pantheon.metrics.MetricCategory; +import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.Subscribers; -import java.util.Collections; +import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; -import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -43,9 +50,14 @@ public class EthPeers { private final String protocolName; private final Subscribers connectCallbacks = new Subscribers<>(); private final Subscribers disconnectCallbacks = new Subscribers<>(); + private final Collection pendingRequests = new ArrayList<>(); - public EthPeers(final String protocolName) { + public EthPeers(final String protocolName, final MetricsSystem metricsSystem) { this.protocolName = protocolName; + metricsSystem.createIntegerGauge(MetricCategory.PEERS, + "pending_peer_requests_current", + "Number of peer requests currently pending because peers are busy", + pendingRequests::size); } void registerConnection(final PeerConnection peerConnection) { @@ -65,6 +77,26 @@ public EthPeer peer(final PeerConnection peerConnection) { return connections.get(peerConnection); } + public CompletableFuture executePeerRequest( + final PeerRequest request, final long minimumBlockNumber) { + final QueuedTask queuedTask = new QueuedTask(request, minimumBlockNumber); + synchronized (this) { + if (!queuedTask.attemptExecution()) { + pendingRequests.add(queuedTask); + } + } + return queuedTask.result; + } + + public void dispatchMessage(final EthPeer peer, final EthMessage ethMessage) { + peer.dispatch(ethMessage); + if (peer.outstandingRequests() < maxOutstandingRequests) { + synchronized (this) { + pendingRequests.removeIf(QueuedTask::attemptExecution); + } + } + } + public long subscribeConnect(final ConnectCallback callback) { return connectCallbacks.subscribe(callback); } @@ -81,10 +113,6 @@ public int peerCount() { return connections.size(); } - public int availablePeerCount() { - return (int) availablePeers().count(); - } - public Stream availablePeers() { return connections.values().stream().filter(EthPeer::readyForRequests); } @@ -93,23 +121,6 @@ public Optional bestPeer() { return availablePeers().max(BEST_CHAIN); } - public Optional idlePeer() { - return idlePeers().min(LEAST_TO_MOST_BUSY); - } - - private Stream idlePeers() { - final List peers = - availablePeers() - .filter(p -> p.outstandingRequests() < maxOutstandingRequests) - .collect(Collectors.toList()); - Collections.shuffle(peers); - return peers.stream(); - } - - public Optional idlePeer(final long withBlocksUpTo) { - return idlePeers().filter(p -> p.chainState().getEstimatedHeight() >= withBlocksUpTo).findAny(); - } - @FunctionalInterface public interface ConnectCallback { void onPeerConnected(EthPeer newPeer); @@ -125,4 +136,48 @@ public String toString() { private void invokeConnectionCallbacks(final EthPeer peer) { connectCallbacks.forEach(cb -> cb.onPeerConnected(peer)); } + + private class QueuedTask { + private final PeerRequest request; + private final CompletableFuture result = new CompletableFuture<>(); + private final long minimumBlockNumber; + + private QueuedTask(final PeerRequest request, final long minimumBlockNumber) { + this.request = request; + this.minimumBlockNumber = minimumBlockNumber; + } + + public boolean attemptExecution() { + if (!result.isDone()) { + final Optional leastBusySuitablePeer = + availablePeers() + .filter(peer -> peer.chainState().getEstimatedHeight() >= minimumBlockNumber) + .min(LEAST_TO_MOST_BUSY); + if (!leastBusySuitablePeer.isPresent()) { + // No peers have the required height. + result.completeExceptionally(new NoAvailablePeersException()); + } else { + // At least one peer has the required height, but we not be able to use it if it's busy + final Optional selectedPeer = + leastBusySuitablePeer.filter( + peer -> peer.outstandingRequests() < maxOutstandingRequests); + selectedPeer.ifPresent( + peer -> { + try { + final ResponseStream responseStream = request.sendRequest(peer); + result.complete(responseStream); + } catch (final PeerNotConnected e) { + result.completeExceptionally(new PeerDisconnectedException(peer)); + } + }); + return selectedPeer.isPresent(); + } + } + return false; + } + } + + public interface PeerRequest { + ResponseStream sendRequest(EthPeer peer) throws PeerNotConnected; + } } 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 da04086647..6fa9f49c6c 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 @@ -71,7 +71,8 @@ public EthProtocolManager( final int networkId, final boolean fastSyncEnabled, final EthScheduler scheduler, - final EthereumWireProtocolConfiguration ethereumWireProtocolConfiguration) { + final EthereumWireProtocolConfiguration ethereumWireProtocolConfiguration, + final MetricsSystem metricsSystem) { this.networkId = networkId; this.scheduler = scheduler; this.blockchain = blockchain; @@ -80,7 +81,7 @@ public EthProtocolManager( this.shutdown = new CountDownLatch(1); genesisHash = blockchain.getBlockHashByNumber(0L).get(); - ethPeers = new EthPeers(getSupportedProtocol()); + ethPeers = new EthPeers(getSupportedProtocol(), metricsSystem); ethMessages = new EthMessages(); ethContext = new EthContext(getSupportedProtocol(), ethPeers, ethMessages, scheduler); @@ -105,7 +106,8 @@ public EthProtocolManager( networkId, fastSyncEnabled, new EthScheduler(syncWorkers, txWorkers, computationWorkers, metricsSystem), - EthereumWireProtocolConfiguration.defaultConfig()); + EthereumWireProtocolConfiguration.defaultConfig(), + metricsSystem); } public EthProtocolManager( @@ -124,7 +126,8 @@ public EthProtocolManager( networkId, fastSyncEnabled, new EthScheduler(syncWorkers, txWorkers, computationWorkers, metricsSystem), - ethereumWireProtocolConfiguration); + ethereumWireProtocolConfiguration, + metricsSystem); } public EthContext ethContext() { @@ -192,7 +195,7 @@ public void processMessage(final Capability cap, final Message message) { peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); return; } - peer.dispatch(ethMessage); + ethPeers.dispatchMessage(peer, ethMessage); ethMessages.dispatch(ethMessage); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/RequestManager.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/RequestManager.java index aa626db960..ae49c56ffc 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/RequestManager.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/RequestManager.java @@ -148,7 +148,7 @@ public void close() { dispatchBufferedResponses(); } - public EthPeer peer() { + public EthPeer getPeer() { return peer; } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java index ec64bd3f32..5e279cefad 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java @@ -41,12 +41,10 @@ public abstract class AbstractGetHeadersFromPeerTask protected final int count; protected final int skip; protected final boolean reverse; - private final long minimumRequiredBlockNumber; protected AbstractGetHeadersFromPeerTask( final ProtocolSchedule protocolSchedule, final EthContext ethContext, - final long minimumRequiredBlockNumber, final int count, final int skip, final boolean reverse, @@ -57,7 +55,6 @@ protected AbstractGetHeadersFromPeerTask( this.count = count; this.skip = skip; this.reverse = reverse; - this.minimumRequiredBlockNumber = minimumRequiredBlockNumber; } @Override @@ -106,10 +103,5 @@ protected Optional> processResponse( return Optional.of(headersList); } - @Override - protected Optional findSuitablePeer() { - return ethContext.getEthPeers().idlePeer(minimumRequiredBlockNumber); - } - protected abstract boolean matchesFirstHeader(BlockHeader firstHeader); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java index 7e71391db2..3411a1bfe9 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java @@ -12,10 +12,15 @@ */ package tech.pegasys.pantheon.ethereum.eth.manager.task; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; + import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PeerRequest; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; @@ -25,7 +30,9 @@ import java.time.Duration; import java.util.Optional; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.TimeoutException; public abstract class AbstractPeerRequestTask extends AbstractPeerTask { @@ -33,7 +40,7 @@ public abstract class AbstractPeerRequestTask extends AbstractPeerTask { private Duration timeout = DEFAULT_TIMEOUT; private final int requestCode; - private volatile ResponseStream responseStream; + private volatile CompletableFuture responseStream; protected AbstractPeerRequestTask( final EthContext ethContext, final int requestCode, final MetricsSystem metricsSystem) { @@ -47,30 +54,53 @@ public AbstractPeerRequestTask setTimeout(final Duration timeout) { } @Override - protected final void executeTaskWithPeer(final EthPeer peer) throws PeerNotConnected { + protected final void executeTask() { final CompletableFuture promise = new CompletableFuture<>(); - responseStream = - sendRequest(peer) - .then( - (streamClosed, message, peer1) -> - handleMessage(promise, streamClosed, message, peer1)); + responseStream = sendRequest(); + responseStream + .thenAccept( + stream -> + stream.then( + (streamClosed, message, peer1) -> + handleMessage(promise, streamClosed, message, peer1))) + .exceptionally( + error -> { + promise.completeExceptionally(error); + return null; + }); promise.whenComplete( (r, t) -> { + final Optional responseStream = cancelAndGetResponseStreamIfCreated(); if (t != null) { t = ExceptionUtils.rootCause(t); - if (t instanceof TimeoutException) { - peer.recordRequestTimeout(requestCode); + if (t instanceof TimeoutException && responseStream.isPresent()) { + responseStream.get().getPeer().recordRequestTimeout(requestCode); } result.get().completeExceptionally(t); } else if (r != null) { - result.get().complete(new PeerTaskResult<>(peer, r)); + // If we got a response we must have had a response stream... + result.get().complete(new PeerTaskResult<>(responseStream.get().getPeer(), r)); } }); ethContext.getScheduler().failAfterTimeout(promise, timeout); } + public CompletableFuture sendRequestToPeer( + final PeerRequest request, final long minimumBlockNumber) { + if (assignedPeer.isPresent()) { + try { + return completedFuture(request.sendRequest(assignedPeer.get())); + } catch (final PeerNotConnected e) { + result.get().completeExceptionally(new PeerDisconnectedException(assignedPeer.get())); + return completedExceptionally(e); + } + } else { + return ethContext.getEthPeers().executePeerRequest(request, minimumBlockNumber); + } + } + private void handleMessage( final CompletableFuture promise, final boolean streamClosed, @@ -90,16 +120,25 @@ private void handleMessage( } } + private Optional cancelAndGetResponseStreamIfCreated() { + try { + responseStream.cancel(false); + return Optional.ofNullable(responseStream.getNow(null)); + } catch (final CancellationException | CompletionException e) { + return Optional.empty(); + } + } + @Override protected void cleanup() { super.cleanup(); - final ResponseStream stream = responseStream; + final ResponseStream stream = responseStream.getNow(null); if (stream != null) { stream.close(); } } - protected abstract ResponseStream sendRequest(EthPeer peer) throws PeerNotConnected; + protected abstract CompletableFuture sendRequest(); protected abstract Optional processResponse( boolean streamClosed, MessageData message, EthPeer peer); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerTask.java index 14d413c2b1..88e25b313e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerTask.java @@ -14,10 +14,7 @@ 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.MetricsSystem; import java.util.Optional; @@ -31,32 +28,6 @@ protected AbstractPeerTask(final EthContext ethContext, final MetricsSystem metr this.ethContext = ethContext; } - @Override - protected void executeTask() { - final EthPeer peer; - if (assignedPeer.isPresent()) { - peer = assignedPeer.get(); - } else { - // Try to find a peer - final Optional maybePeer = findSuitablePeer(); - if (!maybePeer.isPresent()) { - result.get().completeExceptionally(new NoAvailablePeersException()); - return; - } - peer = maybePeer.get(); - } - - try { - executeTaskWithPeer(peer); - } catch (final PeerNotConnected e) { - result.get().completeExceptionally(new PeerDisconnectedException(peer)); - } - } - - protected abstract Optional findSuitablePeer(); - - protected abstract void executeTaskWithPeer(EthPeer peer) throws PeerNotConnected; - public AbstractPeerTask assignPeer(final EthPeer peer) { assignedPeer = Optional.of(peer); return this; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTask.java index a34066ceac..09822853bc 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBlockFromPeerTask.java @@ -21,11 +21,9 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.IncompleteResultsException; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; -import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.metrics.MetricsSystem; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.apache.logging.log4j.LogManager; @@ -63,37 +61,40 @@ public static GetBlockFromPeerTask create( } @Override - protected Optional findSuitablePeer() { - return ethContext.getEthPeers().idlePeer(blockNumber); - } - - @Override - protected void executeTaskWithPeer(final EthPeer peer) throws PeerNotConnected { - LOG.debug("Downloading block {} from peer {}.", hash, peer); - downloadHeader(peer) + protected void executeTask() { + LOG.debug( + "Downloading block {} from peer {}.", + hash, + assignedPeer.map(EthPeer::toString).orElse("")); + downloadHeader() .thenCompose(this::completeBlock) .whenComplete( (r, t) -> { if (t != null) { - LOG.info("Failed to download block {} from peer {}.", hash, peer); + LOG.info( + "Failed to download block {} from peer {}.", + hash, + assignedPeer.map(EthPeer::toString).orElse("")); result.get().completeExceptionally(t); } else if (r.getResult().isEmpty()) { - LOG.info("Failed to download block {} from peer {}.", hash, peer); + LOG.info("Failed to download block {} from peer {}.", hash, r.getPeer()); result.get().completeExceptionally(new IncompleteResultsException()); } else { - LOG.debug("Successfully downloaded block {} from peer {}.", hash, peer); + LOG.debug("Successfully downloaded block {} from peer {}.", hash, r.getPeer()); result.get().complete(new PeerTaskResult<>(r.getPeer(), r.getResult().get(0))); } }); } - private CompletableFuture>> downloadHeader(final EthPeer peer) { + private CompletableFuture>> downloadHeader() { return executeSubTask( - () -> - GetHeadersFromPeerByHashTask.forSingleHash( - protocolSchedule, ethContext, hash, metricsSystem) - .assignPeer(peer) - .run()); + () -> { + final AbstractGetHeadersFromPeerTask task = + GetHeadersFromPeerByHashTask.forSingleHash( + protocolSchedule, ethContext, hash, blockNumber, metricsSystem); + assignedPeer.ifPresent(task::assignPeer); + return task.run(); + }); } private CompletableFuture>> completeBlock( diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java index ed10df2609..c5f8a5dc72 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java @@ -27,7 +27,6 @@ import tech.pegasys.pantheon.ethereum.mainnet.BodyValidation; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; -import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.bytes.Bytes32; @@ -38,6 +37,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -82,11 +82,17 @@ public static GetBodiesFromPeerTask forHeaders( } @Override - protected ResponseStream sendRequest(final EthPeer peer) throws PeerNotConnected { + protected CompletableFuture sendRequest() { final List blockHashes = headers.stream().map(BlockHeader::getHash).collect(Collectors.toList()); - LOG.debug("Requesting {} bodies from peer {}.", blockHashes.size(), peer); - return peer.getBodies(blockHashes); + final long minimumRequiredBlockNumber = headers.get(headers.size() - 1).getNumber(); + + return sendRequestToPeer( + peer -> { + LOG.debug("Requesting {} bodies from peer {}.", blockHashes.size(), peer); + return peer.getBodies(blockHashes); + }, + minimumRequiredBlockNumber); } @Override @@ -123,11 +129,6 @@ protected Optional> processResponse( return Optional.of(blocks); } - @Override - protected Optional findSuitablePeer() { - return this.ethContext.getEthPeers().idlePeer(headers.get(headers.size() - 1).getNumber()); - } - private static class BodyIdentifier { private final Bytes32 transactionsRoot; private final Bytes32 ommersHash; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java index 76f7f46ad3..d5a9ebb5f5 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java @@ -17,12 +17,12 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; 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.RequestManager.ResponseStream; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; -import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.metrics.MetricsSystem; +import java.util.concurrent.CompletableFuture; + import com.google.common.annotations.VisibleForTesting; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,6 +32,7 @@ public class GetHeadersFromPeerByHashTask extends AbstractGetHeadersFromPeerTask private static final Logger LOG = LogManager.getLogger(); private final Hash referenceHash; + private final long minimumRequiredBlockNumber; @VisibleForTesting GetHeadersFromPeerByHashTask( @@ -43,14 +44,8 @@ public class GetHeadersFromPeerByHashTask extends AbstractGetHeadersFromPeerTask final int skip, final boolean reverse, final MetricsSystem metricsSystem) { - super( - protocolSchedule, - ethContext, - minimumRequiredBlockNumber, - count, - skip, - reverse, - metricsSystem); + super(protocolSchedule, ethContext, count, skip, reverse, metricsSystem); + this.minimumRequiredBlockNumber = minimumRequiredBlockNumber; checkNotNull(referenceHash); this.referenceHash = referenceHash; } @@ -133,15 +128,20 @@ public static AbstractGetHeadersFromPeerTask forSingleHash( final ProtocolSchedule protocolSchedule, final EthContext ethContext, final Hash hash, + final long minimumRequiredBlockNumber, final MetricsSystem metricsSystem) { return new GetHeadersFromPeerByHashTask( - protocolSchedule, ethContext, hash, 0, 1, 0, false, metricsSystem); + protocolSchedule, ethContext, hash, minimumRequiredBlockNumber, 1, 0, false, metricsSystem); } @Override - protected ResponseStream sendRequest(final EthPeer peer) throws PeerNotConnected { - LOG.debug("Requesting {} headers from peer {}.", count, peer); - return peer.getHeadersByHash(referenceHash, count, skip, reverse); + protected CompletableFuture sendRequest() { + return sendRequestToPeer( + peer -> { + LOG.debug("Requesting {} headers from peer {}.", count, peer); + return peer.getHeadersByHash(referenceHash, count, skip, reverse); + }, + minimumRequiredBlockNumber); } @Override diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java index 5d096b7231..267081d6be 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java @@ -14,12 +14,12 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; 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.mainnet.ProtocolSchedule; -import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.metrics.MetricsSystem; +import java.util.concurrent.CompletableFuture; + import com.google.common.annotations.VisibleForTesting; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -39,7 +39,7 @@ public class GetHeadersFromPeerByNumberTask extends AbstractGetHeadersFromPeerTa final int skip, final boolean reverse, final MetricsSystem metricsSystem) { - super(protocolSchedule, ethContext, blockNumber, count, skip, reverse, metricsSystem); + super(protocolSchedule, ethContext, count, skip, reverse, metricsSystem); this.blockNumber = blockNumber; } @@ -84,9 +84,13 @@ public static AbstractGetHeadersFromPeerTask forSingleNumber( } @Override - protected ResponseStream sendRequest(final EthPeer peer) throws PeerNotConnected { - LOG.debug("Requesting {} headers from peer {}.", count, peer); - return peer.getHeadersByNumber(blockNumber, count, skip, reverse); + protected CompletableFuture sendRequest() { + return sendRequestToPeer( + peer -> { + LOG.debug("Requesting {} headers from peer {}.", count, peer); + return peer.getHeadersByNumber(blockNumber, count, skip, reverse); + }, + blockNumber); } @Override diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java index 41ed67581b..c26ccb2e8d 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java @@ -21,7 +21,6 @@ import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; import tech.pegasys.pantheon.ethereum.eth.messages.NodeDataMessage; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; -import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.bytes.BytesValue; @@ -32,6 +31,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -62,9 +62,13 @@ public static GetNodeDataFromPeerTask forHashes( } @Override - protected ResponseStream sendRequest(final EthPeer peer) throws PeerNotConnected { - LOG.debug("Requesting {} node data entries from peer {}.", hashes.size(), peer); - return peer.getNodeData(hashes); + protected CompletableFuture sendRequest() { + return sendRequestToPeer( + peer -> { + LOG.debug("Requesting {} node data entries from peer {}.", hashes.size(), peer); + return peer.getNodeData(hashes); + }, + pivotBlockNumber); } @Override @@ -86,7 +90,7 @@ protected Optional> processResponse( private Optional> mapNodeDataByHash(final List nodeData) { final Map nodeDataByHash = new HashMap<>(); - for (BytesValue data : nodeData) { + for (final BytesValue data : nodeData) { final Hash hash = Hash.hash(data); if (!hashes.contains(hash)) { return Optional.empty(); @@ -95,9 +99,4 @@ private Optional> mapNodeDataByHash(final List } return Optional.of(nodeDataByHash); } - - @Override - protected Optional findSuitablePeer() { - return ethContext.getEthPeers().idlePeer(pivotBlockNumber); - } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java index 76165df0bf..e864a209c8 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java @@ -25,7 +25,6 @@ import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; import tech.pegasys.pantheon.ethereum.eth.messages.ReceiptsMessage; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; -import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.metrics.MetricsSystem; import java.util.ArrayList; @@ -34,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -67,15 +67,25 @@ public static GetReceiptsFromPeerTask forHeaders( } @Override - protected ResponseStream sendRequest(final EthPeer peer) throws PeerNotConnected { - LOG.debug("Requesting {} receipts from peer {}.", blockHeaders.size(), peer); + protected CompletableFuture sendRequest() { + final long maximumRequiredBlockNumber = + blockHeaders.stream() + .mapToLong(BlockHeader::getNumber) + .max() + .orElse(BlockHeader.GENESIS_BLOCK_NUMBER); + // Since we have to match up the data by receipt root, we only need to request receipts // for one of the headers with each unique receipt root. final List blockHashes = headersByReceiptsRoot.values().stream() .map(headers -> headers.get(0).getHash()) .collect(toList()); - return peer.getReceipts(blockHashes); + return sendRequestToPeer( + peer -> { + LOG.debug("Requesting {} receipts from peer {}.", blockHeaders.size(), peer); + return peer.getReceipts(blockHashes); + }, + maximumRequiredBlockNumber); } @Override @@ -108,14 +118,4 @@ protected Optional>> processResponse( } return Optional.of(receiptsByHeader); } - - @Override - protected Optional findSuitablePeer() { - final long maximumRequiredBlockNumber = - blockHeaders.stream() - .mapToLong(BlockHeader::getNumber) - .max() - .orElse(BlockHeader.GENESIS_BLOCK_NUMBER); - return this.ethContext.getEthPeers().idlePeer(maximumRequiredBlockNumber); - } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTask.java index ee1c9770ec..b2ddd8b723 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/WaitForPeerTask.java @@ -40,9 +40,7 @@ public static WaitForPeerTask create( protected void executeTask() { final EthPeers ethPeers = ethContext.getEthPeers(); LOG.debug( - "Waiting for new peer connection. {} peers currently connected, {} idle.", - ethPeers.peerCount(), - ethPeers.idlePeer().isPresent() ? "Some peers" : "No peers"); + "Waiting for new peer connection. {} peers currently connected.", ethPeers.peerCount()); // Listen for peer connections and complete task when we hit our target peerListenerId = ethPeers.subscribeConnect( 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 843056878a..70fa790fbc 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 @@ -70,6 +70,7 @@ public void onPeerConnected(final EthPeer peer) { protocolSchedule, ethContext, Hash.wrap(peer.chainState().getBestBlock().getHash()), + 0, metricsSystem) .assignPeer(peer) .run() 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 01dbb73de9..53d6b8be18 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 @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.eth.sync.tasks; +import static java.util.Collections.emptyList; + import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockHeader; @@ -22,12 +24,9 @@ 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; import tech.pegasys.pantheon.metrics.MetricsSystem; -import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -50,7 +49,6 @@ public class ImportBlocksTask extends AbstractPeerTask> { private final BlockHeader referenceHeader; private final int maxBlocks; private final MetricsSystem metricsSystem; - private EthPeer peer; protected ImportBlocksTask( final ProtocolSchedule protocolSchedule, @@ -81,8 +79,7 @@ public static ImportBlocksTask fromHeader( } @Override - protected void executeTaskWithPeer(final EthPeer peer) throws PeerNotConnected { - this.peer = peer; + protected void executeTask() { LOG.debug("Importing blocks from {}", startNumber); downloadHeaders() .thenCompose(this::completeBlocks) @@ -98,52 +95,56 @@ protected void executeTaskWithPeer(final EthPeer peer) throws PeerNotConnected { .get() .complete( new PeerTaskResult<>( - peer, r.stream().map(Block::getHash).collect(Collectors.toList()))); + r.getPeer(), + r.getResult().stream() + .map(Block::getHash) + .collect(Collectors.toList()))); } }); } - @Override - protected Optional findSuitablePeer() { - return ethContext.getEthPeers().idlePeer(referenceHeader.getNumber()); - } - private CompletableFuture>> downloadHeaders() { final AbstractPeerTask> task = GetHeadersFromPeerByHashTask.startingAtHash( - protocolSchedule, - ethContext, - referenceHeader.getHash(), - referenceHeader.getNumber(), - maxBlocks, - metricsSystem) - .assignPeer(peer); + protocolSchedule, + ethContext, + referenceHeader.getHash(), + referenceHeader.getNumber(), + maxBlocks, + metricsSystem); + assignedPeer.ifPresent(task::assignPeer); return executeSubTask(task::run); } - private CompletableFuture> completeBlocks( + private CompletableFuture>> completeBlocks( final PeerTaskResult> headers) { if (headers.getResult().isEmpty()) { - return CompletableFuture.completedFuture(Collections.emptyList()); + return CompletableFuture.completedFuture( + new PeerTaskResult<>(headers.getPeer(), emptyList())); } final CompleteBlocksTask task = CompleteBlocksTask.forHeaders( protocolSchedule, ethContext, headers.getResult(), metricsSystem); - task.assignPeer(peer); - return executeSubTask(() -> ethContext.getScheduler().timeout(task)); + task.assignPeer(headers.getPeer()); + return executeSubTask(() -> ethContext.getScheduler().timeout(task)) + .thenApply(blocks -> new PeerTaskResult<>(headers.getPeer(), blocks)); } - private CompletableFuture> importBlocks(final List blocks) { + private CompletableFuture>> importBlocks( + final PeerTaskResult> blocksResult) { + final List blocks = blocksResult.getResult(); + final EthPeer peer = blocksResult.getPeer(); // Don't import reference block if we already know about it if (protocolContext.getBlockchain().contains(referenceHeader.getHash())) { blocks.removeIf(b -> b.getHash().equals(referenceHeader.getHash())); } if (blocks.isEmpty()) { - return CompletableFuture.completedFuture(Collections.emptyList()); + return CompletableFuture.completedFuture(new PeerTaskResult<>(peer, emptyList())); } final Supplier>> task = PersistBlockTask.forSequentialBlocks( protocolSchedule, protocolContext, blocks, HeaderValidationMode.FULL, metricsSystem); - return executeWorkerSubTask(ethContext.getScheduler(), task); + return executeWorkerSubTask(ethContext.getScheduler(), task) + .thenApply(result -> new PeerTaskResult<>(peer, result)); } } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthContextTestUtil.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthContextTestUtil.java index f0bb526aba..30d300e845 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthContextTestUtil.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthContextTestUtil.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.eth.manager; import tech.pegasys.pantheon.ethereum.eth.manager.DeterministicEthScheduler.TimeoutPolicy; +import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; public class EthContextTestUtil { @@ -21,7 +22,7 @@ public class EthContextTestUtil { public static EthContext createTestEthContext(final TimeoutPolicy timeoutPolicy) { return new EthContext( PROTOCOL_NAME, - new EthPeers(PROTOCOL_NAME), + new EthPeers(PROTOCOL_NAME, new NoOpMetricsSystem()), new EthMessages(), new DeterministicEthScheduler(timeoutPolicy)); } 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 fe477cfe84..e5519dee7a 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 @@ -1045,7 +1045,8 @@ public void transactionMessagesGoToTheCorrectExecutor() { 1, true, ethScheduler, - EthereumWireProtocolConfiguration.defaultConfig())) { + EthereumWireProtocolConfiguration.defaultConfig(), + metricsSystem)) { // Create a transaction pool. This has a side effect of registring a listener for the // transactions message. diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java index 346dab6145..dbdec48364 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java @@ -28,6 +28,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; +import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.util.uint.UInt256; public class EthProtocolManagerTestUtil { @@ -50,7 +51,8 @@ public static EthProtocolManager create( networkId, false, ethScheduler, - EthereumWireProtocolConfiguration.defaultConfig()); + EthereumWireProtocolConfiguration.defaultConfig(), + new NoOpMetricsSystem()); } public static EthProtocolManager create( 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 1a6c66bed3..cd0919b093 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 @@ -544,7 +544,7 @@ public void shouldNotImportBlocksThatAreAlreadyBeingImported() { when(ethScheduler.scheduleSyncWorkerTask(any(Supplier.class))) .thenReturn(new CompletableFuture<>()); final EthContext ethContext = - new EthContext("eth", new EthPeers("eth"), new EthMessages(), ethScheduler); + new EthContext("eth", new EthPeers("eth", metricsSystem), new EthMessages(), ethScheduler); final BlockPropagationManager blockPropagationManager = new BlockPropagationManager<>( syncConfig, diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandlerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandlerTest.java index 3e41a6582c..56533a00d2 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandlerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandlerTest.java @@ -76,13 +76,13 @@ public class FastSyncBlockHandlerTest { private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class); private final ProtocolContext protocolContext = new ProtocolContext<>(blockchain, worldStateArchive, null); + private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private final EthContext ethContext = new EthContext( PROTOCOL_NAME, - new EthPeers(PROTOCOL_NAME), + new EthPeers(PROTOCOL_NAME, metricsSystem), new EthMessages(), new DeterministicEthScheduler()); - private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private final ValidationPolicy validationPolicy = mock(ValidationPolicy.class); private final ValidationPolicy ommerValidationPolicy = mock(ValidationPolicy.class); From 9a2f147271f5107c4e252e50fe7307a8a4c29a58 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 26 Apr 2019 14:59:19 +1000 Subject: [PATCH 02/25] Only start the timer after the request is actually sent so it doesn't include the time queueing for an available peer. --- .../pantheon/ethereum/eth/manager/EthPeers.java | 3 ++- .../eth/manager/task/AbstractPeerRequestTask.java | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java index 6c32ed635c..b525e9fa13 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java @@ -54,7 +54,8 @@ public class EthPeers { public EthPeers(final String protocolName, final MetricsSystem metricsSystem) { this.protocolName = protocolName; - metricsSystem.createIntegerGauge(MetricCategory.PEERS, + metricsSystem.createIntegerGauge( + MetricCategory.PEERS, "pending_peer_requests_current", "Number of peer requests currently pending because peers are busy", pendingRequests::size); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java index 3411a1bfe9..d77f9e2936 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java @@ -59,10 +59,14 @@ protected final void executeTask() { responseStream = sendRequest(); responseStream .thenAccept( - stream -> - stream.then( - (streamClosed, message, peer1) -> - handleMessage(promise, streamClosed, message, peer1))) + stream -> { + // Start the timeout now that the request has actually been sent + ethContext.getScheduler().failAfterTimeout(promise, timeout); + + stream.then( + (streamClosed, message, peer1) -> + handleMessage(promise, streamClosed, message, peer1)); + }) .exceptionally( error -> { promise.completeExceptionally(error); @@ -83,8 +87,6 @@ protected final void executeTask() { result.get().complete(new PeerTaskResult<>(responseStream.get().getPeer(), r)); } }); - - ethContext.getScheduler().failAfterTimeout(promise, timeout); } public CompletableFuture sendRequestToPeer( From 1fa9053e2f52b03ab01f77bf351b3501a0d737bb Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 26 Apr 2019 15:36:57 +1000 Subject: [PATCH 03/25] Encapsulate the future to provide a nicer, more explicit API for working with pending peer requests. Send all peer requests through EthPeers, even if a specific peer has been requested. --- .../ethereum/eth/manager/EthPeers.java | 122 +++++++++++++----- .../manager/task/AbstractPeerRequestTask.java | 64 +++------ .../manager/task/GetBodiesFromPeerTask.java | 5 +- .../task/GetHeadersFromPeerByHashTask.java | 6 +- .../task/GetHeadersFromPeerByNumberTask.java | 6 +- .../manager/task/GetNodeDataFromPeerTask.java | 5 +- .../manager/task/GetReceiptsFromPeerTask.java | 5 +- 7 files changed, 115 insertions(+), 98 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java index b525e9fa13..4e3e88841e 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java @@ -27,8 +27,11 @@ import java.util.Comparator; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -50,7 +53,7 @@ public class EthPeers { private final String protocolName; private final Subscribers connectCallbacks = new Subscribers<>(); private final Subscribers disconnectCallbacks = new Subscribers<>(); - private final Collection pendingRequests = new ArrayList<>(); + private final Collection pendingRequests = new ArrayList<>(); public EthPeers(final String protocolName, final MetricsSystem metricsSystem) { this.protocolName = protocolName; @@ -78,22 +81,23 @@ public EthPeer peer(final PeerConnection peerConnection) { return connections.get(peerConnection); } - public CompletableFuture executePeerRequest( - final PeerRequest request, final long minimumBlockNumber) { - final QueuedTask queuedTask = new QueuedTask(request, minimumBlockNumber); + public PendingPeerRequest executePeerRequest( + final PeerRequest request, final long minimumBlockNumber, final Optional peer) { + final PendingPeerRequest pendingPeerRequest = + new PendingPeerRequest(request, minimumBlockNumber, peer); synchronized (this) { - if (!queuedTask.attemptExecution()) { - pendingRequests.add(queuedTask); + if (!pendingPeerRequest.attemptExecution()) { + pendingRequests.add(pendingPeerRequest); } } - return queuedTask.result; + return pendingPeerRequest; } public void dispatchMessage(final EthPeer peer, final EthMessage ethMessage) { peer.dispatch(ethMessage); if (peer.outstandingRequests() < maxOutstandingRequests) { synchronized (this) { - pendingRequests.removeIf(QueuedTask::attemptExecution); + pendingRequests.removeIf(PendingPeerRequest::attemptExecution); } } } @@ -138,43 +142,95 @@ private void invokeConnectionCallbacks(final EthPeer peer) { connectCallbacks.forEach(cb -> cb.onPeerConnected(peer)); } - private class QueuedTask { + public class PendingPeerRequest { private final PeerRequest request; private final CompletableFuture result = new CompletableFuture<>(); private final long minimumBlockNumber; + private final Optional peer; - private QueuedTask(final PeerRequest request, final long minimumBlockNumber) { + private PendingPeerRequest( + final PeerRequest request, final long minimumBlockNumber, final Optional peer) { this.request = request; this.minimumBlockNumber = minimumBlockNumber; + this.peer = peer; } + /** + * Attempts to find an available peer and execute the peer request. + * + * @return true if the request should be removed from the pending list, otherwise false. + */ public boolean attemptExecution() { + if (result.isDone()) { + return true; + } + final Optional leastBusySuitablePeer = getLeastBusySuitablePeer(); + if (!leastBusySuitablePeer.isPresent()) { + // No peers have the required height. + result.completeExceptionally(new NoAvailablePeersException()); + return true; + } else { + // At least one peer has the required height, but we not be able to use it if it's busy + final Optional selectedPeer = + leastBusySuitablePeer.filter( + peer -> peer.outstandingRequests() < maxOutstandingRequests); + + selectedPeer.ifPresent(this::sendRequest); + return selectedPeer.isPresent(); + } + } + + private synchronized void sendRequest(final EthPeer peer) { + // Recheck if we should send the request now we're inside the synchronized block if (!result.isDone()) { - final Optional leastBusySuitablePeer = - availablePeers() - .filter(peer -> peer.chainState().getEstimatedHeight() >= minimumBlockNumber) - .min(LEAST_TO_MOST_BUSY); - if (!leastBusySuitablePeer.isPresent()) { - // No peers have the required height. - result.completeExceptionally(new NoAvailablePeersException()); - } else { - // At least one peer has the required height, but we not be able to use it if it's busy - final Optional selectedPeer = - leastBusySuitablePeer.filter( - peer -> peer.outstandingRequests() < maxOutstandingRequests); - selectedPeer.ifPresent( - peer -> { - try { - final ResponseStream responseStream = request.sendRequest(peer); - result.complete(responseStream); - } catch (final PeerNotConnected e) { - result.completeExceptionally(new PeerDisconnectedException(peer)); - } - }); - return selectedPeer.isPresent(); + try { + final ResponseStream responseStream = request.sendRequest(peer); + result.complete(responseStream); + } catch (final PeerNotConnected e) { + result.completeExceptionally(new PeerDisconnectedException(peer)); } } - return false; + } + + private Optional getLeastBusySuitablePeer() { + return peer.isPresent() + ? peer + : availablePeers() + .filter(peer -> peer.chainState().getEstimatedHeight() >= minimumBlockNumber) + .min(LEAST_TO_MOST_BUSY); + } + + /** + * Register callbacks for when the request is made or + * + * @param onSuccess handler for when a peer becomes available and the request is sent + * @param onError handler for when there is no peer with sufficient height or the request fails + * to send + */ + public void thenEither( + final Consumer onSuccess, final Consumer onError) { + result.whenComplete( + (result, error) -> { + if (error != null) { + onError.accept(error); + } else { + onSuccess.accept(result); + } + }); + } + + /** + * Abort this request. + * + * @return the response stream if the request has already been sent, otherwise empty. + */ + public synchronized Optional abort() { + try { + result.cancel(false); + return Optional.ofNullable(result.getNow(null)); + } catch (final CancellationException | CompletionException e) { + return Optional.empty(); + } } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java index d77f9e2936..ca6d6eecec 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java @@ -12,17 +12,13 @@ */ package tech.pegasys.pantheon.ethereum.eth.manager.task; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static tech.pegasys.pantheon.util.FutureUtils.completedExceptionally; - import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PeerRequest; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerBreachedProtocolException; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; -import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; import tech.pegasys.pantheon.ethereum.rlp.RLPException; import tech.pegasys.pantheon.metrics.MetricsSystem; @@ -30,9 +26,7 @@ import java.time.Duration; import java.util.Optional; -import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.TimeoutException; public abstract class AbstractPeerRequestTask extends AbstractPeerTask { @@ -40,7 +34,7 @@ public abstract class AbstractPeerRequestTask extends AbstractPeerTask { private Duration timeout = DEFAULT_TIMEOUT; private final int requestCode; - private volatile CompletableFuture responseStream; + private volatile PendingPeerRequest responseStream; protected AbstractPeerRequestTask( final EthContext ethContext, final int requestCode, final MetricsSystem metricsSystem) { @@ -57,25 +51,20 @@ public AbstractPeerRequestTask setTimeout(final Duration timeout) { protected final void executeTask() { final CompletableFuture promise = new CompletableFuture<>(); responseStream = sendRequest(); - responseStream - .thenAccept( - stream -> { - // Start the timeout now that the request has actually been sent - ethContext.getScheduler().failAfterTimeout(promise, timeout); + responseStream.thenEither( + stream -> { + // Start the timeout now that the request has actually been sent + ethContext.getScheduler().failAfterTimeout(promise, timeout); - stream.then( - (streamClosed, message, peer1) -> - handleMessage(promise, streamClosed, message, peer1)); - }) - .exceptionally( - error -> { - promise.completeExceptionally(error); - return null; - }); + stream.then( + (streamClosed, message, peer1) -> + handleMessage(promise, streamClosed, message, peer1)); + }, + promise::completeExceptionally); promise.whenComplete( (r, t) -> { - final Optional responseStream = cancelAndGetResponseStreamIfCreated(); + final Optional responseStream = this.responseStream.abort(); if (t != null) { t = ExceptionUtils.rootCause(t); if (t instanceof TimeoutException && responseStream.isPresent()) { @@ -89,18 +78,9 @@ protected final void executeTask() { }); } - public CompletableFuture sendRequestToPeer( + public PendingPeerRequest sendRequestToPeer( final PeerRequest request, final long minimumBlockNumber) { - if (assignedPeer.isPresent()) { - try { - return completedFuture(request.sendRequest(assignedPeer.get())); - } catch (final PeerNotConnected e) { - result.get().completeExceptionally(new PeerDisconnectedException(assignedPeer.get())); - return completedExceptionally(e); - } - } else { - return ethContext.getEthPeers().executePeerRequest(request, minimumBlockNumber); - } + return ethContext.getEthPeers().executePeerRequest(request, minimumBlockNumber, assignedPeer); } private void handleMessage( @@ -122,25 +102,13 @@ private void handleMessage( } } - private Optional cancelAndGetResponseStreamIfCreated() { - try { - responseStream.cancel(false); - return Optional.ofNullable(responseStream.getNow(null)); - } catch (final CancellationException | CompletionException e) { - return Optional.empty(); - } - } - @Override protected void cleanup() { super.cleanup(); - final ResponseStream stream = responseStream.getNow(null); - if (stream != null) { - stream.close(); - } + responseStream.abort().ifPresent(ResponseStream::close); } - protected abstract CompletableFuture sendRequest(); + protected abstract PendingPeerRequest sendRequest(); protected abstract Optional processResponse( boolean streamClosed, MessageData message, EthPeer peer); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java index c5f8a5dc72..b0e2b6a428 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java @@ -21,7 +21,7 @@ import tech.pegasys.pantheon.ethereum.core.Transaction; 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.EthPeers.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.eth.messages.BlockBodiesMessage; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV62; import tech.pegasys.pantheon.ethereum.mainnet.BodyValidation; @@ -37,7 +37,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -82,7 +81,7 @@ public static GetBodiesFromPeerTask forHeaders( } @Override - protected CompletableFuture sendRequest() { + protected PendingPeerRequest sendRequest() { final List blockHashes = headers.stream().map(BlockHeader::getHash).collect(Collectors.toList()); final long minimumRequiredBlockNumber = headers.get(headers.size() - 1).getNumber(); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java index d5a9ebb5f5..a0a79acb61 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java @@ -17,12 +17,10 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; -import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.MetricsSystem; -import java.util.concurrent.CompletableFuture; - import com.google.common.annotations.VisibleForTesting; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -135,7 +133,7 @@ public static AbstractGetHeadersFromPeerTask forSingleHash( } @Override - protected CompletableFuture sendRequest() { + protected PendingPeerRequest sendRequest() { return sendRequestToPeer( peer -> { LOG.debug("Requesting {} headers from peer {}.", count, peer); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java index 267081d6be..37244110b8 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java @@ -14,12 +14,10 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; -import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.MetricsSystem; -import java.util.concurrent.CompletableFuture; - import com.google.common.annotations.VisibleForTesting; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -84,7 +82,7 @@ public static AbstractGetHeadersFromPeerTask forSingleNumber( } @Override - protected CompletableFuture sendRequest() { + protected PendingPeerRequest sendRequest() { return sendRequestToPeer( peer -> { LOG.debug("Requesting {} headers from peer {}.", count, peer); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java index c26ccb2e8d..b1a7949568 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java @@ -17,7 +17,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.RequestManager.ResponseStream; +import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; import tech.pegasys.pantheon.ethereum.eth.messages.NodeDataMessage; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; @@ -31,7 +31,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.CompletableFuture; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -62,7 +61,7 @@ public static GetNodeDataFromPeerTask forHashes( } @Override - protected CompletableFuture sendRequest() { + protected PendingPeerRequest sendRequest() { return sendRequestToPeer( peer -> { LOG.debug("Requesting {} node data entries from peer {}.", hashes.size(), peer); diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java index e864a209c8..1800adab60 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java @@ -21,7 +21,7 @@ import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; 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.EthPeers.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; import tech.pegasys.pantheon.ethereum.eth.messages.ReceiptsMessage; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; @@ -33,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -67,7 +66,7 @@ public static GetReceiptsFromPeerTask forHeaders( } @Override - protected CompletableFuture sendRequest() { + protected PendingPeerRequest sendRequest() { final long maximumRequiredBlockNumber = blockHeaders.stream() .mapToLong(BlockHeader::getNumber) From c4cca4907d4f237ee847833c5cb5792737e0bc63 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 26 Apr 2019 15:53:34 +1000 Subject: [PATCH 04/25] Split PendingPeerRequest and PeerRequest into separate files. --- .../ethereum/eth/manager/EthPeer.java | 7 + .../ethereum/eth/manager/EthPeers.java | 109 +--------------- .../ethereum/eth/manager/PeerRequest.java | 20 +++ .../eth/manager/PendingPeerRequest.java | 121 ++++++++++++++++++ .../manager/task/AbstractPeerRequestTask.java | 4 +- .../manager/task/GetBodiesFromPeerTask.java | 2 +- .../task/GetHeadersFromPeerByHashTask.java | 2 +- .../task/GetHeadersFromPeerByNumberTask.java | 2 +- .../manager/task/GetNodeDataFromPeerTask.java | 2 +- .../manager/task/GetReceiptsFromPeerTask.java | 2 +- 10 files changed, 157 insertions(+), 114 deletions(-) create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PeerRequest.java create mode 100644 ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.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 7691f94ef7..3f9a631f10 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 @@ -46,6 +46,9 @@ public class EthPeer { private static final Logger LOG = LogManager.getLogger(); + + private static final int MAX_OUTSTANDING_REQUESTS = 5; + private final PeerConnection connection; private final int maxTrackedSeenBlocks = 300; @@ -328,6 +331,10 @@ public int outstandingRequests() { + nodeDataRequestManager.outstandingRequests(); } + public boolean hasAvailableRequestCapacity() { + return outstandingRequests() < MAX_OUTSTANDING_REQUESTS; + } + public BytesValue nodeId() { return connection.getPeerInfo().getNodeId(); } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java index 4e3e88841e..3202deb101 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java @@ -13,11 +13,7 @@ package tech.pegasys.pantheon.ethereum.eth.manager; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer.DisconnectCallback; -import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; -import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; -import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.metrics.MetricCategory; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.Subscribers; @@ -27,11 +23,7 @@ import java.util.Comparator; import java.util.Map; import java.util.Optional; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -48,7 +40,6 @@ public class EthPeers { public static final Comparator LEAST_TO_MOST_BUSY = Comparator.comparing(EthPeer::outstandingRequests); - private final int maxOutstandingRequests = 5; private final Map connections = new ConcurrentHashMap<>(); private final String protocolName; private final Subscribers connectCallbacks = new Subscribers<>(); @@ -84,7 +75,7 @@ public EthPeer peer(final PeerConnection peerConnection) { public PendingPeerRequest executePeerRequest( final PeerRequest request, final long minimumBlockNumber, final Optional peer) { final PendingPeerRequest pendingPeerRequest = - new PendingPeerRequest(request, minimumBlockNumber, peer); + new PendingPeerRequest(this, request, minimumBlockNumber, peer); synchronized (this) { if (!pendingPeerRequest.attemptExecution()) { pendingRequests.add(pendingPeerRequest); @@ -95,7 +86,7 @@ public PendingPeerRequest executePeerRequest( public void dispatchMessage(final EthPeer peer, final EthMessage ethMessage) { peer.dispatch(ethMessage); - if (peer.outstandingRequests() < maxOutstandingRequests) { + if (peer.hasAvailableRequestCapacity()) { synchronized (this) { pendingRequests.removeIf(PendingPeerRequest::attemptExecution); } @@ -141,100 +132,4 @@ public String toString() { private void invokeConnectionCallbacks(final EthPeer peer) { connectCallbacks.forEach(cb -> cb.onPeerConnected(peer)); } - - public class PendingPeerRequest { - private final PeerRequest request; - private final CompletableFuture result = new CompletableFuture<>(); - private final long minimumBlockNumber; - private final Optional peer; - - private PendingPeerRequest( - final PeerRequest request, final long minimumBlockNumber, final Optional peer) { - this.request = request; - this.minimumBlockNumber = minimumBlockNumber; - this.peer = peer; - } - - /** - * Attempts to find an available peer and execute the peer request. - * - * @return true if the request should be removed from the pending list, otherwise false. - */ - public boolean attemptExecution() { - if (result.isDone()) { - return true; - } - final Optional leastBusySuitablePeer = getLeastBusySuitablePeer(); - if (!leastBusySuitablePeer.isPresent()) { - // No peers have the required height. - result.completeExceptionally(new NoAvailablePeersException()); - return true; - } else { - // At least one peer has the required height, but we not be able to use it if it's busy - final Optional selectedPeer = - leastBusySuitablePeer.filter( - peer -> peer.outstandingRequests() < maxOutstandingRequests); - - selectedPeer.ifPresent(this::sendRequest); - return selectedPeer.isPresent(); - } - } - - private synchronized void sendRequest(final EthPeer peer) { - // Recheck if we should send the request now we're inside the synchronized block - if (!result.isDone()) { - try { - final ResponseStream responseStream = request.sendRequest(peer); - result.complete(responseStream); - } catch (final PeerNotConnected e) { - result.completeExceptionally(new PeerDisconnectedException(peer)); - } - } - } - - private Optional getLeastBusySuitablePeer() { - return peer.isPresent() - ? peer - : availablePeers() - .filter(peer -> peer.chainState().getEstimatedHeight() >= minimumBlockNumber) - .min(LEAST_TO_MOST_BUSY); - } - - /** - * Register callbacks for when the request is made or - * - * @param onSuccess handler for when a peer becomes available and the request is sent - * @param onError handler for when there is no peer with sufficient height or the request fails - * to send - */ - public void thenEither( - final Consumer onSuccess, final Consumer onError) { - result.whenComplete( - (result, error) -> { - if (error != null) { - onError.accept(error); - } else { - onSuccess.accept(result); - } - }); - } - - /** - * Abort this request. - * - * @return the response stream if the request has already been sent, otherwise empty. - */ - public synchronized Optional abort() { - try { - result.cancel(false); - return Optional.ofNullable(result.getNow(null)); - } catch (final CancellationException | CompletionException e) { - return Optional.empty(); - } - } - } - - public interface PeerRequest { - ResponseStream sendRequest(EthPeer peer) throws PeerNotConnected; - } } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PeerRequest.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PeerRequest.java new file mode 100644 index 0000000000..3d98d3178c --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PeerRequest.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.manager; + +import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; +import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; + +public interface PeerRequest { + ResponseStream sendRequest(EthPeer peer) throws PeerNotConnected; +} diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java new file mode 100644 index 0000000000..f11950e343 --- /dev/null +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java @@ -0,0 +1,121 @@ +/* + * 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.ethereum.eth.manager.RequestManager.ResponseStream; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; +import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; + +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; + +public class PendingPeerRequest { + private final EthPeers ethPeers; + private final PeerRequest request; + private final CompletableFuture result = new CompletableFuture<>(); + private final long minimumBlockNumber; + private final Optional peer; + + PendingPeerRequest( + final EthPeers ethPeers, + final PeerRequest request, + final long minimumBlockNumber, + final Optional peer) { + this.ethPeers = ethPeers; + this.request = request; + this.minimumBlockNumber = minimumBlockNumber; + this.peer = peer; + } + + /** + * Attempts to find an available peer and execute the peer request. + * + * @return true if the request should be removed from the pending list, otherwise false. + */ + public boolean attemptExecution() { + if (result.isDone()) { + return true; + } + final Optional leastBusySuitablePeer = getLeastBusySuitablePeer(); + if (!leastBusySuitablePeer.isPresent()) { + // No peers have the required height. + result.completeExceptionally(new NoAvailablePeersException()); + return true; + } else { + // At least one peer has the required height, but we not be able to use it if it's busy + final Optional selectedPeer = + leastBusySuitablePeer.filter(EthPeer::hasAvailableRequestCapacity); + + selectedPeer.ifPresent(this::sendRequest); + return selectedPeer.isPresent(); + } + } + + private synchronized void sendRequest(final EthPeer peer) { + // Recheck if we should send the request now we're inside the synchronized block + if (!result.isDone()) { + try { + final ResponseStream responseStream = request.sendRequest(peer); + result.complete(responseStream); + } catch (final PeerNotConnected e) { + result.completeExceptionally(new PeerDisconnectedException(peer)); + } + } + } + + private Optional getLeastBusySuitablePeer() { + return peer.isPresent() + ? peer + : ethPeers + .availablePeers() + .filter(peer -> peer.chainState().getEstimatedHeight() >= minimumBlockNumber) + .min(EthPeers.LEAST_TO_MOST_BUSY); + } + + /** + * Register callbacks for when the request is made or + * + * @param onSuccess handler for when a peer becomes available and the request is sent + * @param onError handler for when there is no peer with sufficient height or the request fails to + * send + */ + public void thenEither( + final Consumer onSuccess, final Consumer onError) { + result.whenComplete( + (result, error) -> { + if (error != null) { + onError.accept(error); + } else { + onSuccess.accept(result); + } + }); + } + + /** + * Abort this request. + * + * @return the response stream if the request has already been sent, otherwise empty. + */ + public synchronized Optional abort() { + try { + result.cancel(false); + return Optional.ofNullable(result.getNow(null)); + } catch (final CancellationException | CompletionException e) { + return Optional.empty(); + } + } +} diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java index ca6d6eecec..94fdb21f20 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java @@ -14,8 +14,8 @@ import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PeerRequest; -import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; +import tech.pegasys.pantheon.ethereum.eth.manager.PeerRequest; +import tech.pegasys.pantheon.ethereum.eth.manager.PendingPeerRequest; 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/task/GetBodiesFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java index b0e2b6a428..505db56ec3 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetBodiesFromPeerTask.java @@ -21,7 +21,7 @@ import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; +import tech.pegasys.pantheon.ethereum.eth.manager.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.eth.messages.BlockBodiesMessage; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV62; import tech.pegasys.pantheon.ethereum.mainnet.BodyValidation; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java index a0a79acb61..454ea45562 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java @@ -17,7 +17,7 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; -import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; +import tech.pegasys.pantheon.ethereum.eth.manager.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.MetricsSystem; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java index 37244110b8..d68c0e2b76 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java @@ -14,7 +14,7 @@ import tech.pegasys.pantheon.ethereum.core.BlockHeader; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; -import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; +import tech.pegasys.pantheon.ethereum.eth.manager.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.metrics.MetricsSystem; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java index b1a7949568..8c4a7aff8a 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java @@ -17,7 +17,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.EthPeers.PendingPeerRequest; +import tech.pegasys.pantheon.ethereum.eth.manager.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; import tech.pegasys.pantheon.ethereum.eth.messages.NodeDataMessage; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java index 1800adab60..ffffe474dd 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java @@ -21,7 +21,7 @@ import tech.pegasys.pantheon.ethereum.core.TransactionReceipt; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; -import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers.PendingPeerRequest; +import tech.pegasys.pantheon.ethereum.eth.manager.PendingPeerRequest; import tech.pegasys.pantheon.ethereum.eth.messages.EthPV63; import tech.pegasys.pantheon.ethereum.eth.messages.ReceiptsMessage; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; From d1103738d88cff276d02044c30b5cbf02bad831c Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Sat, 27 Apr 2019 07:48:06 +1000 Subject: [PATCH 05/25] Fix CheckpointHeaderManagerTest. --- .../ethereum/eth/sync/CheckpointHeaderManagerTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/CheckpointHeaderManagerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/CheckpointHeaderManagerTest.java index b701c42f4f..e03c47315b 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/CheckpointHeaderManagerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/CheckpointHeaderManagerTest.java @@ -85,6 +85,7 @@ public class CheckpointHeaderManagerTest { @Before public void setUp() { + when(syncTargetPeer.hasAvailableRequestCapacity()).thenReturn(true); when(syncTargetPeer.chainState()).thenReturn(new ChainState()); syncTarget = syncState.setSyncTarget(syncTargetPeer, GENESIS); } @@ -123,6 +124,7 @@ public void shouldResetTimeoutWhenHeadersReceived() throws Exception { // Receive response reset(syncTargetPeer); + when(syncTargetPeer.hasAvailableRequestCapacity()).thenReturn(true); respondToHeaderRequests(GENESIS, block(5)); timeout.set(false); assertThat(checkpointHeaderManager.pullCheckpointHeaders(syncTarget)) @@ -131,6 +133,7 @@ public void shouldResetTimeoutWhenHeadersReceived() throws Exception { // Timeout again but shouldn't have reached threshold reset(syncTargetPeer); + when(syncTargetPeer.hasAvailableRequestCapacity()).thenReturn(true); timeout.set(true); when(anyHeadersRequested()).thenReturn(createResponseStream()); assertThat(checkpointHeaderManager.pullCheckpointHeaders(syncTarget)) From a0143a97c20406d07087a36d7ffad4ec32747c9b Mon Sep 17 00:00:00 2001 From: CJ Hare Date: Fri, 26 Apr 2019 16:21:40 +1000 Subject: [PATCH 06/25] JsonRpcError decoding to include message (#1336) --- .../ethereum/jsonrpc/internal/response/JsonRpcError.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index eb0768e364..769650ee41 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -118,7 +118,7 @@ public String getMessage() { public static JsonRpcError fromJson( @JsonProperty("code") final int code, @JsonProperty("message") final String message) { for (final JsonRpcError error : JsonRpcError.values()) { - if (error.getCode() == code) { + if (error.code == code && error.message.equals(message)) { return error; } } From aefb23cae9f974ac7ecd9a22717b0022f553ed20 Mon Sep 17 00:00:00 2001 From: Ivaylo Kirilov Date: Fri, 26 Apr 2019 11:07:04 +0100 Subject: [PATCH 07/25] [PRIV-55] Allow private contract invocations in multiple privacy groups (#1318) * Allow private contract invocations in multiple privacy groups --- ...ctValidPrivateContractDeployedReceipt.java | 2 + .../privacy/PrivateAcceptanceTestBase.java | 59 ++++++ .../dsl/privacy/PrivateTransactions.java | 36 ++++ .../transaction/EeaJsonRpcRequestFactory.java | 10 ++ .../dsl/transaction/Transactions.java | 11 -- .../EeaGetTransactionCountTransaction.java | 47 +++++ .../dsl/transaction/eea/EeaTransactions.java | 5 + ...ry.java => PrivateTransactionBuilder.java} | 124 ++++++++----- ...loyPrivateSmartContractAcceptanceTest.java | 39 ++-- .../privacy/PrivacyClusterAcceptanceTest.java | 168 ++++++++++++++++-- .../privacy/PrivateAcceptanceTestBase.java | 130 -------------- .../privacy/PrivacyPrecompiledContract.java | 120 +++++++------ .../privacy/PrivateKeyValueStorage.java | 26 +-- .../privacy/PrivateTransactionHandler.java | 18 +- .../privacy/PrivateTransactionProcessor.java | 20 ++- .../PrivateTransactionHandlerTest.java | 4 +- .../jsonrpc/JsonRpcMethodsFactory.java | 13 +- .../privacy/EeaGetTransactionCount.java | 72 ++++++++ .../privacy/EeaGetTransactionReceipt.java | 28 ++- .../privacy/EeaSendRawTransaction.java | 12 +- .../internal/parameters/BlockParameter.java | 2 +- .../privacy/EeaGetTransactionCountTest.java | 82 +++++++++ .../privacy/EeaSendRawTransactionTest.java | 20 ++- 23 files changed, 720 insertions(+), 328 deletions(-) create mode 100644 acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivateAcceptanceTestBase.java create mode 100644 acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivateTransactions.java create mode 100644 acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaGetTransactionCountTransaction.java rename acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/{PrivateTransactionFactory.java => PrivateTransactionBuilder.java} (54%) delete mode 100644 acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivateAcceptanceTestBase.java create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCount.java create mode 100644 ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCountTest.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/ExpectValidPrivateContractDeployedReceipt.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/ExpectValidPrivateContractDeployedReceipt.java index 1ed02ec338..50503713f5 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/ExpectValidPrivateContractDeployedReceipt.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/ExpectValidPrivateContractDeployedReceipt.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.privacy; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Eea; import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; @@ -34,5 +35,6 @@ public void verify(final PantheonNode node, final String transactionHash) { getPrivateTransactionReceipt(node, transactionHash); assertEquals(contractAddress, privateTxReceipt.getContractAddress()); + assertNotEquals("0x", privateTxReceipt.getOutput()); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivateAcceptanceTestBase.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivateAcceptanceTestBase.java new file mode 100644 index 0000000000..58e6b6a721 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivateAcceptanceTestBase.java @@ -0,0 +1,59 @@ +/* + * 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.tests.acceptance.dsl.privacy; + +import tech.pegasys.orion.testutil.OrionTestHarness; +import tech.pegasys.orion.testutil.OrionTestHarnessFactory; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; +import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; +import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Eea; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaTransactions; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.PrivateTransactionBuilder; + +import java.io.IOException; + +import org.junit.ClassRule; +import org.junit.rules.TemporaryFolder; + +public class PrivateAcceptanceTestBase extends AcceptanceTestBase { + @ClassRule public static final TemporaryFolder privacy = new TemporaryFolder(); + + protected final Eea eea; + protected final PrivateTransactions privateTransactions; + protected static PrivateTransactionBuilder.Builder privateTransactionBuilder; + protected final PrivateTransactionVerifier privateTransactionVerifier; + + public PrivateAcceptanceTestBase() { + final EeaTransactions eeaTransactions = new EeaTransactions(); + + privateTransactions = new PrivateTransactions(); + eea = new Eea(eeaTransactions); + privateTransactionBuilder = PrivateTransactionBuilder.builder(); + privateTransactionVerifier = new PrivateTransactionVerifier(eea, transactions); + } + + protected static OrionTestHarness createEnclave( + final String pubKey, final String privKey, final String... othernode) throws Exception { + return OrionTestHarnessFactory.create(privacy.newFolder().toPath(), pubKey, privKey, othernode); + } + + protected static PrivacyParameters getPrivacyParameters(final OrionTestHarness testHarness) + throws IOException { + return new PrivacyParameters.Builder() + .setEnabled(true) + .setEnclaveUrl(testHarness.clientUrl()) + .setEnclavePublicKeyUsingFile(testHarness.getConfig().publicKeys().get(0).toFile()) + .setDataDir(privacy.newFolder().toPath()) + .build(); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivateTransactions.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivateTransactions.java new file mode 100644 index 0000000000..80e2d5e92d --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/privacy/PrivateTransactions.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.privacy; + +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaGetTransactionCountTransaction; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaSendRawTransactionTransaction; + +public class PrivateTransactions { + + public PrivateTransactions() {} + + public EeaSendRawTransactionTransaction deployPrivateSmartContract( + final String signedRawPrivateTransaction) { + return new EeaSendRawTransactionTransaction(signedRawPrivateTransaction); + } + + public EeaSendRawTransactionTransaction createPrivateRawTransaction( + final String signedRawPrivateTransaction) { + return new EeaSendRawTransactionTransaction(signedRawPrivateTransaction); + } + + public EeaGetTransactionCountTransaction getTransactionCount( + final String address, final String privacyGroupId) { + return new EeaGetTransactionCountTransaction(address, privacyGroupId); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/EeaJsonRpcRequestFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/EeaJsonRpcRequestFactory.java index d1e0b7cb82..34ae86dd9c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/EeaJsonRpcRequestFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/EeaJsonRpcRequestFactory.java @@ -19,6 +19,7 @@ import org.assertj.core.util.Lists; import org.web3j.protocol.Web3jService; import org.web3j.protocol.core.Request; +import org.web3j.protocol.core.methods.response.EthGetTransactionCount; public class EeaJsonRpcRequestFactory { @@ -45,4 +46,13 @@ public Request eeaGetTransactionReceipt( web3jService, PrivateTransactionReceiptResponse.class); } + + public Request eeaGetTransactionCount( + final String accountAddress, final String privacyGroupId) { + return new Request<>( + "eea_getTransactionCount", + Lists.newArrayList(accountAddress, privacyGroupId), + web3jService, + EthGetTransactionCount.class); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java index e3940de33b..dee24128e9 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java @@ -19,7 +19,6 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransactionBuilder; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransactionSet; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaGetTransactionReceiptTransaction; -import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaSendRawTransactionTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthGetTransactionCountTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthGetTransactionReceiptTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermAddAccountsToWhitelistTransaction; @@ -91,16 +90,6 @@ public TransferTransaction createTransfer( .build(); } - public EeaSendRawTransactionTransaction deployPrivateSmartContract( - final String signedRawPrivateTransaction) { - return new EeaSendRawTransactionTransaction(signedRawPrivateTransaction); - } - - public EeaSendRawTransactionTransaction createPrivateRawTransaction( - final String signedRawPrivateTransaction) { - return new EeaSendRawTransactionTransaction(signedRawPrivateTransaction); - } - public TransferTransactionSet createIncrementalTransfers( final Account sender, final Account recipient, final int etherAmount) { final List transfers = new ArrayList<>(); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaGetTransactionCountTransaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaGetTransactionCountTransaction.java new file mode 100644 index 0000000000..f84be92702 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaGetTransactionCountTransaction.java @@ -0,0 +1,47 @@ +/* + * 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.tests.acceptance.dsl.transaction.eea; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.JsonRequestFactories; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; +import java.math.BigInteger; + +import org.web3j.protocol.core.methods.response.EthGetTransactionCount; + +public class EeaGetTransactionCountTransaction implements Transaction { + + private final String accountAddress; + private String privacyGroupId; + + public EeaGetTransactionCountTransaction( + final String accountAddress, final String privacyGroupId) { + this.accountAddress = accountAddress; + this.privacyGroupId = privacyGroupId; + } + + @Override + public BigInteger execute(final JsonRequestFactories node) { + try { + EthGetTransactionCount result = + node.eea().eeaGetTransactionCount(accountAddress, privacyGroupId).send(); + assertThat(result).isNotNull(); + return result.getTransactionCount(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaTransactions.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaTransactions.java index 1856b318ab..94416718c7 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaTransactions.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/EeaTransactions.java @@ -17,4 +17,9 @@ public class EeaTransactions { public EeaGetTransactionReceiptTransaction getTransactionReceipt(final String transactionHash) { return new EeaGetTransactionReceiptTransaction(transactionHash); } + + public EeaGetTransactionCountTransaction getTransactionCount( + final String address, final String privacyGroupId) { + return new EeaGetTransactionCountTransaction(address, privacyGroupId); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/PrivateTransactionFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/PrivateTransactionBuilder.java similarity index 54% rename from acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/PrivateTransactionFactory.java rename to acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/PrivateTransactionBuilder.java index 847c2c5488..bb8def63d2 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/PrivateTransactionFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eea/PrivateTransactionBuilder.java @@ -18,11 +18,12 @@ import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Wei; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; +import tech.pegasys.pantheon.ethereum.rlp.RLP; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.List; -public class PrivateTransactionFactory { +public class PrivateTransactionBuilder { private static BytesValue EVENT_EMITTER_CONSTRUCTOR = BytesValue.fromHexString( @@ -51,56 +52,85 @@ public class PrivateTransactionFactory { private static BytesValue GET_FUNCTION_CALL = BytesValue.fromHexString("0x3fa4f245"); - public PrivateTransaction createContractTransaction( - final long nonce, - final Address from, - final BytesValue privateFrom, - final List privateFor, - final SECP256K1.KeyPair keypair) { - return privateTransaction( - nonce, null, EVENT_EMITTER_CONSTRUCTOR, from, privateFrom, privateFor, keypair); + public enum TransactionType { + CREATE_CONTRACT, + STORE, + GET } - public PrivateTransaction storeFunctionTransaction( - final long nonce, - final Address to, - final Address from, - final BytesValue privateFrom, - final List privateFor, - final SECP256K1.KeyPair keypair) { - return privateTransaction(nonce, to, SET_FUNCTION_CALL, from, privateFrom, privateFor, keypair); + public static PrivateTransactionBuilder.Builder builder() { + return new PrivateTransactionBuilder.Builder(); } - public PrivateTransaction getFunctionTransaction( - final long nonce, - final Address to, - final Address from, - final BytesValue privateFrom, - final List privateFor, - final SECP256K1.KeyPair keypair) { - return privateTransaction(nonce, to, GET_FUNCTION_CALL, from, privateFrom, privateFor, keypair); - } + public static class Builder { + long nonce; + Address from; + Address to; + BytesValue privateFrom; + List privateFor; + SECP256K1.KeyPair keyPair; + + public Builder nonce(final long nonce) { + this.nonce = nonce; + return this; + } + + public Builder from(final Address from) { + this.from = from; + return this; + } + + public Builder to(final Address to) { + this.to = to; + return this; + } + + public Builder privateFrom(final BytesValue privateFrom) { + this.privateFrom = privateFrom; + return this; + } + + public Builder privateFor(final List privateFor) { + this.privateFor = privateFor; + return this; + } + + public Builder keyPair(final SECP256K1.KeyPair keyPair) { + this.keyPair = keyPair; + return this; + } - public PrivateTransaction privateTransaction( - final long nonce, - final Address to, - final BytesValue payload, - final Address from, - final BytesValue privateFrom, - final List privateFor, - final SECP256K1.KeyPair keypair) { - return PrivateTransaction.builder() - .nonce(nonce) - .gasPrice(Wei.of(1000)) - .gasLimit(3000000) - .to(to) - .value(Wei.ZERO) - .payload(payload) - .sender(from) - .chainId(2018) - .privateFrom(privateFrom) - .privateFor(privateFor) - .restriction(BytesValue.wrap("restricted".getBytes(UTF_8))) - .signAndBuild(keypair); + public String build(final TransactionType type) { + BytesValue payload; + switch (type) { + case CREATE_CONTRACT: + payload = EVENT_EMITTER_CONSTRUCTOR; + break; + case STORE: + payload = SET_FUNCTION_CALL; + break; + case GET: + payload = GET_FUNCTION_CALL; + break; + default: + throw new IllegalStateException("Unexpected value: " + type); + } + return RLP.encode( + PrivateTransaction.builder() + .nonce(nonce) + .gasPrice(Wei.of(1000)) + .gasLimit(63992) + .to(to) + .value(Wei.ZERO) + .payload(payload) + .sender(from) + .chainId(2018) + .privateFrom(privateFrom) + .privateFor(privateFor) + .restriction(BytesValue.wrap("restricted".getBytes(UTF_8))) + .signAndBuild(keyPair) + ::writeTo) + .toString(); + } } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java index 5804f45eb5..08a1e4b9f2 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/DeployPrivateSmartContractAcceptanceTest.java @@ -13,11 +13,14 @@ package tech.pegasys.pantheon.tests.web3j.privacy; import static java.nio.charset.StandardCharsets.UTF_8; +import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor; import tech.pegasys.orion.testutil.OrionTestHarness; import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; +import tech.pegasys.pantheon.tests.acceptance.dsl.privacy.PrivateAcceptanceTestBase; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.PrivateTransactionBuilder.TransactionType; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.math.BigInteger; @@ -50,11 +53,11 @@ public void setUp() throws Exception { enclave = createEnclave("orion_key_0.pub", "orion_key_0.key"); minerNode = pantheon.createPrivateTransactionEnabledMinerNode( - "miner-node", getPrivacyParams(enclave), "key"); + "miner-node", getPrivacyParameters(enclave), "key"); cluster.start(minerNode); deployContract = - PrivateAcceptanceTestBase.builder() + privateTransactionBuilder .nonce(0) .from(minerNode.getAddress()) .to(null) @@ -64,7 +67,7 @@ public void setUp() throws Exception { .build(TransactionType.CREATE_CONTRACT); storeValue = - PrivateAcceptanceTestBase.builder() + privateTransactionBuilder .nonce(1) .from(minerNode.getAddress()) .to(CONTRACT_ADDRESS) @@ -74,7 +77,7 @@ public void setUp() throws Exception { .build(TransactionType.STORE); getValue = - PrivateAcceptanceTestBase.builder() + privateTransactionBuilder .nonce(2) .from(minerNode.getAddress()) .to(CONTRACT_ADDRESS) @@ -87,7 +90,7 @@ public void setUp() throws Exception { @Test public void deployingMustGiveValidReceipt() { final String transactionHash = - minerNode.execute(transactions.deployPrivateSmartContract(deployContract)); + minerNode.execute(privateTransactions.deployPrivateSmartContract(deployContract)); privateTransactionVerifier .validPrivateContractDeployed(CONTRACT_ADDRESS.toString()) @@ -96,10 +99,13 @@ public void deployingMustGiveValidReceipt() { @Test public void privateSmartContractMustEmitEvents() { - minerNode.execute(transactions.deployPrivateSmartContract(deployContract)); + String transactionHash = + minerNode.execute(privateTransactions.deployPrivateSmartContract(deployContract)); - final String transactionHash = - minerNode.execute(transactions.createPrivateRawTransaction(storeValue)); + waitForTransactionToBeMined(transactionHash); + + transactionHash = + minerNode.execute(privateTransactions.createPrivateRawTransaction(storeValue)); privateTransactionVerifier.validEventReturned("1000").verify(minerNode, transactionHash); } @@ -107,12 +113,17 @@ public void privateSmartContractMustEmitEvents() { @Test public void privateSmartContractMustReturnValues() { - minerNode.execute(transactions.deployPrivateSmartContract(deployContract)); + String transactionHash = + minerNode.execute(privateTransactions.deployPrivateSmartContract(deployContract)); - minerNode.execute(transactions.createPrivateRawTransaction(storeValue)); + waitForTransactionToBeMined(transactionHash); - final String transactionHash = - minerNode.execute(transactions.createPrivateRawTransaction(getValue)); + transactionHash = + minerNode.execute(privateTransactions.createPrivateRawTransaction(storeValue)); + + waitForTransactionToBeMined(transactionHash); + + transactionHash = minerNode.execute(privateTransactions.createPrivateRawTransaction(getValue)); privateTransactionVerifier.validOutputReturned("1000").verify(minerNode, transactionHash); } @@ -121,4 +132,8 @@ public void privateSmartContractMustReturnValues() { public void tearDown() { enclave.getOrion().stop(); } + + public void waitForTransactionToBeMined(final String transactionHash) { + waitFor(() -> minerNode.verify(eea.expectSuccessfulTransactionReceipt(transactionHash))); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivacyClusterAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivacyClusterAcceptanceTest.java index 11d4f6d8c0..d1bb8a71ea 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivacyClusterAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivacyClusterAcceptanceTest.java @@ -21,6 +21,9 @@ import tech.pegasys.pantheon.enclave.types.SendRequest; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; +import tech.pegasys.pantheon.tests.acceptance.dsl.privacy.PrivateAcceptanceTestBase; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.PrivateTransactionBuilder; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.PrivateTransactionBuilder.TransactionType; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.math.BigInteger; @@ -74,12 +77,13 @@ public void setUp() throws Exception { enclave3 = createEnclave("orion_key_2.pub", "orion_key_2.key", enclave2.nodeUrl()); node1 = pantheon.createPrivateTransactionEnabledMinerNode( - "node1", getPrivacyParams(enclave1), "key"); + "node1", getPrivacyParameters(enclave1), "key"); node2 = pantheon.createPrivateTransactionEnabledMinerNode( - "node2", getPrivacyParams(enclave2), "key1"); + "node2", getPrivacyParameters(enclave2), "key1"); node3 = - pantheon.createPrivateTransactionEnabledNode("node3", getPrivacyParams(enclave3), "key2"); + pantheon.createPrivateTransactionEnabledNode( + "node3", getPrivacyParameters(enclave3), "key2"); cluster.start(node1, node2, node3); @@ -97,8 +101,15 @@ public void setUp() throws Exception { "SGVsbG8sIFdvcmxkIQ==", enclave2.getPublicKeys().get(0), enclave3.getPublicKeys()); waitFor(() -> orion2.send(sendRequest2)); + // Wait for enclave 1 and enclave 3 to connect + Enclave orion3 = new Enclave(enclave3.clientUrl()); + SendRequest sendRequest3 = + new SendRequest( + "SGVsbG8sIFdvcmxkIQ==", enclave3.getPublicKeys().get(0), enclave1.getPublicKeys()); + waitFor(() -> orion3.send(sendRequest3)); + deployContractFromNode1 = - PrivateAcceptanceTestBase.builder() + PrivateTransactionBuilder.builder() .nonce(0) .from(node1.getAddress()) .to(null) @@ -108,7 +119,7 @@ public void setUp() throws Exception { .build(TransactionType.CREATE_CONTRACT); storeValueFromNode2 = - PrivateAcceptanceTestBase.builder() + PrivateTransactionBuilder.builder() .nonce(0) .from(node2.getAddress()) .to(CONTRACT_ADDRESS) @@ -118,7 +129,7 @@ public void setUp() throws Exception { .build(TransactionType.STORE); getValueFromNode2 = - PrivateAcceptanceTestBase.builder() + PrivateTransactionBuilder.builder() .nonce(1) .from(node2.getAddress()) .to(CONTRACT_ADDRESS) @@ -128,7 +139,7 @@ public void setUp() throws Exception { .build(TransactionType.GET); getValueFromNode3 = - PrivateAcceptanceTestBase.builder() + PrivateTransactionBuilder.builder() .nonce(0) .from(node3.getAddress()) .to(CONTRACT_ADDRESS) @@ -142,7 +153,7 @@ public void setUp() throws Exception { public void node2CanSeeContract() { String transactionHash = - node1.execute(transactions.deployPrivateSmartContract(deployContractFromNode1)); + node1.execute(privateTransactions.deployPrivateSmartContract(deployContractFromNode1)); privateTransactionVerifier .validPrivateContractDeployed(CONTRACT_ADDRESS.toString()) @@ -152,13 +163,14 @@ public void node2CanSeeContract() { @Test public void node2CanExecuteContract() { String transactionHash = - node1.execute(transactions.deployPrivateSmartContract(deployContractFromNode1)); + node1.execute(privateTransactions.deployPrivateSmartContract(deployContractFromNode1)); privateTransactionVerifier .validPrivateContractDeployed(CONTRACT_ADDRESS.toString()) .verify(node2, transactionHash); - transactionHash = node2.execute(transactions.createPrivateRawTransaction(storeValueFromNode2)); + transactionHash = + node2.execute(privateTransactions.createPrivateRawTransaction(storeValueFromNode2)); privateTransactionVerifier.validEventReturned("1000").verify(node1, transactionHash); } @@ -166,17 +178,19 @@ public void node2CanExecuteContract() { @Test public void node2CanSeePrivateTransactionReceipt() { String transactionHash = - node1.execute(transactions.deployPrivateSmartContract(deployContractFromNode1)); + node1.execute(privateTransactions.deployPrivateSmartContract(deployContractFromNode1)); privateTransactionVerifier .validPrivateContractDeployed(CONTRACT_ADDRESS.toString()) .verify(node2, transactionHash); - transactionHash = node2.execute(transactions.createPrivateRawTransaction(storeValueFromNode2)); + transactionHash = + node2.execute(privateTransactions.createPrivateRawTransaction(storeValueFromNode2)); privateTransactionVerifier.validEventReturned("1000").verify(node1, transactionHash); - transactionHash = node2.execute(transactions.createPrivateRawTransaction(getValueFromNode2)); + transactionHash = + node2.execute(privateTransactions.createPrivateRawTransaction(getValueFromNode2)); privateTransactionVerifier.validOutputReturned("1000").verify(node2, transactionHash); @@ -186,27 +200,27 @@ public void node2CanSeePrivateTransactionReceipt() { @Test public void node3CannotSeeContract() { final String transactionHash = - node1.execute(transactions.deployPrivateSmartContract(deployContractFromNode1)); + node1.execute(privateTransactions.deployPrivateSmartContract(deployContractFromNode1)); privateTransactionVerifier.noPrivateContractDeployed().verify(node3, transactionHash); } @Test public void node3CannotExecuteContract() { - node1.execute(transactions.deployPrivateSmartContract(deployContractFromNode1)); + node1.execute(privateTransactions.deployPrivateSmartContract(deployContractFromNode1)); final String transactionHash = - node3.execute(transactions.createPrivateRawTransaction(getValueFromNode3)); + node3.execute(privateTransactions.createPrivateRawTransaction(getValueFromNode3)); privateTransactionVerifier.noValidOutputReturned().verify(node3, transactionHash); } @Test(expected = RuntimeException.class) public void node2ExpectError() { - node1.execute(transactions.deployPrivateSmartContract(deployContractFromNode1)); + node1.execute(privateTransactions.deployPrivateSmartContract(deployContractFromNode1)); String invalidStoreValueFromNode2 = - PrivateAcceptanceTestBase.builder() + PrivateTransactionBuilder.builder() .nonce(0) .from(node2.getAddress()) .to(CONTRACT_ADDRESS) @@ -215,7 +229,123 @@ public void node2ExpectError() { .keyPair(keypair2) .build(TransactionType.STORE); - node2.execute(transactions.createPrivateRawTransaction(invalidStoreValueFromNode2)); + node2.execute(privateTransactions.createPrivateRawTransaction(invalidStoreValueFromNode2)); + } + + @Test + public void node1CanInteractWithMultiplePrivacyGroups() { + final String privacyGroup123 = + "0x393579496e2f4f59545a31784e3753694258314d64424a763942716b364f713766792b37585361496e79593d"; + final String privacyGroup12 = + "0x4479414f69462f796e70632b4a586132594147423062436974536c4f4d4e6d2b53686d422f374d364334773d"; + final String contractForABC = "0x1efee0ab2c1ec40c4b48410e5832d254c2eda0b0"; + + long nextNonce = getNonce(node1, privacyGroup123); + + final String deployContractFor123 = + PrivateTransactionBuilder.builder() + .nonce(nextNonce) + .from(node1.getAddress()) + .to(null) + .privateFrom(BytesValue.wrap(PUBLIC_KEY_1.getBytes(UTF_8))) + .privateFor( + Lists.newArrayList( + BytesValue.wrap(PUBLIC_KEY_2.getBytes(UTF_8)), + BytesValue.wrap(PUBLIC_KEY_3.getBytes(UTF_8)))) + .keyPair(keypair1) + .build(TransactionType.CREATE_CONTRACT); + + String transactionHash = + node1.execute(privateTransactions.deployPrivateSmartContract(deployContractFor123)); + + privateTransactionVerifier + .validPrivateContractDeployed(contractForABC) + .verify(node1, transactionHash); + + nextNonce = getNonce(node1, privacyGroup123); + + final String storeValueFor123 = + PrivateTransactionBuilder.builder() + .nonce(nextNonce) + .from(node1.getAddress()) + .to(Address.fromHexString(contractForABC)) + .privateFrom(BytesValue.wrap(PUBLIC_KEY_1.getBytes(UTF_8))) + .privateFor( + Lists.newArrayList( + BytesValue.wrap(PUBLIC_KEY_2.getBytes(UTF_8)), + BytesValue.wrap(PUBLIC_KEY_3.getBytes(UTF_8)))) + .keyPair(keypair1) + .build(TransactionType.STORE); + + transactionHash = + node1.execute(privateTransactions.createPrivateRawTransaction(storeValueFor123)); + + privateTransactionVerifier.validEventReturned("1000").verify(node1, transactionHash); + + nextNonce = getNonce(node1, privacyGroup12); + + final String storeValueFor12BeforeDeployingContract = + PrivateTransactionBuilder.builder() + .nonce(nextNonce) + .from(node1.getAddress()) + .to(Address.fromHexString(contractForABC)) + .privateFrom(BytesValue.wrap(PUBLIC_KEY_1.getBytes(UTF_8))) + .privateFor(Lists.newArrayList(BytesValue.wrap(PUBLIC_KEY_2.getBytes(UTF_8)))) + .keyPair(keypair1) + .build(TransactionType.STORE); + + transactionHash = + node1.execute( + privateTransactions.createPrivateRawTransaction( + storeValueFor12BeforeDeployingContract)); + + privateTransactionVerifier.noValidOutputReturned().verify(node1, transactionHash); + + nextNonce = getNonce(node1, privacyGroup12); + + final Address contractFor12 = + Address.privateContractAddress( + node1.getAddress(), nextNonce, BytesValue.fromHexString(privacyGroup12)); + + final String deployContractFor12 = + PrivateTransactionBuilder.builder() + .nonce(nextNonce) + .from(node1.getAddress()) + .to(null) + .privateFrom(BytesValue.wrap(PUBLIC_KEY_1.getBytes(UTF_8))) + .privateFor(Lists.newArrayList(BytesValue.wrap(PUBLIC_KEY_2.getBytes(UTF_8)))) + .keyPair(keypair1) + .build(TransactionType.CREATE_CONTRACT); + + transactionHash = + node1.execute(privateTransactions.deployPrivateSmartContract(deployContractFor12)); + + privateTransactionVerifier + .validPrivateContractDeployed(contractFor12.toString()) + .verify(node1, transactionHash); + + nextNonce = getNonce(node1, privacyGroup12); + + final String storeValueFor12 = + PrivateTransactionBuilder.builder() + .nonce(nextNonce) + .from(node1.getAddress()) + .to(contractFor12) + .privateFrom(BytesValue.wrap(PUBLIC_KEY_1.getBytes(UTF_8))) + .privateFor(Lists.newArrayList(BytesValue.wrap(PUBLIC_KEY_2.getBytes(UTF_8)))) + .keyPair(keypair1) + .build(TransactionType.STORE); + + transactionHash = + node1.execute(privateTransactions.createPrivateRawTransaction(storeValueFor12)); + + privateTransactionVerifier.validEventReturned("1000").verify(node1, transactionHash); + } + + private long getNonce(final PantheonNode node, final String privacyGroupId) { + return node.execute( + privateTransactions.getTransactionCount(node.getAddress().toString(), privacyGroupId)) + .longValue(); } @After diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivateAcceptanceTestBase.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivateAcceptanceTestBase.java deleted file mode 100644 index d912b73baa..0000000000 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/web3j/privacy/PrivateAcceptanceTestBase.java +++ /dev/null @@ -1,130 +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.tests.web3j.privacy; - -import tech.pegasys.orion.testutil.OrionTestHarness; -import tech.pegasys.orion.testutil.OrionTestHarnessFactory; -import tech.pegasys.pantheon.crypto.SECP256K1; -import tech.pegasys.pantheon.ethereum.core.Address; -import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; -import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; -import tech.pegasys.pantheon.ethereum.rlp.RLP; -import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; -import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Eea; -import tech.pegasys.pantheon.tests.acceptance.dsl.privacy.PrivateTransactionVerifier; -import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaTransactions; -import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.PrivateTransactionFactory; -import tech.pegasys.pantheon.util.bytes.BytesValue; - -import java.io.IOException; -import java.util.List; - -import org.junit.ClassRule; -import org.junit.rules.TemporaryFolder; - -public class PrivateAcceptanceTestBase extends AcceptanceTestBase { - @ClassRule public static final TemporaryFolder privacy = new TemporaryFolder(); - - protected final Eea eea; - protected static PrivateTransactionFactory privateTx; - protected final PrivateTransactionVerifier privateTransactionVerifier; - - PrivateAcceptanceTestBase() { - final EeaTransactions eeaTransactions = new EeaTransactions(); - eea = new Eea(eeaTransactions); - privateTx = new PrivateTransactionFactory(); - privateTransactionVerifier = new PrivateTransactionVerifier(eea, transactions); - } - - public static PrivateAcceptanceTestBase.Builder builder() { - return new PrivateAcceptanceTestBase.Builder(); - } - - static OrionTestHarness createEnclave( - final String pubKey, final String privKey, final String... othernode) throws Exception { - return OrionTestHarnessFactory.create(privacy.newFolder().toPath(), pubKey, privKey, othernode); - } - - enum TransactionType { - CREATE_CONTRACT, - STORE, - GET - } - - public static class Builder { - long nonce; - Address from; - Address to; - BytesValue privateFrom; - List privateFor; - SECP256K1.KeyPair keyPair; - - public Builder nonce(final long nonce) { - this.nonce = nonce; - return this; - } - - public Builder from(final Address from) { - this.from = from; - return this; - } - - public Builder to(final Address to) { - this.to = to; - return this; - } - - public Builder privateFrom(final BytesValue privateFrom) { - this.privateFrom = privateFrom; - return this; - } - - public Builder privateFor(final List privateFor) { - this.privateFor = privateFor; - return this; - } - - public Builder keyPair(final SECP256K1.KeyPair keyPair) { - this.keyPair = keyPair; - return this; - } - - public String build(final TransactionType type) { - PrivateTransaction pTx; - switch (type) { - case CREATE_CONTRACT: - pTx = privateTx.createContractTransaction(nonce, from, privateFrom, privateFor, keyPair); - break; - case STORE: - pTx = - privateTx.storeFunctionTransaction(nonce, to, from, privateFrom, privateFor, keyPair); - break; - case GET: - pTx = privateTx.getFunctionTransaction(nonce, to, from, privateFrom, privateFor, keyPair); - break; - default: - throw new IllegalStateException("Unexpected value: " + type); - } - return RLP.encode(pTx::writeTo).toString(); - } - } - - static PrivacyParameters getPrivacyParams(final OrionTestHarness testHarness) throws IOException { - return new PrivacyParameters.Builder() - .setEnabled(true) - .setEnclaveUrl(testHarness.clientUrl()) - .setEnclavePublicKeyUsingFile(testHarness.getConfig().publicKeys().get(0).toFile()) - .setDataDir(privacy.newFolder().toPath()) - .build(); - } -} diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java index b4f4eb86ac..1e9aad00ea 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java @@ -20,11 +20,12 @@ import tech.pegasys.pantheon.enclave.types.ReceiveResponse; import tech.pegasys.pantheon.ethereum.core.Gas; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.LogSeries; import tech.pegasys.pantheon.ethereum.core.MutableWorldState; import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; import tech.pegasys.pantheon.ethereum.core.WorldUpdater; +import tech.pegasys.pantheon.ethereum.debug.TraceOptions; import tech.pegasys.pantheon.ethereum.mainnet.AbstractPrecompiledContract; -import tech.pegasys.pantheon.ethereum.mainnet.TransactionProcessor; import tech.pegasys.pantheon.ethereum.privacy.PrivateStateStorage; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransaction; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionProcessor; @@ -32,9 +33,9 @@ import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPInput; import tech.pegasys.pantheon.ethereum.rlp.RLP; import tech.pegasys.pantheon.ethereum.trie.MerklePatriciaTrie; +import tech.pegasys.pantheon.ethereum.vm.DebugOperationTracer; import tech.pegasys.pantheon.ethereum.vm.GasCalculator; import tech.pegasys.pantheon.ethereum.vm.MessageFrame; -import tech.pegasys.pantheon.ethereum.vm.OperationTracer; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.util.bytes.Bytes32; import tech.pegasys.pantheon.util.bytes.BytesValue; @@ -95,67 +96,70 @@ public Gas gasRequirement(final BytesValue input) { @Override public BytesValue compute(final BytesValue input, final MessageFrame messageFrame) { + final String key = new String(input.extractArray(), UTF_8); + final ReceiveRequest receiveRequest = new ReceiveRequest(key, enclavePublicKey); + + ReceiveResponse receiveResponse; try { - String key = new String(input.extractArray(), UTF_8); - ReceiveRequest receiveRequest = new ReceiveRequest(key, enclavePublicKey); - ReceiveResponse receiveResponse = enclave.receive(receiveRequest); - - final BytesValueRLPInput bytesValueRLPInput = - new BytesValueRLPInput( - BytesValue.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); - - PrivateTransaction privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput); - - WorldUpdater publicWorldState = messageFrame.getWorldState(); - - BytesValue privacyGroupId = - BytesValue.wrap(receiveResponse.getPrivacyGroupId().getBytes(Charsets.UTF_8)); - // get the last world state root hash - or create a new one - Hash lastRootHash = - privateStateStorage.getPrivateAccountState(privacyGroupId).orElse(EMPTY_ROOT_HASH); - MutableWorldState disposablePrivateState = - privateWorldStateArchive.getMutable(lastRootHash).get(); - - WorldUpdater privateWorldStateUpdater = disposablePrivateState.updater(); - - TransactionProcessor.Result result = - privateTransactionProcessor.processTransaction( - messageFrame.getBlockchain(), - publicWorldState, - privateWorldStateUpdater, - messageFrame.getBlockHeader(), - privateTransaction, - messageFrame.getMiningBeneficiary(), - OperationTracer.NO_TRACING, - messageFrame.getBlockHashLookup(), - privacyGroupId); - - if (result.isInvalid() || !result.isSuccessful()) { - throw new Exception("Unable to process the private transaction"); - } + receiveResponse = enclave.receive(receiveRequest); + } catch (IOException e) { + LOG.debug("Enclave probably does not have private transaction with key {}.", key, e); + return BytesValue.EMPTY; + } + + final BytesValueRLPInput bytesValueRLPInput = + new BytesValueRLPInput( + BytesValue.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); + + final PrivateTransaction privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput); + + final WorldUpdater publicWorldState = messageFrame.getWorldState(); + + final BytesValue privacyGroupId = + BytesValue.wrap(receiveResponse.getPrivacyGroupId().getBytes(Charsets.UTF_8)); + // get the last world state root hash - or create a new one + final Hash lastRootHash = + privateStateStorage.getPrivateAccountState(privacyGroupId).orElse(EMPTY_ROOT_HASH); + + final MutableWorldState disposablePrivateState = + privateWorldStateArchive.getMutable(lastRootHash).get(); + + final WorldUpdater privateWorldStateUpdater = disposablePrivateState.updater(); + final PrivateTransactionProcessor.Result result = + privateTransactionProcessor.processTransaction( + messageFrame.getBlockchain(), + publicWorldState, + privateWorldStateUpdater, + messageFrame.getBlockHeader(), + privateTransaction, + messageFrame.getMiningBeneficiary(), + new DebugOperationTracer(TraceOptions.DEFAULT), + messageFrame.getBlockHashLookup(), + privacyGroupId); + + if (result.isInvalid() || !result.isSuccessful()) { + LOG.error("Unable to process the private transaction: {}", result.getValidationResult()); + return BytesValue.EMPTY; + } + + if (messageFrame.isPersistingState()) { + privateWorldStateUpdater.commit(); + disposablePrivateState.persist(); - if (messageFrame.isPersistingState()) { - privateWorldStateUpdater.commit(); - disposablePrivateState.persist(); - PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater(); - privateStateUpdater.putPrivateAccountState( - privacyGroupId, disposablePrivateState.rootHash()); - privateStateUpdater.commit(); + final PrivateStateStorage.Updater privateStateUpdater = privateStateStorage.updater(); + privateStateUpdater.putPrivateAccountState(privacyGroupId, disposablePrivateState.rootHash()); + privateStateUpdater.commit(); - Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo)); - PrivateTransactionStorage.Updater privateUpdater = privateTransactionStorage.updater(); + final Bytes32 txHash = keccak256(RLP.encode(privateTransaction::writeTo)); + final PrivateTransactionStorage.Updater privateUpdater = privateTransactionStorage.updater(); + final LogSeries logs = result.getLogs(); + if (!logs.isEmpty()) { privateUpdater.putTransactionLogs(txHash, result.getLogs()); - privateUpdater.putTransactionResult(txHash, result.getOutput()); - privateUpdater.commit(); } - - return result.getOutput(); - } catch (IOException e) { - LOG.fatal("Enclave threw an unhandled exception.", e); - return BytesValue.EMPTY; - } catch (Exception e) { - LOG.fatal(e.getMessage()); - return BytesValue.EMPTY; + privateUpdater.putTransactionResult(txHash, result.getOutput()); + privateUpdater.commit(); } + + return result.getOutput(); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateKeyValueStorage.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateKeyValueStorage.java index 3858a0f9b3..38ceedd4bd 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateKeyValueStorage.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateKeyValueStorage.java @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.privacy; +import static java.nio.charset.StandardCharsets.UTF_8; + import tech.pegasys.pantheon.ethereum.core.Log; import tech.pegasys.pantheon.ethereum.core.LogSeries; import tech.pegasys.pantheon.ethereum.rlp.RLP; @@ -27,8 +29,8 @@ public class PrivateKeyValueStorage implements PrivateTransactionStorage { private final KeyValueStorage keyValueStorage; - private static final BytesValue LOGS_PREFIX = BytesValue.of(1); - private static final BytesValue EVENTS_PREFIX = BytesValue.of(2); + private static final BytesValue EVENTS_KEY_SUFFIX = BytesValue.of("EVENTS".getBytes(UTF_8)); + private static final BytesValue OUTPUT_KEY_SUFFIX = BytesValue.of("OUTPUT".getBytes(UTF_8)); public PrivateKeyValueStorage(final KeyValueStorage keyValueStorage) { this.keyValueStorage = keyValueStorage; @@ -36,26 +38,26 @@ public PrivateKeyValueStorage(final KeyValueStorage keyValueStorage) { @Override public Optional> getEvents(final Bytes32 transactionHash) { - return get(LOGS_PREFIX, transactionHash).map(this::rlpDecodeLog); + return get(transactionHash, EVENTS_KEY_SUFFIX).map(this::rlpDecodeLog); } @Override public Optional getOutput(final Bytes32 transactionHash) { - return get(EVENTS_PREFIX, transactionHash); + return get(transactionHash, OUTPUT_KEY_SUFFIX); } @Override public boolean isPrivateStateAvailable(final Bytes32 transactionHash) { - return get(LOGS_PREFIX, transactionHash).isPresent() - || get(EVENTS_PREFIX, transactionHash).isPresent(); + return get(transactionHash, EVENTS_KEY_SUFFIX).isPresent() + || get(transactionHash, OUTPUT_KEY_SUFFIX).isPresent(); } private List rlpDecodeLog(final BytesValue bytes) { return RLP.input(bytes).readList(Log::readFrom); } - private Optional get(final BytesValue prefix, final BytesValue key) { - return keyValueStorage.get(BytesValues.concatenate(prefix, key)); + private Optional get(final BytesValue key, final BytesValue keySuffix) { + return keyValueStorage.get(BytesValues.concatenate(key, keySuffix)); } @Override @@ -74,19 +76,19 @@ private Updater(final KeyValueStorage.Transaction transaction) { @Override public PrivateTransactionStorage.Updater putTransactionLogs( final Bytes32 transactionHash, final LogSeries logs) { - set(LOGS_PREFIX, transactionHash, RLP.encode(logs::writeTo)); + set(transactionHash, EVENTS_KEY_SUFFIX, RLP.encode(logs::writeTo)); return this; } @Override public PrivateTransactionStorage.Updater putTransactionResult( final Bytes32 transactionHash, final BytesValue events) { - set(EVENTS_PREFIX, transactionHash, events); + set(transactionHash, OUTPUT_KEY_SUFFIX, events); return this; } - private void set(final BytesValue prefix, final BytesValue key, final BytesValue value) { - transaction.put(BytesValues.concatenate(prefix, key), value); + private void set(final BytesValue key, final BytesValue keySuffix, final BytesValue value) { + transaction.put(BytesValues.concatenate(key, keySuffix), value); } @Override diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java index cb0a124f91..cf68e0fe9d 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandler.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.util.Base64; import java.util.List; +import java.util.function.Supplier; import java.util.stream.Collectors; import com.google.common.base.Charsets; @@ -56,8 +57,10 @@ public PrivateTransactionHandler( this.nodeKeyPair = nodeKeyPair; } - public Transaction handle(final PrivateTransaction privateTransaction) throws IOException { - LOG.trace("Handling private transaction"); + public Transaction handle( + final PrivateTransaction privateTransaction, final Supplier nonceSupplier) + throws IOException { + LOG.trace("Handling private transaction {}", privateTransaction.toString()); final SendRequest sendRequest = createSendRequest(privateTransaction); final SendResponse sendResponse; try { @@ -68,7 +71,8 @@ public Transaction handle(final PrivateTransaction privateTransaction) throws IO throw e; } - return createPrivacyMarkerTransaction(sendResponse.getKey(), privateTransaction); + return createPrivacyMarkerTransactionWithNonce( + sendResponse.getKey(), privateTransaction, nonceSupplier.get()); } private SendRequest createSendRequest(final PrivateTransaction privateTransaction) { @@ -91,11 +95,13 @@ private SendRequest createSendRequest(final PrivateTransaction privateTransactio privateFor); } - private Transaction createPrivacyMarkerTransaction( - final String transactionEnclaveKey, final PrivateTransaction privateTransaction) { + private Transaction createPrivacyMarkerTransactionWithNonce( + final String transactionEnclaveKey, + final PrivateTransaction privateTransaction, + final Long nonce) { return Transaction.builder() - .nonce(privateTransaction.getNonce()) + .nonce(nonce) .gasPrice(privateTransaction.getGasPrice()) .gasLimit(privateTransaction.getGasLimit()) .to(privacyPrecompileAddress) diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java index 9676a42ed8..16b9f64ed2 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionProcessor.java @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.privacy; +import static tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.NONCE_TOO_LOW; + import tech.pegasys.pantheon.ethereum.chain.Blockchain; import tech.pegasys.pantheon.ethereum.core.Account; import tech.pegasys.pantheon.ethereum.core.Address; @@ -161,6 +163,15 @@ public Result processTransaction( ? maybePrivateSender : privateWorldState.createAccount(senderAddress, 0, Wei.ZERO); + if (transaction.getNonce() < sender.getNonce()) { + return Result.invalid( + ValidationResult.invalid( + NONCE_TOO_LOW, + String.format( + "transaction nonce %s below sender account nonce %s", + transaction.getNonce(), sender.getNonce()))); + } + final long previousNonce = sender.incrementNonce(); LOG.trace( "Incremented private sender {} nonce ({} -> {})", @@ -172,7 +183,14 @@ public Result processTransaction( final Deque messageFrameStack = new ArrayDeque<>(); if (transaction.isContractCreation()) { final Address privateContractAddress = - Address.privateContractAddress(senderAddress, sender.getNonce() - 1L, privacyGroupId); + Address.privateContractAddress(senderAddress, previousNonce, privacyGroupId); + + LOG.debug( + "Calculated contract address {} from sender {} with nonce {} and privacy group {}", + privateContractAddress.toString(), + senderAddress, + previousNonce, + privacyGroupId.toString()); initialFrame = MessageFrame.builder() diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java index 2ca11c629e..9169f34afe 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/privacy/PrivateTransactionHandlerTest.java @@ -106,7 +106,7 @@ public void setUp() throws IOException { @Test public void validTransactionThroughHandler() throws IOException { final Transaction transactionResponse = - privateTransactionHandler.handle(VALID_PRIVATE_TRANSACTION); + privateTransactionHandler.handle(VALID_PRIVATE_TRANSACTION, () -> 0L); assertThat(transactionResponse.contractAddress()) .isEqualTo(PUBLIC_TRANSACTION.contractAddress()); @@ -118,6 +118,6 @@ public void validTransactionThroughHandler() throws IOException { @Test(expected = IOException.class) public void enclaveIsDownWhileHandling() throws IOException { - brokenPrivateTransactionHandler.handle(VALID_PRIVATE_TRANSACTION); + brokenPrivateTransactionHandler.handle(VALID_PRIVATE_TRANSACTION, () -> 0L); } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java index 8f5c7c93b7..235789915e 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java @@ -88,6 +88,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermReloadPermissionsFromFile; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromWhitelist; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermRemoveNodesFromWhitelist; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.EeaGetTransactionCount; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.EeaGetTransactionReceipt; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.privacy.EeaSendRawTransaction; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; @@ -312,17 +313,19 @@ blockchainQueries, new TransactionTracer(blockReplay), parameter), new AdminPeers(p2pNetwork)); } if (rpcApis.contains(RpcApis.EEA)) { - addMethods( - enabledMethods, - new EeaSendRawTransaction( - new PrivateTransactionHandler(privacyParameters), transactionPool, parameter)); addMethods( enabledMethods, new EeaGetTransactionReceipt( blockchainQueries, new Enclave(privacyParameters.getEnclaveUri()), parameter, - privacyParameters)); + privacyParameters), + new EeaSendRawTransaction( + blockchainQueries, + new PrivateTransactionHandler(privacyParameters), + transactionPool, + parameter), + new EeaGetTransactionCount(parameter, privacyParameters)); } return enabledMethods; } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCount.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCount.java new file mode 100644 index 0000000000..5b1fa05065 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCount.java @@ -0,0 +1,72 @@ +/* + * 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.internal.methods.privacy; + +import tech.pegasys.pantheon.ethereum.core.Account; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.MutableWorldState; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +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.JsonRpcResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.Quantity; +import tech.pegasys.pantheon.ethereum.privacy.PrivateStateStorage; +import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +public class EeaGetTransactionCount implements JsonRpcMethod { + + private final JsonRpcParameter parameters; + private final PrivateStateStorage privateStateStorage; + private final WorldStateArchive privateWorldStateArchive; + + public EeaGetTransactionCount( + final JsonRpcParameter parameters, final PrivacyParameters privacyParameters) { + this.parameters = parameters; + this.privateStateStorage = privacyParameters.getPrivateStateStorage(); + this.privateWorldStateArchive = privacyParameters.getPrivateWorldStateArchive(); + } + + @Override + public String getName() { + return "eea_getTransactionCount"; + } + + @Override + public JsonRpcResponse response(final JsonRpcRequest request) { + if (request.getParamLength() != 2) { + return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS); + } + + final Address address = parameters.required(request.getParams(), 0, Address.class); + final String privacyGroupId = parameters.required(request.getParams(), 1, String.class); + + return privateStateStorage + .getPrivateAccountState(BytesValue.fromHexString(privacyGroupId)) + .map( + lastRootHash -> { + final MutableWorldState privateWorldState = + privateWorldStateArchive.getMutable(lastRootHash).get(); + + final Account maybePrivateSender = privateWorldState.get(address); + + return new JsonRpcSuccessResponse( + request.getId(), Quantity.create(maybePrivateSender.getNonce())); + }) + .orElse(new JsonRpcSuccessResponse(request.getId(), Quantity.create(0))); + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java index 1c41b81138..2a294bb04d 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionReceipt.java @@ -94,8 +94,15 @@ public JsonRpcResponse response(final JsonRpcRequest request) { PrivateTransaction privateTransaction; String privacyGroupId; try { - privateTransaction = getTransactionFromEnclave(transaction, publicKey); - privacyGroupId = getPrivacyGroupIdFromEnclave(transaction, publicKey); + ReceiveResponse receiveResponse = getReceiveResponseFromEnclave(transaction, publicKey); + LOG.trace("Received transaction information from Enclave"); + + final BytesValueRLPInput bytesValueRLPInput = + new BytesValueRLPInput( + BytesValue.wrap(Base64.getDecoder().decode(receiveResponse.getPayload())), false); + + privateTransaction = PrivateTransaction.readFrom(bytesValueRLPInput); + privacyGroupId = receiveResponse.getPrivacyGroupId(); } catch (Exception e) { LOG.error("Failed to fetch transaction from Enclave with error " + e.getMessage()); return new JsonRpcSuccessResponse( @@ -151,26 +158,13 @@ public JsonRpcResponse response(final JsonRpcRequest request) { return new JsonRpcSuccessResponse(request.getId(), result); } - private String getPrivacyGroupIdFromEnclave(final Transaction transaction, final String publicKey) - throws IOException { - LOG.trace("Fetching transaction information from Enclave"); - final ReceiveRequest enclaveRequest = - new ReceiveRequest(new String(transaction.getPayload().extractArray(), UTF_8), publicKey); - ReceiveResponse enclaveResponse = enclave.receive(enclaveRequest); - LOG.trace("Received transaction information from Enclave"); - return enclaveResponse.getPrivacyGroupId(); - } - - private PrivateTransaction getTransactionFromEnclave( + private ReceiveResponse getReceiveResponseFromEnclave( final Transaction transaction, final String publicKey) throws IOException { LOG.trace("Fetching transaction information from Enclave"); final ReceiveRequest enclaveRequest = new ReceiveRequest(new String(transaction.getPayload().extractArray(), UTF_8), publicKey); ReceiveResponse enclaveResponse = enclave.receive(enclaveRequest); - final BytesValueRLPInput bytesValueRLPInput = - new BytesValueRLPInput( - BytesValue.wrap(Base64.getDecoder().decode(enclaveResponse.getPayload())), false); LOG.trace("Received transaction information from Enclave"); - return PrivateTransaction.readFrom(bytesValueRLPInput); + return enclaveResponse; } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java index 2015528cca..c6b6ebc012 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransaction.java @@ -15,12 +15,14 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcErrorConverter.convertTransactionInvalidReason; +import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.exception.InvalidJsonRpcRequestException; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; 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.JsonRpcResponse; @@ -42,14 +44,17 @@ public class EeaSendRawTransaction implements JsonRpcMethod { private static final Logger LOG = LogManager.getLogger(); + private final BlockchainQueries blockchain; private final PrivateTransactionHandler privateTransactionHandler; private final TransactionPool transactionPool; private final JsonRpcParameter parameters; public EeaSendRawTransaction( + final BlockchainQueries blockchain, final PrivateTransactionHandler privateTransactionHandler, final TransactionPool transactionPool, final JsonRpcParameter parameters) { + this.blockchain = blockchain; this.privateTransactionHandler = privateTransactionHandler; this.transactionPool = transactionPool; this.parameters = parameters; @@ -101,10 +106,15 @@ public JsonRpcResponse response(final JsonRpcRequest request) { request.getId(), convertTransactionInvalidReason(errorReason))); } + protected long getNonce(final Address address) { + return blockchain.getTransactionCount(address, blockchain.headBlockNumber()); + } + private Transaction handlePrivateTransaction(final PrivateTransaction privateTransaction) throws InvalidJsonRpcRequestException { try { - return privateTransactionHandler.handle(privateTransaction); + return privateTransactionHandler.handle( + privateTransaction, () -> getNonce(privateTransaction.getSender())); } catch (final IOException e) { throw new InvalidJsonRpcRequestException("Unable to handle private transaction", e); } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/BlockParameter.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/BlockParameter.java index 2918b0f4d3..dfb1ddb08d 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/BlockParameter.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/BlockParameter.java @@ -70,6 +70,6 @@ private enum BlockParameterType { EARLIEST, LATEST, PENDING, - NUMERIC; + NUMERIC } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCountTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCountTest.java new file mode 100644 index 0000000000..6816bfd7cd --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaGetTransactionCountTest.java @@ -0,0 +1,82 @@ +/* + * 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.internal.methods.privacy; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.MutableAccount; +import tech.pegasys.pantheon.ethereum.core.MutableWorldState; +import tech.pegasys.pantheon.ethereum.core.PrivacyParameters; +import tech.pegasys.pantheon.ethereum.core.Wei; +import tech.pegasys.pantheon.ethereum.core.WorldUpdater; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.privacy.PrivateStateStorage; +import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.util.Optional; + +import org.junit.Test; + +public class EeaGetTransactionCountTest { + + private final JsonRpcParameter parameters = new JsonRpcParameter(); + private final PrivateStateStorage privacyStateStorage = mock(PrivateStateStorage.class); + private final WorldStateArchive privateWorldStateArchive = mock(WorldStateArchive.class); + private final PrivacyParameters privacyParameters = mock(PrivacyParameters.class); + private final MutableWorldState mutableWorldState = mock(MutableWorldState.class); + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount mutableAccount = mock(MutableAccount.class); + private final Hash lastRootHash = mock(Hash.class); + private final BytesValue privacyGroupId = BytesValue.wrap("0x123".getBytes(UTF_8)); + + private final Address senderAddress = + Address.fromHexString("0x627306090abab3a6e1400e9345bc60c78a8bef57"); + private final long NONCE = 5; + + @Test + public void verifyTransactionCount() { + when(mutableWorldState.updater()).thenReturn(worldUpdater); + when(mutableAccount.getNonce()).thenReturn(NONCE); + // Create account in storage with given nonce + when(worldUpdater.createAccount(senderAddress, NONCE, Wei.ZERO)).thenReturn(mutableAccount); + when(privateWorldStateArchive.getMutable()).thenReturn(mutableWorldState); + + when(privacyParameters.getPrivateStateStorage()).thenReturn(privacyStateStorage); + when(privacyParameters.getPrivateWorldStateArchive()).thenReturn(privateWorldStateArchive); + + when(privacyStateStorage.getPrivateAccountState(privacyGroupId)) + .thenReturn(Optional.of(lastRootHash)); + when(privateWorldStateArchive.getMutable(lastRootHash)) + .thenReturn(Optional.of(mutableWorldState)); + when(mutableWorldState.get(senderAddress)).thenReturn(mutableAccount); + + final EeaGetTransactionCount eeaGetTransactionCount = + new EeaGetTransactionCount(parameters, privacyParameters); + + final Object[] params = new Object[] {senderAddress, privacyGroupId.toString()}; + final JsonRpcRequest request = new JsonRpcRequest("1", "eea_getTransactionCount", params); + + final JsonRpcSuccessResponse response = + (JsonRpcSuccessResponse) eeaGetTransactionCount.response(request); + + assertEquals("0x5", response.getResult()); + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java index 8f025e843c..d241df9a47 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/privacy/EeaSendRawTransactionTest.java @@ -25,6 +25,7 @@ import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; 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.JsonRpcResponse; @@ -38,6 +39,7 @@ import java.io.IOException; import java.math.BigInteger; import java.util.Optional; +import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; @@ -45,6 +47,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +@SuppressWarnings("unchecked") @RunWith(MockitoJUnitRunner.class) public class EeaSendRawTransactionTest { @@ -94,9 +97,12 @@ public class EeaSendRawTransactionTest { @Mock private PrivateTransactionHandler privateTxHandler; + @Mock private BlockchainQueries blockchainQueries; + @Before public void before() { - method = new EeaSendRawTransaction(privateTxHandler, transactionPool, parameter); + method = + new EeaSendRawTransaction(blockchainQueries, privateTxHandler, transactionPool, parameter); } @Test @@ -174,7 +180,8 @@ public void valueNonZeroTransaction() { public void validTransactionIsSentToTransactionPool() throws IOException { when(parameter.required(any(Object[].class), anyInt(), any())) .thenReturn(VALID_PRIVATE_TRANSACTION_RLP); - when(privateTxHandler.handle(any(PrivateTransaction.class))).thenReturn(PUBLIC_TRANSACTION); + when(privateTxHandler.handle(any(PrivateTransaction.class), any(Supplier.class))) + .thenReturn(PUBLIC_TRANSACTION); when(transactionPool.addLocalTransaction(any(Transaction.class))) .thenReturn(ValidationResult.valid()); @@ -189,7 +196,7 @@ public void validTransactionIsSentToTransactionPool() throws IOException { final JsonRpcResponse actualResponse = method.response(request); assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); - verify(privateTxHandler).handle(any(PrivateTransaction.class)); + verify(privateTxHandler).handle(any(PrivateTransaction.class), any(Supplier.class)); verify(transactionPool).addLocalTransaction(any(Transaction.class)); } @@ -197,7 +204,7 @@ public void validTransactionIsSentToTransactionPool() throws IOException { public void invalidTransactionIsSentToTransactionPool() throws IOException { when(parameter.required(any(Object[].class), anyInt(), any())) .thenReturn(VALID_PRIVATE_TRANSACTION_RLP); - when(privateTxHandler.handle(any(PrivateTransaction.class))) + when(privateTxHandler.handle(any(PrivateTransaction.class), any(Supplier.class))) .thenThrow(new IOException("enclave failed to execute")); final JsonRpcRequest request = @@ -261,7 +268,8 @@ private void verifyErrorForInvalidTransaction( throws IOException { when(parameter.required(any(Object[].class), anyInt(), any())) .thenReturn(VALID_PRIVATE_TRANSACTION_RLP); - when(privateTxHandler.handle(any(PrivateTransaction.class))).thenReturn(PUBLIC_TRANSACTION); + when(privateTxHandler.handle(any(PrivateTransaction.class), any(Supplier.class))) + .thenReturn(PUBLIC_TRANSACTION); when(transactionPool.addLocalTransaction(any(Transaction.class))) .thenReturn(ValidationResult.invalid(transactionInvalidReason)); @@ -275,7 +283,7 @@ private void verifyErrorForInvalidTransaction( final JsonRpcResponse actualResponse = method.response(request); assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); - verify(privateTxHandler).handle(any(PrivateTransaction.class)); + verify(privateTxHandler).handle(any(PrivateTransaction.class), any(Supplier.class)); verify(transactionPool).addLocalTransaction(any(Transaction.class)); } From a701e8cbc3b31b00fc3a354e811362541b7ed8b8 Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Fri, 26 Apr 2019 14:38:10 +0200 Subject: [PATCH 08/25] removed unstable fast sync note in documentation (#1341) --- docs/Reference/Pantheon-CLI-Syntax.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/Reference/Pantheon-CLI-Syntax.md b/docs/Reference/Pantheon-CLI-Syntax.md index 3e1e919f63..457a8b4b10 100644 --- a/docs/Reference/Pantheon-CLI-Syntax.md +++ b/docs/Reference/Pantheon-CLI-Syntax.md @@ -1059,10 +1059,6 @@ Print version information and exit. ## Fast Sync Options -!!! important - Support for fast sync is currently experimental. Fast sync is in active development. The fast sync options are available - but hidden on the command line. - ### sync-mode ```bash tab="Syntax" From b17c2ea50d73f7e8903d4aae3e5e198ec4941ae9 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Sat, 27 Apr 2019 08:01:52 +1000 Subject: [PATCH 09/25] Cache current chain head info (#1335) Store the header for the current chain head and total difficulty to avoid RocksDB lookups when requesting those common values. Also uses that cache to avoid a database lookup when checking if a block has already been imported if the block's parent is the current chain head. --- .../chain/DefaultMutableBlockchain.java | 40 ++++++++++++------ .../eth/sync/BlockPropagationManagerTest.java | 42 +++++++++++++++++-- .../fullsync/FullSyncChainDownloaderTest.java | 25 +++++------ .../eth/sync/fullsync/IncrementerTest.java | 3 +- 4 files changed, 80 insertions(+), 30 deletions(-) diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/DefaultMutableBlockchain.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/DefaultMutableBlockchain.java index 72971d2a3b..102e5802ea 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/DefaultMutableBlockchain.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/DefaultMutableBlockchain.java @@ -48,6 +48,9 @@ public class DefaultMutableBlockchain implements MutableBlockchain { private final Subscribers blockAddedObservers = new Subscribers<>(); + private volatile BlockHeader chainHeader; + private volatile UInt256 totalDifficulty; + public DefaultMutableBlockchain( final Block genesisBlock, final BlockchainStorage blockchainStorage, @@ -56,6 +59,10 @@ public DefaultMutableBlockchain( this.blockchainStorage = blockchainStorage; this.setGenesis(genesisBlock); + final Hash chainHead = blockchainStorage.getChainHead().get(); + chainHeader = blockchainStorage.getBlockHeader(chainHead).get(); + totalDifficulty = blockchainStorage.getTotalDifficulty(chainHead).get(); + metricsSystem.createGauge( MetricCategory.BLOCKCHAIN, "height", @@ -72,25 +79,22 @@ public DefaultMutableBlockchain( @Override public ChainHead getChainHead() { - return blockchainStorage - .getChainHead() - .flatMap(h -> blockchainStorage.getTotalDifficulty(h).map(td -> new ChainHead(h, td))) - .get(); + return new ChainHead(chainHeader.getHash(), totalDifficulty); } @Override public Hash getChainHeadHash() { - return blockchainStorage.getChainHead().get(); + return chainHeader.getHash(); } @Override public long getChainHeadBlockNumber() { - // Head should always be set, so we can call get() - return blockchainStorage - .getChainHead() - .flatMap(blockchainStorage::getBlockHeader) - .map(BlockHeader::getNumber) - .get(); + return chainHeader.getNumber(); + } + + @Override + public BlockHeader getChainHeadHeader() { + return chainHeader; } @Override @@ -171,6 +175,10 @@ private BlockAddedEvent appendBlockHelper( final BlockAddedEvent blockAddedEvent = updateCanonicalChainData(updater, block, td); updater.commit(); + if (blockAddedEvent.isNewCanonicalHead()) { + chainHeader = block.getHeader(); + totalDifficulty = td; + } return blockAddedEvent; } @@ -368,11 +376,17 @@ protected void setGenesis(final Block genesisBlock) { } } - protected boolean blockIsAlreadyTracked(final Block block) { + private boolean blockIsAlreadyTracked(final Block block) { + if (block.getHeader().getParentHash().equals(chainHeader.getHash())) { + // If this block builds on our chain head it would have a higher TD and be the chain head + // but since it isn't we mustn't have imported it yet. + // Saves a db read for the most common case + return false; + } return blockchainStorage.getBlockHeader(block.getHash()).isPresent(); } - protected boolean blockIsConnected(final Block block) { + private boolean blockIsConnected(final Block block) { return blockchainStorage.getBlockHeader(block.getHeader().getParentHash()).isPresent(); } 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 cd0919b093..e068ae5d1f 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 @@ -14,6 +14,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -26,6 +29,7 @@ import tech.pegasys.pantheon.ethereum.core.Block; import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator; import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator.BlockOptions; +import tech.pegasys.pantheon.ethereum.core.BlockImporter; import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; import tech.pegasys.pantheon.ethereum.eth.manager.EthMessages; import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers; @@ -41,6 +45,7 @@ import tech.pegasys.pantheon.ethereum.eth.sync.state.PendingBlocks; import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; +import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.util.uint.UInt256; @@ -77,7 +82,7 @@ public static void setupSuite() { @Before public void setup() { blockchainUtil = BlockchainSetupUtil.forTesting(); - blockchain = spy(blockchainUtil.getBlockchain()); + blockchain = blockchainUtil.getBlockchain(); protocolSchedule = blockchainUtil.getProtocolSchedule(); final ProtocolContext tempProtocolContext = blockchainUtil.getProtocolContext(); protocolContext = @@ -290,6 +295,22 @@ public void importsMixedOutOfOrderMessages() { @Test public void handlesDuplicateAnnouncements() { + final ProtocolSchedule stubProtocolSchedule = spy(protocolSchedule); + final ProtocolSpec stubProtocolSpec = spy(protocolSchedule.getByBlockNumber(2)); + final BlockImporter stubBlockImporter = spy(stubProtocolSpec.getBlockImporter()); + doReturn(stubProtocolSpec).when(stubProtocolSchedule).getByBlockNumber(anyLong()); + doReturn(stubBlockImporter).when(stubProtocolSpec).getBlockImporter(); + final BlockPropagationManager blockPropagationManager = + new BlockPropagationManager<>( + syncConfig, + stubProtocolSchedule, + protocolContext, + ethProtocolManager.ethContext(), + syncState, + pendingBlocks, + metricsSystem, + blockBroadcaster); + blockchainUtil.importFirstBlocks(2); final Block nextBlock = blockchainUtil.getBlock(2); @@ -320,11 +341,26 @@ public void handlesDuplicateAnnouncements() { peer.respondWhile(responder, peer::hasOutstandingRequests); assertThat(blockchain.contains(nextBlock.getHash())).isTrue(); - verify(blockchain, times(1)).appendBlock(any(), any()); + verify(stubBlockImporter, times(1)).importBlock(eq(protocolContext), eq(nextBlock), any()); } @Test public void handlesPendingDuplicateAnnouncements() { + final ProtocolSchedule stubProtocolSchedule = spy(protocolSchedule); + final ProtocolSpec stubProtocolSpec = spy(protocolSchedule.getByBlockNumber(2)); + final BlockImporter stubBlockImporter = spy(stubProtocolSpec.getBlockImporter()); + doReturn(stubProtocolSpec).when(stubProtocolSchedule).getByBlockNumber(anyLong()); + doReturn(stubBlockImporter).when(stubProtocolSpec).getBlockImporter(); + final BlockPropagationManager blockPropagationManager = + new BlockPropagationManager<>( + syncConfig, + stubProtocolSchedule, + protocolContext, + ethProtocolManager.ethContext(), + syncState, + pendingBlocks, + metricsSystem, + blockBroadcaster); blockchainUtil.importFirstBlocks(2); final Block nextBlock = blockchainUtil.getBlock(2); @@ -352,7 +388,7 @@ public void handlesPendingDuplicateAnnouncements() { peer.respondWhile(responder, peer::hasOutstandingRequests); assertThat(blockchain.contains(nextBlock.getHash())).isTrue(); - verify(blockchain, times(1)).appendBlock(any(), any()); + verify(stubBlockImporter, times(1)).importBlock(eq(protocolContext), eq(nextBlock), any()); } @Test diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java index 3f0a3b20e0..12ce030ae2 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java @@ -13,13 +13,8 @@ package tech.pegasys.pantheon.ethereum.eth.sync.fullsync; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.Assumptions.assumeThatObject; import static org.awaitility.Awaitility.await; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain; import tech.pegasys.pantheon.ethereum.ProtocolContext; @@ -58,6 +53,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Function; +import org.awaitility.Awaitility; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -96,7 +92,7 @@ public static Object[][] params() { public void setupTest() { gen = new BlockDataGenerator(); localBlockchainSetup = BlockchainSetupUtil.forTesting(); - localBlockchain = spy(localBlockchainSetup.getBlockchain()); + localBlockchain = localBlockchainSetup.getBlockchain(); otherBlockchainSetup = BlockchainSetupUtil.forTesting(); otherBlockchain = otherBlockchainSetup.getBlockchain(); @@ -229,7 +225,6 @@ public void doesNotSyncToWorseChain() { peer.respondWhileOtherThreadsWork(responder, peer::hasOutstandingRequests); assertThat(syncState.syncTarget()).isNotPresent(); - verify(localBlockchain, times(0)).appendBlock(any(), any()); } @Test @@ -565,12 +560,18 @@ public void requestsCheckpointsFromSyncTarget() { assertThat(syncState.syncTarget()).isPresent(); assertThat(syncState.syncTarget().get().peer()).isEqualTo(bestPeer.getEthPeer()); - int count = 0; while (localBlockchain.getChainHeadBlockNumber() < bestPeerChainHead) { - if (count > 10_000) { - fail("Did not reach chain head soon enough"); - } - count++; + // Wait until there is a request to respond to (or we reached chain head). + // If we don't get a new request within 30 seconds the test will fail because we've probably + // stalled. + Awaitility.await() + .atMost(30, TimeUnit.SECONDS) + .until( + () -> + bestPeer.hasOutstandingRequests() + || otherPeers.stream().anyMatch(RespondingEthPeer::hasOutstandingRequests) + || localBlockchain.getChainHeadBlockNumber() >= bestPeerChainHead); + // Check that any requests for checkpoint headers are only sent to the best peer final long checkpointRequestsToOtherPeers = otherPeers.stream() diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/IncrementerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/IncrementerTest.java index 8698647a47..91b35ee0ba 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/IncrementerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fullsync/IncrementerTest.java @@ -13,7 +13,6 @@ package tech.pegasys.pantheon.ethereum.eth.sync.fullsync; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.spy; import tech.pegasys.pantheon.ethereum.ProtocolContext; import tech.pegasys.pantheon.ethereum.chain.Blockchain; @@ -62,7 +61,7 @@ public void setUp() { metricsSystem = PrometheusMetricsSystem.init(metricsConfiguration); final BlockchainSetupUtil localBlockchainSetup = BlockchainSetupUtil.forTesting(); - localBlockchain = spy(localBlockchainSetup.getBlockchain()); + localBlockchain = localBlockchainSetup.getBlockchain(); final BlockchainSetupUtil otherBlockchainSetup = BlockchainSetupUtil.forTesting(); otherBlockchain = otherBlockchainSetup.getBlockchain(); From 7d69eac6ce2dac27f2f7afa38c4c032aeb17a1d1 Mon Sep 17 00:00:00 2001 From: MadelineMurray <43356962+MadelineMurray@users.noreply.github.com> Date: Sat, 27 Apr 2019 10:03:03 +1000 Subject: [PATCH 10/25] Added net_services (#1306) --- docs/Reference/JSON-RPC-API-Methods.md | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/docs/Reference/JSON-RPC-API-Methods.md b/docs/Reference/JSON-RPC-API-Methods.md index b3f2630fad..1d88588dd0 100644 --- a/docs/Reference/JSON-RPC-API-Methods.md +++ b/docs/Reference/JSON-RPC-API-Methods.md @@ -399,6 +399,48 @@ None "result" : "enode://6a63160d0ccef5e4986d270937c6c8d60a9a4d3b25471cda960900d037c61988ea14da67f69dbfb3497c465d0de1f001bb95598f74b68a39a5156a608c42fa1b@127.0.0.1:30303" } ``` + +### net_services + +Returns enabled services (for example, `jsonrpc`) and the host and port for each service. + +**Parameters** + +None + +**Returns** + +`result` : *objects* - Enabled services + +!!! example + ```bash tab="curl HTTP request" + curl -X POST --data '{"jsonrpc":"2.0","method":"net_services","params":[],"id":1}' http://127.0.0.1:8545 + ``` + + ```bash tab="wscat WS request" + {"jsonrpc":"2.0","method":"net_services","params":[],"id":1} + ``` + + ```json tab="JSON result" + { + "jsonrpc": "2.0", + "id": 1, + "result": { + "jsonrpc": { + "host": "127.0.0.1", + "port": "8545" + }, + "p2p" : { + "host" : "127.0.0.1", + "port" : "30303" + }, + "metrics" : { + "host": "127.0.0.1", + "port": "9545" + } + } + } + ``` ## Eth Methods From 48a8875914516a2eaba67a5c55e3bd61c5273c15 Mon Sep 17 00:00:00 2001 From: MadelineMurray <43356962+MadelineMurray@users.noreply.github.com> Date: Sat, 27 Apr 2019 10:16:30 +1000 Subject: [PATCH 11/25] Updated for returning false (#1309) --- docs/Using-Pantheon/RPC-PubSub.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/Using-Pantheon/RPC-PubSub.md b/docs/Using-Pantheon/RPC-PubSub.md index 165008a2ce..b4979fc476 100644 --- a/docs/Using-Pantheon/RPC-PubSub.md +++ b/docs/Using-Pantheon/RPC-PubSub.md @@ -276,10 +276,8 @@ transaction notifications for the same transaction. Use the `syncing` parameter with `eth_subscribe` to be notified about synchronization progress. -The synchronizing subscription returns an object indicating the synchronization progress. - -Use the [`--ws-refresh-delay` option](../Reference/Pantheon-CLI-Syntax.md#ws-refresh-delay) to configure how -often the synchronizing subscription returns an object. The default is 5000 milliseconds. +When behind the chain head, the synchronizing subscription returns an object indicating the synchronization +progress. When fully synchronized, returns false. !!!example To subscribe to synchronizing notifications: @@ -292,7 +290,7 @@ often the synchronizing subscription returns an object. The default is 5000 mill {"jsonrpc":"2.0","id":1,"result":"0x4"} ``` - Example notification: + Example notification while synchronizing: ```json { @@ -308,7 +306,20 @@ often the synchronizing subscription returns an object. The default is 5000 mill } } ``` - + + Example notification when synchronized with chain head: + + ```json + { + "jsonrpc":"2.0", + "method":"eth_subscription", + "params":{ + "subscription":"0x4", + "result":false + } + } + ``` + ## Unsubscribing Use the [subscription ID](#subscription-id) with `eth_unsubscribe` to cancel a subscription. Only the From 2f2829b2fdba2965d86d5985659b62546e9f5a5c Mon Sep 17 00:00:00 2001 From: MadelineMurray <43356962+MadelineMurray@users.noreply.github.com> Date: Sat, 27 Apr 2019 14:57:41 +1000 Subject: [PATCH 12/25] Added sending private transactions initial content (#1301) --- docs/Privacy/Creating-Sending-Private-Transactions.md | 11 +++++++++++ mkdocs.yml | 1 + 2 files changed, 12 insertions(+) create mode 100644 docs/Privacy/Creating-Sending-Private-Transactions.md diff --git a/docs/Privacy/Creating-Sending-Private-Transactions.md b/docs/Privacy/Creating-Sending-Private-Transactions.md new file mode 100644 index 0000000000..5d6afa7a33 --- /dev/null +++ b/docs/Privacy/Creating-Sending-Private-Transactions.md @@ -0,0 +1,11 @@ +description: Creating and sending private transactions + + +# Creating and Sending Private Transactions + +The [EEA JavaScript library](https://github.com/PegaSysEng/eeajs) is provided to create and send signed +RLP-encoded private transactions. + +!!! note + Private transactions either deploy contracts or call contract functions. + Ether transfers cannot be a private transaction. diff --git a/mkdocs.yml b/mkdocs.yml index befe345662..5c2a5064a0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -89,6 +89,7 @@ nav: - Overview: Privacy/Privacy-Overview.md - Processing Private Transactions: Privacy/Private-Transaction-Processing.md - Configuring a Privacy-Enabled Network: Privacy/Configuring-Privacy.md + - Creating and Sending Private Transactions: Privacy/Creating-Sending-Private-Transactions.md - Permissions: - Overview: Permissions/Permissioning-Overview.md - Local Permissions: Permissions/Local-Permissioning.md From df1ea7980446838c5d3dbd3e8c827defa3fc1ef1 Mon Sep 17 00:00:00 2001 From: MadelineMurray <43356962+MadelineMurray@users.noreply.github.com> Date: Sun, 28 Apr 2019 12:28:26 +1000 Subject: [PATCH 13/25] Upated Docker image to indicate it doesn't run on Windows (#1346) --- docs/Getting-Started/Run-Docker-Image.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/Getting-Started/Run-Docker-Image.md b/docs/Getting-Started/Run-Docker-Image.md index fbcc7be235..e2cc82d6e8 100644 --- a/docs/Getting-Started/Run-Docker-Image.md +++ b/docs/Getting-Started/Run-Docker-Image.md @@ -9,7 +9,13 @@ Use this Docker image to run a single Pantheon node without installing Pantheon. ## Prerequisites -To run Pantheon from the Docker image, you must have [Docker](https://docs.docker.com/install/) installed. +* [Docker](https://docs.docker.com/install/) + +* MacOS or Linux + + !!! important + The Docker image does not run on Windows. + ## Quickstart From 088c58981fb6be45984764b264a5966de5ee7383 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Mon, 29 Apr 2019 05:13:08 +1000 Subject: [PATCH 14/25] [PAN-2573] include static nodes in permissioning logic (#1339) * combine bootnodes and staticNodes and pass the combined collection when building permissioning config; renamed error code that specifically called out bootnodes --- .../PermRemoveNodesFromWhitelist.java | 4 ++-- .../internal/response/JsonRpcError.java | 3 ++- .../PermRemoveNodesFromWhitelistTest.java | 4 ++-- ...odeLocalConfigPermissioningController.java | 14 ++++++------- .../NodePermissioningControllerFactory.java | 6 +++--- .../WhitelistOperationResult.java | 2 +- .../SyncStatusNodePermissioningProvider.java | 10 +++++----- ...ocalConfigPermissioningControllerTest.java | 2 +- .../tech/pegasys/pantheon/RunnerBuilder.java | 18 +++++++++++------ .../pegasys/pantheon/cli/PantheonCommand.java | 4 ++-- .../tech/pegasys/pantheon/RunnerTest.java | 20 +++++++++++++++++++ 11 files changed, 57 insertions(+), 30 deletions(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelist.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelist.java index 51244180c0..4b60e29678 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelist.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelist.java @@ -70,9 +70,9 @@ public JsonRpcResponse response(final JsonRpcRequest req) { return new JsonRpcErrorResponse(req.getId(), JsonRpcError.WHITELIST_PERSIST_FAILURE); case ERROR_WHITELIST_FILE_SYNC: return new JsonRpcErrorResponse(req.getId(), JsonRpcError.WHITELIST_FILE_SYNC); - case ERROR_BOOTNODE_CANNOT_BE_REMOVED: + case ERROR_FIXED_NODE_CANNOT_BE_REMOVED: return new JsonRpcErrorResponse( - req.getId(), JsonRpcError.NODE_WHITELIST_BOOTNODE_CANNOT_BE_REMOVED); + req.getId(), JsonRpcError.NODE_WHITELIST_FIXED_NODE_CANNOT_BE_REMOVED); default: throw new Exception(); } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index 769650ee41..d79b946954 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -70,7 +70,8 @@ public enum JsonRpcError { NODE_WHITELIST_DUPLICATED_ENTRY(-32000, "Request contains duplicate nodes"), NODE_WHITELIST_EXISTING_ENTRY(-32000, "Cannot add an existing node to whitelist"), NODE_WHITELIST_MISSING_ENTRY(-32000, "Cannot remove an absent node from whitelist"), - NODE_WHITELIST_BOOTNODE_CANNOT_BE_REMOVED(-32000, "Cannot remove a bootnode from whitelist"), + NODE_WHITELIST_FIXED_NODE_CANNOT_BE_REMOVED( + -32000, "Cannot remove a fixed node (bootnode or static node) from whitelist"), // Permissioning/persistence errors WHITELIST_PERSIST_FAILURE( diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelistTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelistTest.java index e1f99834f8..4e30735891 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelistTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelistTest.java @@ -174,11 +174,11 @@ public void shouldReturnCantRemoveBootnodeWhenRemovingBootnode() { final JsonRpcRequest request = buildRequest(Lists.newArrayList(enode1)); final JsonRpcResponse expected = new JsonRpcErrorResponse( - request.getId(), JsonRpcError.NODE_WHITELIST_BOOTNODE_CANNOT_BE_REMOVED); + request.getId(), JsonRpcError.NODE_WHITELIST_FIXED_NODE_CANNOT_BE_REMOVED); when(nodeLocalConfigPermissioningController.removeNodes(any())) .thenReturn( - new NodesWhitelistResult(WhitelistOperationResult.ERROR_BOOTNODE_CANNOT_BE_REMOVED)); + new NodesWhitelistResult(WhitelistOperationResult.ERROR_FIXED_NODE_CANNOT_BE_REMOVED)); final JsonRpcResponse actual = method.response(request); diff --git a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningController.java b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningController.java index 656f466852..0d3bc076a1 100644 --- a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningController.java +++ b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningController.java @@ -37,7 +37,7 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning private static final Logger LOG = LogManager.getLogger(); private LocalPermissioningConfiguration configuration; - private final List bootnodes; + private final List fixedNodes; private final EnodeURL selfEnode; private final List nodesWhitelist = new ArrayList<>(); private final WhitelistPersistor whitelistPersistor; @@ -46,22 +46,22 @@ public class NodeLocalConfigPermissioningController implements NodePermissioning public NodeLocalConfigPermissioningController( final LocalPermissioningConfiguration permissioningConfiguration, - final List bootnodes, + final List fixedNodes, final EnodeURL selfEnode) { this( permissioningConfiguration, - bootnodes, + fixedNodes, selfEnode, new WhitelistPersistor(permissioningConfiguration.getNodePermissioningConfigFilePath())); } public NodeLocalConfigPermissioningController( final LocalPermissioningConfiguration configuration, - final List bootnodes, + final List fixedNodes, final EnodeURL selfEnode, final WhitelistPersistor whitelistPersistor) { this.configuration = configuration; - this.bootnodes = bootnodes; + this.fixedNodes = fixedNodes; this.selfEnode = selfEnode; this.whitelistPersistor = whitelistPersistor; readNodesFromConfig(configuration); @@ -115,9 +115,9 @@ public NodesWhitelistResult removeNodes(final List enodeURLs) { final List peers = enodeURLs.stream().map(EnodeURL::fromString).collect(Collectors.toList()); - boolean anyBootnode = peers.stream().anyMatch(bootnodes::contains); + boolean anyBootnode = peers.stream().anyMatch(fixedNodes::contains); if (anyBootnode) { - return new NodesWhitelistResult(WhitelistOperationResult.ERROR_BOOTNODE_CANNOT_BE_REMOVED); + return new NodesWhitelistResult(WhitelistOperationResult.ERROR_FIXED_NODE_CANNOT_BE_REMOVED); } for (EnodeURL peer : peers) { diff --git a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodePermissioningControllerFactory.java b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodePermissioningControllerFactory.java index 457bdbf04d..16b70b8551 100644 --- a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodePermissioningControllerFactory.java +++ b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/NodePermissioningControllerFactory.java @@ -29,7 +29,7 @@ public class NodePermissioningControllerFactory { public NodePermissioningController create( final PermissioningConfiguration permissioningConfiguration, final Synchronizer synchronizer, - final Collection bootnodes, + final Collection fixedNodes, final EnodeURL selfEnode, final TransactionSimulator transactionSimulator) { @@ -42,7 +42,7 @@ public NodePermissioningController create( if (localPermissioningConfiguration.isNodeWhitelistEnabled()) { NodeLocalConfigPermissioningController localProvider = new NodeLocalConfigPermissioningController( - localPermissioningConfiguration, new ArrayList<>(bootnodes), selfEnode); + localPermissioningConfiguration, new ArrayList<>(fixedNodes), selfEnode); providers.add(localProvider); } } @@ -59,7 +59,7 @@ public NodePermissioningController create( } final SyncStatusNodePermissioningProvider syncStatusProvider = - new SyncStatusNodePermissioningProvider(synchronizer, bootnodes); + new SyncStatusNodePermissioningProvider(synchronizer, fixedNodes); syncStatusProviderOptional = Optional.of(syncStatusProvider); } else { syncStatusProviderOptional = Optional.empty(); diff --git a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/WhitelistOperationResult.java b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/WhitelistOperationResult.java index 6ada2a5bae..ecf32ef42d 100644 --- a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/WhitelistOperationResult.java +++ b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/WhitelistOperationResult.java @@ -19,7 +19,7 @@ public enum WhitelistOperationResult { ERROR_EXISTING_ENTRY, ERROR_INVALID_ENTRY, ERROR_ABSENT_ENTRY, - ERROR_BOOTNODE_CANNOT_BE_REMOVED, + ERROR_FIXED_NODE_CANNOT_BE_REMOVED, ERROR_WHITELIST_PERSIST_FAIL, ERROR_WHITELIST_FILE_SYNC } diff --git a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/node/provider/SyncStatusNodePermissioningProvider.java b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/node/provider/SyncStatusNodePermissioningProvider.java index 6ca25926bc..9cf4e1a000 100644 --- a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/node/provider/SyncStatusNodePermissioningProvider.java +++ b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/node/provider/SyncStatusNodePermissioningProvider.java @@ -27,18 +27,18 @@ public class SyncStatusNodePermissioningProvider implements NodePermissioningProvider { private final Synchronizer synchronizer; - private final Collection bootnodes = new HashSet<>(); + private final Collection fixedNodes = new HashSet<>(); private OptionalLong syncStatusObserverId; private boolean hasReachedSync = false; private Optional hasReachedSyncCallback = Optional.empty(); public SyncStatusNodePermissioningProvider( - final Synchronizer synchronizer, final Collection bootnodes) { + final Synchronizer synchronizer, final Collection fixedNodes) { checkNotNull(synchronizer); this.synchronizer = synchronizer; long id = this.synchronizer.observeSyncStatus(this::handleSyncStatusUpdate); this.syncStatusObserverId = OptionalLong.of(id); - this.bootnodes.addAll(bootnodes); + this.fixedNodes.addAll(fixedNodes); } private void handleSyncStatusUpdate(final SyncStatus syncStatus) { @@ -74,7 +74,7 @@ private synchronized void runCallback() { } /** - * Before reaching a sync'd state, the node will only be allowed to talk to its bootnodes + * Before reaching a sync'd state, the node will only be allowed to talk to its fixedNodes * (outgoing connections). After reaching a sync'd state, it is expected that other providers will * check the permissions (most likely the smart contract based provider). That's why we always * return true after reaching a sync'd state. @@ -89,7 +89,7 @@ public boolean isPermitted(final EnodeURL sourceEnode, final EnodeURL destinatio if (hasReachedSync) { return true; } else { - return bootnodes.contains(destinationEnode); + return fixedNodes.contains(destinationEnode); } } diff --git a/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java b/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java index 0bdede436a..d621dcf856 100644 --- a/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java +++ b/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/NodeLocalConfigPermissioningControllerTest.java @@ -353,7 +353,7 @@ public void whenRemovingNodeDoesNotRemoveShouldNotifyWhitelistModifiedSubscriber @Test public void whenRemovingBootnodeShouldReturnRemoveBootnodeError() { NodesWhitelistResult expected = - new NodesWhitelistResult(WhitelistOperationResult.ERROR_BOOTNODE_CANNOT_BE_REMOVED); + new NodesWhitelistResult(WhitelistOperationResult.ERROR_FIXED_NODE_CANNOT_BE_REMOVED); bootnodesList.add(EnodeURL.fromString(enode1)); controller.addNodes(Lists.newArrayList(enode1, enode2)); diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java index 02379df5e9..162b212f70 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java @@ -73,6 +73,7 @@ import java.net.URI; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -81,6 +82,7 @@ import java.util.Set; import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import io.vertx.core.Vertx; @@ -408,15 +410,19 @@ private Optional buildNodePermissioningController( final List bootnodesAsEnodeURLs, final Synchronizer synchronizer, final TransactionSimulator transactionSimulator) { + Collection fixedNodes = getFixedNodes(bootnodesAsEnodeURLs, staticNodes); return permissioningConfiguration.map( config -> new NodePermissioningControllerFactory() - .create( - config, - synchronizer, - bootnodesAsEnodeURLs, - getSelfEnode(), - transactionSimulator)); + .create(config, synchronizer, fixedNodes, getSelfEnode(), transactionSimulator)); + } + + @VisibleForTesting + public static Collection getFixedNodes( + final Collection someFixedNodes, final Collection moreFixedNodes) { + Collection fixedNodes = new ArrayList<>(someFixedNodes); + fixedNodes.addAll(moreFixedNodes); + return fixedNodes; } private FilterManager createFilterManager( 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 a795d5863a..f528fe580b 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -1219,8 +1219,8 @@ public PantheonExceptionHandler exceptionHandler() { } private Set loadStaticNodes() throws IOException { - final String staticNodesFilname = "static-nodes.json"; - final Path staticNodesPath = dataDir().resolve(staticNodesFilname); + final String staticNodesFilename = "static-nodes.json"; + final Path staticNodesPath = dataDir().resolve(staticNodesFilename); return StaticNodesParser.fromPath(staticNodesPath); } diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java index ff87eea409..3c4d078c86 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java @@ -52,6 +52,8 @@ import java.io.IOException; import java.net.InetAddress; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -78,6 +80,24 @@ public final class RunnerTest { @Rule public final TemporaryFolder temp = new TemporaryFolder(); + @Test + public void getFixedNodes() { + EnodeURL staticNode = + EnodeURL.fromString( + "enode://8f4b88336cc40ef2516d8b27df812e007fb2384a61e93635f1899051311344f3dcdbb49a4fe49a79f66d2f589a9f282e8cc4f1d7381e8ef7e4fcc6b0db578c77@127.0.0.1:30301"); + EnodeURL bootnode = + EnodeURL.fromString( + "enode://8f4b88336cc40ef2516d8b27df812e007fb2384a61e93635f1899051311344f3dcdbb49a4fe49a79f66d2f589a9f282e8cc4f1d7381e8ef7e4fcc6b0db578c77@127.0.0.1:30302"); + final List bootnodes = new ArrayList(); + bootnodes.add(bootnode); + Collection staticNodes = new ArrayList(); + staticNodes.add(staticNode); + Collection fixedNodes = RunnerBuilder.getFixedNodes(bootnodes, staticNodes); + assertThat(fixedNodes).containsExactlyInAnyOrder(staticNode, bootnode); + // bootnodes should be unchanged + assertThat(bootnodes).containsExactly(bootnode); + } + @Test public void fullSyncFromGenesis() throws Exception { syncFromGenesis(SyncMode.FULL); From 46626ebb3c0042a59b5ae0c60fcd4433770d3c4b Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 29 Apr 2019 07:08:51 +1000 Subject: [PATCH 15/25] Add test for hasAvailableRequestCapacity. --- .../ethereum/eth/manager/EthPeerTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeerTest.java index 0792bd8759..cc86ea2c4d 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeerTest.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.eth.manager; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -81,6 +82,38 @@ public void getNodeDataStream() throws PeerNotConnected { messageStream(getStream, targetMessage, otherMessage); } + @Test + public void shouldHaveAvailableCapacityUntilOutstandingRequestLimitIsReached() + throws PeerNotConnected { + final EthPeer peer = createPeer(); + assertThat(peer.hasAvailableRequestCapacity()).isTrue(); + assertThat(peer.outstandingRequests()).isEqualTo(0); + + peer.getBodies(asList(gen.hash(), gen.hash())); + assertThat(peer.hasAvailableRequestCapacity()).isTrue(); + assertThat(peer.outstandingRequests()).isEqualTo(1); + + peer.getReceipts(asList(gen.hash(), gen.hash())); + assertThat(peer.hasAvailableRequestCapacity()).isTrue(); + assertThat(peer.outstandingRequests()).isEqualTo(2); + + peer.getNodeData(asList(gen.hash(), gen.hash())); + assertThat(peer.hasAvailableRequestCapacity()).isTrue(); + assertThat(peer.outstandingRequests()).isEqualTo(3); + + peer.getHeadersByHash(gen.hash(), 4, 1, false); + assertThat(peer.hasAvailableRequestCapacity()).isTrue(); + assertThat(peer.outstandingRequests()).isEqualTo(4); + + peer.getHeadersByNumber(1, 1, 1, false); + assertThat(peer.hasAvailableRequestCapacity()).isFalse(); + assertThat(peer.outstandingRequests()).isEqualTo(5); + + peer.dispatch(new EthMessage(peer, BlockBodiesMessage.create(emptyList()))); + assertThat(peer.hasAvailableRequestCapacity()).isTrue(); + assertThat(peer.outstandingRequests()).isEqualTo(4); + } + @Test public void closeStreamsOnPeerDisconnect() throws PeerNotConnected { final EthPeer peer = createPeer(); From adb03fb7d9a46c4a7c5405c60e6aa03d461b39b1 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 29 Apr 2019 08:54:30 +1000 Subject: [PATCH 16/25] Add more tests. --- .../ethereum/eth/manager/EthPeers.java | 11 +- .../ethereum/eth/manager/EthPeersTest.java | 183 +++++++++++++++++- 2 files changed, 190 insertions(+), 4 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java index 3202deb101..db6c407b04 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java @@ -66,6 +66,7 @@ void registerDisconnect(final PeerConnection connection) { disconnectCallbacks.forEach(callback -> callback.onDisconnect(peer)); peer.handleDisconnect(); } + checkPendingConnections(); } public EthPeer peer(final PeerConnection peerConnection) { @@ -87,9 +88,13 @@ public PendingPeerRequest executePeerRequest( public void dispatchMessage(final EthPeer peer, final EthMessage ethMessage) { peer.dispatch(ethMessage); if (peer.hasAvailableRequestCapacity()) { - synchronized (this) { - pendingRequests.removeIf(PendingPeerRequest::attemptExecution); - } + checkPendingConnections(); + } + } + + private void checkPendingConnections() { + synchronized (this) { + pendingRequests.removeIf(PendingPeerRequest::attemptExecution); } } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java index 9e889da142..4c16f39ed5 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java @@ -12,20 +12,42 @@ */ package tech.pegasys.pantheon.ethereum.eth.manager; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +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 tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; +import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; +import tech.pegasys.pantheon.ethereum.eth.messages.NodeDataMessage; +import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; +import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; import tech.pegasys.pantheon.util.uint.UInt256; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.function.Consumer; + import org.junit.Before; import org.junit.Test; public class EthPeersTest { private EthProtocolManager ethProtocolManager; + private EthPeers ethPeers; + private final PeerRequest peerRequest = mock(PeerRequest.class); + private final ResponseStream responseStream = mock(ResponseStream.class); @Before - public void setup() { + public void setup() throws Exception { + when(peerRequest.sendRequest(any())).thenReturn(responseStream); ethProtocolManager = EthProtocolManagerTestUtil.create(); + ethPeers = ethProtocolManager.ethContext().getEthPeers(); } @Test @@ -64,4 +86,163 @@ public void comparesPeersWithTdAndNoHeight() { assertThat(EthPeers.BEST_CHAIN.compare(peerA, peerA)).isEqualTo(0); assertThat(EthPeers.BEST_CHAIN.compare(peerB, peerB)).isEqualTo(0); } + + @Test + public void shouldExecutePeerRequestImmediatelyWhenPeerIsAvailable() throws Exception { + final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 10, Optional.empty()); + + verify(peerRequest).sendRequest(peer.getEthPeer()); + assertRequestSuccessful(pendingRequest); + } + + @Test + public void shouldUseLeastBusyPeerForRequest() throws Exception { + final RespondingEthPeer idlePeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + final RespondingEthPeer workingPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + workingPeer.getEthPeer().getNodeData(emptyList()); + + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 10, Optional.empty()); + + verify(peerRequest).sendRequest(idlePeer.getEthPeer()); + assertRequestSuccessful(pendingRequest); + } + + @Test + public void shouldFailWithNoAvailablePeersWhenNoPeersConnected() { + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 10, Optional.empty()); + + verifyZeroInteractions(peerRequest); + assertRequestFailure(pendingRequest, NoAvailablePeersException.class); + } + + @Test + public void shouldFailWhenNoPeerWithSufficientHeight() { + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 100); + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 200, Optional.empty()); + + verifyZeroInteractions(peerRequest); + assertRequestFailure(pendingRequest, NoAvailablePeersException.class); + } + + @Test + public void shouldFailWhenAllPeersWithSufficientHeightHaveDisconnected() throws Exception { + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 100); + final RespondingEthPeer suitablePeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + useAllAvailableCapacity(suitablePeer.getEthPeer()); + + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 200, Optional.empty()); + + verifyZeroInteractions(peerRequest); + assertNotDone(pendingRequest); + + suitablePeer.disconnect(DisconnectReason.TOO_MANY_PEERS); + assertRequestFailure(pendingRequest, NoAvailablePeersException.class); + } + + @Test + public void shouldFailWithPeerNotConnectedIfPeerRequestThrows() throws Exception { + final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + when(peerRequest.sendRequest(peer.getEthPeer())).thenThrow(new PeerNotConnected("Oh dear")); + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 100, Optional.empty()); + + assertRequestFailure(pendingRequest, PeerDisconnectedException.class); + } + + @Test + public void shouldDelayExecutionUntilPeerHasCapacity() throws Exception { + final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + useAllAvailableCapacity(peer.getEthPeer()); + + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 100, Optional.empty()); + verifyZeroInteractions(peerRequest); + + freeUpCapacity(peer.getEthPeer()); + + verify(peerRequest).sendRequest(peer.getEthPeer()); + assertRequestSuccessful(pendingRequest); + } + + @Test + public void shouldDelayExecutionUntilPeerWithSufficientHeightHasCapacity() throws Exception { + // Create a peer that has available capacity but not the required height + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 10); + + final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + useAllAvailableCapacity(peer.getEthPeer()); + + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 100, Optional.empty()); + verifyZeroInteractions(peerRequest); + + freeUpCapacity(peer.getEthPeer()); + + verify(peerRequest).sendRequest(peer.getEthPeer()); + assertRequestSuccessful(pendingRequest); + } + + @Test + public void shouldNotExecuteAbortedRequest() throws Exception { + final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + useAllAvailableCapacity(peer.getEthPeer()); + + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 100, Optional.empty()); + verifyZeroInteractions(peerRequest); + + pendingRequest.abort(); + + freeUpCapacity(peer.getEthPeer()); + + verifyZeroInteractions(peerRequest); + assertRequestFailure(pendingRequest, CancellationException.class); + } + + private void freeUpCapacity(final EthPeer ethPeer) { + ethPeers.dispatchMessage(ethPeer, new EthMessage(ethPeer, NodeDataMessage.create(emptyList()))); + } + + private void useAllAvailableCapacity(final EthPeer peer) throws PeerNotConnected { + while (peer.hasAvailableRequestCapacity()) { + peer.getNodeData(emptyList()); + } + assertThat(peer.hasAvailableRequestCapacity()).isFalse(); + } + + @SuppressWarnings("unchecked") + private void assertRequestSuccessful(final PendingPeerRequest pendingRequest) { + final Consumer onSuccess = mock(Consumer.class); + pendingRequest.thenEither(onSuccess, error -> fail("Request should have executed", error)); + verify(onSuccess).accept(any()); + } + + @SuppressWarnings("unchecked") + private void assertRequestFailure( + final PendingPeerRequest pendingRequest, final Class reason) { + final Consumer errorHandler = mock(Consumer.class); + pendingRequest.thenEither( + responseStream -> fail("Should not have performed request"), errorHandler); + + verify(errorHandler).accept(any(reason)); + } + + @SuppressWarnings("unchecked") + private void assertNotDone(final PendingPeerRequest pendingRequest) { + final Consumer onSuccess = mock(Consumer.class); + final Consumer onError = mock(Consumer.class); + pendingRequest.thenEither(onSuccess, onError); + + verifyZeroInteractions(onSuccess); + verifyZeroInteractions(onError); + } } From 46edd1bf8b901f99be5602197c73671e803a5e0b Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 29 Apr 2019 09:27:18 +1000 Subject: [PATCH 17/25] Prefer least recently used peers to more recently ones when they have the same number of outstanding requests. Ensures we cycle round all our peers to avoid sybil attacks. --- .../protocol/Istanbul64ProtocolManager.java | 5 ++ .../ethereum/eth/manager/EthPeer.java | 52 ++++++++----------- .../ethereum/eth/manager/EthPeers.java | 11 ++-- .../eth/manager/EthProtocolManager.java | 8 ++- .../eth/manager/EthContextTestUtil.java | 3 +- .../ethereum/eth/manager/EthPeerTest.java | 29 ++++++++++- .../ethereum/eth/manager/EthPeersTest.java | 28 +++++++++- .../eth/manager/EthProtocolManagerTest.java | 22 ++++++++ .../manager/EthProtocolManagerTestUtil.java | 2 + .../eth/manager/RequestManagerTest.java | 3 +- .../eth/sync/BlockPropagationManagerTest.java | 7 ++- .../fastsync/FastSyncBlockHandlerTest.java | 3 +- .../ethereum/eth/transactions/TestNode.java | 1 + .../IbftLegacyPantheonControllerBuilder.java | 1 + .../controller/PantheonControllerBuilder.java | 1 + 15 files changed, 136 insertions(+), 40 deletions(-) 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 d7ed51ca45..262c23c062 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 @@ -21,6 +21,7 @@ import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.metrics.MetricsSystem; +import java.time.Clock; import java.util.List; /** This allows for interoperability with Quorum, but shouldn't be used otherwise. */ @@ -34,6 +35,7 @@ public Istanbul64ProtocolManager( final int syncWorkers, final int txWorkers, final int computationWorkers, + final Clock clock, final MetricsSystem metricsSystem, final EthereumWireProtocolConfiguration ethereumWireProtocolConfiguration) { super( @@ -44,6 +46,7 @@ public Istanbul64ProtocolManager( syncWorkers, txWorkers, computationWorkers, + clock, metricsSystem, ethereumWireProtocolConfiguration); } @@ -56,6 +59,7 @@ public Istanbul64ProtocolManager( final int syncWorkers, final int txWorkers, final int computationWorkers, + final Clock clock, final MetricsSystem metricsSystem) { super( blockchain, @@ -65,6 +69,7 @@ public Istanbul64ProtocolManager( syncWorkers, txWorkers, computationWorkers, + clock, metricsSystem); } 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 3f9a631f10..24f2ba4d9a 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 @@ -31,6 +31,7 @@ import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.uint.UInt256; +import java.time.Clock; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -55,10 +56,12 @@ public class EthPeer { private final Set knownBlocks; private final String protocolName; + private final Clock clock; private final ChainState chainHeadState; private final AtomicBoolean statusHasBeenSentToPeer = new AtomicBoolean(false); private final AtomicBoolean statusHasBeenReceivedFromPeer = new AtomicBoolean(false); + private volatile long lastRequestTimestamp = 0; private final RequestManager headersRequestManager = new RequestManager(this); private final RequestManager bodiesRequestManager = new RequestManager(this); private final RequestManager receiptsRequestManager = new RequestManager(this); @@ -71,9 +74,11 @@ public class EthPeer { EthPeer( final PeerConnection connection, final String protocolName, - final Consumer onStatusesExchanged) { + final Consumer onStatusesExchanged, + final Clock clock) { this.connection = connection; this.protocolName = protocolName; + this.clock = clock; knownBlocks = Collections.newSetFromMap( Collections.synchronizedMap( @@ -124,13 +129,13 @@ public void unsubscribeDisconnect(final long id) { public ResponseStream send(final MessageData messageData) throws PeerNotConnected { switch (messageData.getCode()) { case EthPV62.GET_BLOCK_HEADERS: - return sendHeadersRequest(messageData); + return sendRequest(headersRequestManager, messageData); case EthPV62.GET_BLOCK_BODIES: - return sendBodiesRequest(messageData); + return sendRequest(bodiesRequestManager, messageData); case EthPV63.GET_RECEIPTS: - return sendReceiptsRequest(messageData); + return sendRequest(receiptsRequestManager, messageData); case EthPV63.GET_NODE_DATA: - return sendNodeDataRequest(messageData); + return sendRequest(nodeDataRequestManager, messageData); default: connection.sendForProtocol(protocolName, messageData); return null; @@ -142,7 +147,7 @@ public ResponseStream getHeadersByHash( throws PeerNotConnected { final GetBlockHeadersMessage message = GetBlockHeadersMessage.create(hash, maxHeaders, skip, reverse); - return sendHeadersRequest(message); + return sendRequest(headersRequestManager, message); } public ResponseStream getHeadersByNumber( @@ -150,44 +155,29 @@ public ResponseStream getHeadersByNumber( throws PeerNotConnected { final GetBlockHeadersMessage message = GetBlockHeadersMessage.create(blockNumber, maxHeaders, skip, reverse); - return sendHeadersRequest(message); + return sendRequest(headersRequestManager, message); } - private ResponseStream sendHeadersRequest(final MessageData messageData) throws PeerNotConnected { - return headersRequestManager.dispatchRequest( + private ResponseStream sendRequest( + final RequestManager requestManager, final MessageData messageData) throws PeerNotConnected { + lastRequestTimestamp = clock.millis(); + return requestManager.dispatchRequest( () -> connection.sendForProtocol(protocolName, messageData)); } public ResponseStream getBodies(final List blockHashes) throws PeerNotConnected { final GetBlockBodiesMessage message = GetBlockBodiesMessage.create(blockHashes); - return sendBodiesRequest(message); - } - - private ResponseStream sendBodiesRequest(final MessageData messageData) throws PeerNotConnected { - return bodiesRequestManager.dispatchRequest( - () -> connection.sendForProtocol(protocolName, messageData)); + return sendRequest(bodiesRequestManager, message); } public ResponseStream getReceipts(final List blockHashes) throws PeerNotConnected { final GetReceiptsMessage message = GetReceiptsMessage.create(blockHashes); - return sendReceiptsRequest(message); - } - - private ResponseStream sendReceiptsRequest(final MessageData messageData) - throws PeerNotConnected { - return receiptsRequestManager.dispatchRequest( - () -> connection.sendForProtocol(protocolName, messageData)); + return sendRequest(receiptsRequestManager, message); } public ResponseStream getNodeData(final Iterable nodeHashes) throws PeerNotConnected { final GetNodeDataMessage message = GetNodeDataMessage.create(nodeHashes); - return sendNodeDataRequest(message); - } - - private ResponseStream sendNodeDataRequest(final MessageData messageData) - throws PeerNotConnected { - return nodeDataRequestManager.dispatchRequest( - () -> connection.sendForProtocol(protocolName, messageData)); + return sendRequest(nodeDataRequestManager, message); } boolean validateReceivedMessage(final EthMessage message) { @@ -331,6 +321,10 @@ public int outstandingRequests() { + nodeDataRequestManager.outstandingRequests(); } + public long getLastRequestTimestamp() { + return lastRequestTimestamp; + } + public boolean hasAvailableRequestCapacity() { return outstandingRequests() < MAX_OUTSTANDING_REQUESTS; } diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java index db6c407b04..46df29220a 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeers.java @@ -18,6 +18,7 @@ import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.Subscribers; +import java.time.Clock; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -38,16 +39,19 @@ public class EthPeers { public static final Comparator BEST_CHAIN = TOTAL_DIFFICULTY.thenComparing(CHAIN_HEIGHT); public static final Comparator LEAST_TO_MOST_BUSY = - Comparator.comparing(EthPeer::outstandingRequests); + Comparator.comparing(EthPeer::outstandingRequests) + .thenComparing(EthPeer::getLastRequestTimestamp); private final Map connections = new ConcurrentHashMap<>(); private final String protocolName; + private final Clock clock; private final Subscribers connectCallbacks = new Subscribers<>(); private final Subscribers disconnectCallbacks = new Subscribers<>(); private final Collection pendingRequests = new ArrayList<>(); - public EthPeers(final String protocolName, final MetricsSystem metricsSystem) { + public EthPeers(final String protocolName, final Clock clock, final MetricsSystem metricsSystem) { this.protocolName = protocolName; + this.clock = clock; metricsSystem.createIntegerGauge( MetricCategory.PEERS, "pending_peer_requests_current", @@ -56,7 +60,8 @@ public EthPeers(final String protocolName, final MetricsSystem metricsSystem) { } void registerConnection(final PeerConnection peerConnection) { - final EthPeer peer = new EthPeer(peerConnection, protocolName, this::invokeConnectionCallbacks); + final EthPeer peer = + new EthPeer(peerConnection, protocolName, this::invokeConnectionCallbacks, clock); connections.putIfAbsent(peerConnection, peer); } 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 6fa9f49c6c..031bf5ce06 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 @@ -35,6 +35,7 @@ import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.uint.UInt256; +import java.time.Clock; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -72,6 +73,7 @@ public EthProtocolManager( final boolean fastSyncEnabled, final EthScheduler scheduler, final EthereumWireProtocolConfiguration ethereumWireProtocolConfiguration, + final Clock clock, final MetricsSystem metricsSystem) { this.networkId = networkId; this.scheduler = scheduler; @@ -81,7 +83,7 @@ public EthProtocolManager( this.shutdown = new CountDownLatch(1); genesisHash = blockchain.getBlockHashByNumber(0L).get(); - ethPeers = new EthPeers(getSupportedProtocol(), metricsSystem); + ethPeers = new EthPeers(getSupportedProtocol(), clock, metricsSystem); ethMessages = new EthMessages(); ethContext = new EthContext(getSupportedProtocol(), ethPeers, ethMessages, scheduler); @@ -99,6 +101,7 @@ public EthProtocolManager( final int syncWorkers, final int txWorkers, final int computationWorkers, + final Clock clock, final MetricsSystem metricsSystem) { this( blockchain, @@ -107,6 +110,7 @@ public EthProtocolManager( fastSyncEnabled, new EthScheduler(syncWorkers, txWorkers, computationWorkers, metricsSystem), EthereumWireProtocolConfiguration.defaultConfig(), + clock, metricsSystem); } @@ -118,6 +122,7 @@ public EthProtocolManager( final int syncWorkers, final int txWorkers, final int computationWorkers, + final Clock clock, final MetricsSystem metricsSystem, final EthereumWireProtocolConfiguration ethereumWireProtocolConfiguration) { this( @@ -127,6 +132,7 @@ public EthProtocolManager( fastSyncEnabled, new EthScheduler(syncWorkers, txWorkers, computationWorkers, metricsSystem), ethereumWireProtocolConfiguration, + clock, metricsSystem); } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthContextTestUtil.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthContextTestUtil.java index 30d300e845..4abd2382f1 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthContextTestUtil.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthContextTestUtil.java @@ -14,6 +14,7 @@ import tech.pegasys.pantheon.ethereum.eth.manager.DeterministicEthScheduler.TimeoutPolicy; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; +import tech.pegasys.pantheon.testutil.TestClock; public class EthContextTestUtil { @@ -22,7 +23,7 @@ public class EthContextTestUtil { public static EthContext createTestEthContext(final TimeoutPolicy timeoutPolicy) { return new EthContext( PROTOCOL_NAME, - new EthPeers(PROTOCOL_NAME, new NoOpMetricsSystem()), + new EthPeers(PROTOCOL_NAME, TestClock.fixed(), new NoOpMetricsSystem()), new EthMessages(), new DeterministicEthScheduler(timeoutPolicy)); } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeerTest.java index cc86ea2c4d..87349ee747 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeerTest.java @@ -29,6 +29,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection.PeerNotConnected; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; +import tech.pegasys.pantheon.testutil.TestClock; import java.util.HashSet; import java.util.Set; @@ -39,6 +40,7 @@ public class EthPeerTest { private static final BlockDataGenerator gen = new BlockDataGenerator(); + private final TestClock clock = new TestClock(); @Test public void getHeadersStream() throws PeerNotConnected { @@ -114,6 +116,31 @@ public void shouldHaveAvailableCapacityUntilOutstandingRequestLimitIsReached() assertThat(peer.outstandingRequests()).isEqualTo(4); } + @Test + public void shouldTrackLastRequestTime() throws PeerNotConnected { + final EthPeer peer = createPeer(); + + clock.stepMillis(10_000); + peer.getBodies(asList(gen.hash(), gen.hash())); + assertThat(peer.getLastRequestTimestamp()).isEqualTo(clock.millis()); + + clock.stepMillis(10_000); + peer.getReceipts(asList(gen.hash(), gen.hash())); + assertThat(peer.getLastRequestTimestamp()).isEqualTo(clock.millis()); + + clock.stepMillis(10_000); + peer.getNodeData(asList(gen.hash(), gen.hash())); + assertThat(peer.getLastRequestTimestamp()).isEqualTo(clock.millis()); + + clock.stepMillis(10_000); + peer.getHeadersByHash(gen.hash(), 4, 1, false); + assertThat(peer.getLastRequestTimestamp()).isEqualTo(clock.millis()); + + clock.stepMillis(10_000); + peer.getHeadersByNumber(1, 1, 1, false); + assertThat(peer.getLastRequestTimestamp()).isEqualTo(clock.millis()); + } + @Test public void closeStreamsOnPeerDisconnect() throws PeerNotConnected { final EthPeer peer = createPeer(); @@ -316,7 +343,7 @@ private EthPeer createPeer() { final Set caps = new HashSet<>(singletonList(EthProtocol.ETH63)); final PeerConnection peerConnection = new MockPeerConnection(caps); final Consumer onPeerReady = (peer) -> {}; - return new EthPeer(peerConnection, EthProtocol.NAME, onPeerReady); + return new EthPeer(peerConnection, EthProtocol.NAME, onPeerReady, clock); } @FunctionalInterface diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java index 4c16f39ed5..c0b8317da5 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java @@ -103,7 +103,7 @@ public void shouldUseLeastBusyPeerForRequest() throws Exception { EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); final RespondingEthPeer workingPeer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); - workingPeer.getEthPeer().getNodeData(emptyList()); + useRequestSlot(workingPeer.getEthPeer()); final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 10, Optional.empty()); @@ -112,6 +112,26 @@ public void shouldUseLeastBusyPeerForRequest() throws Exception { assertRequestSuccessful(pendingRequest); } + @Test + public void shouldUseLeastRecentlyUsedPeerWhenBothHaveSameNumberOfOutstandingRequests() + throws Exception { + final RespondingEthPeer mostRecentlyUsedPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + final RespondingEthPeer leastRecentlyUsedPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + useRequestSlot(mostRecentlyUsedPeer.getEthPeer()); + freeUpCapacity(mostRecentlyUsedPeer.getEthPeer()); + + assertThat(leastRecentlyUsedPeer.getEthPeer().outstandingRequests()) + .isEqualTo(mostRecentlyUsedPeer.getEthPeer().outstandingRequests()); + + final PendingPeerRequest pendingRequest = + ethPeers.executePeerRequest(peerRequest, 10, Optional.empty()); + + verify(peerRequest).sendRequest(leastRecentlyUsedPeer.getEthPeer()); + assertRequestSuccessful(pendingRequest); + } + @Test public void shouldFailWithNoAvailablePeersWhenNoPeersConnected() { final PendingPeerRequest pendingRequest = @@ -214,11 +234,15 @@ private void freeUpCapacity(final EthPeer ethPeer) { private void useAllAvailableCapacity(final EthPeer peer) throws PeerNotConnected { while (peer.hasAvailableRequestCapacity()) { - peer.getNodeData(emptyList()); + useRequestSlot(peer); } assertThat(peer.hasAvailableRequestCapacity()).isFalse(); } + private void useRequestSlot(final EthPeer peer) throws PeerNotConnected { + peer.getNodeData(emptyList()); + } + @SuppressWarnings("unchecked") private void assertRequestSuccessful(final PendingPeerRequest pendingRequest) { final Consumer onSuccess = mock(Consumer.class); 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 e5519dee7a..50f6e81c5d 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 @@ -118,6 +118,7 @@ public void disconnectOnUnsolicitedMessage() { 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final MessageData messageData = @@ -139,6 +140,7 @@ public void disconnectOnFailureToSendStatusMessage() { 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final MessageData messageData = @@ -161,6 +163,7 @@ public void disconnectOnWrongChainId() { 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final MessageData messageData = @@ -194,6 +197,7 @@ public void disconnectOnWrongGenesisHash() { 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final MessageData messageData = @@ -227,6 +231,7 @@ public void doNotDisconnectOnValidMessage() { 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final MessageData messageData = @@ -252,6 +257,7 @@ public void respondToGetHeaders() throws ExecutionException, InterruptedExceptio 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final long startBlock = 5L; @@ -293,6 +299,7 @@ public void respondToGetHeadersWithinLimits() throws ExecutionException, Interru 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), new EthereumWireProtocolConfiguration(limit, limit, limit, limit))) { final long startBlock = 5L; @@ -333,6 +340,7 @@ public void respondToGetHeadersReversed() throws ExecutionException, Interrupted 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final long endBlock = 10L; @@ -372,6 +380,7 @@ public void respondToGetHeadersWithSkip() throws ExecutionException, Interrupted 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final long startBlock = 5L; @@ -414,6 +423,7 @@ public void respondToGetHeadersReversedWithSkip() 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final long endBlock = 10L; @@ -477,6 +487,7 @@ public void respondToGetHeadersPartial() throws ExecutionException, InterruptedE 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final long startBlock = blockchain.getChainHeadBlockNumber() - 1L; @@ -517,6 +528,7 @@ public void respondToGetHeadersEmpty() throws ExecutionException, InterruptedExc 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final long startBlock = blockchain.getChainHeadBlockNumber() + 1; @@ -554,6 +566,7 @@ public void respondToGetBodies() throws ExecutionException, InterruptedException 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { // Setup blocks query @@ -607,6 +620,7 @@ public void respondToGetBodiesWithinLimits() throws ExecutionException, Interrup 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), new EthereumWireProtocolConfiguration(limit, limit, limit, limit))) { // Setup blocks query @@ -659,6 +673,7 @@ public void respondToGetBodiesPartial() throws ExecutionException, InterruptedEx 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { // Setup blocks query @@ -705,6 +720,7 @@ public void respondToGetReceipts() throws ExecutionException, InterruptedExcepti 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { // Setup blocks query @@ -757,6 +773,7 @@ public void respondToGetReceiptsWithinLimits() throws ExecutionException, Interr 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), new EthereumWireProtocolConfiguration(limit, limit, limit, limit))) { // Setup blocks query @@ -808,6 +825,7 @@ public void respondToGetReceiptsPartial() throws ExecutionException, Interrupted 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { // Setup blocks query @@ -856,6 +874,7 @@ public void respondToGetNodeData() throws Exception { 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { // Setup node data query @@ -907,6 +926,7 @@ public void newBlockMinedSendsNewBlockMessageToAllPeers() { 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig()); @@ -979,6 +999,7 @@ public void shouldSuccessfullyRespondToGetHeadersRequestLessThanZero() 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig())) { final long startBlock = 1L; @@ -1046,6 +1067,7 @@ public void transactionMessagesGoToTheCorrectExecutor() { true, ethScheduler, EthereumWireProtocolConfiguration.defaultConfig(), + TestClock.fixed(), metricsSystem)) { // Create a transaction pool. This has a side effect of registring a listener for the diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java index dbdec48364..fd419fe95d 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthProtocolManagerTestUtil.java @@ -29,6 +29,7 @@ import tech.pegasys.pantheon.ethereum.p2p.wire.DefaultMessage; import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; +import tech.pegasys.pantheon.testutil.TestClock; import tech.pegasys.pantheon.util.uint.UInt256; public class EthProtocolManagerTestUtil { @@ -52,6 +53,7 @@ public static EthProtocolManager create( false, ethScheduler, EthereumWireProtocolConfiguration.defaultConfig(), + TestClock.fixed(), new NoOpMetricsSystem()); } diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/RequestManagerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/RequestManagerTest.java index 934c230ee7..57efcae3ab 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/RequestManagerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/RequestManagerTest.java @@ -22,6 +22,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.RawMessage; +import tech.pegasys.pantheon.testutil.TestClock; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.ArrayList; @@ -219,6 +220,6 @@ private EthPeer createPeer() { final Set caps = new HashSet<>(Collections.singletonList(EthProtocol.ETH63)); final PeerConnection peerConnection = new MockPeerConnection(caps); final Consumer onPeerReady = (peer) -> {}; - return new EthPeer(peerConnection, EthProtocol.NAME, onPeerReady); + return new EthPeer(peerConnection, EthProtocol.NAME, onPeerReady, TestClock.fixed()); } } 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 e068ae5d1f..c3f0dfd33e 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 @@ -48,6 +48,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; +import tech.pegasys.pantheon.testutil.TestClock; import tech.pegasys.pantheon.util.uint.UInt256; import java.util.Collections; @@ -580,7 +581,11 @@ public void shouldNotImportBlocksThatAreAlreadyBeingImported() { when(ethScheduler.scheduleSyncWorkerTask(any(Supplier.class))) .thenReturn(new CompletableFuture<>()); final EthContext ethContext = - new EthContext("eth", new EthPeers("eth", metricsSystem), new EthMessages(), ethScheduler); + new EthContext( + "eth", + new EthPeers("eth", TestClock.fixed(), metricsSystem), + new EthMessages(), + ethScheduler); final BlockPropagationManager blockPropagationManager = new BlockPropagationManager<>( syncConfig, diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandlerTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandlerTest.java index 56533a00d2..217f94c0e6 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandlerTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/sync/fastsync/FastSyncBlockHandlerTest.java @@ -43,6 +43,7 @@ import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; +import tech.pegasys.pantheon.testutil.TestClock; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -80,7 +81,7 @@ public class FastSyncBlockHandlerTest { private final EthContext ethContext = new EthContext( PROTOCOL_NAME, - new EthPeers(PROTOCOL_NAME, metricsSystem), + new EthPeers(PROTOCOL_NAME, TestClock.fixed(), metricsSystem), new EthMessages(), new DeterministicEthScheduler()); private final ValidationPolicy validationPolicy = mock(ValidationPolicy.class); 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 a35cf5a7ec..69a8b647d7 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 @@ -117,6 +117,7 @@ public TestNode( 1, 1, 1, + TestClock.fixed(), new NoOpMetricsSystem(), EthereumWireProtocolConfiguration.defaultConfig()); diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java index ded1abfb9c..0fd426c913 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/IbftLegacyPantheonControllerBuilder.java @@ -92,6 +92,7 @@ protected EthProtocolManager createEthProtocolManager( syncConfig.downloaderParallelism(), syncConfig.transactionsParallelism(), syncConfig.computationParallelism(), + clock, metricsSystem, ethereumWireProtocolConfiguration); } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java index 26eb99f8a3..569d89b542 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/controller/PantheonControllerBuilder.java @@ -302,6 +302,7 @@ protected EthProtocolManager createEthProtocolManager( syncConfig.downloaderParallelism(), syncConfig.transactionsParallelism(), syncConfig.computationParallelism(), + clock, metricsSystem, ethereumWireProtocolConfiguration); } From 76436ff80c75524ae76e98fdeb9daa16544c245a Mon Sep 17 00:00:00 2001 From: Jeremy McNevin Date: Wed, 1 May 2019 19:15:40 -0500 Subject: [PATCH 18/25] Removing smart quotes (#1381) --- docs/Configuring-Pantheon/FreeGas.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Configuring-Pantheon/FreeGas.md b/docs/Configuring-Pantheon/FreeGas.md index 658480eff5..82619f0316 100644 --- a/docs/Configuring-Pantheon/FreeGas.md +++ b/docs/Configuring-Pantheon/FreeGas.md @@ -31,7 +31,7 @@ block and contract size limits to the maximum values. Set the block size limit (measured in gas) to the maximum accepted by Truffle (`0x1fffffffffffff`) in the genesis file: ```json -“gasLimit”: “0x1fffffffffffff” +"gasLimit": "0x1fffffffffffff" ``` ### 2. Set Contract Size @@ -39,7 +39,7 @@ Set the block size limit (measured in gas) to the maximum accepted by Truffle (` Set the contract size limit to the maximum supported size (in bytes) in the `config` section of the genesis file: ```json -“contractSizeLimit”: 2147483647 +"contractSizeLimit": 2147483647 ``` ### 3. Start Pantheon with Minimum Gas Price of 0 @@ -79,5 +79,5 @@ Update the `truffle-config.js` file: 1. Set the gas limit for a transaction (that is, contract creation) to be the block gas limit - 1 ```js - gas: “0x1ffffffffffffe” - ``` \ No newline at end of file + gas: "0x1ffffffffffffe" + ``` From 77ed3ab92ca891ed694592d4442783832ecb1a1f Mon Sep 17 00:00:00 2001 From: Trent Mohay <37158202+rain-on@users.noreply.github.com> Date: Thu, 2 May 2019 13:38:24 +1000 Subject: [PATCH 19/25] Update Log message in IBFT Controller (#1387) --- .../pantheon/consensus/ibft/statemachine/IbftController.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 f792eeff92..aa34266409 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 @@ -127,7 +127,10 @@ private

> void consumeMessage( public void handleNewBlockEvent(final NewChainHead newChainHead) { final BlockHeader newBlockHeader = newChainHead.getNewChainHeadHeader(); final BlockHeader currentMiningParent = currentHeightManager.getParentBlockHeader(); - LOG.debug("Handling New Chain head event, chain height={}", currentMiningParent.getNumber()); + LOG.debug( + "New chain head detected (block number={})," + " currently mining on top of {}.", + newBlockHeader.getNumber(), + currentMiningParent.getNumber()); if (newBlockHeader.getNumber() < currentMiningParent.getNumber()) { LOG.trace( "Discarding NewChainHead event, was for previous block height. chainHeight={} eventHeight={}", From 0ba9122c1baa7115421206e39b60adf76b2a25b2 Mon Sep 17 00:00:00 2001 From: MadelineMurray <43356962+MadelineMurray@users.noreply.github.com> Date: Thu, 2 May 2019 14:30:48 +1000 Subject: [PATCH 20/25] Fixed typo (#1388) --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index fef9b03334..702acb5837 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -98,7 +98,7 @@ nav: - Transactions: - Creating and Sending Transactions: Using-Pantheon/Transactions/Transactions.md - Transaction Pool: Using-Pantheon/Transactions/Transaction-Pool.md - - Using Truffle with Panthen: Using-Pantheon/Truffle.md + - Using Truffle with Pantheon: Using-Pantheon/Truffle.md - Events and Logs: - Overview: Using-Pantheon/Events-and-Logs.md - Accessing Logs Using JSON-RPC API: Using-Pantheon/Accessing-Logs-Using-JSON-RPC.md From d0124e41af0ac0b37cc7519a6c3a0490f2439b1f Mon Sep 17 00:00:00 2001 From: Nicolas MASSART Date: Thu, 2 May 2019 13:31:03 +0200 Subject: [PATCH 21/25] update python mkdocs requirements versions (#1374) and removed changes made in application JS as the new version of material theme fixes the issue with hotjar form --- .../javascripts/application.c6f2d7d2.js | 6 ---- docs/custom_theme/main.html | 28 ------------------- docs/requirements.txt | 14 +++++----- 3 files changed, 7 insertions(+), 41 deletions(-) delete mode 100644 docs/custom_theme/assets/javascripts/application.c6f2d7d2.js diff --git a/docs/custom_theme/assets/javascripts/application.c6f2d7d2.js b/docs/custom_theme/assets/javascripts/application.c6f2d7d2.js deleted file mode 100644 index f56cf228f1..0000000000 --- a/docs/custom_theme/assets/javascripts/application.c6f2d7d2.js +++ /dev/null @@ -1,6 +0,0 @@ -!function(e,t){for(var n in t)e[n]=t[n]}(window,function(n){var r={};function i(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,i),t.l=!0,t.exports}return i.m=n,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)i.d(n,r,function(e){return t[e]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=14)}([function(e,t,n){"use strict";var r={Listener:function(){function e(e,t,n){var r=this;this.els_=Array.prototype.slice.call("string"==typeof e?document.querySelectorAll(e):[].concat(e)),this.handler_="function"==typeof n?{update:n}:n,this.events_=[].concat(t),this.update_=function(e){return r.handler_.update(e)}}var t=e.prototype;return t.listen=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.addEventListener(e,n.update_,!1)})}),"function"==typeof this.handler_.setup&&this.handler_.setup()},t.unlisten=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.removeEventListener(e,n.update_)})}),"function"==typeof this.handler_.reset&&this.handler_.reset()},e}(),MatchMedia:function(e,t){this.handler_=function(e){e.matches?t.listen():t.unlisten()};var n=window.matchMedia(e);n.addListener(this.handler_),this.handler_(n)}},i={Shadow:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement&&n.parentNode instanceof HTMLElement))throw new ReferenceError;if(this.el_=n.parentNode,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLElement))throw new ReferenceError;this.header_=n,this.height_=0,this.active_=!1}var t=e.prototype;return t.setup=function(){for(var e=this.el_;e=e.previousElementSibling;){if(!(e instanceof HTMLElement))throw new ReferenceError;this.height_+=e.offsetHeight}this.update()},t.update=function(e){if(!e||"resize"!==e.type&&"orientationchange"!==e.type){var t=window.pageYOffset>=this.height_;t!==this.active_&&(this.header_.dataset.mdState=(this.active_=t)?"shadow":"")}else this.height_=0,this.setup()},t.reset=function(){this.header_.dataset.mdState="",this.height_=0,this.active_=!1},e}(),Title:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement))throw new ReferenceError;if(this.el_=n,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLHeadingElement))throw new ReferenceError;this.header_=n,this.active_=!1}var t=e.prototype;return t.setup=function(){var t=this;Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.update=function(e){var t=this,n=window.pageYOffset>=this.header_.offsetTop;n!==this.active_&&(this.el_.dataset.mdState=(this.active_=n)?"active":""),"resize"!==e.type&&"orientationchange"!==e.type||Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.width="",this.active_=!1},e}()},o={Blur:function(){function e(e){this.els_="string"==typeof e?document.querySelectorAll(e):e,this.index_=0,this.offset_=window.pageYOffset,this.dir_=!1,this.anchors_=[].reduce.call(this.els_,function(e,t){var n=decodeURIComponent(t.hash);return e.concat(document.getElementById(n.substring(1))||[])},[])}var t=e.prototype;return t.setup=function(){this.update()},t.update=function(){var e=window.pageYOffset,t=this.offset_-e<0;if(this.dir_!==t&&(this.index_=this.index_=t?0:this.els_.length-1),0!==this.anchors_.length){if(this.offset_<=e)for(var n=this.index_+1;ne)){this.index_=r;break}0=this.offset_?"lock"!==this.el_.dataset.mdState&&(this.el_.dataset.mdState="lock"):"lock"===this.el_.dataset.mdState&&(this.el_.dataset.mdState="")},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.height="",this.height_=0},e}()},c=n(6),l=n.n(c);var u={Adapter:{GitHub:function(o){var e,t;function n(e){var t;t=o.call(this,e)||this;var n=/^.+github\.com\/([^/]+)\/?([^/]+)?.*$/.exec(t.base_);if(n&&3===n.length){var r=n[1],i=n[2];t.base_="https://api.github.com/users/"+r+"/repos",t.name_=i}return t}return t=o,(e=n).prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t,n.prototype.fetch_=function(){var i=this;return function n(r){return void 0===r&&(r=0),fetch(i.base_+"?per_page=30&page="+r).then(function(e){return e.json()}).then(function(e){if(!(e instanceof Array))throw new TypeError;if(i.name_){var t=e.find(function(e){return e.name===i.name_});return t||30!==e.length?t?[i.format_(t.stargazers_count)+" Stars",i.format_(t.forks_count)+" Forks"]:[]:n(r+1)}return[e.length+" Repositories"]})}()},n}(function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLAnchorElement))throw new ReferenceError;this.el_=t,this.base_=this.el_.href,this.salt_=this.hash_(this.base_)}var t=e.prototype;return t.fetch=function(){var n=this;return new Promise(function(t){var e=l.a.getJSON(n.salt_+".cache-source");void 0!==e?t(e):n.fetch_().then(function(e){l.a.set(n.salt_+".cache-source",e,{expires:1/96}),t(e)})})},t.fetch_=function(){throw new Error("fetch_(): Not implemented")},t.format_=function(e){return 1e4=this.el_.children[0].offsetTop+-43;e!==this.active_&&(this.el_.dataset.mdState=(this.active_=e)?"hidden":"")},t.reset=function(){this.el_.dataset.mdState="",this.active_=!1},e}()};t.a={Event:r,Header:i,Nav:o,Search:a,Sidebar:s,Source:u,Tabs:f}},function(t,e,n){(function(e){t.exports=e.lunr=n(25)}).call(this,n(4))},function(e,f,d){"use strict";(function(t){var e=d(8),n=setTimeout;function r(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],u(e,this)}function i(n,r){for(;3===n._state;)n=n._value;0!==n._state?(n._handled=!0,o._immediateFn(function(){var e=1===n._state?r.onFulfilled:r.onRejected;if(null!==e){var t;try{t=e(n._value)}catch(e){return void s(r.promise,e)}a(r.promise,t)}else(1===n._state?a:s)(r.promise,n._value)})):n._deferreds.push(r)}function a(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var n=e.then;if(e instanceof o)return t._state=3,t._value=e,void c(t);if("function"==typeof n)return void u((r=n,i=e,function(){r.apply(i,arguments)}),t)}t._state=1,t._value=e,c(t)}catch(e){s(t,e)}var r,i}function s(e,t){e._state=2,e._value=t,c(e)}function c(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;t"+n+""};this.stack_=[],n.forEach(function(e,t){var n,r=a.docs_.get(t),i=u.createElement("li",{class:"md-search-result__item"},u.createElement("a",{href:r.location,title:r.title,class:"md-search-result__link",tabindex:"-1"},u.createElement("article",{class:"md-search-result__article md-search-result__article--document"},u.createElement("h1",{class:"md-search-result__title"},{__html:r.title.replace(s,c)}),r.text.length?u.createElement("p",{class:"md-search-result__teaser"},{__html:r.text.replace(s,c)}):{}))),o=e.map(function(t){return function(){var e=a.docs_.get(t.ref);i.appendChild(u.createElement("a",{href:e.location,title:e.title,class:"md-search-result__link","data-md-rel":"anchor",tabindex:"-1"},u.createElement("article",{class:"md-search-result__article"},u.createElement("h1",{class:"md-search-result__title"},{__html:e.title.replace(s,c)}),e.text.length?u.createElement("p",{class:"md-search-result__teaser"},{__html:function(e,t){var n=t;if(e.length>n){for(;" "!==e[n]&&0<--n;);return e.substring(0,n)+"..."}return e}(e.text.replace(s,c),400)}):{})))}});(n=a.stack_).push.apply(n,[function(){return a.list_.appendChild(i)}].concat(o))});var i=this.el_.parentNode;if(!(i instanceof HTMLElement))throw new ReferenceError;for(;this.stack_.length&&i.offsetHeight>=i.scrollHeight-16;)this.stack_.shift()();var o=this.list_.querySelectorAll("[data-md-rel=anchor]");switch(Array.prototype.forEach.call(o,function(r){["click","keydown"].forEach(function(n){r.addEventListener(n,function(e){if("keydown"!==n||13===e.keyCode){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.checked&&(t.checked=!1,t.dispatchEvent(new CustomEvent("change"))),e.preventDefault(),setTimeout(function(){document.location.href=r.href},100)}})})}),n.size){case 0:this.meta_.textContent=this.message_.none;break;case 1:this.meta_.textContent=this.message_.one;break;default:this.meta_.textContent=this.message_.other.replace("#",n.size)}}}else{var l=function(e){a.docs_=e.reduce(function(e,t){var n,r,i,o=t.location.split("#"),a=o[0],s=o[1];return t.text=(n=t.text,r=document.createTextNode(n),(i=document.createElement("p")).appendChild(r),i.innerHTML),s&&(t.parent=e.get(a),t.parent&&!t.parent.done&&(t.parent.title=t.title,t.parent.text=t.text,t.parent.done=!0)),t.text=t.text.replace(/\n/g," ").replace(/\s+/g," ").replace(/\s+([,.:;!?])/g,function(e,t){return t}),t.parent&&t.parent.title===t.title||e.set(t.location,t),e},new Map);var i=a.docs_,o=a.lang_;a.stack_=[],a.index_=d()(function(){var e,t=this,n={"search.pipeline.trimmer":d.a.trimmer,"search.pipeline.stopwords":d.a.stopWordFilter},r=Object.keys(n).reduce(function(e,t){return h(t).match(/^false$/i)||e.push(n[t]),e},[]);this.pipeline.reset(),r&&(e=this.pipeline).add.apply(e,r),1===o.length&&"en"!==o[0]&&d.a[o[0]]?this.use(d.a[o[0]]):1=t.scrollHeight-16;)a.stack_.splice(0,10).forEach(function(e){return e()})})};setTimeout(function(){return"function"==typeof a.data_?a.data_().then(l):l(a.data_)},250)}},e}()}).call(this,i(3))},function(e,t,n){"use strict";var r=/[|\\{}()[\]^$+*?.]/g;e.exports=function(e){if("string"!=typeof e)throw new TypeError("Expected a string");return e.replace(r,"\\$&")}},function(e,n,r){"use strict";(function(t){r.d(n,"a",function(){return e});var e=function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLElement))throw new ReferenceError;this.el_=t}return e.prototype.initialize=function(e){e.length&&this.el_.children.length&&this.el_.children[this.el_.children.length-1].appendChild(t.createElement("ul",{class:"md-source__facts"},e.map(function(e){return t.createElement("li",{class:"md-source__fact"},e)}))),this.el_.dataset.mdState="done"},e}()}).call(this,r(3))},,,function(e,n,c){"use strict";c.r(n),function(o){c.d(n,"app",function(){return t});c(15),c(16),c(17),c(18),c(19),c(20),c(21);var r=c(2),e=c(5),a=c.n(e),i=c(0);window.Promise=window.Promise||r.a;var s=function(e){var t=document.getElementsByName("lang:"+e)[0];if(!(t instanceof HTMLMetaElement))throw new ReferenceError;return t.content};var t={initialize:function(t){new i.a.Event.Listener(document,"DOMContentLoaded",function(){if(!(document.body instanceof HTMLElement))throw new ReferenceError;Modernizr.addTest("ios",function(){return!!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)});var e=document.querySelectorAll("table:not([class])");if(Array.prototype.forEach.call(e,function(e){var t=o.createElement("div",{class:"md-typeset__scrollwrap"},o.createElement("div",{class:"md-typeset__table"}));e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.children[0].appendChild(e)}),a.a.isSupported()){var t=document.querySelectorAll(".codehilite > pre, pre > code");Array.prototype.forEach.call(t,function(e,t){var n="__code_"+t,r=o.createElement("button",{class:"md-clipboard",title:s("clipboard.copy"),"data-clipboard-target":"#"+n+" pre, #"+n+" code"},o.createElement("span",{class:"md-clipboard__message"})),i=e.parentNode;i.id=n,i.insertBefore(r,e)}),new a.a(".md-clipboard").on("success",function(e){var t=e.trigger.querySelector(".md-clipboard__message");if(!(t instanceof HTMLElement))throw new ReferenceError;e.clearSelection(),t.dataset.mdTimer&&clearTimeout(parseInt(t.dataset.mdTimer,10)),t.classList.add("md-clipboard__message--active"),t.innerHTML=s("clipboard.copied"),t.dataset.mdTimer=setTimeout(function(){t.classList.remove("md-clipboard__message--active"),t.dataset.mdTimer=""},2e3).toString()})}if(!Modernizr.details){var n=document.querySelectorAll("details > summary");Array.prototype.forEach.call(n,function(e){e.addEventListener("click",function(e){var t=e.target.parentNode;t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")})})}var r=function(){if(document.location.hash){var e=document.getElementById(document.location.hash.substring(1));if(!e)return;for(var t=e.parentNode;t&&!(t instanceof HTMLDetailsElement);)t=t.parentNode;if(t&&!t.open){t.open=!0;var n=location.hash;location.hash=" ",location.hash=n}}};if(window.addEventListener("hashchange",r),r(),Modernizr.ios){var i=document.querySelectorAll("[data-md-scrollfix]");Array.prototype.forEach.call(i,function(t){t.addEventListener("touchstart",function(){var e=t.scrollTop;0===e?t.scrollTop=1:e+t.offsetHeight===t.scrollHeight&&(t.scrollTop=e-1)})})}}).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Shadow("[data-md-component=container]","[data-md-component=header]")).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Title("[data-md-component=title]",".md-typeset h1")).listen(),document.querySelector("[data-md-component=hero]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=hero]")).listen(),document.querySelector("[data-md-component=tabs]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=tabs]")).listen(),new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=navigation]","[data-md-component=header]"))),document.querySelector("[data-md-component=toc]")&&new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=toc]","[data-md-component=header]"))),new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,"scroll",new i.a.Nav.Blur("[data-md-component=toc] .md-nav__link")));var e=document.querySelectorAll("[data-md-component=collapsible]");Array.prototype.forEach.call(e,function(e){new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(e.previousElementSibling,"click",new i.a.Nav.Collapse(e)))}),new i.a.Event.MatchMedia("(max-width: 1219px)",new i.a.Event.Listener("[data-md-component=navigation] [data-md-toggle]","change",new i.a.Nav.Scrolling("[data-md-component=navigation] nav"))),document.querySelector("[data-md-component=search]")&&(new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-toggle=search]","change",new i.a.Search.Lock("[data-md-toggle=search]"))),new i.a.Event.Listener("[data-md-component=query]",["focus","keyup","change"],new i.a.Search.Result("[data-md-component=result]",function(){return fetch(t.url.base+"/search/search_index.json",{credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){return e.docs.map(function(e){return e.location=t.url.base+"/"+e.location,e})})})).listen(),new i.a.Event.Listener("[data-md-component=reset]","click",function(){setTimeout(function(){var e=document.querySelector("[data-md-component=query]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.focus()},10)}).listen(),new i.a.Event.Listener("[data-md-toggle=search]","change",function(e){setTimeout(function(e){if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.focus()}},400,e.target)}).listen(),new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener("[data-md-component=query]","focus",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked||(e.checked=!0,e.dispatchEvent(new CustomEvent("change")))})),new i.a.Event.Listener(window,"keydown",function(e){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;var n=document.querySelector("[data-md-component=query]");if(!(n instanceof HTMLInputElement))throw new ReferenceError;if(!(document.activeElement instanceof HTMLElement&&("true"===document.activeElement.contentEditable||document.activeElement.isContentEditable)||"TEXTAREA"===document.activeElement.tagName||"INPUT"===document.activeElement.tagName||e.metaKey||e.ctrlKey))if(t.checked){if(13===e.keyCode){if(n===document.activeElement){e.preventDefault();var r=document.querySelector("[data-md-component=search] [href][data-md-state=active]");r instanceof HTMLLinkElement&&(window.location=r.getAttribute("href"),t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur())}}else if(9===e.keyCode||27===e.keyCode)t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur();else if(-1!==[8,37,39].indexOf(e.keyCode))n!==document.activeElement&&n.focus();else if(-1!==[38,40].indexOf(e.keyCode)){var i=e.keyCode,o=Array.prototype.slice.call(document.querySelectorAll("[data-md-component=query], [data-md-component=search] [href]")),a=o.find(function(e){if(!(e instanceof HTMLElement))throw new ReferenceError;return"active"===e.dataset.mdState});a&&(a.dataset.mdState="");var s=Math.max(0,(o.indexOf(a)+o.length+(38===i?-1:1))%o.length);return o[s]&&(o[s].dataset.mdState="active",o[s].focus()),e.preventDefault(),e.stopPropagation(),!1}}else document.activeElement&&!document.activeElement.form&&(70!==e.keyCode&&83!==e.keyCode||(n.focus(),e.preventDefault()))}).listen(),new i.a.Event.Listener(window,"keypress",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t!==document.activeElement&&t.focus()}}).listen()),new i.a.Event.Listener(document.body,"keydown",function(e){if(9===e.keyCode){var t=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[for]:not([tabindex])");Array.prototype.forEach.call(t,function(e){e.offsetHeight&&(e.tabIndex=0)})}}).listen(),new i.a.Event.Listener(document.body,"mousedown",function(){var e=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[tabindex]");Array.prototype.forEach.call(e,function(e){e.removeAttribute("tabIndex")})}).listen(),document.body.addEventListener("click",function(){"tabbing"===document.body.dataset.mdState&&(document.body.dataset.mdState="")}),new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-component=navigation] [href^='#']","click",function(){var e=document.querySelector("[data-md-toggle=drawer]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked&&(e.checked=!1,e.dispatchEvent(new CustomEvent("change")))})),function(){var e=document.querySelector("[data-md-source]");if(!e)return r.a.resolve([]);if(!(e instanceof HTMLAnchorElement))throw new ReferenceError;switch(e.dataset.mdSource){case"github":return new i.a.Source.Adapter.GitHub(e).fetch();default:return r.a.resolve([])}}().then(function(t){var e=document.querySelectorAll("[data-md-source]");Array.prototype.forEach.call(e,function(e){new i.a.Source.Repository(e).initialize(t)})});var n=function(){var e=document.querySelectorAll("details");Array.prototype.forEach.call(e,function(e){e.setAttribute("open","")})};new i.a.Event.MatchMedia("print",{listen:n,unlisten:function(){}}),window.onbeforeprint=n}}}.call(this,c(3))},function(e,t,n){e.exports=n.p+"assets/images/icons/bitbucket.1b09e088.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/github.f0b8504a.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/gitlab.6dd19c00.svg"},function(e,t){e.exports="/Users/nicolas/Develop/mkdocs-material/material/application.3020aac5.css"},function(e,t){e.exports="/Users/nicolas/Develop/mkdocs-material/material/application-palette.224b79ff.css"},function(e,t){!function(){if("undefined"!=typeof window)try{var e=new window.CustomEvent("test",{cancelable:!0});if(e.preventDefault(),!0!==e.defaultPrevented)throw new Error("Could not prevent default")}catch(e){var t=function(e,t){var n,r;return t=t||{bubbles:!1,cancelable:!1,detail:void 0},(n=document.createEvent("CustomEvent")).initCustomEvent(e,t.bubbles,t.cancelable,t.detail),r=n.preventDefault,n.preventDefault=function(){r.call(this);try{Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})}catch(e){this.defaultPrevented=!0}},n};t.prototype=window.Event.prototype,window.CustomEvent=t}}()},function(e,t,n){window.fetch||(window.fetch=n(7).default||n(7))},function(e,i,o){(function(e){var t=void 0!==e&&e||"undefined"!=typeof self&&self||window,n=Function.prototype.apply;function r(e,t){this._id=e,this._clearFn=t}i.setTimeout=function(){return new r(n.call(setTimeout,t,arguments),clearTimeout)},i.setInterval=function(){return new r(n.call(setInterval,t,arguments),clearInterval)},i.clearTimeout=i.clearInterval=function(e){e&&e.close()},r.prototype.unref=r.prototype.ref=function(){},r.prototype.close=function(){this._clearFn.call(t,this._id)},i.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},i.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},i._unrefActive=i.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;0<=t&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},o(23),i.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,i.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,o(4))},function(e,t,n){(function(e,p){!function(n,r){"use strict";if(!n.setImmediate){var i,o,t,a,e,s=1,c={},l=!1,u=n.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(n);f=f&&f.setTimeout?f:n,i="[object process]"==={}.toString.call(n.process)?function(e){p.nextTick(function(){h(e)})}:function(){if(n.postMessage&&!n.importScripts){var e=!0,t=n.onmessage;return n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",e=function(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(a)&&h(+e.data.slice(a.length))},n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),function(e){n.postMessage(a+e,"*")}):n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){h(e.data)},function(e){t.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(o=u.documentElement,function(e){var t=u.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):function(e){setTimeout(h,0,e)},f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n=this.length)return D.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},D.QueryLexer.prototype.width=function(){return this.pos-this.start},D.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},D.QueryLexer.prototype.backup=function(){this.pos-=1},D.QueryLexer.prototype.acceptDigitRun=function(){for(var e,t;47<(t=(e=this.next()).charCodeAt(0))&&t<58;);e!=D.QueryLexer.EOS&&this.backup()},D.QueryLexer.prototype.more=function(){return this.pos - {% if lang.t("search.language") != "en" %} - {% set languages = lang.t("search.language").split(",") %} - {% if languages | length and languages[0] != "" %} - {% set path = "assets/javascripts/lunr/" %} - - {% for language in languages | map("trim") %} - {% if language != "en" %} - {% if language == "ja" %} - - {% endif %} - {% if language in ("da", "de", "es", "fi", "fr", "hu", "it", "ja", "nl", "no", "pt", "ro", "ru", "sv", "th", "tr") %} - - {% endif %} - {% endif %} - {% endfor %} - {% if languages | length > 1 %} - - {% endif %} - {% endif %} - {% endif %} - - {% for path in config["extra_javascript"] %} - - {% endfor %} -{% endblock %} - {% block analytics %} {% endblock %} diff --git a/docs/requirements.txt b/docs/requirements.txt index 62b0ceade9..addd4f6856 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,7 @@ -mkdocs>=1.0 -pymdown-extensions==6.0 -mkdocs-material>=4.1 -Markdown==3.0.1 -markdown-fenced-code-tabs==1.0.5 -markdown-include==0.5.1 -MarkupSafe==1.1.0 +mkdocs>=1.0.4 +pymdown-extensions>=6.0 +mkdocs-material>=4.2 +Markdown>=3.1 +markdown-fenced-code-tabs>=1.0 +markdown-include>=0.5 +MarkupSafe>=1.1 From d8e3a22ff209b4bafc082073f46ee54a3076d353 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 2 May 2019 12:10:09 -0400 Subject: [PATCH 22/25] Handle case where peers advertise a listening port of 0 (#1391) --- .../ethereum/p2p/network/netty/DeFramer.java | 9 +++- .../pantheon/ethereum/p2p/peers/Peer.java | 2 +- .../pantheon/ethereum/p2p/wire/PeerInfo.java | 6 +++ .../p2p/network/netty/DeFramerTest.java | 53 +++++++++++++++++++ .../pantheon/cli/DefaultCommandValues.java | 1 - .../pegasys/pantheon/cli/PantheonCommand.java | 2 +- .../pegasys/pantheon/util/enode/EnodeURL.java | 3 +- 7 files changed, 70 insertions(+), 6 deletions(-) diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramer.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramer.java index 918c55aeb9..088bbab447 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramer.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramer.java @@ -168,12 +168,17 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L } private Peer createPeer(final PeerInfo peerInfo, final ChannelHandlerContext ctx) { - InetSocketAddress remoteAddress = ((InetSocketAddress) ctx.channel().remoteAddress()); + final InetSocketAddress remoteAddress = ((InetSocketAddress) ctx.channel().remoteAddress()); + int port = peerInfo.getPort(); + if (port == 0) { + // Most peers advertise a port of "0", just set a default best guess in this case + port = EnodeURL.DEFAULT_LISTENING_PORT; + } return DefaultPeer.fromEnodeURL( EnodeURL.builder() .nodeId(peerInfo.getNodeId()) .ipAddress(remoteAddress.getAddress()) - .listeningPort(peerInfo.getPort()) + .listeningPort(port) .build()); } diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/Peer.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/Peer.java index 863e1e9f07..fda98361e7 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/Peer.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/peers/Peer.java @@ -27,7 +27,7 @@ public interface Peer extends PeerId { * @return The generated peer ID. */ static BytesValue randomId() { - final byte[] id = new byte[64]; + final byte[] id = new byte[EnodeURL.NODE_ID_SIZE]; SecureRandomProvider.publicSecureRandom().nextBytes(id); return BytesValue.wrap(id); } diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/wire/PeerInfo.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/wire/PeerInfo.java index f7bd0daf56..0630d444ec 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/wire/PeerInfo.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/wire/PeerInfo.java @@ -77,6 +77,12 @@ public List getCapabilities() { return capabilities; } + /** + * This value is meant to represent the port at which a peer is listening for connections. + * However, most peers actually advertise a port of "0" so this value is not reliable. + * + * @return (Unreliable) The tcp port on which the peer is listening for connections + */ public int getPort() { return port; } diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramerTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramerTest.java index 2188bb05e2..e588b0248e 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramerTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramerTest.java @@ -172,6 +172,59 @@ public void decode_handlesHello() throws ExecutionException, InterruptedExceptio assertThat(out.size()).isEqualTo(1); } + @Test + public void decode_handlesHelloFromPeerWithAdvertisedPortOf0() + throws ExecutionException, InterruptedException { + ChannelFuture future = NettyMocks.channelFuture(false); + when(channel.closeFuture()).thenReturn(future); + + final Peer peer = createRemotePeer(); + final PeerInfo remotePeerInfo = + new PeerInfo( + peerInfo.getVersion(), + peerInfo.getClientId(), + peerInfo.getCapabilities(), + 0, + peer.getId()); + final DeFramer deFramer = createDeFramer(null); + + HelloMessage helloMessage = HelloMessage.create(remotePeerInfo); + ByteBuf data = Unpooled.wrappedBuffer(helloMessage.getData().extractArray()); + when(framer.deframe(eq(data))) + .thenReturn(new RawMessage(helloMessage.getCode(), helloMessage.getData())) + .thenReturn(null); + List out = new ArrayList<>(); + deFramer.decode(ctx, data, out); + + assertThat(connectFuture).isDone(); + assertThat(connectFuture).isNotCompletedExceptionally(); + PeerConnection peerConnection = connectFuture.get(); + assertThat(peerConnection.getPeerInfo()).isEqualTo(remotePeerInfo); + assertThat(out).isEmpty(); + + final EnodeURL expectedEnode = + EnodeURL.builder() + .ipAddress(remoteAddress.getAddress()) + .nodeId(peer.getId()) + // Listening port should be replaced with default port + .listeningPort(EnodeURL.DEFAULT_LISTENING_PORT) + .build(); + assertThat(peerConnection.getPeer().getEnodeURL()).isEqualTo(expectedEnode); + + // Next phase of pipeline should be setup + verify(pipeline, times(1)).addLast(any()); + + // Next message should be pushed out + PingMessage nextMessage = PingMessage.get(); + ByteBuf nextData = Unpooled.wrappedBuffer(nextMessage.getData().extractArray()); + when(framer.deframe(eq(nextData))) + .thenReturn(new RawMessage(nextMessage.getCode(), nextMessage.getData())) + .thenReturn(null); + verify(pipeline, times(1)).addLast(any()); + deFramer.decode(ctx, nextData, out); + assertThat(out.size()).isEqualTo(1); + } + @Test public void decode_handlesUnexpectedPeerId() { ChannelFuture future = NettyMocks.channelFuture(false); 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 98e189223f..9bbcd26be1 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/DefaultCommandValues.java @@ -57,7 +57,6 @@ public interface DefaultCommandValues { SyncMode DEFAULT_SYNC_MODE = SyncMode.FULL; int FAST_SYNC_MAX_WAIT_TIME = 0; int FAST_SYNC_MIN_PEER_COUNT = 5; - int P2P_PORT = 30303; int DEFAULT_MAX_PEERS = 25; static Path getDefaultPantheonDataPath(final Object command) { 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 c5523c22b7..e009e9135f 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -240,7 +240,7 @@ void setBootnodes(final List values) { paramLabel = MANDATORY_PORT_FORMAT_HELP, description = "Port on which to listen for p2p communication (default: ${DEFAULT-VALUE})", arity = "1") - private final Integer p2pPort = P2P_PORT; + private final Integer p2pPort = EnodeURL.DEFAULT_LISTENING_PORT; @Option( names = {"--network-id"}, diff --git a/util/src/main/java/tech/pegasys/pantheon/util/enode/EnodeURL.java b/util/src/main/java/tech/pegasys/pantheon/util/enode/EnodeURL.java index fa08ac32bd..33467a07bb 100644 --- a/util/src/main/java/tech/pegasys/pantheon/util/enode/EnodeURL.java +++ b/util/src/main/java/tech/pegasys/pantheon/util/enode/EnodeURL.java @@ -29,7 +29,8 @@ public class EnodeURL { - private static final int NODE_ID_SIZE = 64; + public static final int DEFAULT_LISTENING_PORT = 30303; + public static final int NODE_ID_SIZE = 64; private static final Pattern DISCPORT_QUERY_STRING_REGEX = Pattern.compile("^discport=([0-9]{1,5})$"); private static final Pattern NODE_ID_PATTERN = Pattern.compile("^[0-9a-fA-F]{128}$"); From ca960e8f075951f4facca9bddb0a41356371e650 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 2 May 2019 13:20:52 -0400 Subject: [PATCH 23/25] Add explanatory comment about default port (#1392) --- .../pantheon/ethereum/p2p/network/netty/DeFramer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramer.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramer.java index 088bbab447..a505268de2 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramer.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/network/netty/DeFramer.java @@ -171,7 +171,10 @@ private Peer createPeer(final PeerInfo peerInfo, final ChannelHandlerContext ctx final InetSocketAddress remoteAddress = ((InetSocketAddress) ctx.channel().remoteAddress()); int port = peerInfo.getPort(); if (port == 0) { - // Most peers advertise a port of "0", just set a default best guess in this case + // Most peers advertise a port of "0", just set a default best guess in this case. + // We can't simply use the remote address port because peers initiating inbound connections + // are free to choose any port they want for their side of the connection. The remote port + // does not actually correspond to the peer's listening port. port = EnodeURL.DEFAULT_LISTENING_PORT; } return DefaultPeer.fromEnodeURL( From fa7820098a8c8c63eb3daa5fed4f2643e6483385 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 3 May 2019 04:52:42 +1000 Subject: [PATCH 24/25] Rename thenEither to then. Include a hash in the getNodeData request when testing. --- .../ethereum/eth/manager/PendingPeerRequest.java | 2 +- .../eth/manager/task/AbstractPeerRequestTask.java | 2 +- .../pantheon/ethereum/eth/manager/EthPeersTest.java | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java index f11950e343..674d7fd698 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java @@ -93,7 +93,7 @@ private Optional getLeastBusySuitablePeer() { * @param onError handler for when there is no peer with sufficient height or the request fails to * send */ - public void thenEither( + public void then( final Consumer onSuccess, final Consumer onError) { result.whenComplete( (result, error) -> { diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java index 94fdb21f20..0574f6ccf2 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/task/AbstractPeerRequestTask.java @@ -51,7 +51,7 @@ public AbstractPeerRequestTask setTimeout(final Duration timeout) { protected final void executeTask() { final CompletableFuture promise = new CompletableFuture<>(); responseStream = sendRequest(); - responseStream.thenEither( + responseStream.then( stream -> { // Start the timeout now that the request has actually been sent ethContext.getScheduler().failAfterTimeout(promise, timeout); diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java index c0b8317da5..07c26f82ea 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.eth.manager; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; @@ -21,6 +22,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.eth.manager.RequestManager.ResponseStream; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.NoAvailablePeersException; import tech.pegasys.pantheon.ethereum.eth.manager.exceptions.PeerDisconnectedException; @@ -240,13 +242,13 @@ private void useAllAvailableCapacity(final EthPeer peer) throws PeerNotConnected } private void useRequestSlot(final EthPeer peer) throws PeerNotConnected { - peer.getNodeData(emptyList()); + peer.getNodeData(singletonList(Hash.ZERO)); } @SuppressWarnings("unchecked") private void assertRequestSuccessful(final PendingPeerRequest pendingRequest) { final Consumer onSuccess = mock(Consumer.class); - pendingRequest.thenEither(onSuccess, error -> fail("Request should have executed", error)); + pendingRequest.then(onSuccess, error -> fail("Request should have executed", error)); verify(onSuccess).accept(any()); } @@ -254,7 +256,7 @@ private void assertRequestSuccessful(final PendingPeerRequest pendingRequest) { private void assertRequestFailure( final PendingPeerRequest pendingRequest, final Class reason) { final Consumer errorHandler = mock(Consumer.class); - pendingRequest.thenEither( + pendingRequest.then( responseStream -> fail("Should not have performed request"), errorHandler); verify(errorHandler).accept(any(reason)); @@ -264,7 +266,7 @@ private void assertRequestFailure( private void assertNotDone(final PendingPeerRequest pendingRequest) { final Consumer onSuccess = mock(Consumer.class); final Consumer onError = mock(Consumer.class); - pendingRequest.thenEither(onSuccess, onError); + pendingRequest.then(onSuccess, onError); verifyZeroInteractions(onSuccess); verifyZeroInteractions(onError); From 6fd5b17a3fb358d4fe11aa91c29db12173369d33 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 3 May 2019 05:04:14 +1000 Subject: [PATCH 25/25] Spotless. --- .../pantheon/ethereum/eth/manager/PendingPeerRequest.java | 3 +-- .../pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java index 674d7fd698..3cd1d1d964 100644 --- a/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java +++ b/ethereum/eth/src/main/java/tech/pegasys/pantheon/ethereum/eth/manager/PendingPeerRequest.java @@ -93,8 +93,7 @@ private Optional getLeastBusySuitablePeer() { * @param onError handler for when there is no peer with sufficient height or the request fails to * send */ - public void then( - final Consumer onSuccess, final Consumer onError) { + public void then(final Consumer onSuccess, final Consumer onError) { result.whenComplete( (result, error) -> { if (error != null) { diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java index 07c26f82ea..f1139dcf80 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/manager/EthPeersTest.java @@ -256,8 +256,7 @@ private void assertRequestSuccessful(final PendingPeerRequest pendingRequest) { private void assertRequestFailure( final PendingPeerRequest pendingRequest, final Class reason) { final Consumer errorHandler = mock(Consumer.class); - pendingRequest.then( - responseStream -> fail("Should not have performed request"), errorHandler); + pendingRequest.then(responseStream -> fail("Should not have performed request"), errorHandler); verify(errorHandler).accept(any(reason)); }