Move connect decision into protocol layer (#4759)

Move the decision making for connecting or not connecting to peers into the eth layer. Future changes will take advantage if this to improve peering.

Signed-off-by: Stefan <stefan.pingel@consensys.net>

---------

Signed-off-by: Stefan <stefan.pingel@consensys.net>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
pull/5344/head
Stefan Pingel 2 years ago committed by GitHub
parent 067a263374
commit 534a369574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java
  2. 2
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java
  3. 2
      acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestPermissioningPlugin.java
  4. 39
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/OpenTelemetryAcceptanceTest.java
  5. 118
      besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
  6. 72
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  7. 13
      besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java
  8. 12
      besu/src/main/java/org/hyperledger/besu/controller/BesuController.java
  9. 53
      besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java
  10. 2
      besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java
  11. 2
      besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java
  12. 2
      besu/src/test/java/org/hyperledger/besu/PrivacyTest.java
  13. 5
      besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java
  14. 7
      besu/src/test/java/org/hyperledger/besu/RunnerTest.java
  15. 2
      besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java
  16. 2
      besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java
  17. 4
      besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java
  18. 64
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  19. 16
      besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java
  20. 28
      besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java
  21. 5
      besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java
  22. 25
      besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java
  23. 4
      besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java
  24. 6
      consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/protocol/BftProtocolManager.java
  25. 4
      consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/EthSynchronizerUpdaterTest.java
  26. 4
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/JsonRpcExecutor.java
  27. 9
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java
  28. 3
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java
  29. 17
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/MockPeerConnection.java
  30. 3
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminPeersTest.java
  31. 135
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java
  32. 398
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java
  33. 67
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java
  34. 6
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapProtocolManager.java
  35. 2
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiter.java
  36. 27
      ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager/MockPeerConnection.java
  37. 32
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/EthPeerTestUtil.java
  38. 62
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthPeerTest.java
  39. 18
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthPeersTest.java
  40. 64
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java
  41. 3
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RequestManagerTest.java
  42. 6
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java
  43. 10
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java
  44. 4
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java
  45. 15
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java
  46. 2
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java
  47. 23
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java
  48. 18
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java
  49. 31
      ethereum/mock-p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/testing/MockNetwork.java
  50. 4
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java
  51. 12
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/NetworkingConfiguration.java
  52. 47
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/RlpxConfiguration.java
  53. 7
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/DiscoveryPeer.java
  54. 64
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java
  55. 8
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/NetworkRunner.java
  56. 4
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/NoopP2PNetwork.java
  57. 12
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetwork.java
  58. 9
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/ProtocolManager.java
  59. 13
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeer.java
  60. 29
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/Peer.java
  61. 488
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java
  62. 56
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java
  63. 16
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/PeerConnection.java
  64. 254
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/RlpxConnection.java
  65. 8
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java
  66. 8
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java
  67. 3
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerInbound.java
  68. 3
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java
  69. 2
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java
  70. 6
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyPeerConnection.java
  71. 23
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/ShouldConnectCallback.java
  72. 66
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/config/RlpxConfigurationTest.java
  73. 26
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java
  74. 10
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java
  75. 77
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetworkTest.java
  76. 40
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java
  77. 707
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgentTest.java
  78. 3
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnectionTest.java
  79. 10
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/MockConnectionInitializer.java
  80. 15
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/MockPeerConnection.java
  81. 269
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/RlpxConnectionTest.java
  82. 3
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramerTest.java
  83. 2
      ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/node/InsufficientPeersPermissioningProvider.java
  84. 22
      ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/node/InsufficientPeersPermissioningProviderTest.java
  85. 13
      ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java
  86. 3
      util/src/main/java/org/hyperledger/besu/util/Subscribers.java

@ -168,7 +168,6 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
.build();
final int maxPeers = 25;
final int minPeers = 25;
builder
.synchronizerConfiguration(new SynchronizerConfiguration.Builder().build())
@ -187,7 +186,11 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
node.getPkiKeyStoreConfiguration()
.map(pkiConfig -> new PkiBlockCreationConfigurationProvider().load(pkiConfig)))
.evmConfiguration(EvmConfiguration.DEFAULT)
.maxPeers(maxPeers);
.maxPeers(maxPeers)
.lowerBoundPeers(maxPeers)
.maxRemotelyInitiatedPeers(15)
.networkConfiguration(node.getNetworkingConfiguration())
.randomPeerPriority(false);
node.getGenesisConfig()
.map(GenesisConfigFile::fromConfig)
@ -205,8 +208,6 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
.discovery(node.isDiscoveryEnabled())
.p2pAdvertisedHost(node.getHostName())
.p2pListenPort(0)
.maxPeers(maxPeers)
.minPeers(minPeers)
.networkingConfiguration(node.getNetworkingConfiguration())
.jsonRpcConfiguration(node.jsonRpcConfiguration())
.webSocketConfiguration(node.webSocketConfiguration())

@ -412,7 +412,7 @@ public class BesuNodeConfigurationBuilder {
.withCrlPath(toPath(crl));
break;
}
} catch (Exception e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
this.tlsConfiguration = Optional.of(builder.build());

@ -54,7 +54,7 @@ public class TestPermissioningPlugin implements BesuPlugin {
if (sourceEnode.toString().contains(bobNode)
|| destinationEnode.toString().contains(bobNode)) {
boolean isBobTalkingToAlice =
final boolean isBobTalkingToAlice =
sourceEnode.toString().contains(aliceNode)
|| destinationEnode.toString().contains(aliceNode);
if (isBobTalkingToAlice) {

@ -128,7 +128,7 @@ public class OpenTelemetryAcceptanceTest extends AcceptanceTestBase {
@BeforeEach
public void setUp() throws Exception {
System.setProperty("root.log.level", "DEBUG");
Server server =
final Server server =
NettyServerBuilder.forPort(4317)
.addService(fakeTracesCollector)
.addService(fakeMetricsCollector)
@ -136,14 +136,14 @@ public class OpenTelemetryAcceptanceTest extends AcceptanceTestBase {
.start();
closer.register(server::shutdownNow);
MetricsConfiguration configuration =
final MetricsConfiguration configuration =
MetricsConfiguration.builder()
.protocol(MetricsProtocol.OPENTELEMETRY)
.enabled(true)
.port(0)
.hostsAllowlist(singletonList("*"))
.build();
Map<String, String> env = new HashMap<>();
final Map<String, String> env = new HashMap<>();
env.put("OTEL_METRIC_EXPORT_INTERVAL", "1000");
env.put("OTEL_TRACES_SAMPLER", "always_on");
env.put("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317");
@ -173,7 +173,7 @@ public class OpenTelemetryAcceptanceTest extends AcceptanceTestBase {
WaitUtils.waitFor(
30,
() -> {
List<ResourceMetrics> resourceMetrics = fakeMetricsCollector.getReceivedMetrics();
final List<ResourceMetrics> resourceMetrics = fakeMetricsCollector.getReceivedMetrics();
assertThat(resourceMetrics.isEmpty()).isFalse();
});
}
@ -186,26 +186,26 @@ public class OpenTelemetryAcceptanceTest extends AcceptanceTestBase {
() -> {
// call the json RPC endpoint to generate a trace.
net.netVersion().verify(metricsNode);
List<ResourceSpans> spans = fakeTracesCollector.getReceivedSpans();
final List<ResourceSpans> spans = fakeTracesCollector.getReceivedSpans();
assertThat(spans.isEmpty()).isFalse();
Span internalSpan = spans.get(0).getScopeSpans(0).getSpans(0);
final Span internalSpan = spans.get(0).getScopeSpans(0).getSpans(0);
assertThat(internalSpan.getKind()).isEqualTo(Span.SpanKind.SPAN_KIND_INTERNAL);
ByteString parent = internalSpan.getParentSpanId();
final ByteString parent = internalSpan.getParentSpanId();
assertThat(parent.isEmpty()).isFalse();
Span serverSpan = spans.get(0).getScopeSpans(0).getSpans(1);
final Span serverSpan = spans.get(0).getScopeSpans(0).getSpans(1);
assertThat(serverSpan.getKind()).isEqualTo(Span.SpanKind.SPAN_KIND_SERVER);
ByteString rootSpanId = serverSpan.getParentSpanId();
final ByteString rootSpanId = serverSpan.getParentSpanId();
assertThat(rootSpanId.isEmpty()).isTrue();
});
}
@Test
public void traceReportingWithTraceId() {
Duration timeout = Duration.ofSeconds(1);
final Duration timeout = Duration.ofSeconds(1);
WaitUtils.waitFor(
60,
() -> {
OpenTelemetry openTelemetry =
final OpenTelemetry openTelemetry =
OpenTelemetrySdk.builder()
.setPropagators(
ContextPropagators.create(
@ -213,7 +213,7 @@ public class OpenTelemetryAcceptanceTest extends AcceptanceTestBase {
.setTracerProvider(
SdkTracerProvider.builder().setSampler(Sampler.alwaysOn()).build())
.build();
Call.Factory client =
final Call.Factory client =
OkHttpTelemetry.builder(openTelemetry)
.build()
.newCallFactory(
@ -222,7 +222,7 @@ public class OpenTelemetryAcceptanceTest extends AcceptanceTestBase {
.readTimeout(timeout)
.writeTimeout(timeout)
.build());
Request request =
final Request request =
new Request.Builder()
.url("http://localhost:" + metricsNode.getJsonRpcPort().get())
.post(
@ -230,18 +230,19 @@ public class OpenTelemetryAcceptanceTest extends AcceptanceTestBase {
"{\"jsonrpc\":\"2.0\",\"method\":\"net_version\",\"params\":[],\"id\":255}",
MediaType.get("application/json")))
.build();
Response response = client.newCall(request).execute();
final Response response = client.newCall(request).execute();
try {
assertThat(response.code()).isEqualTo(200);
List<ResourceSpans> spans = new ArrayList<>(fakeTracesCollector.getReceivedSpans());
final List<ResourceSpans> spans =
new ArrayList<>(fakeTracesCollector.getReceivedSpans());
assertThat(spans.isEmpty()).isFalse();
Span internalSpan = spans.get(0).getScopeSpans(0).getSpans(0);
final Span internalSpan = spans.get(0).getScopeSpans(0).getSpans(0);
assertThat(internalSpan.getKind()).isEqualTo(Span.SpanKind.SPAN_KIND_INTERNAL);
ByteString parent = internalSpan.getParentSpanId();
final ByteString parent = internalSpan.getParentSpanId();
assertThat(parent.isEmpty()).isFalse();
Span serverSpan = spans.get(0).getScopeSpans(0).getSpans(1);
final Span serverSpan = spans.get(0).getScopeSpans(0).getSpans(1);
assertThat(serverSpan.getKind()).isEqualTo(Span.SpanKind.SPAN_KIND_SERVER);
ByteString rootSpanId = serverSpan.getParentSpanId();
final ByteString rootSpanId = serverSpan.getParentSpanId();
assertThat(rootSpanId.isEmpty()).isFalse();
} finally {
response.close();

@ -72,6 +72,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.FlexiblePrivacyPrecompiledContract;
@ -168,10 +169,6 @@ public class RunnerBuilder {
private NatMethod natMethod = NatMethod.AUTO;
private String natManagerServiceName;
private boolean natMethodFallbackEnabled;
private int maxPeers;
private int minPeers;
private boolean limitRemoteWireConnectionsEnabled = false;
private float fractionRemoteConnectionsAllowed;
private EthNetworkConfig ethNetworkConfig;
private EthstatsOptions ethstatsOptions;
private JsonRpcConfiguration jsonRpcConfiguration;
@ -189,7 +186,6 @@ public class RunnerBuilder {
private Optional<String> identityString = Optional.empty();
private BesuPluginContextImpl besuPluginContext;
private boolean autoLogBloomCaching = true;
private boolean randomPeerPriority;
private StorageProvider storageProvider;
private RpcEndpointServiceImpl rpcEndpointServiceImpl;
private JsonRpcIpcConfiguration jsonRpcIpcConfiguration;
@ -354,52 +350,6 @@ public class RunnerBuilder {
return this;
}
/**
* Add Max peers.
*
* @param maxPeers the max peers
* @return the runner builder
*/
public RunnerBuilder maxPeers(final int maxPeers) {
this.maxPeers = maxPeers;
return this;
}
/**
* Enable Limit remote wire connections.
*
* @param limitRemoteWireConnectionsEnabled the limit remote wire connections enabled
* @return the runner builder
*/
public RunnerBuilder limitRemoteWireConnectionsEnabled(
final boolean limitRemoteWireConnectionsEnabled) {
this.limitRemoteWireConnectionsEnabled = limitRemoteWireConnectionsEnabled;
return this;
}
/**
* Add Fraction remote connections allowed.
*
* @param fractionRemoteConnectionsAllowed the fraction remote connections allowed
* @return the runner builder
*/
public RunnerBuilder fractionRemoteConnectionsAllowed(
final float fractionRemoteConnectionsAllowed) {
this.fractionRemoteConnectionsAllowed = fractionRemoteConnectionsAllowed;
return this;
}
/**
* Enable Random peer priority.
*
* @param randomPeerPriority the random peer priority
* @return the runner builder
*/
public RunnerBuilder randomPeerPriority(final boolean randomPeerPriority) {
this.randomPeerPriority = randomPeerPriority;
return this;
}
/**
* Add EthStatsOptions
*
@ -684,12 +634,8 @@ public class RunnerBuilder {
RlpxConfiguration.create()
.setBindHost(p2pListenInterface)
.setBindPort(p2pListenPort)
.setPeerUpperBound(maxPeers)
.setPeerLowerBound(minPeers)
.setSupportedProtocols(subProtocols)
.setClientId(BesuInfo.nodeName(identityString))
.setLimitRemoteWireConnectionsEnabled(limitRemoteWireConnectionsEnabled)
.setFractionRemoteWireConnectionsAllowed(fractionRemoteConnectionsAllowed);
.setClientId(BesuInfo.nodeName(identityString));
networkingConfiguration.setRlpx(rlpxConfiguration).setDiscovery(discoveryConfiguration);
final PeerPermissionsDenylist bannedNodes = PeerPermissionsDenylist.create();
@ -719,23 +665,27 @@ public class RunnerBuilder {
final NatService natService = new NatService(buildNatManager(natMethod), fallbackEnabled);
final NetworkBuilder inactiveNetwork = caps -> new NoopP2PNetwork();
final NetworkBuilder activeNetwork =
caps ->
DefaultP2PNetwork.builder()
.vertx(vertx)
.nodeKey(nodeKey)
.config(networkingConfiguration)
.legacyForkIdEnabled(legacyForkIdEnabled)
.peerPermissions(peerPermissions)
.metricsSystem(metricsSystem)
.supportedCapabilities(caps)
.natService(natService)
.randomPeerPriority(randomPeerPriority)
.storageProvider(storageProvider)
.p2pTLSConfiguration(p2pTLSConfiguration)
.blockchain(context.getBlockchain())
.blockNumberForks(besuController.getGenesisConfigOptions().getForkBlockNumbers())
.timestampForks(besuController.getGenesisConfigOptions().getForkBlockTimestamps())
.build();
caps -> {
final EthPeers ethPeers = besuController.getEthPeers();
return DefaultP2PNetwork.builder()
.vertx(vertx)
.nodeKey(nodeKey)
.config(networkingConfiguration)
.legacyForkIdEnabled(legacyForkIdEnabled)
.peerPermissions(peerPermissions)
.metricsSystem(metricsSystem)
.supportedCapabilities(caps)
.natService(natService)
.storageProvider(storageProvider)
.p2pTLSConfiguration(p2pTLSConfiguration)
.blockchain(context.getBlockchain())
.blockNumberForks(besuController.getGenesisConfigOptions().getForkBlockNumbers())
.timestampForks(besuController.getGenesisConfigOptions().getForkBlockTimestamps())
.allConnectionsSupplier(ethPeers::getAllConnections)
.allActiveConnectionsSupplier(ethPeers::getAllActiveConnections)
.peersLowerBound(ethPeers.getPeerLowerBound())
.build();
};
final NetworkRunner networkRunner =
NetworkRunner.builder()
@ -745,6 +695,8 @@ public class RunnerBuilder {
.metricsSystem(metricsSystem)
.build();
besuController.getEthPeers().setRlpxAgent(networkRunner.getRlpxAgent());
final P2PNetwork network = networkRunner.getNetwork();
// ForkId in Ethereum Node Record needs updating when we transition to a new protocol spec
context
@ -1423,26 +1375,6 @@ public class RunnerBuilder {
return MetricsService.create(vertx, configuration, metricsSystem);
}
/**
* Gets minimum peers.
*
* @return the minimum peers
*/
public int getMinPeers() {
return minPeers;
}
/**
* Add Minimum peers.
*
* @param minPeers the minimum peers
* @return the runner builder
*/
public RunnerBuilder minPeers(final int minPeers) {
this.minPeers = minPeers;
return this;
}
/**
* Add Legacy fork id.
*

@ -15,6 +15,7 @@
package org.hyperledger.besu.cli;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
@ -320,6 +321,10 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
private RocksDBPlugin rocksDBPlugin;
private int maxPeers;
private int maxRemoteInitiatedPeers;
private int peersLowerBound;
// CLI options defined by user at runtime.
// Options parsing is done with CLI library Picocli https://picocli.info/
@ -434,8 +439,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
description = "Maximum P2P connections that can be established (default: ${DEFAULT-VALUE})")
private final Integer maxPeers = DEFAULT_MAX_PEERS;
private int minPeers;
@Option(
names = {"--remote-connections-limit-enabled"},
description =
@ -464,7 +467,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
names = {"--random-peer-priority-enabled"},
description =
"Allow for incoming connections to be prioritized randomly. This will prevent (typically small, stable) networks from forming impenetrable peer cliques. (default: ${DEFAULT-VALUE})")
private final Boolean randomPeerPriority = false;
private final Boolean randomPeerPriority = Boolean.FALSE;
@Option(
names = {"--banned-node-ids", "--banned-node-id"},
@ -1489,7 +1492,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
validateOptions();
configure();
configureNativeLibs();
initController();
besuController = initController();
besuPluginContext.beforeExternalServices();
@ -1686,8 +1689,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
p2pTLSConfiguration,
p2PDiscoveryOptionGroup.peerDiscoveryEnabled,
ethNetworkConfig,
p2PDiscoveryOptionGroup.maxPeers,
p2PDiscoveryOptionGroup.minPeers,
p2PDiscoveryOptionGroup.p2pHost,
p2PDiscoveryOptionGroup.p2pInterface,
p2PDiscoveryOptionGroup.p2pPort,
@ -1974,17 +1975,29 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
}
private void ensureValidPeerBoundParams() {
final int min = unstableNetworkingOptions.toDomainObject().getRlpx().getPeerLowerBound();
final int max = p2PDiscoveryOptionGroup.maxPeers;
if (min > max) {
logger.warn("`--Xp2p-peer-lower-bound` " + min + " must not exceed --max-peers " + max);
// modify the --X lower-bound value if it's not valid, we don't want unstable defaults
// breaking things
logger.warn("setting --Xp2p-peer-lower-bound=" + max);
unstableNetworkingOptions.toDomainObject().getRlpx().setPeerLowerBound(max);
p2PDiscoveryOptionGroup.minPeers = max;
maxPeers = p2PDiscoveryOptionGroup.maxPeers;
peersLowerBound = unstableNetworkingOptions.toDomainObject().getPeerLowerBound();
if (peersLowerBound > maxPeers) {
logger.warn(
"`--Xp2p-peer-lower-bound` "
+ peersLowerBound
+ " must not exceed --max-peers "
+ maxPeers);
logger.warn("setting --Xp2p-peer-lower-bound=" + maxPeers);
peersLowerBound = maxPeers;
}
final Boolean isLimitRemoteWireConnectionsEnabled =
p2PDiscoveryOptionGroup.isLimitRemoteWireConnectionsEnabled;
if (isLimitRemoteWireConnectionsEnabled) {
final float fraction =
Fraction.fromPercentage(p2PDiscoveryOptionGroup.maxRemoteConnectionsPercentage)
.getValue();
checkState(
fraction >= 0.0 && fraction <= 1.0,
"Fraction of remote connections allowed must be between 0.0 and 1.0 (inclusive).");
maxRemoteInitiatedPeers = (int) Math.floor(fraction * maxPeers);
} else {
p2PDiscoveryOptionGroup.minPeers = min;
maxRemoteInitiatedPeers = maxPeers;
}
}
@ -1996,7 +2009,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
|| rpcEndpointServiceImpl.hasNamespace(apiName);
if (!jsonRPCHttpOptionGroup.rpcHttpApis.stream().allMatch(configuredApis)) {
List<String> invalidHttpApis = new ArrayList<String>(jsonRPCHttpOptionGroup.rpcHttpApis);
final List<String> invalidHttpApis =
new ArrayList<String>(jsonRPCHttpOptionGroup.rpcHttpApis);
invalidHttpApis.removeAll(VALID_APIS);
throw new ParameterException(
this.commandLine,
@ -2005,12 +2019,12 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
}
if (!jsonRPCWebsocketOptionGroup.rpcWsApis.stream().allMatch(configuredApis)) {
List<String> invalidWsApis = new ArrayList<String>(jsonRPCWebsocketOptionGroup.rpcWsApis);
final List<String> invalidWsApis =
new ArrayList<String>(jsonRPCWebsocketOptionGroup.rpcWsApis);
invalidWsApis.removeAll(VALID_APIS);
throw new ParameterException(
this.commandLine,
"Invalid value for option '--rpc-ws-api': invalid entries found "
+ invalidWsApis.toString());
"Invalid value for option '--rpc-ws-api': invalid entries found " + invalidWsApis);
}
final boolean validHttpApiMethods =
@ -2212,8 +2226,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
}
}
private void initController() {
besuController = buildController();
private BesuController initController() {
return buildController();
}
/**
@ -2241,6 +2255,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
updateNetworkConfig(network), genesisConfigOverrides, getDefaultSyncModeIfNotSet())
.synchronizerConfiguration(buildSyncConfig())
.ethProtocolConfiguration(unstableEthProtocolOptions.toDomainObject())
.networkConfiguration(unstableNetworkingOptions.toDomainObject())
.dataDirectory(dataDir())
.miningParameters(
new MiningParameters.Builder()
@ -2284,6 +2299,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
.evmConfiguration(unstableEvmOptions.toDomainObject())
.dataStorageConfiguration(dataStorageOptions.toDomainObject())
.maxPeers(p2PDiscoveryOptionGroup.maxPeers)
.lowerBoundPeers(peersLowerBound)
.maxRemotelyInitiatedPeers(maxRemoteInitiatedPeers)
.randomPeerPriority(p2PDiscoveryOptionGroup.randomPeerPriority)
.chainPruningConfiguration(unstableChainPruningOptions.toDomainObject());
}
@ -2944,8 +2962,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
final Optional<TLSConfiguration> p2pTLSConfiguration,
final boolean peerDiscoveryEnabled,
final EthNetworkConfig ethNetworkConfig,
final int maxPeers,
final int minPeers,
final String p2pAdvertisedHost,
final String p2pListenInterface,
final int p2pListenPort,
@ -2979,14 +2995,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
.p2pAdvertisedHost(p2pAdvertisedHost)
.p2pListenInterface(p2pListenInterface)
.p2pListenPort(p2pListenPort)
.maxPeers(maxPeers)
.minPeers(minPeers)
.limitRemoteWireConnectionsEnabled(
p2PDiscoveryOptionGroup.isLimitRemoteWireConnectionsEnabled)
.fractionRemoteConnectionsAllowed(
Fraction.fromPercentage(p2PDiscoveryOptionGroup.maxRemoteConnectionsPercentage)
.getValue())
.randomPeerPriority(p2PDiscoveryOptionGroup.randomPeerPriority)
.networkingConfiguration(unstableNetworkingOptions.toDomainObject())
.legacyForkId(unstableEthProtocolOptions.toDomainObject().isLegacyEth64ForkIdEnabled())
.graphQLConfiguration(graphQLConfiguration)

@ -27,13 +27,13 @@ import picocli.CommandLine;
/** The Networking Cli options. */
public class NetworkingOptions implements CLIOptions<NetworkingConfiguration> {
public static final String PEER_LOWER_BOUND_FLAG = "--Xp2p-peer-lower-bound";
private final String INITIATE_CONNECTIONS_FREQUENCY_FLAG =
"--Xp2p-initiate-connections-frequency";
private final String CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_FLAG =
"--Xp2p-check-maintained-connections-frequency";
private final String DNS_DISCOVERY_SERVER_OVERRIDE_FLAG = "--Xp2p-dns-discovery-server";
private final String DISCOVERY_PROTOCOL_V5_ENABLED = "--Xv5-discovery-enabled";
private final String P2P_PEER_LOWER_BOUND_FLAG = "--Xp2p-peer-lower-bound";
/** The constant FILTER_ON_ENR_FORK_ID. */
public static final String FILTER_ON_ENR_FORK_ID = "--Xfilter-on-enr-fork-id";
@ -80,10 +80,10 @@ public class NetworkingOptions implements CLIOptions<NetworkingConfiguration> {
@CommandLine.Option(
hidden = true,
names = {P2P_PEER_LOWER_BOUND_FLAG},
names = PEER_LOWER_BOUND_FLAG,
description =
"Lower bound on the target number of P2P connections (default: ${DEFAULT-VALUE})")
private final Integer peerLowerBound = DefaultCommandValues.DEFAULT_P2P_PEER_LOWER_BOUND;
private Integer peerLowerBoundConfig = DefaultCommandValues.DEFAULT_P2P_PEER_LOWER_BOUND;
private NetworkingOptions() {}
@ -109,6 +109,7 @@ public class NetworkingOptions implements CLIOptions<NetworkingConfiguration> {
cliOptions.initiateConnectionsFrequencySec =
networkingConfig.getInitiateConnectionsFrequencySec();
cliOptions.dnsDiscoveryServerOverride = networkingConfig.getDnsDiscoveryServerOverride();
cliOptions.peerLowerBoundConfig = networkingConfig.getPeerLowerBound();
return cliOptions;
}
@ -121,7 +122,7 @@ public class NetworkingOptions implements CLIOptions<NetworkingConfiguration> {
config.setDnsDiscoveryServerOverride(dnsDiscoveryServerOverride);
config.getDiscovery().setDiscoveryV5Enabled(isPeerDiscoveryV5Enabled);
config.getDiscovery().setFilterOnEnrForkId(filterOnEnrForkId);
config.getRlpx().setPeerLowerBound(peerLowerBound);
config.setPeerLowerBound(peerLowerBoundConfig);
return config;
}
@ -132,7 +133,9 @@ public class NetworkingOptions implements CLIOptions<NetworkingConfiguration> {
CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_FLAG,
OptionParser.format(checkMaintainedConnectionsFrequencySec),
INITIATE_CONNECTIONS_FREQUENCY_FLAG,
OptionParser.format(initiateConnectionsFrequencySec));
OptionParser.format(initiateConnectionsFrequencySec),
PEER_LOWER_BOUND_FLAG,
OptionParser.format((peerLowerBoundConfig)));
if (dnsDiscoveryServerOverride.isPresent()) {
retval.add(DNS_DISCOVERY_SERVER_OVERRIDE_FLAG);

@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager;
import org.hyperledger.besu.ethereum.eth.sync.SyncMode;
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
@ -73,6 +74,7 @@ public class BesuController implements java.io.Closeable {
private final MiningParameters miningParameters;
private final PluginServiceFactory additionalPluginServices;
private final SyncState syncState;
private final EthPeers ethPeers;
/**
* Instantiates a new Besu controller.
@ -108,7 +110,8 @@ public class BesuController implements java.io.Closeable {
final JsonRpcMethods additionalJsonRpcMethodsFactory,
final NodeKey nodeKey,
final List<Closeable> closeables,
final PluginServiceFactory additionalPluginServices) {
final PluginServiceFactory additionalPluginServices,
final EthPeers ethPeers) {
this.protocolSchedule = protocolSchedule;
this.protocolContext = protocolContext;
this.ethProtocolManager = ethProtocolManager;
@ -124,6 +127,7 @@ public class BesuController implements java.io.Closeable {
this.closeables = closeables;
this.miningParameters = miningParameters;
this.additionalPluginServices = additionalPluginServices;
this.ethPeers = ethPeers;
}
/**
@ -207,6 +211,10 @@ public class BesuController implements java.io.Closeable {
return miningCoordinator;
}
public EthPeers getEthPeers() {
return ethPeers;
}
@Override
public void close() {
closeables.forEach(this::tryClose);
@ -215,7 +223,7 @@ public class BesuController implements java.io.Closeable {
private void tryClose(final Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {
} catch (final IOException e) {
LOG.error("Unable to close resource.", e);
}
}

@ -76,6 +76,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfigurati
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
@ -169,9 +170,15 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
protected EvmConfiguration evmConfiguration;
/** The Max peers. */
protected int maxPeers;
private int peerLowerBound;
private int maxRemotelyInitiatedPeers;
/** The Chain pruner configuration. */
protected ChainPrunerConfiguration chainPrunerConfiguration = ChainPrunerConfiguration.DEFAULT;
private NetworkingConfiguration networkingConfiguration;
private Boolean randomPeerPriority;
/**
* Storage provider besu controller builder.
*
@ -443,6 +450,29 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
return this;
}
/**
* Lower bound of peers where we stop actively trying to initiate new outgoing connections
*
* @param peerLowerBound lower bound of peers where we stop actively trying to initiate new
* outgoing connections
* @return the besu controller builder
*/
public BesuControllerBuilder lowerBoundPeers(final int peerLowerBound) {
this.peerLowerBound = peerLowerBound;
return this;
}
/**
* Maximum number of remotely initiated peer connections
*
* @param maxRemotelyInitiatedPeers aximum number of remotely initiated peer connections
* @return the besu controller builder
*/
public BesuControllerBuilder maxRemotelyInitiatedPeers(final int maxRemotelyInitiatedPeers) {
this.maxRemotelyInitiatedPeers = maxRemotelyInitiatedPeers;
return this;
}
/**
* Chain pruning configuration besu controller builder.
*
@ -455,6 +485,17 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
return this;
}
public BesuControllerBuilder networkConfiguration(
final NetworkingConfiguration networkingConfiguration) {
this.networkingConfiguration = networkingConfiguration;
return this;
}
public BesuControllerBuilder randomPeerPriority(final Boolean randomPeerPriority) {
this.randomPeerPriority = randomPeerPriority;
return this;
}
/**
* Build besu controller.
*
@ -475,6 +516,7 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
checkNotNull(storageProvider, "Must supply a storage provider");
checkNotNull(gasLimitCalculator, "Missing gas limit calculator");
checkNotNull(evmConfiguration, "Missing evm config");
checkNotNull(networkingConfiguration, "Missing network configuration");
prepForBuild();
final ProtocolSchedule protocolSchedule = createProtocolSchedule();
@ -557,9 +599,13 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
currentProtocolSpecSupplier,
clock,
metricsSystem,
maxPeers,
maxMessageSize,
messagePermissioningProviders);
messagePermissioningProviders,
nodeKey.getPublicKey().getEncodedBytes(),
peerLowerBound,
maxPeers,
maxRemotelyInitiatedPeers,
randomPeerPriority);
final EthMessages ethMessages = new EthMessages();
final EthMessages snapMessages = new EthMessages();
@ -679,7 +725,8 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
additionalJsonRpcMethodFactory,
nodeKey,
closeables,
additionalPluginServices);
additionalPluginServices,
ethPeers);
}
/**

@ -287,7 +287,7 @@ public class TransitionBesuControllerBuilder extends BesuControllerBuilder {
@Override
public BesuController build() {
BesuController controller = super.build();
final BesuController controller = super.build();
PostMergeContext.get().setSyncState(controller.getSyncState());
return controller;
}

@ -48,6 +48,7 @@ import org.hyperledger.besu.ethereum.eth.sync.SyncMode;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver;
import org.hyperledger.besu.ethereum.privacy.PrivateTransaction;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap;
@ -199,6 +200,7 @@ public class PrivacyReorgTest {
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.gasLimitCalculator(GasLimitCalculator.constant())
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.build();
}

@ -38,6 +38,7 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.SyncMode;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider;
import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
@ -120,6 +121,7 @@ public class PrivacyTest {
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.gasLimitCalculator(GasLimitCalculator.constant())
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.build();
}

@ -55,11 +55,13 @@ import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager;
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
@ -138,6 +140,7 @@ public final class RunnerBuilderTest {
when(besuController.getSynchronizer()).thenReturn(mock(Synchronizer.class));
when(besuController.getMiningCoordinator()).thenReturn(mock(MiningCoordinator.class));
when(besuController.getMiningCoordinator()).thenReturn(mock(MergeMiningCoordinator.class));
when(besuController.getEthPeers()).thenReturn(mock(EthPeers.class));
final GenesisConfigOptions genesisConfigOptions = mock(GenesisConfigOptions.class);
when(genesisConfigOptions.getForkBlockNumbers()).thenReturn(Collections.emptyList());
when(genesisConfigOptions.getForkBlockTimestamps()).thenReturn(Collections.emptyList());
@ -389,6 +392,7 @@ public final class RunnerBuilderTest {
.storageProvider(mock(KeyValueStorageProvider.class))
.rpcEndpointService(new RpcEndpointServiceImpl())
.besuPluginContext(mock(BesuPluginContextImpl.class))
.networkingConfiguration(NetworkingConfiguration.create())
.build();
assertThat(runner.getJsonRpcPort()).isPresent();
@ -435,6 +439,7 @@ public final class RunnerBuilderTest {
.storageProvider(mock(KeyValueStorageProvider.class))
.rpcEndpointService(new RpcEndpointServiceImpl())
.besuPluginContext(mock(BesuPluginContextImpl.class))
.networkingConfiguration(NetworkingConfiguration.create())
.build();
verify(mockTransitionCoordinator, times(1)).getPreMergeObject();

@ -52,6 +52,7 @@ import org.hyperledger.besu.ethereum.mainnet.BlockImportResult;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
@ -188,7 +189,6 @@ public final class RunnerTest {
.discovery(true)
.p2pAdvertisedHost(listenHost)
.p2pListenPort(0)
.maxPeers(3)
.metricsSystem(noOpMetricsSystem)
.permissioningService(new PermissioningServiceImpl())
.staticNodes(emptySet())
@ -459,6 +459,11 @@ public final class RunnerTest {
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.gasLimitCalculator(GasLimitCalculator.constant())
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.randomPeerPriority(Boolean.FALSE)
.maxPeers(25)
.lowerBoundPeers(25)
.maxRemotelyInitiatedPeers(15)
.build();
}
}

@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.util.RawBlockIterator;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
@ -98,6 +99,7 @@ public final class RlpBlockExporterTest {
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.gasLimitCalculator(GasLimitCalculator.constant())
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.build();
}

@ -37,6 +37,7 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.SyncMode;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.testutil.TestClock;
@ -432,6 +433,7 @@ public abstract class JsonBlockImporterTest {
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.gasLimitCalculator(GasLimitCalculator.constant())
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.build();
}
}

@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.SyncMode;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.testutil.BlockTestUtil;
@ -75,6 +76,7 @@ public final class RlpBlockImporterTest {
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.gasLimitCalculator(GasLimitCalculator.constant())
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.build();
final RlpBlockImporter.ImportResult result =
rlpBlockImporter.importBlockchain(source, targetController, false);
@ -107,6 +109,7 @@ public final class RlpBlockImporterTest {
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.gasLimitCalculator(GasLimitCalculator.constant())
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.build();
assertThatThrownBy(
@ -136,6 +139,7 @@ public final class RlpBlockImporterTest {
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
.gasLimitCalculator(GasLimitCalculator.constant())
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.build();
final RlpBlockImporter.ImportResult result =

@ -249,8 +249,6 @@ public class BesuCommandTest extends CommandTestAbstract {
MAINNET_DISCOVERY_URL));
verify(mockRunnerBuilder).p2pAdvertisedHost(eq("127.0.0.1"));
verify(mockRunnerBuilder).p2pListenPort(eq(30303));
verify(mockRunnerBuilder).maxPeers(eq(maxPeers));
verify(mockRunnerBuilder).fractionRemoteConnectionsAllowed(eq(0.6f));
verify(mockRunnerBuilder).jsonRpcConfiguration(eq(DEFAULT_JSON_RPC_CONFIGURATION));
verify(mockRunnerBuilder).graphQLConfiguration(eq(DEFAULT_GRAPH_QL_CONFIGURATION));
verify(mockRunnerBuilder).webSocketConfiguration(eq(DEFAULT_WEB_SOCKET_CONFIGURATION));
@ -271,6 +269,8 @@ public class BesuCommandTest extends CommandTestAbstract {
verify(mockControllerBuilder).storageProvider(storageProviderArgumentCaptor.capture());
verify(mockControllerBuilder).gasLimitCalculator(eq(GasLimitCalculator.constant()));
verify(mockControllerBuilder).maxPeers(eq(maxPeers));
verify(mockControllerBuilder).lowerBoundPeers(eq(maxPeers));
verify(mockControllerBuilder).maxRemotelyInitiatedPeers(eq((int) Math.floor(0.6 * maxPeers)));
verify(mockControllerBuilder).build();
assertThat(storageProviderArgumentCaptor.getValue()).isNotNull();
@ -401,7 +401,6 @@ public class BesuCommandTest extends CommandTestAbstract {
verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture());
verify(mockRunnerBuilder).p2pAdvertisedHost(eq("1.2.3.4"));
verify(mockRunnerBuilder).p2pListenPort(eq(1234));
verify(mockRunnerBuilder).maxPeers(eq(42));
verify(mockRunnerBuilder).jsonRpcConfiguration(eq(jsonRpcConfiguration));
verify(mockRunnerBuilder).graphQLConfiguration(eq(graphQLConfiguration));
verify(mockRunnerBuilder).webSocketConfiguration(eq(webSocketConfiguration));
@ -876,9 +875,6 @@ public class BesuCommandTest extends CommandTestAbstract {
MAINNET_DISCOVERY_URL));
verify(mockRunnerBuilder).p2pAdvertisedHost(eq("127.0.0.1"));
verify(mockRunnerBuilder).p2pListenPort(eq(30303));
verify(mockRunnerBuilder).maxPeers(eq(25));
verify(mockRunnerBuilder).limitRemoteWireConnectionsEnabled(eq(true));
verify(mockRunnerBuilder).fractionRemoteConnectionsAllowed(eq(0.6f));
verify(mockRunnerBuilder).jsonRpcConfiguration(eq(jsonRpcConfiguration));
verify(mockRunnerBuilder).graphQLConfiguration(eq(graphQLConfiguration));
verify(mockRunnerBuilder).webSocketConfiguration(eq(webSocketConfiguration));
@ -1581,8 +1577,8 @@ public class BesuCommandTest extends CommandTestAbstract {
final int maxPeers = 123;
parseCommand("--max-peers", String.valueOf(maxPeers));
verify(mockRunnerBuilder).maxPeers(intArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
verify(mockControllerBuilder).maxPeers(intArgumentCaptor.capture());
verify(mockControllerBuilder).build();
assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers);
@ -1613,10 +1609,10 @@ public class BesuCommandTest extends CommandTestAbstract {
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
verify(mockRunnerBuilder).maxPeers(intArgumentCaptor.capture());
verify(mockControllerBuilder).maxPeers(intArgumentCaptor.capture());
assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers);
verify(mockRunnerBuilder).minPeers(intArgumentCaptor.capture());
verify(mockControllerBuilder).lowerBoundPeers(intArgumentCaptor.capture());
assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers);
verify(mockRunnerBuilder).build();
@ -1648,10 +1644,10 @@ public class BesuCommandTest extends CommandTestAbstract {
"--Xp2p-peer-lower-bound",
String.valueOf(minPeers));
verify(mockRunnerBuilder).maxPeers(intArgumentCaptor.capture());
verify(mockControllerBuilder).maxPeers(intArgumentCaptor.capture());
assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers);
verify(mockRunnerBuilder).minPeers(intArgumentCaptor.capture());
verify(mockControllerBuilder).lowerBoundPeers(intArgumentCaptor.capture());
assertThat(intArgumentCaptor.getValue()).isEqualTo(minPeers);
verify(mockRunnerBuilder).build();
@ -1665,16 +1661,16 @@ public class BesuCommandTest extends CommandTestAbstract {
final int remoteConnectionsPercentage = 12;
parseCommand(
"--remote-connections-limit-enabled",
"--remote-connections-limit-enabled=true",
"--remote-connections-max-percentage",
String.valueOf(remoteConnectionsPercentage));
verify(mockRunnerBuilder).fractionRemoteConnectionsAllowed(floatCaptor.capture());
verify(mockRunnerBuilder).build();
verify(mockControllerBuilder).maxRemotelyInitiatedPeers(intArgumentCaptor.capture());
verify(mockControllerBuilder).build();
assertThat(floatCaptor.getValue())
assertThat(intArgumentCaptor.getValue())
.isEqualTo(
Fraction.fromPercentage(Percentage.fromInt(remoteConnectionsPercentage)).getValue());
(int) Math.floor(25 * Fraction.fromPercentage(remoteConnectionsPercentage).getValue()));
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
@ -1706,22 +1702,6 @@ public class BesuCommandTest extends CommandTestAbstract {
"should be a number between 0 and 100 inclusive");
}
@Test
public void enableRandomConnectionPrioritization() {
parseCommand("--random-peer-priority-enabled");
verify(mockRunnerBuilder).randomPeerPriority(eq(true));
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void randomConnectionPrioritizationDisabledByDefault() {
parseCommand();
verify(mockRunnerBuilder).randomPeerPriority(eq(false));
assertThat(commandOutput.toString(UTF_8)).isEmpty();
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
}
@Test
public void syncMode_fast() {
parseCommand("--sync-mode", "FAST");
@ -1915,7 +1895,7 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test
public void launcherDefaultOptionValue() {
TestBesuCommand besuCommand = parseCommand();
final TestBesuCommand besuCommand = parseCommand();
assertThat(besuCommand.getLauncherOptions().isLauncherMode()).isFalse();
assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isFalse();
@ -3379,7 +3359,7 @@ public class BesuCommandTest extends CommandTestAbstract {
@Test
public void rpcWsApiPropertyMustBeUsed() {
TestBesuCommand command = parseCommand("--rpc-ws-enabled", "--rpc-ws-api", "ETH, NET");
final TestBesuCommand command = parseCommand("--rpc-ws-enabled", "--rpc-ws-api", "ETH, NET");
assertThat(command).isNotNull();
verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture());
@ -5556,6 +5536,20 @@ public class BesuCommandTest extends CommandTestAbstract {
"PoS checkpoint sync can't be used with TTD = 0 and checkpoint totalDifficulty = 0");
}
@Test
public void checkP2pPeerLowerBound_isSet() {
final int lowerBound = 13;
parseCommand("--Xp2p-peer-lower-bound", String.valueOf(lowerBound));
verify(mockControllerBuilder).lowerBoundPeers(intArgumentCaptor.capture());
verify(mockControllerBuilder).build();
assertThat(intArgumentCaptor.getValue()).isEqualTo(lowerBound);
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
}
@Test
public void kzgTrustedSetupFileRequiresDataBlobEnabledNetwork() throws IOException {
final Path genesisFileWithoutBlobs =

@ -17,7 +17,6 @@ package org.hyperledger.besu.cli;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@ -228,8 +227,14 @@ public abstract class CommandTestAbstract {
when(mockControllerBuilder.reorgLoggingThreshold(anyLong())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.dataStorageConfiguration(any())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.evmConfiguration(any())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.networkConfiguration(any())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.randomPeerPriority(any())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.chainPruningConfiguration(any())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.lowerBoundPeers(anyInt())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.maxRemotelyInitiatedPeers(anyInt()))
.thenReturn(mockControllerBuilder);
// doReturn used because of generic BesuController
doReturn(mockController).when(mockControllerBuilder).build();
lenient().when(mockController.getProtocolManager()).thenReturn(mockEthProtocolManager);
@ -255,13 +260,6 @@ public abstract class CommandTestAbstract {
when(mockRunnerBuilder.p2pListenPort(anyInt())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.p2pListenInterface(anyString())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.permissioningConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.maxPeers(anyInt())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.minPeers(anyInt())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.limitRemoteWireConnectionsEnabled(anyBoolean()))
.thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.fractionRemoteConnectionsAllowed(anyFloat()))
.thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.randomPeerPriority(anyBoolean())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.p2pEnabled(anyBoolean())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.natMethod(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.natManagerServiceName(any())).thenReturn(mockRunnerBuilder);
@ -379,7 +377,7 @@ public abstract class CommandTestAbstract {
// reset GlobalOpenTelemetry
GlobalOpenTelemetry.resetForTest();
TestBesuCommand besuCommand = getTestBesuCommand(testType);
final TestBesuCommand besuCommand = getTestBesuCommand(testType);
besuCommands.add(besuCommand);
final File defaultKeyFile =

@ -16,7 +16,6 @@ package org.hyperledger.besu.cli.options;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.cli.DefaultCommandValues.DEFAULT_P2P_PEER_LOWER_BOUND;
import org.hyperledger.besu.cli.options.unstable.NetworkingOptions;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
@ -129,32 +128,6 @@ public class NetworkingOptionsTest
assertThat(commandOutput.toString(UTF_8)).isEmpty();
}
@Test
public void checkP2pPeerLowerBound_isSet() {
final int lowerBound = 13;
final TestBesuCommand cmd = parseCommand("--Xp2p-peer-lower-bound", String.valueOf(lowerBound));
final NetworkingOptions options = cmd.getNetworkingOptions();
final NetworkingConfiguration networkingConfig = options.toDomainObject();
assertThat(networkingConfig.getRlpx().getPeerLowerBound()).isEqualTo(lowerBound);
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
}
@Test
public void checkP2pPeerLowerBound_isNotSet() {
final TestBesuCommand cmd = parseCommand();
final NetworkingOptions options = cmd.getNetworkingOptions();
final NetworkingConfiguration networkingConfig = options.toDomainObject();
assertThat(networkingConfig.getRlpx().getPeerLowerBound())
.isEqualTo(DEFAULT_P2P_PEER_LOWER_BOUND);
assertThat(commandErrorOutput.toString(UTF_8)).isEmpty();
assertThat(commandOutput.toString(UTF_8)).isEmpty();
}
@Test
public void checkFilterByForkIdNotSet() {
final TestBesuCommand cmd = parseCommand();
@ -203,6 +176,7 @@ public class NetworkingOptionsTest
NetworkingConfiguration.DEFAULT_INITIATE_CONNECTIONS_FREQUENCY_SEC + 10);
config.setCheckMaintainedConnectionsFrequency(
NetworkingConfiguration.DEFAULT_CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_SEC + 10);
config.setPeerLowerBound(NetworkingConfiguration.DEFAULT_PEER_LOWER_BOUND - 10);
return config;
}

@ -29,6 +29,7 @@ import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.Keccak256ConfigOptions;
import org.hyperledger.besu.cryptoservices.NodeKey;
import org.hyperledger.besu.cryptoservices.NodeKeyUtils;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader;
@ -41,6 +42,7 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage;
@ -74,6 +76,7 @@ import org.mockito.junit.MockitoJUnitRunner;
public class BesuControllerBuilderTest {
private BesuControllerBuilder besuControllerBuilder;
private static final NodeKey nodeKey = NodeKeyUtils.generate();
@Mock GenesisConfigFile genesisConfigFile;
@Mock GenesisConfigOptions genesisConfigOptions;
@ -87,7 +90,6 @@ public class BesuControllerBuilderTest {
@Mock PrivacyParameters privacyParameters;
@Mock Clock clock;
@Mock TransactionPoolConfiguration poolConfiguration;
@Mock NodeKey nodeKey;
@Mock StorageProvider storageProvider;
@Mock GasLimitCalculator gasLimitCalculator;
@Mock WorldStateStorage worldStateStorage;
@ -157,6 +159,7 @@ public class BesuControllerBuilderTest {
.nodeKey(nodeKey)
.storageProvider(storageProvider)
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.networkId(networkId);
}

@ -28,6 +28,7 @@ import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.consensus.merge.MergeContext;
import org.hyperledger.besu.cryptoservices.NodeKey;
import org.hyperledger.besu.cryptoservices.NodeKeyUtils;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.GasLimitCalculator;
@ -47,6 +48,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfigurati
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
import org.hyperledger.besu.ethereum.mainnet.feemarket.LondonFeeMarket;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage;
import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat;
@ -79,6 +81,7 @@ import org.mockito.junit.MockitoJUnitRunner;
public class MergeBesuControllerBuilderTest {
private MergeBesuControllerBuilder besuControllerBuilder;
private static final NodeKey nodeKey = NodeKeyUtils.generate();
@Mock GenesisConfigFile genesisConfigFile;
@Mock GenesisConfigOptions genesisConfigOptions;
@ -90,7 +93,6 @@ public class MergeBesuControllerBuilderTest {
@Mock PrivacyParameters privacyParameters;
@Mock Clock clock;
@Mock TransactionPoolConfiguration poolConfiguration;
@Mock NodeKey nodeKey;
@Mock StorageProvider storageProvider;
@Mock GasLimitCalculator gasLimitCalculator;
@Mock WorldStateStorage worldStateStorage;
@ -161,6 +163,7 @@ public class MergeBesuControllerBuilderTest {
.nodeKey(nodeKey)
.storageProvider(storageProvider)
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create())
.networkId(networkId);
}
@ -169,7 +172,7 @@ public class MergeBesuControllerBuilderTest {
when(genesisConfigOptions.getTerminalTotalDifficulty())
.thenReturn(Optional.of(UInt256.valueOf(1500L)));
Difficulty terminalTotalDifficulty =
final Difficulty terminalTotalDifficulty =
visitWithMockConfigs(new MergeBesuControllerBuilder())
.build()
.getProtocolContext()
@ -181,9 +184,9 @@ public class MergeBesuControllerBuilderTest {
@Test
public void assertConfiguredBlock() {
Blockchain mockChain = mock(Blockchain.class);
final Blockchain mockChain = mock(Blockchain.class);
when(mockChain.getBlockHeader(anyLong())).thenReturn(Optional.of(mock(BlockHeader.class)));
MergeContext mergeContext =
final MergeContext mergeContext =
besuControllerBuilder.createConsensusContext(
mockChain,
mock(WorldStateArchive.class),
@ -205,10 +208,10 @@ public class MergeBesuControllerBuilderTest {
mock(WorldStateArchive.class),
this.besuControllerBuilder.createProtocolSchedule()));
assertThat(mergeContext).isNotNull();
Difficulty over = Difficulty.of(10000L);
Difficulty under = Difficulty.of(10L);
final Difficulty over = Difficulty.of(10000L);
final Difficulty under = Difficulty.of(10L);
BlockHeader parent =
final BlockHeader parent =
headerGenerator
.difficulty(under)
.parentHash(genesisState.getBlock().getHash())
@ -218,7 +221,7 @@ public class MergeBesuControllerBuilderTest {
.buildHeader();
blockchain.appendBlock(new Block(parent, BlockBody.empty()), Collections.emptyList());
BlockHeader terminal =
final BlockHeader terminal =
headerGenerator
.difficulty(over)
.parentHash(parent.getHash())
@ -233,9 +236,9 @@ public class MergeBesuControllerBuilderTest {
@Test
public void assertNoFinalizedBlockWhenNotStored() {
Blockchain mockChain = mock(Blockchain.class);
final Blockchain mockChain = mock(Blockchain.class);
when(mockChain.getFinalized()).thenReturn(Optional.empty());
MergeContext mergeContext =
final MergeContext mergeContext =
besuControllerBuilder.createConsensusContext(
mockChain,
mock(WorldStateArchive.class),
@ -252,7 +255,7 @@ public class MergeBesuControllerBuilderTest {
when(mockChain.getFinalized()).thenReturn(Optional.of(finalizedHeader.getHash()));
when(mockChain.getBlockHeader(finalizedHeader.getHash()))
.thenReturn(Optional.of(finalizedHeader));
MergeContext mergeContext =
final MergeContext mergeContext =
besuControllerBuilder.createConsensusContext(
mockChain,
mock(WorldStateArchive.class),

@ -44,6 +44,7 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage;
import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat;
@ -144,7 +145,8 @@ public class QbftBesuControllerBuilderTest {
.nodeKey(nodeKey)
.storageProvider(storageProvider)
.gasLimitCalculator(gasLimitCalculator)
.evmConfiguration(EvmConfiguration.DEFAULT);
.evmConfiguration(EvmConfiguration.DEFAULT)
.networkConfiguration(NetworkingConfiguration.create());
}
@Test

@ -20,6 +20,7 @@ import org.hyperledger.besu.consensus.common.bft.events.BftEvents;
import org.hyperledger.besu.consensus.common.bft.network.PeerConnectionTracker;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Message;
@ -107,6 +108,11 @@ public class BftProtocolManager implements ProtocolManager {
peers.add(peerConnection);
}
@Override
public boolean shouldConnect(final Peer peer, final boolean incoming) {
return false; // for now the EthProtocolManager takes care of this
}
@Override
public void handleDisconnect(
final PeerConnection peerConnection,

@ -42,7 +42,7 @@ public class EthSynchronizerUpdaterTest {
@Test
public void ethPeerIsMissingResultInNoUpdate() {
when(ethPeers.peer(any())).thenReturn(null);
when(ethPeers.peer(any(PeerConnection.class))).thenReturn(null);
final EthSynchronizerUpdater updater = new EthSynchronizerUpdater(ethPeers);
@ -53,7 +53,7 @@ public class EthSynchronizerUpdaterTest {
@Test
public void chainStateUpdateIsAttemptedIfEthPeerExists() {
when(ethPeers.peer(any())).thenReturn(ethPeer);
when(ethPeers.peer(any(PeerConnection.class))).thenReturn(ethPeer);
when(ethPeer.chainState()).thenReturn(chainState);
final EthSynchronizerUpdater updater = new EthSynchronizerUpdater(ethPeers);

@ -91,11 +91,11 @@ public class JsonRpcExecutor {
return rpcProcessor.process(
id, method, span, new JsonRpcRequestContext(requestBody, optionalUser, alive));
} catch (IllegalArgumentException e) {
} catch (final IllegalArgumentException e) {
try {
final Integer id = jsonRpcRequest.getInteger("id", null);
return new JsonRpcErrorResponse(id, INVALID_REQUEST);
} catch (ClassCastException idNotIntegerException) {
} catch (final ClassCastException idNotIntegerException) {
return new JsonRpcErrorResponse(null, INVALID_REQUEST);
}
}

@ -79,7 +79,8 @@ public class AdminJsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase {
List.of(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
TestClock.fixed(),
Collections.emptyList()));
Collections.emptyList(),
Bytes.random(64)));
peerList.add(
new EthPeer(
MockPeerConnection.create(info2, addr30301, addr60302),
@ -88,7 +89,8 @@ public class AdminJsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase {
List.of(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
TestClock.fixed(),
Collections.emptyList()));
Collections.emptyList(),
Bytes.random(64)));
peerList.add(
new EthPeer(
MockPeerConnection.create(info3, addr30301, addr60303),
@ -97,7 +99,8 @@ public class AdminJsonRpcHttpServiceTest extends JsonRpcHttpServiceTestBase {
List.of(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
TestClock.fixed(),
Collections.emptyList()));
Collections.emptyList(),
Bytes.random(64)));
when(ethPeersMock.streamAllPeers()).thenReturn(peerList.stream());
when(peerDiscoveryMock.getPeerCount()).thenReturn(peerList.size());

@ -64,6 +64,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import com.google.common.collect.Lists;
import io.vertx.core.Vertx;
@ -280,6 +281,8 @@ public class JsonRpcHttpServiceRpcApisTest {
.blockchain(blockchain)
.blockNumberForks(Collections.emptyList())
.timestampForks(Collections.emptyList())
.allConnectionsSupplier(Stream::empty)
.allActiveConnectionsSupplier(Stream::empty)
.build();
p2pNetwork.start();

@ -17,12 +17,16 @@ package org.hyperledger.besu.ethereum.api.jsonrpc;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.PeerInfo;
import java.net.InetSocketAddress;
import org.apache.tuweni.bytes.Bytes;
public class MockPeerConnection {
PeerInfo peerInfo;
InetSocketAddress localAddress;
@ -32,8 +36,11 @@ public class MockPeerConnection {
final PeerInfo peerInfo,
final InetSocketAddress localAddress,
final InetSocketAddress remoteAddress) {
PeerConnection peerConnection = mock(PeerConnection.class);
final PeerConnection peerConnection = mock(PeerConnection.class);
when(peerConnection.getPeerInfo()).thenReturn(peerInfo);
if (peerInfo != null) {
when(peerConnection.getPeer()).thenReturn(createPeer(peerInfo.getNodeId()));
}
when(peerConnection.getLocalAddress()).thenReturn(localAddress);
when(peerConnection.getRemoteAddress()).thenReturn(remoteAddress);
when(peerConnection.getRemoteEnode())
@ -47,4 +54,12 @@ public class MockPeerConnection {
return peerConnection;
}
public static EnodeURLImpl.Builder enodeBuilder() {
return EnodeURLImpl.builder().ipAddress("127.0.0.1").useDefaultPorts().nodeId(Peer.randomId());
}
public static Peer createPeer(final Bytes nodeId) {
return DefaultPeer.fromEnodeURL(enodeBuilder().nodeId(nodeId).build());
}
}

@ -125,7 +125,8 @@ public class AdminPeersTest {
List.of(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
TestClock.fixed(),
Collections.emptyList());
Collections.emptyList(),
Bytes.random(64));
return Lists.newArrayList(ethPeer);
}

@ -68,7 +68,7 @@ public class EthPeer implements Comparable<EthPeer> {
private static final int MAX_OUTSTANDING_REQUESTS = 5;
private final PeerConnection connection;
private PeerConnection connection;
private final int maxTrackedSeenBlocks = 300;
@ -81,6 +81,7 @@ public class EthPeer implements Comparable<EthPeer> {
return size() > maxTrackedSeenBlocks;
}
}));
private final Bytes localNodeId;
private Optional<BlockHeader> checkpointHeader = Optional.empty();
@ -89,7 +90,7 @@ public class EthPeer implements Comparable<EthPeer> {
private final Clock clock;
private final List<NodeMessagePermissioningProvider> permissioningProviders;
private final ChainState chainHeadState = new ChainState();
private final AtomicBoolean statusHasBeenSentToPeer = new AtomicBoolean(false);
private final AtomicBoolean readyForRequests = new AtomicBoolean(false);
private final AtomicBoolean statusHasBeenReceivedFromPeer = new AtomicBoolean(false);
private final AtomicBoolean fullyValidated = new AtomicBoolean(false);
private final AtomicInteger lastProtocolVersion = new AtomicInteger(0);
@ -101,6 +102,7 @@ public class EthPeer implements Comparable<EthPeer> {
private final AtomicReference<Consumer<EthPeer>> onStatusesExchanged = new AtomicReference<>();
private final PeerReputation reputation = new PeerReputation();
private final Map<PeerValidator, Boolean> validationStatus = new ConcurrentHashMap<>();
private final Bytes id;
private static final Map<Integer, Integer> roundMessages;
@ -126,7 +128,8 @@ public class EthPeer implements Comparable<EthPeer> {
final List<PeerValidator> peerValidators,
final int maxMessageSize,
final Clock clock,
final List<NodeMessagePermissioningProvider> permissioningProviders) {
final List<NodeMessagePermissioningProvider> permissioningProviders,
final Bytes localNodeId) {
this.connection = connection;
this.protocolName = protocolName;
this.maxMessageSize = maxMessageSize;
@ -137,6 +140,8 @@ public class EthPeer implements Comparable<EthPeer> {
fullyValidated.set(peerValidators.isEmpty());
this.requestManagers = new ConcurrentHashMap<>();
this.localNodeId = localNodeId;
this.id = connection.getPeer().getId();
initEthRequestManagers();
initSnapRequestManagers();
@ -228,13 +233,32 @@ public class EthPeer implements Comparable<EthPeer> {
public RequestManager.ResponseStream send(
final MessageData messageData, final String protocolName) throws PeerNotConnected {
if (connection.getAgreedCapabilities().stream()
return send(messageData, protocolName, this.connection);
}
/**
* This method is only used for sending the status message, as it is possible that we have
* multiple connections to the same peer at that time.
*
* @param messageData the data to send
* @param protocolName the protocol to use for sending
* @param connectionToUse the connection to use for sending
* @return the response stream from the peer
* @throws PeerNotConnected if the peer is not connected
*/
public RequestManager.ResponseStream send(
final MessageData messageData,
final String protocolName,
final PeerConnection connectionToUse)
throws PeerNotConnected {
if (connectionToUse.getAgreedCapabilities().stream()
.noneMatch(capability -> capability.getName().equalsIgnoreCase(protocolName))) {
LOG.debug("Protocol {} unavailable for this peer {}", protocolName, this);
return null;
}
if (permissioningProviders.stream()
.anyMatch(p -> !p.isMessagePermitted(connection.getRemoteEnode(), messageData.getCode()))) {
.anyMatch(
p -> !p.isMessagePermitted(connectionToUse.getRemoteEnode(), messageData.getCode()))) {
LOG.info(
"Permissioning blocked sending of message code {} to {}", messageData.getCode(), this);
if (LOG.isDebugEnabled()) {
@ -243,7 +267,8 @@ public class EthPeer implements Comparable<EthPeer> {
permissioningProviders.stream()
.filter(
p ->
!p.isMessagePermitted(connection.getRemoteEnode(), messageData.getCode())));
!p.isMessagePermitted(
connectionToUse.getRemoteEnode(), messageData.getCode())));
}
return null;
}
@ -266,7 +291,7 @@ public class EthPeer implements Comparable<EthPeer> {
}
}
connection.sendForProtocol(protocolName, messageData);
connectionToUse.sendForProtocol(protocolName, messageData);
return null;
}
@ -433,27 +458,51 @@ public class EthPeer implements Comparable<EthPeer> {
knownBlocks.add(hash);
}
public void registerStatusSent() {
statusHasBeenSentToPeer.set(true);
maybeExecuteStatusesExchangedCallback();
public void registerStatusSent(final PeerConnection connection) {
synchronized (this) {
connection.setStatusSent();
maybeExecuteStatusesExchangedCallback(connection);
}
}
public void registerStatusReceived(
final Hash hash, final Difficulty td, final int protocolVersion) {
final Hash hash,
final Difficulty td,
final int protocolVersion,
final PeerConnection connection) {
chainHeadState.statusReceived(hash, td);
lastProtocolVersion.set(protocolVersion);
statusHasBeenReceivedFromPeer.set(true);
maybeExecuteStatusesExchangedCallback();
synchronized (this) {
connection.setStatusReceived();
maybeExecuteStatusesExchangedCallback(connection);
}
}
private void maybeExecuteStatusesExchangedCallback() {
if (readyForRequests()) {
final Consumer<EthPeer> callback = onStatusesExchanged.getAndSet(null);
if (callback == null) {
return;
private void maybeExecuteStatusesExchangedCallback(final PeerConnection newConnection) {
synchronized (this) {
if (newConnection.getStatusExchanged()) {
if (!this.connection.equals(newConnection)) {
if (readyForRequests.get()) {
// We have two connections that are ready for requests, figure out which connection to
// keep
if (compareDuplicateConnections(this.connection, newConnection) > 0) {
LOG.trace("Changed connection from {} to {}", this.connection, newConnection);
this.connection = newConnection;
}
} else {
// use the new connection for now, as it is ready for requests, which the "old" one is
// not
this.connection = newConnection;
}
}
readyForRequests.set(true);
final Consumer<EthPeer> peerConsumer = onStatusesExchanged.getAndSet(null);
if (peerConsumer != null) {
LOG.trace("Status message exchange successful. {}", this);
peerConsumer.accept(this);
}
}
LOG.debug("Status message exchange successful with a peer on a matching chain. {}", this);
callback.accept(this);
}
}
@ -463,7 +512,7 @@ public class EthPeer implements Comparable<EthPeer> {
* @return true if the peer is ready to accept requests for data.
*/
public boolean readyForRequests() {
return statusHasBeenSentToPeer.get() && statusHasBeenReceivedFromPeer.get();
return readyForRequests.get();
}
/**
@ -475,15 +524,6 @@ public class EthPeer implements Comparable<EthPeer> {
return statusHasBeenReceivedFromPeer.get();
}
/**
* Return true if we have sent a status message to this peer.
*
* @return true if we have sent a status message to this peer.
*/
boolean statusHasBeenSentToPeer() {
return statusHasBeenSentToPeer.get();
}
public boolean hasSeenBlock(final Hash hash) {
return knownBlocks.contains(hash);
}
@ -559,7 +599,7 @@ public class EthPeer implements Comparable<EthPeer> {
isFullyValidated(),
isDisconnected(),
connection.getPeerInfo().getClientId(),
System.identityHashCode(connection),
connection,
connection.getPeer().getEnodeURLString());
}
@ -590,6 +630,41 @@ public class EthPeer implements Comparable<EthPeer> {
return checkpointHeader;
}
public Bytes getId() {
return id;
}
/**
* Compares two connections to the same peer to determine which connection should be kept
*
* @param a The first connection
* @param b The second connection
* @return A negative value if {@code a} should be kept, a positive value is {@code b} should be
* kept
*/
private int compareDuplicateConnections(final PeerConnection a, final PeerConnection b) {
if (a.isDisconnected() != b.isDisconnected()) {
// One connection has failed - prioritize the one that hasn't failed
return a.isDisconnected() ? 1 : -1;
}
final Bytes peerId = a.getPeer().getId();
// peerId is the id of the other node
if (a.inboundInitiated() != b.inboundInitiated()) {
// If we have connections initiated in different directions, keep the connection initiated
// by the node with the lower id
if (localNodeId.compareTo(peerId) < 0) {
return a.inboundInitiated() ? 1 : -1;
} else {
return a.inboundInitiated() ? -1 : 1;
}
}
// Otherwise, keep older connection
LOG.trace("comparing timestamps " + a.getInitiatedAt() + " with " + b.getInitiatedAt());
return a.getInitiatedAt() < b.getInitiatedAt() ? -1 : 1;
}
@FunctionalInterface
public interface DisconnectCallback {
void onDisconnect(EthPeer peer);

@ -17,16 +17,19 @@ package org.hyperledger.besu.ethereum.eth.manager;
import org.hyperledger.besu.ethereum.eth.manager.EthPeer.DisconnectCallback;
import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider;
import org.hyperledger.besu.util.Subscribers;
import java.time.Clock;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@ -34,12 +37,18 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalNotification;
import org.apache.tuweni.bytes.Bytes;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -57,52 +66,64 @@ public class EthPeers {
public static final Comparator<EthPeer> LEAST_TO_MOST_BUSY =
Comparator.comparing(EthPeer::outstandingRequests)
.thenComparing(EthPeer::getLastRequestTimestamp);
public static final int NODE_ID_LENGTH = 64;
private final Map<PeerConnection, EthPeer> connections = new ConcurrentHashMap<>();
private final Map<Bytes, EthPeer> completeConnections = new ConcurrentHashMap<>();
private final Cache<PeerConnection, EthPeer> incompleteConnections =
CacheBuilder.newBuilder()
.expireAfterWrite(Duration.ofSeconds(20L))
.concurrencyLevel(1)
.removalListener(this::onCacheRemoval)
.build();
private final String protocolName;
private final Clock clock;
private final List<NodeMessagePermissioningProvider> permissioningProviders;
private final int maxPeers;
private final int maxMessageSize;
private final Subscribers<ConnectCallback> connectCallbacks = Subscribers.create();
private final Subscribers<DisconnectCallback> disconnectCallbacks = Subscribers.create();
private final Collection<PendingPeerRequest> pendingRequests = new CopyOnWriteArrayList<>();
private final int peerLowerBound;
private final int peerUpperBound;
private final int maxRemotelyInitiatedConnections;
private final Boolean randomPeerPriority;
private final Bytes nodeIdMask = Bytes.random(NODE_ID_LENGTH);
private final Supplier<ProtocolSpec> currentProtocolSpecSupplier;
private Comparator<EthPeer> bestPeerComparator;
private final Bytes localNodeId;
private RlpxAgent rlpxAgent;
public EthPeers(
final String protocolName,
final Supplier<ProtocolSpec> currentProtocolSpecSupplier,
final Clock clock,
final MetricsSystem metricsSystem,
final int maxPeers,
final int maxMessageSize) {
this(
protocolName,
currentProtocolSpecSupplier,
clock,
metricsSystem,
maxPeers,
maxMessageSize,
Collections.emptyList());
}
private final Counter connectedPeersCounter;
public EthPeers(
final String protocolName,
final Supplier<ProtocolSpec> currentProtocolSpecSupplier,
final Clock clock,
final MetricsSystem metricsSystem,
final int maxPeers,
final int maxMessageSize,
final List<NodeMessagePermissioningProvider> permissioningProviders) {
final List<NodeMessagePermissioningProvider> permissioningProviders,
final Bytes localNodeId,
final int peerLowerBound,
final int peerUpperBound,
final int maxRemotelyInitiatedConnections,
final Boolean randomPeerPriority) {
this.protocolName = protocolName;
this.currentProtocolSpecSupplier = currentProtocolSpecSupplier;
this.clock = clock;
this.permissioningProviders = permissioningProviders;
this.maxPeers = maxPeers;
this.maxMessageSize = maxMessageSize;
this.bestPeerComparator = HEAVIEST_CHAIN;
this.localNodeId = localNodeId;
this.peerLowerBound = peerLowerBound;
this.peerUpperBound = peerUpperBound;
this.maxRemotelyInitiatedConnections = maxRemotelyInitiatedConnections;
this.randomPeerPriority = randomPeerPriority;
LOG.trace(
"MaxPeers: {}, Lower Bound: {}, Max Remote: {}",
peerUpperBound,
peerLowerBound,
maxRemotelyInitiatedConnections);
metricsSystem.createIntegerGauge(
BesuMetricCategory.ETHEREUM,
"peer_count",
@ -113,39 +134,83 @@ public class EthPeers {
"pending_peer_requests_current",
"Number of peer requests currently pending because peers are busy",
pendingRequests::size);
metricsSystem.createIntegerGauge(
BesuMetricCategory.ETHEREUM,
"peer_limit",
"The maximum number of peers this node allows to connect",
() -> peerUpperBound);
connectedPeersCounter =
metricsSystem.createCounter(
BesuMetricCategory.PEERS, "connected_total", "Total number of peers connected");
}
public void registerNewConnection(
final PeerConnection newConnection, final List<PeerValidator> peerValidators) {
final Bytes id = newConnection.getPeer().getId();
synchronized (this) {
EthPeer ethPeer = completeConnections.get(id);
if (ethPeer == null) {
final Optional<EthPeer> peerInList =
incompleteConnections.asMap().values().stream()
.filter(p -> p.getId().equals(id))
.findFirst();
ethPeer =
peerInList.orElse(
new EthPeer(
newConnection,
protocolName,
this::ethPeerStatusExchanged,
peerValidators,
maxMessageSize,
clock,
permissioningProviders,
localNodeId));
}
incompleteConnections.put(newConnection, ethPeer);
}
}
public void registerConnection(
final PeerConnection peerConnection, final List<PeerValidator> peerValidators) {
final EthPeer peer =
new EthPeer(
peerConnection,
protocolName,
this::invokeConnectionCallbacks,
peerValidators,
maxMessageSize,
clock,
permissioningProviders);
connections.putIfAbsent(peerConnection, peer);
LOG.debug("Adding new EthPeer {}", peer.nodeId());
}
public void registerDisconnect(final PeerConnection connection) {
final EthPeer peer = connections.remove(connection);
if (peer != null) {
disconnectCallbacks.forEach(callback -> callback.onDisconnect(peer));
peer.handleDisconnect();
abortPendingRequestsAssignedToDisconnectedPeers();
LOG.debug("Disconnected EthPeer {}", peer);
public int getPeerLowerBound() {
return peerLowerBound;
}
@NotNull
private List<PeerConnection> getIncompleteConnections(final Bytes id) {
return incompleteConnections.asMap().keySet().stream()
.filter(nrc -> nrc.getPeer().getId().equals(id))
.collect(Collectors.toList());
}
public boolean registerDisconnect(final PeerConnection connection) {
final Bytes id = connection.getPeer().getId();
final EthPeer peer = completeConnections.get(id);
return registerDisconnect(id, peer, connection);
}
private boolean registerDisconnect(
final Bytes id, final EthPeer peer, final PeerConnection connection) {
incompleteConnections.invalidate(connection);
boolean removed = false;
if (peer != null && peer.getConnection().equals(connection)) {
if (!peerHasIncompleteConnection(id)) {
removed = completeConnections.remove(id, peer);
disconnectCallbacks.forEach(callback -> callback.onDisconnect(peer));
peer.handleDisconnect();
abortPendingRequestsAssignedToDisconnectedPeers();
LOG.debug("Disconnected EthPeer {}", peer);
}
}
reattemptPendingPeerRequests();
return removed;
}
private boolean peerHasIncompleteConnection(final Bytes id) {
return getIncompleteConnections(id).stream().anyMatch(conn -> !conn.isDisconnected());
}
private void abortPendingRequestsAssignedToDisconnectedPeers() {
synchronized (this) {
final Iterator<PendingPeerRequest> iterator = pendingRequests.iterator();
while (iterator.hasNext()) {
final PendingPeerRequest request = iterator.next();
for (final PendingPeerRequest request : pendingRequests) {
if (request.getAssignedPeer().map(EthPeer::isDisconnected).orElse(false)) {
request.abort();
}
@ -153,8 +218,17 @@ public class EthPeers {
}
}
public EthPeer peer(final PeerConnection peerConnection) {
return connections.get(peerConnection);
public EthPeer peer(final PeerConnection connection) {
try {
return incompleteConnections.get(
connection, () -> completeConnections.get(connection.getPeer().getId()));
} catch (final ExecutionException e) {
throw new RuntimeException(e);
}
}
public EthPeer peer(final Bytes peerId) {
return completeConnections.get(peerId);
}
public PendingPeerRequest executePeerRequest(
@ -179,7 +253,7 @@ public class EthPeers {
public void dispatchMessage(
final EthPeer peer, final EthMessage ethMessage, final String protocolName) {
Optional<RequestManager> maybeRequestManager = peer.dispatch(ethMessage, protocolName);
final Optional<RequestManager> maybeRequestManager = peer.dispatch(ethMessage, protocolName);
if (maybeRequestManager.isPresent() && peer.hasAvailableRequestCapacity()) {
reattemptPendingPeerRequests();
}
@ -216,28 +290,30 @@ public class EthPeers {
}
public int peerCount() {
return connections.size();
removeDisconnectedPeers();
return completeConnections.size();
}
public int getMaxPeers() {
return maxPeers;
return peerUpperBound;
}
public Stream<EthPeer> streamAllPeers() {
return connections.values().stream();
return completeConnections.values().stream();
}
private void removeDisconnectedPeers() {
final Collection<EthPeer> peerStream = connections.values();
for (EthPeer p : peerStream) {
if (p.isDisconnected()) {
connections.remove(p.getConnection());
}
}
completeConnections
.values()
.forEach(
ep -> {
if (ep.isDisconnected()) {
registerDisconnect(ep.getId(), ep, ep.getConnection());
}
});
}
public Stream<EthPeer> streamAvailablePeers() {
removeDisconnectedPeers();
return streamAllPeers()
.filter(EthPeer::readyForRequests)
.filter(peer -> !peer.isDisconnected());
@ -269,6 +345,43 @@ public class EthPeers {
return bestPeerComparator;
}
public void setRlpxAgent(final RlpxAgent rlpxAgent) {
this.rlpxAgent = rlpxAgent;
}
public Stream<PeerConnection> getAllActiveConnections() {
return completeConnections.values().stream()
.map(EthPeer::getConnection)
.filter(c -> !c.isDisconnected());
}
public Stream<PeerConnection> getAllConnections() {
return Stream.concat(
completeConnections.values().stream().map(EthPeer::getConnection),
incompleteConnections.asMap().keySet().stream())
.distinct()
.filter(c -> !c.isDisconnected());
}
public boolean shouldConnect(final Peer peer, final boolean inbound) {
if (peerCount() >= peerUpperBound) {
return false;
}
final Bytes id = peer.getId();
final EthPeer ethPeer = completeConnections.get(id);
if (ethPeer != null && !ethPeer.isDisconnected()) {
return false;
}
final List<PeerConnection> incompleteConnections = getIncompleteConnections(id);
if (!incompleteConnections.isEmpty()) {
if (incompleteConnections.stream()
.anyMatch(c -> !c.isDisconnected() && (!inbound || (inbound && c.inboundInitiated())))) {
return false;
}
}
return true;
}
public void disconnectWorstUselessPeer() {
streamAvailablePeers()
.sorted(getBestChainComparator())
@ -293,18 +406,173 @@ public class EthPeers {
@Override
public String toString() {
if (connections.isEmpty()) {
if (completeConnections.isEmpty()) {
return "0 EthPeers {}";
}
final String connectionsList =
connections.values().stream()
completeConnections.values().stream()
.sorted()
.map(EthPeer::toString)
.collect(Collectors.joining(", \n"));
return connections.size() + " EthPeers {\n" + connectionsList + '}';
return completeConnections.size() + " EthPeers {\n" + connectionsList + '}';
}
private void ethPeerStatusExchanged(final EthPeer peer) {
if (addPeerToEthPeers(peer)) {
connectedPeersCounter.inc();
connectCallbacks.forEach(cb -> cb.onPeerConnected(peer));
}
}
private int comparePeerPriorities(final EthPeer p1, final EthPeer p2) {
final PeerConnection a = p1.getConnection();
final PeerConnection b = p2.getConnection();
final boolean aCanExceedPeerLimits = canExceedPeerLimits(a);
final boolean bCanExceedPeerLimits = canExceedPeerLimits(b);
if (aCanExceedPeerLimits && !bCanExceedPeerLimits) {
return -1;
} else if (bCanExceedPeerLimits && !aCanExceedPeerLimits) {
return 1;
} else {
return randomPeerPriority
? compareByMaskedNodeId(a, b)
: compareConnectionInitiationTimes(a, b);
}
}
private boolean canExceedPeerLimits(final PeerConnection a) {
if (rlpxAgent == null) {
return true;
}
return rlpxAgent.canExceedConnectionLimits(a.getPeer());
}
private int compareConnectionInitiationTimes(final PeerConnection a, final PeerConnection b) {
return Math.toIntExact(a.getInitiatedAt() - b.getInitiatedAt());
}
private void invokeConnectionCallbacks(final EthPeer peer) {
connectCallbacks.forEach(cb -> cb.onPeerConnected(peer));
private int compareByMaskedNodeId(final PeerConnection a, final PeerConnection b) {
return a.getPeer().getId().xor(nodeIdMask).compareTo(b.getPeer().getId().xor(nodeIdMask));
}
private void enforceRemoteConnectionLimits() {
if (!shouldLimitRemoteConnections() || peerCount() < maxRemotelyInitiatedConnections) {
// Nothing to do
return;
}
getActivePrioritizedPeers()
.filter(p -> p.getConnection().inboundInitiated())
.filter(p -> !canExceedPeerLimits(p.getConnection()))
.skip(maxRemotelyInitiatedConnections)
.forEach(
conn -> {
LOG.trace(
"Too many remotely initiated connections. Disconnect low-priority connection: {}, maxRemote={}",
conn,
maxRemotelyInitiatedConnections);
conn.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS);
});
}
private Stream<EthPeer> getActivePrioritizedPeers() {
return completeConnections.values().stream()
.filter(p -> !p.isDisconnected())
.sorted(this::comparePeerPriorities);
}
private void enforceConnectionLimits() {
if (peerCount() < peerUpperBound) {
// Nothing to do - we're under our limits
return;
}
getActivePrioritizedPeers()
.filter(p -> !p.isDisconnected())
.skip(peerUpperBound)
.map(EthPeer::getConnection)
.filter(c -> !canExceedPeerLimits(c))
.forEach(
conn -> {
LOG.trace(
"Too many connections. Disconnect low-priority connection: {}, maxConnections={}",
conn,
peerUpperBound);
conn.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS);
});
}
private boolean remoteConnectionLimitReached() {
return shouldLimitRemoteConnections()
&& countUntrustedRemotelyInitiatedConnections() >= maxRemotelyInitiatedConnections;
}
private boolean shouldLimitRemoteConnections() {
return maxRemotelyInitiatedConnections < peerUpperBound;
}
private long countUntrustedRemotelyInitiatedConnections() {
return completeConnections.values().stream()
.map(ep -> ep.getConnection())
.filter(c -> c.inboundInitiated())
.filter(c -> !c.isDisconnected())
.filter(conn -> !canExceedPeerLimits(conn))
.count();
}
private void onCacheRemoval(
final RemovalNotification<PeerConnection, EthPeer> removalNotification) {
if (removalNotification.wasEvicted()) {
final PeerConnection peerConnectionRemoved = removalNotification.getKey();
final PeerConnection peerConnectionOfEthPeer = removalNotification.getValue().getConnection();
if (!peerConnectionRemoved.equals(peerConnectionOfEthPeer)) {
// If this connection is not the connection of the EthPeer by now we can disconnect
peerConnectionRemoved.disconnect(DisconnectMessage.DisconnectReason.ALREADY_CONNECTED);
}
}
}
private boolean addPeerToEthPeers(final EthPeer peer) {
// We have a connection to a peer that is on the right chain and is willing to connect to us.
// Figure out whether we want to keep this peer and add it to the EthPeers connections.
if (completeConnections.containsValue(peer)) {
return false;
}
final PeerConnection connection = peer.getConnection();
final Bytes id = peer.getId();
if (!randomPeerPriority) {
// Disconnect if too many peers
if (!canExceedPeerLimits(connection) && peerCount() >= peerUpperBound) {
LOG.trace(
"Too many peers. Disconnect connection: {}, max connections {}",
connection,
peerUpperBound);
connection.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS);
return false;
}
// Disconnect if too many remotely-initiated connections
if (connection.inboundInitiated()
&& !canExceedPeerLimits(connection)
&& remoteConnectionLimitReached()) {
LOG.trace(
"Too many remotely-initiated connections. Disconnect incoming connection: {}, maxRemote={}",
connection,
maxRemotelyInitiatedConnections);
connection.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS);
return false;
}
final boolean added = (completeConnections.putIfAbsent(id, peer) == null);
if (added) {
LOG.trace("Added peer {} with connection {} to completeConnections", id, connection);
} else {
LOG.trace("Did not add peer {} with connection {} to completeConnections", id, connection);
}
return added;
} else {
// randomPeerPriority! Add the peer and if there are too many connections fix it
completeConnections.putIfAbsent(id, peer);
enforceRemoteConnectionLimits();
enforceConnectionLimits();
return completeConnections.containsKey(id);
}
}
}

@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.forkid.ForkId;
import org.hyperledger.besu.ethereum.forkid.ForkIdManager;
import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection.PeerNotConnected;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
@ -283,7 +284,7 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver {
// Handle STATUS processing
if (code == EthPV62.STATUS) {
handleStatusMessage(ethPeer, messageData);
handleStatusMessage(ethPeer, message);
return;
} else if (!ethPeer.statusHasBeenReceived()) {
// Peers are required to send status messages before any other message type
@ -352,17 +353,12 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver {
@Override
public void handleNewConnection(final PeerConnection connection) {
ethPeers.registerConnection(connection, peerValidators);
ethPeers.registerNewConnection(connection, peerValidators);
final EthPeer peer = ethPeers.peer(connection);
if (peer.statusHasBeenSentToPeer()) {
return;
}
final Capability cap = connection.capability(getSupportedProtocol());
final ForkId latestForkId =
cap.getVersion() >= 64 ? forkIdManager.getForkIdForChainHead() : null;
// TODO: look to consolidate code below if possible
// making status non-final and implementing it above would be one way.
final StatusMessage status =
StatusMessage.create(
cap.getVersion(),
@ -372,42 +368,58 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver {
genesisHash,
latestForkId);
try {
LOG.debug("Sending status message to {}.", peer);
peer.send(status, getSupportedProtocol());
peer.registerStatusSent();
LOG.trace("Sending status message to {} for connection {}.", peer.getId(), connection);
peer.send(status, getSupportedProtocol(), connection);
peer.registerStatusSent(connection);
} catch (final PeerNotConnected peerNotConnected) {
// Nothing to do.
}
LOG.trace("{}", ethPeers);
}
@Override
public boolean shouldConnect(final Peer peer, final boolean incoming) {
if (peer.getForkId().map(forkId -> forkIdManager.peerCheck(forkId)).orElse(true)) {
LOG.trace("ForkId OK or not available");
if (ethPeers.shouldConnect(peer, incoming)) {
LOG.trace("EthPeers should connect is TRUE");
return true;
}
}
LOG.trace("Should connect in EthProtocolManager returns false");
return false;
}
@Override
public void handleDisconnect(
final PeerConnection connection,
final DisconnectReason reason,
final boolean initiatedByPeer) {
ethPeers.registerDisconnect(connection);
LOG.debug(
"Disconnect - {} - {} - {} - {} peers left",
initiatedByPeer ? "Inbound" : "Outbound",
reason,
connection.getPeerInfo(),
ethPeers.peerCount());
LOG.trace("{}", ethPeers);
if (ethPeers.registerDisconnect(connection)) {
LOG.debug(
"Disconnect - {} - {} - {} - {} peers left\n{}",
initiatedByPeer ? "Inbound" : "Outbound",
reason,
connection.getPeer().getId(),
ethPeers.peerCount(),
ethPeers);
}
}
private void handleStatusMessage(final EthPeer peer, final MessageData data) {
final StatusMessage status = StatusMessage.readFrom(data);
private void handleStatusMessage(final EthPeer peer, final Message message) {
final StatusMessage status = StatusMessage.readFrom(message.getData());
final ForkId forkId = status.forkId();
peer.getConnection().getPeer().setForkId(forkId);
try {
if (!status.networkId().equals(networkId)) {
LOG.debug("Mismatched network id: {}, EthPeer {}", status.networkId(), peer);
peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED);
} else if (!forkIdManager.peerCheck(status.forkId()) && status.protocolVersion() > 63) {
} else if (!forkIdManager.peerCheck(forkId) && status.protocolVersion() > 63) {
LOG.debug(
"{} has matching network id ({}), but non-matching fork id: {}",
peer,
networkId,
status.forkId());
forkId);
peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED);
} else if (forkIdManager.peerCheck(status.genesisHash())) {
LOG.debug(
@ -421,9 +433,16 @@ public class EthProtocolManager implements ProtocolManager, MinedBlockObserver {
LOG.debug("Post-merge disconnect: peer still PoW {}", peer);
handleDisconnect(peer.getConnection(), DisconnectReason.SUBPROTOCOL_TRIGGERED, false);
} else {
LOG.debug("Received status message from {}: {}", peer, status);
LOG.debug(
"Received status message from {}: {} with connection {}",
peer,
status,
message.getConnection());
peer.registerStatusReceived(
status.bestHash(), status.totalDifficulty(), status.protocolVersion());
status.bestHash(),
status.totalDifficulty(),
status.protocolVersion(),
message.getConnection());
}
} catch (final RLPException e) {
LOG.debug("Unable to parse status message from peer {}.", peer, e);

@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator;
import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractSnapMessageData;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
@ -137,6 +138,11 @@ public class SnapProtocolManager implements ProtocolManager {
@Override
public void handleNewConnection(final PeerConnection connection) {}
@Override
public boolean shouldConnect(final Peer peer, final boolean incoming) {
return false; // EthManager is taking care of this for now
}
@Override
public void handleDisconnect(
final PeerConnection connection,

@ -65,7 +65,7 @@ public class TrailingPeerLimiter implements BlockAddedObserver {
while (!trailingPeers.isEmpty() && trailingPeers.size() > maxTrailingPeers) {
final EthPeer peerToDisconnect = trailingPeers.remove(0);
LOG.debug("Enforcing trailing peers limit by disconnecting {}", peerToDisconnect);
peerToDisconnect.disconnect(DisconnectReason.TOO_MANY_PEERS);
peerToDisconnect.disconnect(DisconnectReason.USELESS_PEER);
}
}

@ -40,6 +40,8 @@ public class MockPeerConnection implements PeerConnection {
private final Peer peer;
private final PeerInfo peerInfo;
private Optional<DisconnectReason> disconnectReason = Optional.empty();
private boolean statusSent;
private boolean statusReceived;
public MockPeerConnection(final Set<Capability> caps, final PeerSendHandler onSend) {
this.caps = caps;
@ -111,11 +113,36 @@ public class MockPeerConnection implements PeerConnection {
throw new UnsupportedOperationException();
}
@Override
public long getInitiatedAt() {
return 0;
}
@Override
public boolean inboundInitiated() {
return false;
}
@Override
public boolean isDisconnected() {
return disconnected;
}
@Override
public void setStatusSent() {
this.statusSent = true;
}
@Override
public void setStatusReceived() {
this.statusReceived = true;
}
@Override
public boolean getStatusExchanged() {
return statusSent && statusReceived;
}
@FunctionalInterface
public interface PeerSendHandler {
void exec(Capability cap, MessageData msg, PeerConnection connection);

@ -0,0 +1,32 @@
/*
* Copyright contributors to Hyperledger Besu
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.eth;
import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.apache.tuweni.bytes.Bytes;
public class EthPeerTestUtil {
public static EnodeURLImpl.Builder enodeBuilder() {
return EnodeURLImpl.builder().ipAddress("127.0.0.1").useDefaultPorts().nodeId(Peer.randomId());
}
public static Peer createPeer(final Bytes nodeId) {
return DefaultPeer.fromEnodeURL(enodeBuilder().nodeId(nodeId).build());
}
}

@ -18,15 +18,14 @@ 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;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.eth.EthPeerTestUtil;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.messages.BlockBodiesMessage;
import org.hyperledger.besu.ethereum.eth.messages.BlockHeadersMessage;
@ -35,6 +34,7 @@ import org.hyperledger.besu.ethereum.eth.messages.ReceiptsMessage;
import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection.PeerNotConnected;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.PeerInfo;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.PingMessage;
@ -43,7 +43,9 @@ import org.hyperledger.besu.testutil.TestClock;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -55,7 +57,10 @@ import org.junit.Test;
public class EthPeerTest {
private static final BlockDataGenerator gen = new BlockDataGenerator();
private final TestClock clock = new TestClock();
private static final Bytes NODE_ID = Bytes.random(32);
private static final Bytes NODE_ID = Bytes.random(64);
private static final Bytes NODE_ID_ZERO =
Bytes.fromHexString(
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010");
@Test
public void getHeadersStream() throws PeerNotConnected {
@ -340,10 +345,10 @@ public class EthPeerTest {
when(trueProvider.isMessagePermitted(any(), anyInt())).thenReturn(true);
when(falseProvider.isMessagePermitted(any(), anyInt())).thenReturn(false);
final EthPeer peer = createPeer(Collections.emptyList(), List.of(falseProvider, trueProvider));
// use failOnSend callback
final EthPeer peer =
createPeer(Collections.emptyList(), List.of(falseProvider, trueProvider), getFailOnSend());
peer.send(PingMessage.get());
verify(peer.getConnection(), times(0)).sendForProtocol(any(), eq(PingMessage.get()));
}
@Test
@ -357,13 +362,13 @@ public class EthPeerTest {
@Test
public void compareTo_withDifferentNodeId() {
final EthPeer peer1 = createPeerWithPeerInfo(NODE_ID);
final EthPeer peer2 = createPeerWithPeerInfo(Bytes.fromHexString("0x00"));
final EthPeer peer2 = createPeerWithPeerInfo(NODE_ID_ZERO);
assertThat(peer1.compareTo(peer2)).isEqualTo(1);
assertThat(peer2.compareTo(peer1)).isEqualTo(-1);
}
@Test
public void recordUsefullResponse() {
public void recordUsefulResponse() {
final EthPeer peer = createPeer();
peer.recordUselessResponse("bodies");
final EthPeer peer2 = createPeer();
@ -463,11 +468,13 @@ public class EthPeerTest {
private EthPeer createPeerWithPeerInfo(final Bytes nodeId) {
final PeerConnection peerConnection = mock(PeerConnection.class);
final Consumer<EthPeer> onPeerReady = (peer) -> {};
// Use a non-eth protocol name to ensure that EthPeer with sub-protocols such as Istanbul
// that extend the sub-protocol work correctly
PeerInfo peerInfo = new PeerInfo(1, "clientId", Collections.emptyList(), 30303, nodeId);
when(peerConnection.getPeerInfo()).thenReturn(peerInfo);
when(peerConnection.getPeer()).thenReturn(EthPeerTestUtil.createPeer(peerInfo.getNodeId()));
final Consumer<EthPeer> onPeerReady = (peer) -> {};
return new EthPeer(
peerConnection,
"foo",
@ -475,13 +482,32 @@ public class EthPeerTest {
Collections.emptyList(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
clock,
Collections.emptyList());
Collections.emptyList(),
Bytes.random(64));
}
private MockPeerConnection.PeerSendHandler getFailOnSend() {
return (cap, message, conn) -> {
fail("should not call send");
};
}
private MockPeerConnection.PeerSendHandler getNoOpSend() {
return (cap, msg, conn) -> {};
}
private EthPeer createPeer(
final List<PeerValidator> peerValidators,
final List<NodeMessagePermissioningProvider> permissioningProviders) {
final PeerConnection peerConnection = mock(PeerConnection.class);
return createPeer(peerValidators, permissioningProviders, getNoOpSend());
}
private EthPeer createPeer(
final List<PeerValidator> peerValidators,
final List<NodeMessagePermissioningProvider> permissioningProviders,
final MockPeerConnection.PeerSendHandler onSend) {
final PeerConnection peerConnection = getPeerConnection(onSend);
final Consumer<EthPeer> onPeerReady = (peer) -> {};
// Use a non-eth protocol name to ensure that EthPeer with sub-protocols such as Istanbul
// that extend the sub-protocol work correctly
@ -492,7 +518,17 @@ public class EthPeerTest {
peerValidators,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
clock,
permissioningProviders);
permissioningProviders,
Bytes.random(64));
}
private PeerConnection getPeerConnection(final MockPeerConnection.PeerSendHandler onSend) {
// Use a non-eth protocol name to ensure that EthPeer with sub-protocols such as Istanbul
// that extend the sub-protocol work correctly
final Set<Capability> caps =
new HashSet<>(Collections.singletonList(Capability.create("foo", 63)));
return new MockPeerConnection(caps, onSend);
}
@FunctionalInterface

@ -337,22 +337,6 @@ public class EthPeersTest {
assertThat(peerToDisconnect.getEthPeer().isDisconnected()).isTrue(); // peer is disconnected
}
@Test
public void removeClosedConnectionWhenStreamAvailablePeers() {
final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000);
// Check connection is there and connection was not removed
ethPeers.streamAvailablePeers();
assertThat(ethPeers.streamAllPeers().count()).isEqualTo(1);
// Disconnect peer
peer.getEthPeer().disconnect(DisconnectReason.UNKNOWN);
// Call method again, connection should be removed
ethPeers.streamAvailablePeers();
assertThat(ethPeers.streamAllPeers().count()).isEqualTo(0);
}
@Test
public void toString_hasExpectedInfo() {
assertThat(ethPeers.toString()).isEqualTo("0 EthPeers {}");
@ -360,7 +344,7 @@ public class EthPeersTest {
final EthPeer peerA =
EthProtocolManagerTestUtil.createPeer(ethProtocolManager, Difficulty.of(50), 20)
.getEthPeer();
ethPeers.registerConnection(peerA.getConnection(), Collections.emptyList());
ethPeers.registerNewConnection(peerA.getConnection(), Collections.emptyList());
assertThat(ethPeers.toString()).contains("1 EthPeers {");
assertThat(ethPeers.toString()).contains(peerA.getShortNodeId());
}

@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture;
import org.hyperledger.besu.ethereum.eth.EthProtocol;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler.TimeoutPolicy;
import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager;
import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
@ -45,6 +46,8 @@ import java.util.Collections;
import java.util.Optional;
import java.util.OptionalLong;
import org.apache.tuweni.bytes.Bytes;
public class EthProtocolManagerTestUtil {
public static EthProtocolManager create(
@ -71,17 +74,22 @@ public class EthProtocolManagerTestUtil {
final EthProtocolConfiguration ethereumWireProtocolConfiguration,
final Optional<MergePeerFilter> mergePeerFilter) {
EthPeers peers =
final EthPeers peers =
new EthPeers(
EthProtocol.NAME,
() -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()),
TestClock.fixed(),
new NoOpMetricsSystem(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.emptyList(),
Bytes.random(64),
25,
25,
25,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE);
EthMessages messages = new EthMessages();
EthScheduler ethScheduler = new DeterministicEthScheduler(TimeoutPolicy.NEVER_TIMEOUT);
EthContext ethContext = new EthContext(peers, messages, ethScheduler);
false);
final EthMessages messages = new EthMessages();
final EthScheduler ethScheduler = new DeterministicEthScheduler(TimeoutPolicy.NEVER_TIMEOUT);
final EthContext ethContext = new EthContext(peers, messages, ethScheduler);
return new EthProtocolManager(
blockchain,
@ -185,15 +193,21 @@ public class EthProtocolManagerTestUtil {
final WorldStateArchive worldStateArchive,
final TransactionPool transactionPool,
final EthProtocolConfiguration configuration) {
EthPeers peers =
final EthPeers peers =
new EthPeers(
EthProtocol.NAME,
() -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()),
TestClock.fixed(),
new NoOpMetricsSystem(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.emptyList(),
Bytes.random(64),
25,
25,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE);
EthMessages messages = new EthMessages();
25,
false);
final EthMessages messages = new EthMessages();
return create(
blockchain,
@ -214,15 +228,21 @@ public class EthProtocolManagerTestUtil {
final TransactionPool transactionPool,
final EthProtocolConfiguration configuration,
final ForkIdManager forkIdManager) {
EthPeers peers =
final EthPeers peers =
new EthPeers(
EthProtocol.NAME,
() -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()),
TestClock.fixed(),
new NoOpMetricsSystem(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.emptyList(),
Bytes.random(64),
25,
25,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE);
EthMessages messages = new EthMessages();
25,
false);
final EthMessages messages = new EthMessages();
return create(
blockchain,
@ -240,15 +260,20 @@ public class EthProtocolManagerTestUtil {
final ProtocolSchedule protocolSchedule,
final Blockchain blockchain,
final EthScheduler ethScheduler) {
EthPeers peers =
final EthPeers peers =
new EthPeers(
EthProtocol.NAME,
() -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()),
TestClock.fixed(),
new NoOpMetricsSystem(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.emptyList(),
Bytes.random(64),
25,
25,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE);
EthMessages messages = new EthMessages();
25,
false);
final EthMessages messages = new EthMessages();
return create(
blockchain,
@ -392,6 +417,17 @@ public class EthProtocolManagerTestUtil {
.build();
}
public static RespondingEthPeer createPeer(
final EthProtocolManager ethProtocolManager,
final SnapProtocolManager snapProtocolManager,
final long estimatedHeight) {
return RespondingEthPeer.builder()
.ethProtocolManager(ethProtocolManager)
.estimatedHeight(estimatedHeight)
.snapProtocolManager(snapProtocolManager)
.build();
}
public static RespondingEthPeer createPeer(
final EthProtocolManager ethProtocolManager,
final long estimatedHeight,

@ -308,7 +308,8 @@ public class RequestManagerTest {
Collections.emptyList(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
TestClock.fixed(),
Collections.emptyList());
Collections.emptyList(),
Bytes.random(64));
}
@Test

@ -129,11 +129,11 @@ public class RespondingEthPeer {
final MockPeerConnection peerConnection =
new MockPeerConnection(
caps, (cap, msg, conn) -> outgoingMessages.add(new OutgoingMessage(cap, msg)));
ethPeers.registerConnection(peerConnection, peerValidators);
ethPeers.registerNewConnection(peerConnection, peerValidators);
final EthPeer peer = ethPeers.peer(peerConnection);
peer.registerStatusReceived(chainHeadHash, totalDifficulty, 63);
peer.registerStatusReceived(chainHeadHash, totalDifficulty, 63, peerConnection);
estimatedHeight.ifPresent(height -> peer.chainState().update(chainHeadHash, height));
peer.registerStatusSent();
peer.registerStatusSent(peerConnection);
return new RespondingEthPeer(
ethProtocolManager, snapProtocolManager, peerConnection, peer, outgoingMessages);

@ -45,11 +45,13 @@ import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.testutil.TestClock;
import java.time.ZoneId;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@ -93,8 +95,14 @@ public abstract class AbstractMessageTaskTest<T, R> {
() -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()),
TestClock.fixed(),
metricsSystem,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.emptyList(),
Bytes.random(64),
MAX_PEERS,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE));
MAX_PEERS,
MAX_PEERS,
false));
final EthMessages ethMessages = new EthMessages();
final EthScheduler ethScheduler =
new DeterministicEthScheduler(

@ -37,6 +37,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Test;
/**
@ -166,6 +167,7 @@ public abstract class PeerMessageTaskTest<T>
Collections.emptyList(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
TestClock.fixed(),
Collections.emptyList());
Collections.emptyList(),
Bytes.random(64));
}
}

@ -623,8 +623,13 @@ public abstract class AbstractBlockPropagationManagerTest {
() -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()),
TestClock.fixed(),
metricsSystem,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.emptyList(),
Bytes.random(64),
25,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE),
25,
25,
false),
new EthMessages(),
ethScheduler);
final BlockPropagationManager blockPropagationManager =
@ -749,6 +754,7 @@ public abstract class AbstractBlockPropagationManagerTest {
return invocation.getArgument(0, Supplier.class).get();
}
});
final EthContext ethContext =
new EthContext(
new EthPeers(
@ -756,8 +762,13 @@ public abstract class AbstractBlockPropagationManagerTest {
() -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()),
TestClock.fixed(),
metricsSystem,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.emptyList(),
Bytes.random(64),
25,
25,
25,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE),
false),
new EthMessages(),
ethScheduler);
final BlockPropagationManager blockPropagationManager =

@ -145,7 +145,7 @@ public class TrailingPeerLimiterTest {
final List<EthPeer> disconnected = asList(disconnectedPeers);
for (final EthPeer peer : peers) {
if (disconnected.contains(peer)) {
verify(peer).disconnect(DisconnectReason.TOO_MANY_PEERS);
verify(peer).disconnect(DisconnectReason.USELESS_PEER);
} else {
verify(peer, never()).disconnect(any(DisconnectReason.class));
}

@ -54,12 +54,15 @@ import org.hyperledger.besu.ethereum.p2p.network.NetworkRunner;
import org.hyperledger.besu.ethereum.p2p.network.P2PNetwork;
import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider;
import org.hyperledger.besu.testutil.TestClock;
import java.io.Closeable;
@ -128,15 +131,26 @@ public class TestNode implements Closeable {
when(syncState.isInitialSyncPhaseDone()).thenReturn(true);
final EthMessages ethMessages = new EthMessages();
final NodeMessagePermissioningProvider nmpp =
new NodeMessagePermissioningProvider() {
@Override
public boolean isMessagePermitted(final EnodeURL destinationEnode, final int code) {
return true;
}
};
final EthPeers ethPeers =
new EthPeers(
EthProtocol.NAME,
() -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()),
TestClock.fixed(),
metricsSystem,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.singletonList(nmpp),
Bytes.random(64),
25,
25,
25,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE);
false);
final EthScheduler scheduler = new EthScheduler(1, 1, 1, metricsSystem);
final EthContext ethContext = new EthContext(ethPeers, ethMessages, scheduler);
@ -183,10 +197,15 @@ public class TestNode implements Closeable {
.blockchain(blockchain)
.blockNumberForks(Collections.emptyList())
.timestampForks(Collections.emptyList())
.allConnectionsSupplier(ethPeers::getAllConnections)
.allActiveConnectionsSupplier(ethPeers::getAllActiveConnections)
.build())
.metricsSystem(new NoOpMetricsSystem())
.build();
network = networkRunner.getNetwork();
final RlpxAgent rlpxAgent = network.getRlpxAgent();
rlpxAgent.subscribeConnectRequest((p, d) -> true);
ethPeers.setRlpxAgent(rlpxAgent);
network.subscribeDisconnect(
(connection, reason, initiatedByPeer) -> disconnections.put(connection, reason));

@ -51,6 +51,8 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider;
import org.hyperledger.besu.testutil.TestClock;
import java.math.BigInteger;
@ -58,6 +60,7 @@ import java.util.Collections;
import java.util.Optional;
import java.util.function.Function;
import org.apache.tuweni.bytes.Bytes;
import org.assertj.core.api.Condition;
import org.junit.Before;
import org.junit.Test;
@ -95,14 +98,27 @@ public class TransactionPoolFactoryTest {
public void setup() {
when(blockchain.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(mock(Hash.class)));
when(context.getBlockchain()).thenReturn(blockchain);
final NodeMessagePermissioningProvider nmpp =
new NodeMessagePermissioningProvider() {
@Override
public boolean isMessagePermitted(final EnodeURL destinationEnode, final int code) {
return true;
}
};
ethPeers =
new EthPeers(
"ETH",
() -> protocolSpec,
TestClock.fixed(),
new NoOpMetricsSystem(),
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.singletonList(nmpp),
Bytes.random(64),
25,
25,
25,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE);
false);
when(ethContext.getEthMessages()).thenReturn(ethMessages);
when(ethContext.getEthPeers()).thenReturn(ethPeers);

@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.wire.DefaultMessage;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Message;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.PeerInfo;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.ShouldConnectCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.util.Subscribers;
@ -168,6 +169,9 @@ public final class MockNetwork {
connectCallbacks.subscribe(callback);
}
@Override
public void subscribeConnectRequest(final ShouldConnectCallback callback) {}
@Override
public void subscribeDisconnect(final DisconnectCallback callback) {
disconnectCallbacks.subscribe(callback);
@ -237,6 +241,8 @@ public final class MockNetwork {
private final Peer to;
private final MockNetwork network;
private boolean statusSent;
private boolean statusReceived;
MockPeerConnection(final Peer source, final Peer target, final MockNetwork network) {
from = source;
@ -308,5 +314,30 @@ public final class MockNetwork {
public InetSocketAddress getRemoteAddress() {
throw new UnsupportedOperationException();
}
@Override
public long getInitiatedAt() {
return 0;
}
@Override
public boolean inboundInitiated() {
return false;
}
@Override
public void setStatusSent() {
this.statusSent = true;
}
@Override
public void setStatusReceived() {
this.statusReceived = true;
}
@Override
public boolean getStatusExchanged() {
return statusSent && statusReceived;
}
}
}

@ -43,9 +43,9 @@ public class DiscoveryConfiguration {
bootnodes.stream().filter(e -> !e.isRunningDiscovery()).collect(Collectors.toList());
if (invalidEnodes.size() > 0) {
String invalidBootnodes =
final String invalidBootnodes =
invalidEnodes.stream().map(EnodeURL::toString).collect(Collectors.joining(","));
String errorMsg =
final String errorMsg =
"Bootnodes must have discovery enabled. Invalid bootnodes: " + invalidBootnodes + ".";
throw new IllegalArgumentException(errorMsg);
}

@ -22,12 +22,14 @@ import java.util.Optional;
public class NetworkingConfiguration {
public static final int DEFAULT_INITIATE_CONNECTIONS_FREQUENCY_SEC = 30;
public static final int DEFAULT_CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_SEC = 60;
public static final int DEFAULT_PEER_LOWER_BOUND = 25;
private DiscoveryConfiguration discovery = new DiscoveryConfiguration();
private RlpxConfiguration rlpx = new RlpxConfiguration();
private int initiateConnectionsFrequencySec = DEFAULT_INITIATE_CONNECTIONS_FREQUENCY_SEC;
private int checkMaintainedConnectionsFrequencySec =
DEFAULT_CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_SEC;
private Integer peerLowerBound = DEFAULT_PEER_LOWER_BOUND;
private Optional<String> dnsDiscoveryServerOverride = Optional.empty();
public static NetworkingConfiguration create() {
@ -84,6 +86,16 @@ public class NetworkingConfiguration {
return this;
}
public Integer getPeerLowerBound() {
return peerLowerBound;
}
public NetworkingConfiguration setPeerLowerBound(final Integer peerLowerBoundConfig) {
checkArgument(peerLowerBoundConfig > 0);
this.peerLowerBound = peerLowerBoundConfig;
return this;
}
@Override
public boolean equals(final Object o) {
if (o == this) {

@ -14,8 +14,6 @@
*/
package org.hyperledger.besu.ethereum.p2p.config;
import static com.google.common.base.Preconditions.checkState;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol;
import org.hyperledger.besu.util.NetworkUtility;
@ -29,10 +27,6 @@ public class RlpxConfiguration {
private String clientId = "TestClient/1.0.0";
private String bindHost = NetworkUtility.INADDR_ANY;
private int bindPort = 30303;
private int peerUpperBound = 100;
private int peerLowerBound = 64;
private boolean limitRemoteWireConnectionsEnabled = false;
private float fractionRemoteWireConnectionsAllowed = DEFAULT_FRACTION_REMOTE_CONNECTIONS_ALLOWED;
private List<SubProtocol> supportedProtocols = Collections.emptyList();
public static RlpxConfiguration create() {
@ -71,15 +65,6 @@ public class RlpxConfiguration {
return this;
}
public RlpxConfiguration setPeerUpperBound(final int peers) {
peerUpperBound = peers;
return this;
}
public int getPeerUpperBound() {
return peerUpperBound;
}
public String getClientId() {
return clientId;
}
@ -89,29 +74,6 @@ public class RlpxConfiguration {
return this;
}
public RlpxConfiguration setLimitRemoteWireConnectionsEnabled(
final boolean limitRemoteWireConnectionsEnabled) {
this.limitRemoteWireConnectionsEnabled = limitRemoteWireConnectionsEnabled;
return this;
}
public RlpxConfiguration setFractionRemoteWireConnectionsAllowed(
final float fractionRemoteWireConnectionsAllowed) {
checkState(
fractionRemoteWireConnectionsAllowed >= 0.0 && fractionRemoteWireConnectionsAllowed <= 1.0,
"Fraction of remote connections allowed must be between 0.0 and 1.0 (inclusive).");
this.fractionRemoteWireConnectionsAllowed = fractionRemoteWireConnectionsAllowed;
return this;
}
public int getMaxRemotelyInitiatedConnections() {
if (!limitRemoteWireConnectionsEnabled) {
return peerUpperBound;
}
return (int) Math.floor(peerUpperBound * fractionRemoteWireConnectionsAllowed);
}
@Override
public boolean equals(final Object o) {
if (this == o) {
@ -137,13 +99,4 @@ public class RlpxConfiguration {
sb.append('}');
return sb.toString();
}
public RlpxConfiguration setPeerLowerBound(final int peers) {
peerLowerBound = peers;
return this;
}
public int getPeerLowerBound() {
return peerLowerBound;
}
}

@ -132,6 +132,7 @@ public class DiscoveryPeer extends DefaultPeer {
return endpoint;
}
@Override
public Optional<NodeRecord> getNodeRecord() {
return Optional.ofNullable(nodeRecord);
}
@ -141,10 +142,16 @@ public class DiscoveryPeer extends DefaultPeer {
this.forkId = ForkId.fromRawForkId(nodeRecord.get("eth"));
}
@Override
public Optional<ForkId> getForkId() {
return this.forkId;
}
@Override
public void setForkId(final ForkId forkId) {
this.forkId = Optional.ofNullable(forkId);
}
public boolean discoveryEndpointMatches(final DiscoveryPeer peer) {
return peer.getEndpoint().getHost().equals(endpoint.getHost())
&& peer.getEndpoint().getUdpPort() == endpoint.getUdpPort();

@ -43,6 +43,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.ShouldConnectCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.nat.NatMethod;
@ -68,6 +69,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -183,7 +185,7 @@ public class DefaultP2PNetwork implements P2PNetwork {
this.peerPermissions = peerPermissions;
// set the requirement here that the number of peers be greater than the lower bound
final int peerLowerBound = config.getRlpx().getPeerLowerBound();
final int peerLowerBound = rlpxAgent.getPeerLowerBound();
LOG.debug("setting peerLowerBound {}", peerLowerBound);
peerDiscoveryAgent.addPeerRequirement(() -> rlpxAgent.getConnectionCount() >= peerLowerBound);
subscribeDisconnect(reputationManager);
@ -302,6 +304,11 @@ public class DefaultP2PNetwork implements P2PNetwork {
}
}
@Override
public RlpxAgent getRlpxAgent() {
return rlpxAgent;
}
@Override
public boolean addMaintainedConnectionPeer(final Peer peer) {
if (localNode.isReady()
@ -356,11 +363,15 @@ public class DefaultP2PNetwork implements P2PNetwork {
if (!localNode.isReady()) {
return;
}
final EnodeURL localEnodeURL = localNode.getPeer().getEnodeURL();
final List<Bytes> doNotConnectTo =
rlpxAgent
.streamActiveConnections()
.map(c -> c.getPeer().getId())
.collect(Collectors.toList());
doNotConnectTo.add(localNode.getPeer().getEnodeURL().getNodeId());
maintainedPeers
.streamPeers()
.filter(peer -> !peer.getEnodeURL().getNodeId().equals(localEnodeURL.getNodeId()))
.filter(p -> !rlpxAgent.getPeerConnection(p).isPresent())
.filter(p -> !doNotConnectTo.contains(p.getId()))
.forEach(rlpxAgent::connect);
}
@ -379,6 +390,11 @@ public class DefaultP2PNetwork implements P2PNetwork {
return rlpxAgent.streamConnections().collect(Collectors.toList());
}
@Override
public int getPeerCount() {
return getRlpxAgent().getConnectionCount();
}
@Override
public Stream<DiscoveryPeer> streamDiscoveredPeers() {
return peerDiscoveryAgent.streamDiscoveredPeers();
@ -399,6 +415,12 @@ public class DefaultP2PNetwork implements P2PNetwork {
rlpxAgent.subscribeConnect(callback);
}
@Override
public void subscribeConnectRequest(final ShouldConnectCallback callback) {
rlpxAgent.subscribeConnectRequest(callback);
}
;
@Override
public void subscribeDisconnect(final DisconnectCallback callback) {
rlpxAgent.subscribeDisconnect(callback);
@ -474,8 +496,6 @@ public class DefaultP2PNetwork implements P2PNetwork {
private PeerPermissions peerPermissions = PeerPermissions.noop();
private NatService natService = new NatService(Optional.empty());
private boolean randomPeerPriority;
private MetricsSystem metricsSystem;
private StorageProvider storageProvider;
private Optional<TLSConfiguration> p2pTLSConfiguration = Optional.empty();
@ -483,6 +503,9 @@ public class DefaultP2PNetwork implements P2PNetwork {
private List<Long> blockNumberForks;
private List<Long> timestampForks;
private boolean legacyForkIdEnabled = false;
private Supplier<Stream<PeerConnection>> allConnectionsSupplier;
private Supplier<Stream<PeerConnection>> allActiveConnectionsSupplier;
private int peersLowerBound;
public P2PNetwork build() {
validate();
@ -526,6 +549,8 @@ public class DefaultP2PNetwork implements P2PNetwork {
checkState(peerDiscoveryAgent != null || vertx != null, "Vertx must be set.");
checkState(blockNumberForks != null, "BlockNumberForks must be set.");
checkState(timestampForks != null, "TimestampForks must be set.");
checkState(allConnectionsSupplier != null, "Supplier must be set.");
checkState(allActiveConnectionsSupplier != null, "Supplier must be set.");
}
private PeerDiscoveryAgent createDiscoveryAgent() {
@ -546,6 +571,7 @@ public class DefaultP2PNetwork implements P2PNetwork {
private RlpxAgent createRlpxAgent(
final LocalNode localNode, final PeerPrivileges peerPrivileges) {
return RlpxAgent.builder()
.nodeKey(nodeKey)
.config(config.getRlpx())
@ -553,8 +579,10 @@ public class DefaultP2PNetwork implements P2PNetwork {
.peerPrivileges(peerPrivileges)
.localNode(localNode)
.metricsSystem(metricsSystem)
.randomPeerPriority(randomPeerPriority)
.p2pTLSConfiguration(p2pTLSConfiguration)
.allConnectionsSupplier(allConnectionsSupplier)
.allActiveConnectionsSupplier(allActiveConnectionsSupplier)
.peersLowerBound(peersLowerBound)
.build();
}
@ -570,11 +598,6 @@ public class DefaultP2PNetwork implements P2PNetwork {
return this;
}
public Builder randomPeerPriority(final boolean randomPeerPriority) {
this.randomPeerPriority = randomPeerPriority;
return this;
}
public Builder vertx(final Vertx vertx) {
checkNotNull(vertx);
this.vertx = vertx;
@ -662,5 +685,22 @@ public class DefaultP2PNetwork implements P2PNetwork {
this.legacyForkIdEnabled = legacyForkIdEnabled;
return this;
}
public Builder allConnectionsSupplier(
final Supplier<Stream<PeerConnection>> allConnectionsSupplier) {
this.allConnectionsSupplier = allConnectionsSupplier;
return this;
}
public Builder allActiveConnectionsSupplier(
final Supplier<Stream<PeerConnection>> allActiveConnectionsSupplier) {
this.allActiveConnectionsSupplier = allActiveConnectionsSupplier;
return this;
}
public Builder peersLowerBound(final int peersLowerBound) {
this.peersLowerBound = peersLowerBound;
return this;
}
}
}

@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.ethereum.p2p.network;
import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
@ -154,6 +155,9 @@ public class NetworkRunner implements AutoCloseable {
protocolManager.handleNewConnection(connection);
});
network.subscribeConnectRequest(
(peer, incoming) -> protocolManager.shouldConnect(peer, incoming));
network.subscribeDisconnect(
(connection, disconnectReason, initiatedByPeer) -> {
if (Collections.disjoint(
@ -170,6 +174,10 @@ public class NetworkRunner implements AutoCloseable {
stop();
}
public RlpxAgent getRlpxAgent() {
return network.getRlpxAgent();
}
public static class Builder {
private NetworkBuilder networkProvider;
List<ProtocolManager> protocolManagers = new ArrayList<>();

@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.DisconnectCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.MessageCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.ShouldConnectCallback;
import org.hyperledger.besu.plugin.data.EnodeURL;
import java.io.IOException;
@ -52,6 +53,9 @@ public class NoopP2PNetwork implements P2PNetwork {
@Override
public void subscribeConnect(final ConnectCallback callback) {}
@Override
public void subscribeConnectRequest(final ShouldConnectCallback callback) {}
@Override
public void subscribeDisconnect(final DisconnectCallback callback) {}

@ -19,9 +19,11 @@ import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.ConnectCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.DisconnectCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.MessageCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Message;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.ShouldConnectCallback;
import org.hyperledger.besu.plugin.data.EnodeURL;
import java.io.Closeable;
@ -83,8 +85,9 @@ public interface P2PNetwork extends Closeable {
*
* @param callback The callback to invoke when a new connection is established
*/
void subscribeConnect(ConnectCallback callback);
void subscribeConnect(final ConnectCallback callback);
void subscribeConnectRequest(final ShouldConnectCallback callback);
/**
* Subscribe a {@link Consumer} to all incoming new Peer disconnect events.
*
@ -108,7 +111,7 @@ public interface P2PNetwork extends Closeable {
* disconnected even if it is not in the maintained peer list. See {@link
* #addMaintainedConnectionPeer(Peer)} for details on the maintained peer list.
*
* @param peer The peer to which connections are not longer required
* @param peer The peer to which connections are no longer required
* @return boolean representing whether the peer was removed from the maintained peer list
*/
boolean removeMaintainedConnectionPeer(final Peer peer);
@ -149,4 +152,9 @@ public interface P2PNetwork extends Closeable {
Optional<EnodeURL> getLocalEnode();
void updateNodeRecord();
default RlpxAgent getRlpxAgent() {
return null;
}
;
}

@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.ethereum.p2p.network;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Message;
@ -58,6 +59,14 @@ public interface ProtocolManager extends AutoCloseable {
*/
void handleNewConnection(PeerConnection peerConnection);
/**
* Call this to find out whether we should try to connect to a certain peer
*
* @param peer the peer that we are trying to connect to
* @param incoming true if the connection is incoming
* @return true, if the ProtocolManager wants to connect to the peer, false otherwise
*/
boolean shouldConnect(Peer peer, final boolean incoming);
/**
* Handles peer disconnects.
*

@ -14,15 +14,18 @@
*/
package org.hyperledger.besu.ethereum.p2p.peers;
import org.hyperledger.besu.ethereum.forkid.ForkId;
import org.hyperledger.besu.plugin.data.EnodeURL;
import java.net.URI;
import java.util.Objects;
import java.util.Optional;
/** The default, basic representation of an Ethereum {@link Peer}. */
public class DefaultPeer extends DefaultPeerId implements Peer {
private final EnodeURL enodeURL;
private ForkId forkId;
protected DefaultPeer(final EnodeURL enodeURL) {
super(enodeURL.getNodeId());
@ -60,6 +63,16 @@ public class DefaultPeer extends DefaultPeerId implements Peer {
return enodeURL;
}
@Override
public Optional<ForkId> getForkId() {
return Optional.ofNullable(forkId);
}
@Override
public void setForkId(final ForkId forkId) {
this.forkId = forkId;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {

@ -15,9 +15,13 @@
package org.hyperledger.besu.ethereum.p2p.peers;
import org.hyperledger.besu.crypto.SecureRandomProvider;
import org.hyperledger.besu.ethereum.forkid.ForkId;
import org.hyperledger.besu.plugin.data.EnodeURL;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.ethereum.beacon.discovery.schema.NodeRecord;
public interface Peer extends PeerId {
@ -47,4 +51,29 @@ public interface Peer extends PeerId {
default String getEnodeURLString() {
return this.getEnodeURL().toString();
}
/**
* Returns the node record
*
* @return The node record wrapped in an Optional
*/
default Optional<NodeRecord> getNodeRecord() {
return Optional.empty();
}
/**
* Returns the fork id
*
* @return The fork id wrapped in an Optional
*/
default Optional<ForkId> getForkId() {
return Optional.empty();
}
/**
* Sets the fork id
*
* @param forkId The fork id
*/
void setForkId(ForkId forkId);
}

@ -16,9 +16,7 @@ package org.hyperledger.besu.ethereum.p2p.rlpx;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.isNull;
import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.cryptoservices.NodeKey;
import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration;
import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer;
@ -30,31 +28,33 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.connections.ConnectionInitializer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnectionEvents;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerRlpxPermissions;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.RlpxConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.NettyConnectionInitializer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.NettyTLSConnectionInitializer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.ShouldConnectCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.util.Subscribers;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.tuweni.bytes.Bytes;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -65,23 +65,20 @@ public class RlpxAgent {
private final PeerConnectionEvents connectionEvents;
private final ConnectionInitializer connectionInitializer;
private final Subscribers<ConnectCallback> connectSubscribers = Subscribers.create();
private final List<ShouldConnectCallback> connectRequestSubscribers = new ArrayList<>();
private final PeerRlpxPermissions peerPermissions;
private final PeerPrivileges peerPrivileges;
private final int upperBoundConnections;
private final int lowerBoundConnections;
private final boolean randomPeerPriority;
private final int maxRemotelyInitiatedConnections;
// xor'ing with this mask will allow us to randomly let new peers connect
// without allowing the counterparty to play nodeId farming games
private final Bytes nodeIdMask = Bytes.random(SECPPublicKey.BYTE_LENGTH);
final Map<Bytes, RlpxConnection> connectionsById = new ConcurrentHashMap<>();
private final AtomicBoolean started = new AtomicBoolean(false);
private final AtomicBoolean stopped = new AtomicBoolean(false);
private final Counter connectedPeersCounter;
private final int lowerBound;
private final Supplier<Stream<PeerConnection>> allConnectionsSupplier;
private final Supplier<Stream<PeerConnection>> allActiveConnectionsSupplier;
private final Cache<Bytes, CompletableFuture<PeerConnection>> peersConnectingCache =
CacheBuilder.newBuilder()
.expireAfterWrite(
Duration.ofSeconds(30L)) // we will at most try to connect every 30 seconds
.concurrencyLevel(1)
.build();
private RlpxAgent(
final LocalNode localNode,
@ -89,32 +86,17 @@ public class RlpxAgent {
final ConnectionInitializer connectionInitializer,
final PeerRlpxPermissions peerPermissions,
final PeerPrivileges peerPrivileges,
final int upperBoundConnections,
final int lowerBoundConnections,
final int maxRemotelyInitiatedConnections,
final boolean randomPeerPriority,
final MetricsSystem metricsSystem) {
final int peersLowerBound,
final Supplier<Stream<PeerConnection>> allConnectionsSupplier,
final Supplier<Stream<PeerConnection>> allActiveConnectionsSupplier) {
this.localNode = localNode;
this.connectionEvents = connectionEvents;
this.connectionInitializer = connectionInitializer;
this.peerPermissions = peerPermissions;
this.peerPrivileges = peerPrivileges;
this.upperBoundConnections = upperBoundConnections;
this.lowerBoundConnections = lowerBoundConnections;
this.randomPeerPriority = randomPeerPriority;
this.maxRemotelyInitiatedConnections =
Math.min(upperBoundConnections, maxRemotelyInitiatedConnections);
// Setup metrics
connectedPeersCounter =
metricsSystem.createCounter(
BesuMetricCategory.PEERS, "connected_total", "Total number of peers connected");
metricsSystem.createIntegerGauge(
BesuMetricCategory.ETHEREUM,
"peer_limit",
"The maximum number of peers this node allows to connect",
() -> upperBoundConnections);
this.lowerBound = peersLowerBound;
this.allConnectionsSupplier = allConnectionsSupplier;
this.allActiveConnectionsSupplier = allActiveConnectionsSupplier;
}
public static Builder builder() {
@ -156,50 +138,58 @@ public class RlpxAgent {
return connectionInitializer.stop();
}
public Stream<? extends PeerConnection> streamConnections() {
return connectionsById.values().stream()
.filter(RlpxConnection::isActive)
.map(RlpxConnection::getPeerConnection);
public Stream<PeerConnection> streamConnections() {
try {
return allConnectionsSupplier.get();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
public Stream<PeerConnection> streamActiveConnections() {
try {
return allActiveConnectionsSupplier.get();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
public int getConnectionCount() {
return Math.toIntExact(streamConnections().count());
try {
return (int) allActiveConnectionsSupplier.get().count();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
public void connect(final Stream<? extends Peer> peerStream) {
if (!localNode.isReady()) {
return;
}
peerStream
.takeWhile(peer -> Math.max(0, lowerBoundConnections - getConnectionCount()) > 0)
.filter(peer -> !connectionsById.containsKey(peer.getId()))
.filter(peer -> peer.getEnodeURL().isListening())
.filter(peerPermissions::allowNewOutboundConnectionTo)
.forEach(this::connect);
}
private String logConnectionsByIdToString() {
final String connectionsList =
connectionsById.values().stream()
.map(RlpxConnection::toString)
.collect(Collectors.joining(",\n"));
return connectionsById.size() + " ConnectionsById {\n" + connectionsList + "}";
peerStream.forEach(this::connect);
}
public void disconnect(final Bytes peerId, final DisconnectReason reason) {
final RlpxConnection connection = connectionsById.remove(peerId);
if (connection != null) {
connection.disconnect(reason);
try {
allActiveConnectionsSupplier
.get()
.filter(c -> c.getPeer().getId().equals(peerId))
.forEach(c -> c.disconnect(reason));
final CompletableFuture<PeerConnection> peerConnectionCompletableFuture =
getMapOfCompletableFutures().get(peerId);
if (peerConnectionCompletableFuture != null) {
if (!peerConnectionCompletableFuture.isDone()) {
peerConnectionCompletableFuture.cancel(true);
} else if (!peerConnectionCompletableFuture.isCompletedExceptionally()
&& !peerConnectionCompletableFuture.isCancelled()) {
peerConnectionCompletableFuture.get().disconnect(reason);
}
}
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
public Optional<CompletableFuture<PeerConnection>> getPeerConnection(final Peer peer) {
final RlpxConnection connection = connectionsById.get(peer.getId());
return Optional.ofNullable(connection)
.filter(conn -> !conn.isFailedOrDisconnected())
.map(RlpxConnection::getFuture);
}
/**
* Connect to the peer
*
@ -224,82 +214,55 @@ public class RlpxAgent {
return CompletableFuture.failedFuture((new IllegalArgumentException(errorMsg)));
}
// Shortcut checks if we're already connected
final Optional<CompletableFuture<PeerConnection>> peerConnection = getPeerConnection(peer);
if (peerConnection.isPresent()) {
return peerConnection.get();
}
// Check max peers
if (!peerPrivileges.canExceedConnectionLimits(peer)
&& getConnectionCount() >= upperBoundConnections) {
final String errorMsg =
"Max peer connections established ("
+ upperBoundConnections
+ "). Cannot connect to peer: "
+ peer;
return CompletableFuture.failedFuture(new IllegalStateException(errorMsg));
}
// Check permissions
if (!peerPermissions.allowNewOutboundConnectionTo(peer)) {
return CompletableFuture.failedFuture(peerPermissions.newOutboundConnectionException(peer));
}
// Initiate connection or return existing connection
final AtomicReference<CompletableFuture<PeerConnection>> connectionFuture =
new AtomicReference<>();
connectionsById.compute(
peer.getId(),
(id, existingConnection) -> {
if (existingConnection != null && !existingConnection.isFailedOrDisconnected()) {
// We're already connected or connecting
connectionFuture.set(existingConnection.getFuture());
return existingConnection;
} else {
// We're initiating a new connection
final CompletableFuture<PeerConnection> future = initiateOutboundConnection(peer);
connectionFuture.set(future);
final RlpxConnection newConnection = RlpxConnection.outboundConnection(peer, future);
newConnection.subscribeConnectionEstablished(
(conn) -> {
this.dispatchConnect(conn.getPeerConnection());
this.enforceConnectionLimits();
},
(failedConn) -> cleanUpPeerConnection(failedConn.getId()));
return newConnection;
final CompletableFuture<PeerConnection> peerConnectionCompletableFuture;
if (checkWhetherToConnect(peer, false)) {
try {
synchronized (this) {
peerConnectionCompletableFuture =
peersConnectingCache.get(
peer.getId(), () -> createPeerConnectionCompletableFuture(peer));
}
} catch (final ExecutionException e) {
throw new RuntimeException(e);
}
} else {
final String errorMsg =
"None of the ProtocolManagers wants to connect to peer " + peer.getId();
LOG.trace(errorMsg);
return CompletableFuture.failedFuture((new RuntimeException(errorMsg)));
}
return peerConnectionCompletableFuture;
}
@NotNull
private CompletableFuture<PeerConnection> createPeerConnectionCompletableFuture(final Peer peer) {
final CompletableFuture<PeerConnection> peerConnectionCompletableFuture =
initiateOutboundConnection(peer);
peerConnectionCompletableFuture.whenComplete(
(peerConnection, throwable) -> {
if (throwable == null) {
dispatchConnect(peerConnection);
}
});
return peerConnectionCompletableFuture;
}
LOG.atTrace().setMessage("{}").addArgument(this::logConnectionsByIdToString).log();
return connectionFuture.get();
private boolean checkWhetherToConnect(final Peer peer, final boolean incoming) {
return connectRequestSubscribers.stream()
.anyMatch(callback -> callback.shouldConnect(peer, incoming));
}
private void setupListeners() {
connectionInitializer.subscribeIncomingConnect(this::handleIncomingConnection);
connectionEvents.subscribeDisconnect(this::handleDisconnect);
peerPermissions.subscribeUpdate(this::handlePermissionsUpdate);
}
private void handleDisconnect(
final PeerConnection peerConnection,
final DisconnectReason disconnectReason,
final boolean initiatedByPeer) {
LOG.atTrace().setMessage("{}").addArgument(this::logConnectionsByIdToString).log();
cleanUpPeerConnection(peerConnection.getPeer().getId());
}
private void cleanUpPeerConnection(final Bytes peerId) {
connectionsById.compute(
peerId,
(id, trackedConnection) -> {
if (isNull(trackedConnection) || trackedConnection.isFailedOrDisconnected()) {
// Remove if failed or disconnected
return null;
}
return trackedConnection;
});
}
private void handlePermissionsUpdate(
final boolean permissionsRestricted, final Optional<List<Peer>> peers) {
if (!permissionsRestricted) {
@ -307,23 +270,23 @@ public class RlpxAgent {
return;
}
final List<RlpxConnection> connectionsToCheck =
peers
.map(
updatedPeers ->
updatedPeers.stream()
.map(peer -> connectionsById.get(peer.getId()))
.filter(connection -> !isNull(connection))
.collect(Collectors.toList()))
.orElse(new ArrayList<>(connectionsById.values()));
final Stream<PeerConnection> connectionsToCheck;
if (peers.isPresent()) {
final List<Bytes> changedPeersIds =
peers.get().stream().map(p -> p.getId()).collect(Collectors.toList());
connectionsToCheck =
streamConnections().filter(c -> changedPeersIds.contains(c.getPeer().getId()));
} else {
connectionsToCheck = streamConnections();
}
connectionsToCheck.forEach(
connection -> {
if (!peerPermissions.allowOngoingConnection(
connection.getPeer(), connection.initiatedRemotely())) {
connection.getPeer(), connection.inboundInitiated())) {
LOG.debug(
"Disconnecting from peer that is not permitted to maintain ongoing connection: {}",
connection.getPeerConnection());
connection);
connection.disconnect(DisconnectReason.REQUESTED);
}
});
@ -347,6 +310,10 @@ public class RlpxAgent {
});
}
public boolean canExceedConnectionLimits(final Peer peer) {
return peerPrivileges.canExceedConnectionLimits(peer);
}
private void handleIncomingConnection(final PeerConnection peerConnection) {
final Peer peer = peerConnection.getPeer();
// Deny connection if our local node isn't ready
@ -355,28 +322,7 @@ public class RlpxAgent {
peerConnection.disconnect(DisconnectReason.UNKNOWN);
return;
}
if (!randomPeerPriority) {
// Disconnect if too many peers
if (!peerPrivileges.canExceedConnectionLimits(peer)
&& getConnectionCount() >= upperBoundConnections) {
LOG.debug(
"Too many peers. Disconnect incoming connection: {} currentCount {}, max {}",
peerConnection,
getConnectionCount(),
upperBoundConnections);
peerConnection.disconnect(DisconnectReason.TOO_MANY_PEERS);
return;
}
// Disconnect if too many remotely-initiated connections
if (!peerPrivileges.canExceedConnectionLimits(peer) && remoteConnectionLimitReached()) {
LOG.debug(
"Too many remotely-initiated connections. Disconnect incoming connection: {}, maxRemote={}",
peerConnection,
maxRemotelyInitiatedConnections);
peerConnection.disconnect(DisconnectReason.TOO_MANY_PEERS);
return;
}
}
// Disconnect if not permitted
if (!peerPermissions.allowNewInboundConnectionFrom(peer)) {
LOG.debug(
@ -385,171 +331,11 @@ public class RlpxAgent {
return;
}
// Track this new connection, deduplicating existing connection if necessary
final AtomicBoolean newConnectionAccepted = new AtomicBoolean(false);
final RlpxConnection inboundConnection = RlpxConnection.inboundConnection(peerConnection);
// Our disconnect handler runs connectionsById.compute(), so don't actually execute the
// disconnect command until we've returned from our compute() calculation
final AtomicReference<Runnable> disconnectAction = new AtomicReference<>();
connectionsById.compute(
peer.getId(),
(nodeId, existingConnection) -> {
if (existingConnection == null) {
// The new connection is unique, set it and return
LOG.debug("Inbound connection established with {}", peerConnection.getPeer().getId());
newConnectionAccepted.set(true);
return inboundConnection;
}
// We already have an existing connection, figure out which connection to keep
if (compareDuplicateConnections(inboundConnection, existingConnection) < 0) {
// Keep the inbound connection
LOG.debug(
"Duplicate connection detected, disconnecting previously established connection in favor of new inbound connection for peer: {}",
peerConnection.getPeer().getId());
disconnectAction.set(
() -> existingConnection.disconnect(DisconnectReason.ALREADY_CONNECTED));
newConnectionAccepted.set(true);
return inboundConnection;
} else {
// Keep the existing connection
LOG.debug(
"Duplicate connection detected, disconnecting inbound connection in favor of previously established connection for peer: {}",
peerConnection.getPeer().getId());
disconnectAction.set(
() -> inboundConnection.disconnect(DisconnectReason.ALREADY_CONNECTED));
return existingConnection;
}
});
if (!isNull(disconnectAction.get())) {
disconnectAction.get().run();
}
if (newConnectionAccepted.get()) {
if (checkWhetherToConnect(peer, true)) {
dispatchConnect(peerConnection);
}
// Check remote connections again to control for race conditions
enforceRemoteConnectionLimits();
enforceConnectionLimits();
LOG.atTrace().setMessage("{}").addArgument(this::logConnectionsByIdToString).log();
}
private boolean shouldLimitRemoteConnections() {
return maxRemotelyInitiatedConnections < upperBoundConnections;
}
private boolean remoteConnectionLimitReached() {
return shouldLimitRemoteConnections()
&& countUntrustedRemotelyInitiatedConnections() >= maxRemotelyInitiatedConnections;
}
private long countUntrustedRemotelyInitiatedConnections() {
return connectionsById.values().stream()
.filter(RlpxConnection::isActive)
.filter(RlpxConnection::initiatedRemotely)
.filter(conn -> !peerPrivileges.canExceedConnectionLimits(conn.getPeer()))
.count();
}
private void enforceRemoteConnectionLimits() {
if (!shouldLimitRemoteConnections()
|| connectionsById.size() < maxRemotelyInitiatedConnections) {
// Nothing to do
return;
}
getActivePrioritizedConnections()
.filter(RlpxConnection::initiatedRemotely)
.filter(conn -> !peerPrivileges.canExceedConnectionLimits(conn.getPeer()))
.skip(maxRemotelyInitiatedConnections)
.forEach(
conn -> {
LOG.debug(
"Too many remotely initiated connections. Disconnect low-priority connection: {}, maxRemote={}",
conn,
maxRemotelyInitiatedConnections);
conn.disconnect(DisconnectReason.TOO_MANY_PEERS);
});
}
private void enforceConnectionLimits() {
if (connectionsById.size() < upperBoundConnections) {
// Nothing to do - we're under our limits
return;
}
getActivePrioritizedConnections()
.skip(upperBoundConnections)
.filter(c -> !peerPrivileges.canExceedConnectionLimits(c.getPeer()))
.forEach(
conn -> {
LOG.debug(
"Too many connections. Disconnect low-priority connection: {}, maxConnections={}",
conn,
upperBoundConnections);
conn.disconnect(DisconnectReason.TOO_MANY_PEERS);
});
}
private Stream<RlpxConnection> getActivePrioritizedConnections() {
return connectionsById.values().stream()
.filter(RlpxConnection::isActive)
.sorted(this::comparePeerPriorities);
}
private int comparePeerPriorities(final RlpxConnection a, final RlpxConnection b) {
final boolean aIgnoresPeerLimits = peerPrivileges.canExceedConnectionLimits(a.getPeer());
final boolean bIgnoresPeerLimits = peerPrivileges.canExceedConnectionLimits(b.getPeer());
if (aIgnoresPeerLimits && !bIgnoresPeerLimits) {
return -1;
} else if (bIgnoresPeerLimits && !aIgnoresPeerLimits) {
return 1;
} else {
return randomPeerPriority
? compareByMaskedNodeId(a, b)
: compareConnectionInitiationTimes(a, b);
}
}
private int compareConnectionInitiationTimes(final RlpxConnection a, final RlpxConnection b) {
return Math.toIntExact(a.getInitiatedAt() - b.getInitiatedAt());
}
private int compareByMaskedNodeId(final RlpxConnection a, final RlpxConnection b) {
return a.getPeer().getId().xor(nodeIdMask).compareTo(b.getPeer().getId().xor(nodeIdMask));
}
/**
* Compares two connections to the same peer to determine which connection should be kept
*
* @param a The first connection
* @param b The second connection
* @return A negative value if {@code a} should be kept, a positive value is {@code b} should be
* kept
*/
private int compareDuplicateConnections(final RlpxConnection a, final RlpxConnection b) {
checkState(localNode.isReady());
checkState(Objects.equals(a.getPeer().getId(), b.getPeer().getId()));
if (a.isFailedOrDisconnected() != b.isFailedOrDisconnected()) {
// One connection has failed - prioritize the one that hasn't failed
return a.isFailedOrDisconnected() ? 1 : -1;
}
final Bytes peerId = a.getPeer().getId();
final Bytes localId = localNode.getPeer().getId();
// at this point a.Id == b.Id
if (a.initiatedRemotely() != b.initiatedRemotely()) {
// If we have connections initiated in different directions, keep the connection initiated
// by the node with the lower id
if (localId.compareTo(peerId) < 0) {
return a.initiatedLocally() ? -1 : 1;
} else {
return a.initiatedLocally() ? 1 : -1;
}
peerConnection.disconnect(DisconnectReason.UNKNOWN);
}
// Otherwise, keep older connection
LOG.debug("comparing timestamps " + a.getInitiatedAt() + " with " + b.getInitiatedAt());
return Math.toIntExact(a.getInitiatedAt() - b.getInitiatedAt());
}
public void subscribeMessage(final Capability capability, final MessageCallback callback) {
@ -560,15 +346,27 @@ public class RlpxAgent {
connectSubscribers.subscribe(callback);
}
public void subscribeConnectRequest(final ShouldConnectCallback callback) {
connectRequestSubscribers.add(callback);
}
public void subscribeDisconnect(final DisconnectCallback callback) {
connectionEvents.subscribeDisconnect(callback);
}
private void dispatchConnect(final PeerConnection connection) {
connectedPeersCounter.inc();
connectSubscribers.forEach(c -> c.onConnect(connection));
}
@VisibleForTesting
public ConcurrentMap<Bytes, CompletableFuture<PeerConnection>> getMapOfCompletableFutures() {
return peersConnectingCache.asMap();
}
public int getPeerLowerBound() {
return lowerBound;
}
public static class Builder {
private NodeKey nodeKey;
private LocalNode localNode;
@ -577,9 +375,11 @@ public class RlpxAgent {
private PeerPermissions peerPermissions;
private ConnectionInitializer connectionInitializer;
private PeerConnectionEvents connectionEvents;
private boolean randomPeerPriority;
private MetricsSystem metricsSystem;
private Optional<TLSConfiguration> p2pTLSConfiguration;
private Supplier<Stream<PeerConnection>> allConnectionsSupplier;
private Supplier<Stream<PeerConnection>> allActiveConnectionsSupplier;
private int peersLowerBound;
private Builder() {}
@ -616,11 +416,9 @@ public class RlpxAgent {
connectionInitializer,
rlpxPermissions,
peerPrivileges,
config.getPeerUpperBound(),
config.getPeerLowerBound(),
config.getMaxRemotelyInitiatedConnections(),
randomPeerPriority,
metricsSystem);
peersLowerBound,
allConnectionsSupplier,
allActiveConnectionsSupplier);
}
private void validate() {
@ -680,13 +478,25 @@ public class RlpxAgent {
return this;
}
public Builder randomPeerPriority(final boolean randomPeerPriority) {
this.randomPeerPriority = randomPeerPriority;
public Builder p2pTLSConfiguration(final Optional<TLSConfiguration> p2pTLSConfiguration) {
this.p2pTLSConfiguration = p2pTLSConfiguration;
return this;
}
public Builder p2pTLSConfiguration(final Optional<TLSConfiguration> p2pTLSConfiguration) {
this.p2pTLSConfiguration = p2pTLSConfiguration;
public Builder allConnectionsSupplier(
final Supplier<Stream<PeerConnection>> allConnectionsSupplier) {
this.allConnectionsSupplier = allConnectionsSupplier;
return this;
}
public Builder allActiveConnectionsSupplier(
final Supplier<Stream<PeerConnection>> allActiveConnectionsSupplier) {
this.allActiveConnectionsSupplier = allActiveConnectionsSupplier;
return this;
}
public Builder peersLowerBound(final int peersLowerBound) {
this.peersLowerBound = peersLowerBound;
return this;
}
}

@ -32,9 +32,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import com.google.common.base.MoreObjects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -53,6 +51,10 @@ public abstract class AbstractPeerConnection implements PeerConnection {
private final AtomicBoolean disconnected = new AtomicBoolean(false);
protected final PeerConnectionEventDispatcher connectionEventDispatcher;
private final LabelledMetric<Counter> outboundMessagesCounter;
private final long initiatedAt;
private final boolean inboundInitiated;
private boolean statusSent;
private boolean statusReceived;
protected AbstractPeerConnection(
final Peer peer,
@ -62,7 +64,8 @@ public abstract class AbstractPeerConnection implements PeerConnection {
final String connectionId,
final CapabilityMultiplexer multiplexer,
final PeerConnectionEventDispatcher connectionEventDispatcher,
final LabelledMetric<Counter> outboundMessagesCounter) {
final LabelledMetric<Counter> outboundMessagesCounter,
final boolean inboundInitiated) {
this.peer = peer;
this.peerInfo = peerInfo;
this.localAddress = localAddress;
@ -76,6 +79,10 @@ public abstract class AbstractPeerConnection implements PeerConnection {
}
this.connectionEventDispatcher = connectionEventDispatcher;
this.outboundMessagesCounter = outboundMessagesCounter;
this.inboundInitiated = inboundInitiated;
this.initiatedAt = System.currentTimeMillis();
LOG.debug("New PeerConnection ({}) established with peer {}", this, peer.getId());
}
@Override
@ -147,7 +154,7 @@ public abstract class AbstractPeerConnection implements PeerConnection {
// Always ensure the context gets closed immediately even if we previously sent a disconnect
// message and are waiting to close.
closeConnectionImmediately();
LOG.debug("Terminating connection {}, reason {}", System.identityHashCode(this), reason);
LOG.debug("Terminating connection {}, reason {}", this, reason);
}
protected abstract void closeConnectionImmediately();
@ -179,6 +186,16 @@ public abstract class AbstractPeerConnection implements PeerConnection {
return remoteAddress;
}
@Override
public long getInitiatedAt() {
return initiatedAt;
}
@Override
public boolean inboundInitiated() {
return inboundInitiated;
}
@Override
public boolean equals(final Object o) {
if (o == this) {
@ -197,14 +214,31 @@ public abstract class AbstractPeerConnection implements PeerConnection {
return Objects.hash(connectionId, peer);
}
@Override
public void setStatusSent() {
this.statusSent = true;
}
@Override
public void setStatusReceived() {
this.statusReceived = true;
}
@Override
public boolean getStatusExchanged() {
return statusReceived && statusSent;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("nodeId", peerInfo.getNodeId())
.add("clientId", peerInfo.getClientId())
.add(
"caps",
agreedCapabilities.stream().map(Capability::toString).collect(Collectors.joining(", ")))
.toString();
return "[Connection with hashCode "
+ hashCode()
+ " with peer "
+ this.peer.getId()
+ " inboundInitiated "
+ inboundInitiated
+ " initAt "
+ initiatedAt
+ "]";
}
}

@ -122,4 +122,20 @@ public interface PeerConnection {
default EnodeURL getRemoteEnode() {
return getPeer().getEnodeURL();
}
/**
* Get the difference, measured in milliseconds, between the time this connection was initiated
* and midnight, January 1, 1970 UTC
*
* @return the time when this connection was initiated.
*/
long getInitiatedAt();
boolean inboundInitiated();
void setStatusSent();
void setStatusReceived();
boolean getStatusExchanged();
}

@ -1,254 +0,0 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.rlpx.connections;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import org.apache.tuweni.bytes.Bytes;
public abstract class RlpxConnection {
private final long initiatedAt;
protected final CompletableFuture<PeerConnection> future;
private RlpxConnection(final CompletableFuture<PeerConnection> future) {
this.future = future;
this.initiatedAt = System.currentTimeMillis();
}
public static RlpxConnection inboundConnection(final PeerConnection peerConnection) {
return new RemotelyInitiatedRlpxConnection(peerConnection);
}
public static RlpxConnection outboundConnection(
final Peer peer, final CompletableFuture<PeerConnection> future) {
return new LocallyInitiatedRlpxConnection(peer, future);
}
public abstract Peer getPeer();
public abstract void disconnect(DisconnectReason reason);
public Bytes getId() {
return getPeer().getId();
}
public abstract PeerConnection getPeerConnection() throws ConnectionNotEstablishedException;
public CompletableFuture<PeerConnection> getFuture() {
return future;
}
public abstract boolean isActive();
public abstract boolean isPending();
public abstract boolean isFailedOrDisconnected();
public abstract boolean initiatedRemotely();
public void subscribeConnectionEstablished(
final RlpxConnectCallback successCallback, final RlpxConnectFailedCallback failedCallback) {
future.whenComplete(
(conn, err) -> {
if (err != null) {
failedCallback.onFailure(this);
} else {
successCallback.onConnect(this);
}
});
}
public boolean initiatedLocally() {
return !initiatedRemotely();
}
public long getInitiatedAt() {
return initiatedAt;
}
private static class RemotelyInitiatedRlpxConnection extends RlpxConnection {
private final PeerConnection peerConnection;
private RemotelyInitiatedRlpxConnection(final PeerConnection peerConnection) {
super(CompletableFuture.completedFuture(peerConnection));
this.peerConnection = peerConnection;
}
@Override
public Peer getPeer() {
return peerConnection.getPeer();
}
@Override
public void disconnect(final DisconnectReason reason) {
peerConnection.disconnect(reason);
}
@Override
public PeerConnection getPeerConnection() {
return peerConnection;
}
@Override
public boolean isActive() {
return !peerConnection.isDisconnected();
}
@Override
public boolean isPending() {
return false;
}
@Override
public boolean isFailedOrDisconnected() {
return peerConnection.isDisconnected();
}
@Override
public boolean initiatedRemotely() {
return true;
}
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof RemotelyInitiatedRlpxConnection)) {
return false;
}
final RemotelyInitiatedRlpxConnection that = (RemotelyInitiatedRlpxConnection) o;
return Objects.equals(peerConnection, that.peerConnection);
}
@Override
public int hashCode() {
return Objects.hash(peerConnection);
}
@Override
public String toString() {
return "RemotelyInitiatedRlpxConnection initiatedAt:"
+ getInitiatedAt()
+ " to "
+ peerConnection.getPeer().getId()
+ " disconnected? "
+ isFailedOrDisconnected();
}
}
private static class LocallyInitiatedRlpxConnection extends RlpxConnection {
private final Peer peer;
private LocallyInitiatedRlpxConnection(
final Peer peer, final CompletableFuture<PeerConnection> future) {
super(future);
this.peer = peer;
}
@Override
public Peer getPeer() {
return peer;
}
@Override
public void disconnect(final DisconnectReason reason) {
future.thenAccept((conn) -> conn.disconnect(reason));
}
@Override
public PeerConnection getPeerConnection() throws ConnectionNotEstablishedException {
if (!future.isDone() || future.isCompletedExceptionally()) {
throw new ConnectionNotEstablishedException(
"Cannot access PeerConnection before connection is fully established.");
}
return future.getNow(null);
}
@Override
public boolean isActive() {
return future.isDone()
&& !future.isCompletedExceptionally()
&& !getPeerConnection().isDisconnected();
}
@Override
public boolean isPending() {
return !future.isDone();
}
@Override
public boolean isFailedOrDisconnected() {
return future.isCompletedExceptionally()
|| (future.isDone() && getPeerConnection().isDisconnected());
}
@Override
public boolean initiatedRemotely() {
return false;
}
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof LocallyInitiatedRlpxConnection)) {
return false;
}
final LocallyInitiatedRlpxConnection that = (LocallyInitiatedRlpxConnection) o;
return Objects.equals(peer, that.peer) && Objects.equals(future, that.future);
}
@Override
public int hashCode() {
return Objects.hash(peer, future);
}
@Override
public String toString() {
return "LocallyInitiatedRlpxConnection initiatedAt:"
+ getInitiatedAt()
+ " to "
+ getPeer().getId()
+ " disconnected? "
+ isFailedOrDisconnected();
}
}
public static class ConnectionNotEstablishedException extends IllegalStateException {
public ConnectionNotEstablishedException(final String message) {
super(message);
}
}
@FunctionalInterface
public interface RlpxConnectCallback {
void onConnect(RlpxConnection connection);
}
@FunctionalInterface
public interface RlpxConnectFailedCallback {
void onFailure(RlpxConnection connection);
}
}

@ -59,6 +59,7 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler<Byte
private final MetricsSystem metricsSystem;
private final FramerProvider framerProvider;
private final boolean inboundInitiated;
AbstractHandshakeHandler(
final List<SubProtocol> subProtocols,
@ -68,7 +69,8 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler<Byte
final PeerConnectionEventDispatcher connectionEventDispatcher,
final MetricsSystem metricsSystem,
final HandshakerProvider handshakerProvider,
final FramerProvider framerProvider) {
final FramerProvider framerProvider,
final boolean inboundInitiated) {
this.subProtocols = subProtocols;
this.localNode = localNode;
this.expectedPeer = expectedPeer;
@ -77,6 +79,7 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler<Byte
this.metricsSystem = metricsSystem;
this.handshaker = handshakerProvider.buildInstance();
this.framerProvider = framerProvider;
this.inboundInitiated = inboundInitiated;
}
/**
@ -117,7 +120,8 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler<Byte
expectedPeer,
connectionEventDispatcher,
connectionFuture,
metricsSystem);
metricsSystem,
inboundInitiated);
ctx.channel()
.pipeline()

@ -69,6 +69,7 @@ final class DeFramer extends ByteToMessageDecoder {
// The peer we are expecting to connect to, if such a peer is known
private final Optional<Peer> expectedPeer;
private final List<SubProtocol> subProtocols;
private final boolean inboundInitiated;
private boolean hellosExchanged;
private final LabelledMetric<Counter> outboundMessagesCounter;
@ -79,13 +80,15 @@ final class DeFramer extends ByteToMessageDecoder {
final Optional<Peer> expectedPeer,
final PeerConnectionEventDispatcher connectionEventDispatcher,
final CompletableFuture<PeerConnection> connectFuture,
final MetricsSystem metricsSystem) {
final MetricsSystem metricsSystem,
final boolean inboundInitiated) {
this.framer = framer;
this.subProtocols = subProtocols;
this.localNode = localNode;
this.expectedPeer = expectedPeer;
this.connectFuture = connectFuture;
this.connectionEventDispatcher = connectionEventDispatcher;
this.inboundInitiated = inboundInitiated;
this.outboundMessagesCounter =
metricsSystem.createLabelledCounter(
BesuMetricCategory.NETWORK,
@ -140,7 +143,8 @@ final class DeFramer extends ByteToMessageDecoder {
peerInfo,
capabilityMultiplexer,
connectionEventDispatcher,
outboundMessagesCounter);
outboundMessagesCounter,
inboundInitiated);
// Check peer is who we expected
if (expectedPeer.isPresent()

@ -49,7 +49,8 @@ final class HandshakeHandlerInbound extends AbstractHandshakeHandler {
connectionEventDispatcher,
metricsSystem,
handshakerProvider,
framerProvider);
framerProvider,
true);
handshaker.prepareResponder(nodeKey);
}

@ -59,7 +59,8 @@ final class HandshakeHandlerOutbound extends AbstractHandshakeHandler {
connectionEventDispatcher,
metricsSystem,
handshakerProvider,
framerProvider);
framerProvider,
false);
handshaker.prepareInitiator(
nodeKey, SignatureAlgorithmFactory.getInstance().createPublicKey(peer.getId()));
this.first = handshaker.firstMessage();

@ -138,7 +138,7 @@ public class NettyConnectionInitializer
@Override
public CompletableFuture<Void> stop() {
CompletableFuture<Void> stoppedFuture = new CompletableFuture<>();
final CompletableFuture<Void> stoppedFuture = new CompletableFuture<>();
if (!started.get() || !stopped.compareAndSet(false, true)) {
stoppedFuture.completeExceptionally(
new IllegalStateException("Illegal attempt to stop " + this.getClass().getSimpleName()));

@ -43,7 +43,8 @@ final class NettyPeerConnection extends AbstractPeerConnection {
final PeerInfo peerInfo,
final CapabilityMultiplexer multiplexer,
final PeerConnectionEventDispatcher connectionEventDispatcher,
final LabelledMetric<Counter> outboundMessagesCounter) {
final LabelledMetric<Counter> outboundMessagesCounter,
final boolean inboundInitiated) {
super(
peer,
peerInfo,
@ -52,7 +53,8 @@ final class NettyPeerConnection extends AbstractPeerConnection {
ctx.channel().id().asLongText(),
multiplexer,
connectionEventDispatcher,
outboundMessagesCounter);
outboundMessagesCounter,
inboundInitiated);
this.ctx = ctx;
ctx.channel()

@ -0,0 +1,23 @@
/*
* Copyright contributors to Hyperledger Besu
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.rlpx.wire;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
@FunctionalInterface
public interface ShouldConnectCallback {
boolean shouldConnect(final Peer peer, final boolean incoming);
}

@ -1,66 +0,0 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.config;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
public class RlpxConfigurationTest {
@Test
public void getMaxRemotelyInitiatedConnections_remoteLimitsDisabled() {
final RlpxConfiguration config =
RlpxConfiguration.create()
.setFractionRemoteWireConnectionsAllowed(.5f)
.setLimitRemoteWireConnectionsEnabled(false)
.setPeerUpperBound(20);
assertThat(config.getMaxRemotelyInitiatedConnections()).isEqualTo(20);
}
@Test
public void getMaxRemotelyInitiatedConnections_remoteLimitsEnabled() {
final RlpxConfiguration config =
RlpxConfiguration.create()
.setFractionRemoteWireConnectionsAllowed(.5f)
.setLimitRemoteWireConnectionsEnabled(true)
.setPeerUpperBound(20);
assertThat(config.getMaxRemotelyInitiatedConnections()).isEqualTo(10);
}
@Test
public void getMaxRemotelyInitiatedConnections_remoteLimitsEnabledWithNonIntegerRatio() {
final RlpxConfiguration config =
RlpxConfiguration.create()
.setFractionRemoteWireConnectionsAllowed(.5f)
.setLimitRemoteWireConnectionsEnabled(true)
.setPeerUpperBound(25);
assertThat(config.getMaxRemotelyInitiatedConnections()).isEqualTo(12);
}
@Test
public void getMaxRemotelyInitiatedConnections_remoteLimitsEnabledRoundsToZero() {
final RlpxConfiguration config =
RlpxConfiguration.create()
.setFractionRemoteWireConnectionsAllowed(.5f)
.setLimitRemoteWireConnectionsEnabled(true)
.setPeerUpperBound(1);
assertThat(config.getMaxRemotelyInitiatedConnections()).isEqualTo(0);
}
}

@ -40,7 +40,6 @@ import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.peers.PeerTestHelper;
import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.MockPeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MockSubProtocol;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
@ -196,25 +195,8 @@ public final class DefaultP2PNetworkTest {
maintainedPeers.add(peer);
// Don't connect to an already connected peer
final CompletableFuture<PeerConnection> connectionFuture =
CompletableFuture.completedFuture(MockPeerConnection.create(peer));
when(rlpxAgent.getPeerConnection(peer)).thenReturn(Optional.of(connectionFuture));
network.checkMaintainedConnectionPeers();
verify(rlpxAgent, times(0)).connect(peer);
}
@Test
public void checkMaintainedConnectionPeers_connectingPeer() {
final DefaultP2PNetwork network = network();
final Peer peer = PeerTestHelper.createPeer();
network.start();
maintainedPeers.add(peer);
// Don't connect when connection is already pending.
final CompletableFuture<PeerConnection> connectionFuture = new CompletableFuture<>();
when(rlpxAgent.getPeerConnection(peer)).thenReturn(Optional.of(connectionFuture));
when(rlpxAgent.streamActiveConnections())
.thenReturn(Stream.of(MockPeerConnection.create(peer)));
network.checkMaintainedConnectionPeers();
verify(rlpxAgent, times(0)).connect(peer);
}
@ -410,6 +392,8 @@ public final class DefaultP2PNetworkTest {
.supportedCapabilities(Capability.create("eth", 63))
.storageProvider(new InMemoryKeyValueStorageProvider())
.blockNumberForks(Collections.emptyList())
.timestampForks(Collections.emptyList());
.timestampForks(Collections.emptyList())
.allConnectionsSupplier(Stream::empty)
.allActiveConnectionsSupplier(Stream::empty);
}
}

@ -39,6 +39,7 @@ import org.hyperledger.besu.plugin.data.EnodeURL;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Stream;
import io.vertx.core.Vertx;
import org.assertj.core.api.Assertions;
@ -81,9 +82,12 @@ public class NetworkingServiceLifecycleTest {
final Block blockMock = mock(Block.class);
when(blockMock.getHash()).thenReturn(Hash.ZERO);
when(blockchainMock.getGenesisBlock()).thenReturn(blockMock);
builder.blockchain(blockchainMock);
builder.blockNumberForks(Collections.emptyList());
builder.timestampForks(Collections.emptyList());
builder
.blockchain(blockchainMock)
.blockNumberForks(Collections.emptyList())
.timestampForks(Collections.emptyList())
.allConnectionsSupplier(Stream::empty)
.allActiveConnectionsSupplier(Stream::empty);
return builder;
}

@ -49,6 +49,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import io.vertx.core.Vertx;
import org.apache.tuweni.bytes.Bytes;
@ -79,6 +80,8 @@ public class P2PNetworkTest {
final NodeKey nodeKey = NodeKeyUtils.generate();
try (final P2PNetwork listener = builder().nodeKey(nodeKey).build();
final P2PNetwork connector = builder().build()) {
listener.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
connector.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
listener.start();
connector.start();
@ -101,6 +104,8 @@ public class P2PNetworkTest {
final NodeKey listenNodeKey = NodeKeyUtils.generate();
try (final P2PNetwork listener = builder().nodeKey(listenNodeKey).build();
final P2PNetwork connector = builder().build()) {
listener.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
connector.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
listener.start();
connector.start();
@ -123,67 +128,6 @@ public class P2PNetworkTest {
}
}
/**
* Tests that max peers setting is honoured and inbound connections that would exceed the limit
* are correctly disconnected.
*
* @throws Exception On Failure
*/
@Test
public void limitMaxPeers() throws Exception {
final NodeKey nodeKey = NodeKeyUtils.generate();
final int maxPeers = 1;
final NetworkingConfiguration listenerConfig =
NetworkingConfiguration.create()
.setDiscovery(DiscoveryConfiguration.create().setActive(false))
.setRlpx(
RlpxConfiguration.create()
.setBindPort(0)
.setPeerUpperBound(maxPeers)
.setSupportedProtocols(MockSubProtocol.create()));
try (final P2PNetwork listener = builder().nodeKey(nodeKey).config(listenerConfig).build();
final P2PNetwork connector1 = builder().build();
final P2PNetwork connector2 = builder().build()) {
// Setup listener and first connection
listener.start();
connector1.start();
final EnodeURL listenerEnode = listener.getLocalEnode().get();
final Bytes listenId = listenerEnode.getNodeId();
final int listenPort = listenerEnode.getListeningPort().get();
final Peer listeningPeer = createPeer(listenId, listenPort);
Assertions.assertThat(
connector1
.connect(listeningPeer)
.get(30L, TimeUnit.SECONDS)
.getPeerInfo()
.getNodeId())
.isEqualTo(listenId);
// Setup second connection and check that connection is not accepted
final CompletableFuture<PeerConnection> peerFuture = new CompletableFuture<>();
final CompletableFuture<DisconnectReason> reasonFuture = new CompletableFuture<>();
connector2.subscribeDisconnect(
(peerConnection, reason, initiatedByPeer) -> {
peerFuture.complete(peerConnection);
reasonFuture.complete(reason);
});
connector2.start();
Assertions.assertThat(
connector2
.connect(listeningPeer)
.get(30L, TimeUnit.SECONDS)
.getPeerInfo()
.getNodeId())
.isEqualTo(listenId);
Assertions.assertThat(peerFuture.get(30L, TimeUnit.SECONDS).getPeerInfo().getNodeId())
.isEqualTo(listenId);
assertThat(reasonFuture.get(30L, TimeUnit.SECONDS))
.isEqualByComparingTo(DisconnectReason.TOO_MANY_PEERS);
}
}
@Test
public void rejectPeerWithNoSharedCaps() throws Exception {
final NodeKey listenerNodeKey = NodeKeyUtils.generate();
@ -197,6 +141,9 @@ public class P2PNetworkTest {
builder().nodeKey(listenerNodeKey).supportedCapabilities(cap1).build();
final P2PNetwork connector =
builder().nodeKey(connectorNodeKey).supportedCapabilities(cap2).build()) {
listener.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
connector.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
listener.start();
connector.start();
final EnodeURL listenerEnode = listener.getLocalEnode().get();
@ -215,6 +162,8 @@ public class P2PNetworkTest {
try (final P2PNetwork localNetwork = builder().peerPermissions(localDenylist).build();
final P2PNetwork remoteNetwork = builder().build()) {
localNetwork.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
remoteNetwork.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
localNetwork.start();
remoteNetwork.start();
@ -262,6 +211,8 @@ public class P2PNetworkTest {
try (final P2PNetwork localNetwork = builder().peerPermissions(peerPermissions).build();
final P2PNetwork remoteNetwork = builder().build()) {
localNetwork.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
remoteNetwork.getRlpxAgent().subscribeConnectRequest((p, d) -> true);
localNetwork.start();
remoteNetwork.start();
@ -323,6 +274,8 @@ public class P2PNetworkTest {
.storageProvider(new InMemoryKeyValueStorageProvider())
.blockNumberForks(Collections.emptyList())
.timestampForks(Collections.emptyList())
.blockchain(blockchainMock);
.blockchain(blockchainMock)
.allConnectionsSupplier(Stream::empty)
.allActiveConnectionsSupplier(Stream::empty);
}
}

@ -44,6 +44,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractMessageData;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Message;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MockSubProtocol;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.ShouldConnectCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
@ -58,6 +59,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import io.vertx.core.Vertx;
import org.apache.tuweni.bytes.Bytes;
@ -89,6 +91,8 @@ public class P2PPlainNetworkTest {
final NodeKey nodeKey = NodeKeyUtils.generate();
try (final P2PNetwork listener = builder("partner1client1").nodeKey(nodeKey).build();
final P2PNetwork connector = builder("partner2client1").build()) {
listener.getRlpxAgent().subscribeConnectRequest(testCallback);
connector.getRlpxAgent().subscribeConnectRequest(testCallback);
listener.start();
connector.start();
@ -111,6 +115,8 @@ public class P2PPlainNetworkTest {
final NodeKey listenNodeKey = NodeKeyUtils.generate();
try (final P2PNetwork listener = builder("partner1client1").nodeKey(listenNodeKey).build();
final P2PNetwork connector = builder("partner2client1").build()) {
listener.getRlpxAgent().subscribeConnectRequest(testCallback);
connector.getRlpxAgent().subscribeConnectRequest(testCallback);
listener.start();
connector.start();
@ -142,19 +148,20 @@ public class P2PPlainNetworkTest {
@Test
public void limitMaxPeers() throws Exception {
final NodeKey nodeKey = NodeKeyUtils.generate();
final int maxPeers = 1;
final NetworkingConfiguration listenerConfig =
NetworkingConfiguration.create()
.setDiscovery(DiscoveryConfiguration.create().setActive(false))
.setRlpx(
RlpxConfiguration.create()
.setBindPort(0)
.setPeerUpperBound(maxPeers)
.setSupportedProtocols(MockSubProtocol.create()));
try (final P2PNetwork listener =
builder("partner1client1").nodeKey(nodeKey).config(listenerConfig).build();
final P2PNetwork connector1 = builder("partner1client1").build();
final P2PNetwork connector2 = builder("partner2client1").build()) {
listener.getRlpxAgent().subscribeConnectRequest(testCallback);
connector1.getRlpxAgent().subscribeConnectRequest(testCallback);
connector2.getRlpxAgent().subscribeConnectRequest((p, d) -> false);
// Setup listener and first connection
listener.start();
@ -181,17 +188,7 @@ public class P2PPlainNetworkTest {
reasonFuture.complete(reason);
});
connector2.start();
Assertions.assertThat(
connector2
.connect(listeningPeer)
.get(30L, TimeUnit.SECONDS)
.getPeerInfo()
.getNodeId())
.isEqualTo(listenId);
Assertions.assertThat(peerFuture.get(30L, TimeUnit.SECONDS).getPeerInfo().getNodeId())
.isEqualTo(listenId);
assertThat(reasonFuture.get(30L, TimeUnit.SECONDS))
.isEqualByComparingTo(DisconnectReason.TOO_MANY_PEERS);
Assertions.assertThat(connector2.connect(listeningPeer)).isCompletedExceptionally();
}
}
@ -214,6 +211,9 @@ public class P2PPlainNetworkTest {
.nodeKey(connectorNodeKey)
.supportedCapabilities(cap2)
.build()) {
listener.getRlpxAgent().subscribeConnectRequest(testCallback);
connector.getRlpxAgent().subscribeConnectRequest(testCallback);
listener.start();
connector.start();
final EnodeURL listenerEnode = listener.getLocalEnode().get();
@ -233,6 +233,8 @@ public class P2PPlainNetworkTest {
try (final P2PNetwork localNetwork =
builder("partner1client1").peerPermissions(localDenylist).build();
final P2PNetwork remoteNetwork = builder("partner2client1").build()) {
localNetwork.getRlpxAgent().subscribeConnectRequest(testCallback);
remoteNetwork.getRlpxAgent().subscribeConnectRequest(testCallback);
localNetwork.start();
remoteNetwork.start();
@ -281,6 +283,8 @@ public class P2PPlainNetworkTest {
try (final P2PNetwork localNetwork =
builder("partner1client1").peerPermissions(peerPermissions).build();
final P2PNetwork remoteNetwork = builder("partner2client1").build()) {
localNetwork.getRlpxAgent().subscribeConnectRequest(testCallback);
remoteNetwork.getRlpxAgent().subscribeConnectRequest(testCallback);
localNetwork.start();
remoteNetwork.start();
@ -330,6 +334,8 @@ public class P2PPlainNetworkTest {
final NodeKey nodeKey = NodeKeyUtils.generate();
try (final P2PNetwork listener = builder("partner1client1").nodeKey(nodeKey).build();
final P2PNetwork connector = builder("partner2client1").build()) {
listener.getRlpxAgent().subscribeConnectRequest(testCallback);
connector.getRlpxAgent().subscribeConnectRequest(testCallback);
final CompletableFuture<DisconnectReason> disconnectReasonFuture = new CompletableFuture<>();
listener.subscribeDisconnect(
@ -378,6 +384,8 @@ public class P2PPlainNetworkTest {
}
}
private final ShouldConnectCallback testCallback = (p, d) -> true;
private static class LargeMessageData extends AbstractMessageData {
public static final int VALID_ETH_MESSAGE_CODE = 0x07;
@ -410,7 +418,7 @@ public class P2PPlainNetworkTest {
private static Path toPath(final String path) {
try {
return Path.of(Objects.requireNonNull(P2PPlainNetworkTest.class.getResource(path)).toURI());
} catch (URISyntaxException e) {
} catch (final URISyntaxException e) {
throw new RuntimeException("Error converting to URI.", e);
}
}
@ -449,6 +457,8 @@ public class P2PPlainNetworkTest {
.storageProvider(new InMemoryKeyValueStorageProvider())
.blockNumberForks(Collections.emptyList())
.timestampForks(Collections.emptyList())
.blockchain(blockchainMock);
.blockchain(blockchainMock)
.allConnectionsSupplier(Stream::empty)
.allActiveConnectionsSupplier(Stream::empty);
}
}

@ -181,7 +181,8 @@ public class AbstractPeerConnectionTest {
connectionId,
multiplexer,
connectionEventDispatcher,
outboundMessagesCounter);
outboundMessagesCounter,
true);
}
@Override

@ -44,7 +44,7 @@ public class MockConnectionInitializer implements ConnectionInitializer {
}
public void completePendingFutures() {
for (Map.Entry<Peer, CompletableFuture<PeerConnection>> conn :
for (final Map.Entry<Peer, CompletableFuture<PeerConnection>> conn :
incompleteConnections.entrySet()) {
conn.getValue().complete(MockPeerConnection.create(conn.getKey()));
}
@ -57,7 +57,7 @@ public class MockConnectionInitializer implements ConnectionInitializer {
@Override
public CompletableFuture<InetSocketAddress> start() {
InetSocketAddress socketAddress =
final InetSocketAddress socketAddress =
new InetSocketAddress("127.0.0.1", NEXT_PORT.incrementAndGet());
return CompletableFuture.completedFuture(socketAddress);
}
@ -76,12 +76,14 @@ public class MockConnectionInitializer implements ConnectionInitializer {
public CompletableFuture<PeerConnection> connect(final Peer peer) {
if (autoDisconnectCounter > 0) {
autoDisconnectCounter--;
MockPeerConnection mockPeerConnection = MockPeerConnection.create(peer, eventDispatcher);
final MockPeerConnection mockPeerConnection =
MockPeerConnection.create(peer, eventDispatcher, false);
mockPeerConnection.disconnect(DisconnectMessage.DisconnectReason.CLIENT_QUITTING);
return CompletableFuture.completedFuture(mockPeerConnection);
}
if (autocompleteConnections) {
return CompletableFuture.completedFuture(MockPeerConnection.create(peer, eventDispatcher));
return CompletableFuture.completedFuture(
MockPeerConnection.create(peer, eventDispatcher, false));
} else {
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
incompleteConnections.put(peer, future);

@ -47,7 +47,8 @@ public class MockPeerConnection extends AbstractPeerConnection {
final String connectionId,
final CapabilityMultiplexer multiplexer,
final PeerConnectionEventDispatcher connectionEventDispatcher,
final LabelledMetric<Counter> outboundMessagesCounter) {
final LabelledMetric<Counter> outboundMessagesCounter,
final boolean inboundInitiated) {
super(
peer,
peerInfo,
@ -56,7 +57,8 @@ public class MockPeerConnection extends AbstractPeerConnection {
connectionId,
multiplexer,
connectionEventDispatcher,
outboundMessagesCounter);
outboundMessagesCounter,
inboundInitiated);
}
public static MockPeerConnection create() {
@ -64,11 +66,13 @@ public class MockPeerConnection extends AbstractPeerConnection {
}
public static MockPeerConnection create(final Peer peer) {
return create(peer, mock(PeerConnectionEventDispatcher.class));
return create(peer, mock(PeerConnectionEventDispatcher.class), true);
}
public static MockPeerConnection create(
final Peer peer, final PeerConnectionEventDispatcher eventDispatcher) {
final Peer peer,
final PeerConnectionEventDispatcher eventDispatcher,
final boolean inboundInitiated) {
final List<SubProtocol> subProtocols = Arrays.asList(MockSubProtocol.create("eth"));
final List<Capability> caps = Arrays.asList(Capability.create("eth", 63));
final CapabilityMultiplexer multiplexer = new CapabilityMultiplexer(subProtocols, caps, caps);
@ -83,7 +87,8 @@ public class MockPeerConnection extends AbstractPeerConnection {
Integer.toString(connectionId.incrementAndGet()),
multiplexer,
eventDispatcher,
NoOpMetricsSystem.NO_OP_LABELLED_3_COUNTER);
NoOpMetricsSystem.NO_OP_LABELLED_3_COUNTER,
inboundInitiated);
}
@Override

@ -1,269 +0,0 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.rlpx.connections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hyperledger.besu.ethereum.p2p.peers.PeerTestHelper.createPeer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.RlpxConnection.ConnectionNotEstablishedException;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import java.util.concurrent.CompletableFuture;
import org.junit.Test;
public class RlpxConnectionTest {
@Test
public void getPeer_pendingOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
assertThat(conn.getPeer()).isEqualTo(peer);
}
@Test
public void getPeer_establishedOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
future.complete(peerConnection(peer));
assertThat(conn.getPeer()).isEqualTo(peer);
}
@Test
public void getPeer_inboundConnection() {
final Peer peer = createPeer();
final PeerConnection peerConnection = peerConnection(peer);
final RlpxConnection conn = RlpxConnection.inboundConnection(peerConnection);
assertThat(conn.getPeer()).isEqualTo(peer);
}
@Test
public void disconnect_pendingOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
final DisconnectReason reason = DisconnectReason.REQUESTED;
conn.disconnect(reason);
assertThat(conn.isFailedOrDisconnected()).isFalse();
// Resolve future
final PeerConnection peerConnection = peerConnection(peer);
future.complete(peerConnection);
// Check disconnect was issued
verify(peerConnection).disconnect(reason);
assertThat(conn.isFailedOrDisconnected()).isTrue();
}
@Test
public void disconnect_activeOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
final PeerConnection peerConnection = peerConnection(peer);
future.complete(peerConnection);
final DisconnectReason reason = DisconnectReason.REQUESTED;
conn.disconnect(reason);
// Check disconnect was issued
assertThat(conn.isFailedOrDisconnected()).isTrue();
verify(peerConnection).disconnect(reason);
}
@Test
public void disconnect_failedOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
future.completeExceptionally(new IllegalStateException("whoops"));
assertThat(conn.isFailedOrDisconnected()).isTrue();
final DisconnectReason reason = DisconnectReason.REQUESTED;
conn.disconnect(reason);
assertThat(conn.isFailedOrDisconnected()).isTrue();
}
@Test
public void disconnect_inboundConnection() {
final Peer peer = createPeer();
final PeerConnection peerConnection = peerConnection(peer);
final RlpxConnection conn = RlpxConnection.inboundConnection(peerConnection);
assertThat(conn.isFailedOrDisconnected()).isFalse();
final DisconnectReason reason = DisconnectReason.REQUESTED;
conn.disconnect(reason);
// Check disconnect was issued
assertThat(conn.isFailedOrDisconnected()).isTrue();
verify(peerConnection).disconnect(reason);
}
@Test
public void getPeerConnection_pendingOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
assertThatThrownBy(conn::getPeerConnection)
.isInstanceOf(ConnectionNotEstablishedException.class);
}
@Test
public void getPeerConnection_activeOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
final PeerConnection peerConnection = peerConnection(peer);
future.complete(peerConnection);
assertThat(conn.getPeerConnection()).isEqualTo(peerConnection);
}
@Test
public void getPeerConnection_failedOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
future.completeExceptionally(new IllegalStateException("whoops"));
assertThatThrownBy(conn::getPeerConnection)
.isInstanceOf(ConnectionNotEstablishedException.class);
}
@Test
public void getPeerConnection_disconnectedOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
final PeerConnection peerConnection = peerConnection(peer);
future.complete(peerConnection);
conn.disconnect(DisconnectReason.REQUESTED);
assertThat(conn.getPeerConnection()).isEqualTo(peerConnection);
}
@Test
public void getPeerConnection_activeInboundConnection() {
final Peer peer = createPeer();
final PeerConnection peerConnection = peerConnection(peer);
final RlpxConnection conn = RlpxConnection.inboundConnection(peerConnection);
assertThat(conn.getPeerConnection()).isEqualTo(peerConnection);
}
@Test
public void getPeerConnection_disconnectedInboundConnection() {
final Peer peer = createPeer();
final PeerConnection peerConnection = peerConnection(peer);
final RlpxConnection conn = RlpxConnection.inboundConnection(peerConnection);
conn.disconnect(DisconnectReason.REQUESTED);
assertThat(conn.getPeerConnection()).isEqualTo(peerConnection);
}
@Test
public void checkState_pendingOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
assertThat(conn.initiatedRemotely()).isFalse();
assertThat(conn.isActive()).isFalse();
assertThat(conn.isPending()).isTrue();
assertThat(conn.isFailedOrDisconnected()).isFalse();
}
@Test
public void checkState_activeOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
final PeerConnection peerConnection = peerConnection(peer);
future.complete(peerConnection);
assertThat(conn.initiatedRemotely()).isFalse();
assertThat(conn.isActive()).isTrue();
assertThat(conn.isPending()).isFalse();
assertThat(conn.isFailedOrDisconnected()).isFalse();
}
@Test
public void checkState_failedOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
future.completeExceptionally(new IllegalStateException("whoops"));
assertThat(conn.initiatedRemotely()).isFalse();
assertThat(conn.isActive()).isFalse();
assertThat(conn.isPending()).isFalse();
assertThat(conn.isFailedOrDisconnected()).isTrue();
}
@Test
public void checkState_disconnectedOutboundConnection() {
final Peer peer = createPeer();
final CompletableFuture<PeerConnection> future = new CompletableFuture<>();
final RlpxConnection conn = RlpxConnection.outboundConnection(peer, future);
final PeerConnection peerConnection = peerConnection(peer);
future.complete(peerConnection);
conn.disconnect(DisconnectReason.UNKNOWN);
assertThat(conn.initiatedRemotely()).isFalse();
assertThat(conn.isActive()).isFalse();
assertThat(conn.isPending()).isFalse();
assertThat(conn.isFailedOrDisconnected()).isTrue();
}
@Test
public void checkState_activeInboundConnection() {
final Peer peer = createPeer();
final PeerConnection peerConnection = peerConnection(peer);
final RlpxConnection conn = RlpxConnection.inboundConnection(peerConnection);
assertThat(conn.initiatedRemotely()).isTrue();
assertThat(conn.isActive()).isTrue();
assertThat(conn.isPending()).isFalse();
assertThat(conn.isFailedOrDisconnected()).isFalse();
}
@Test
public void checkState_disconnectedInboundConnection() {
final Peer peer = createPeer();
final PeerConnection peerConnection = peerConnection(peer);
final RlpxConnection conn = RlpxConnection.inboundConnection(peerConnection);
conn.disconnect(DisconnectReason.UNKNOWN);
assertThat(conn.initiatedRemotely()).isTrue();
assertThat(conn.isActive()).isFalse();
assertThat(conn.isPending()).isFalse();
assertThat(conn.isFailedOrDisconnected()).isTrue();
}
private PeerConnection peerConnection(final Peer peer) {
return spy(MockPeerConnection.create(peer));
}
}

@ -421,6 +421,7 @@ public class DeFramerTest {
Optional.ofNullable(expectedPeer),
connectionEventDispatcher,
connectFuture,
new NoOpMetricsSystem());
new NoOpMetricsSystem(),
true);
}
}

@ -63,7 +63,7 @@ public class InsufficientPeersPermissioningProvider implements ContextualNodePer
@Override
public Optional<Boolean> isPermitted(
final EnodeURL sourceEnode, final EnodeURL destinationEnode) {
Optional<EnodeURL> maybeSelfEnode = p2pNetwork.getLocalEnode();
final Optional<EnodeURL> maybeSelfEnode = p2pNetwork.getLocalEnode();
if (nonBootnodePeerConnections > 0) {
return Optional.empty();
} else if (!maybeSelfEnode.isPresent()) {

@ -183,21 +183,21 @@ public class InsufficientPeersPermissioningProviderTest {
ArgumentCaptor.forClass(ConnectCallback.class);
verify(p2pNetwork).subscribeConnect(callbackCaptor.capture());
final ConnectCallback connectCallback = callbackCaptor.getValue();
final ConnectCallback incomingConnectCallback = callbackCaptor.getValue();
final Runnable updatePermsCallback = mock(Runnable.class);
provider.subscribeToUpdates(updatePermsCallback);
connectCallback.onConnect(peerConnectionMatching(ENODE_2));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_2));
verify(updatePermsCallback, times(0)).run();
connectCallback.onConnect(peerConnectionMatching(ENODE_3));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_3));
verify(updatePermsCallback, times(0)).run();
connectCallback.onConnect(peerConnectionMatching(ENODE_4));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_4));
verify(updatePermsCallback, times(1)).run();
connectCallback.onConnect(peerConnectionMatching(ENODE_5));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_5));
verify(updatePermsCallback, times(1)).run();
connectCallback.onConnect(peerConnectionMatching(ENODE_3));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_3));
verify(updatePermsCallback, times(1)).run();
}
@ -215,7 +215,7 @@ public class InsufficientPeersPermissioningProviderTest {
final ArgumentCaptor<ConnectCallback> connectCallbackCaptor =
ArgumentCaptor.forClass(ConnectCallback.class);
verify(p2pNetwork).subscribeConnect(connectCallbackCaptor.capture());
final ConnectCallback connectCallback = connectCallbackCaptor.getValue();
final ConnectCallback incomingConnectCallback = connectCallbackCaptor.getValue();
final ArgumentCaptor<DisconnectCallback> disconnectCallbackCaptor =
ArgumentCaptor.forClass(DisconnectCallback.class);
@ -226,13 +226,13 @@ public class InsufficientPeersPermissioningProviderTest {
provider.subscribeToUpdates(updatePermsCallback);
connectCallback.onConnect(peerConnectionMatching(ENODE_2));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_2));
verify(updatePermsCallback, times(0)).run();
connectCallback.onConnect(peerConnectionMatching(ENODE_3));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_3));
verify(updatePermsCallback, times(0)).run();
connectCallback.onConnect(peerConnectionMatching(ENODE_4));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_4));
verify(updatePermsCallback, times(1)).run();
connectCallback.onConnect(peerConnectionMatching(ENODE_5));
incomingConnectCallback.onConnect(peerConnectionMatching(ENODE_5));
verify(updatePermsCallback, times(1)).run();
disconnectCallback.onDisconnect(peerConnectionMatching(ENODE_2), null, true);
verify(updatePermsCallback, times(1)).run();

@ -65,6 +65,7 @@ import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
import org.hyperledger.besu.util.Subscribers;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
@ -83,6 +84,7 @@ public class RetestethContext {
private static final PoWHasher NO_WORK_HASHER =
(final long nonce, final long number, EpochCalculator epochCalc, final Bytes headerHash) ->
new PoWSolution(nonce, Hash.ZERO, UInt256.ZERO, Hash.ZERO);
public static final int MAX_PEERS = 25;
private final ReentrantLock contextLock = new ReentrantLock();
private Address coinbase;
@ -198,6 +200,8 @@ public class RetestethContext {
blockReplay = new BlockReplay(protocolSchedule, blockchainQueries.getBlockchain());
final Bytes localNodeKey = Bytes.wrap(new byte[64]);
// mining support
final Supplier<ProtocolSpec> currentProtocolSpecSupplier =
@ -208,8 +212,13 @@ public class RetestethContext {
currentProtocolSpecSupplier,
retestethClock,
metricsSystem,
0,
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE);
EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE,
Collections.emptyList(),
localNodeKey,
MAX_PEERS,
MAX_PEERS,
MAX_PEERS,
false);
final SyncState syncState = new SyncState(blockchain, ethPeers);
ethScheduler = new EthScheduler(1, 1, 1, 1, metricsSystem);

@ -131,8 +131,7 @@ public class Subscribers<T> {
action.accept(subscriber);
} catch (final Exception e) {
if (suppressCallbackExceptions) {
LOG.info("Error in callback: {}", e.getMessage());
LOG.debug("Error in callback: ", e);
LOG.debug("Error in callback: {}", e);
} else {
throw e;
}

Loading…
Cancel
Save