Skip to content

Commit 765e127

Browse files
authored
Add VC beaconAPI pools CLI params (#9611)
1 parent ccefe6a commit 765e127

File tree

6 files changed

+205
-34
lines changed

6 files changed

+205
-34
lines changed

teku/src/main/java/tech/pegasys/teku/cli/options/ValidatorOptions.java

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,27 @@ public class ValidatorOptions {
138138
arity = "1")
139139
private int executorThreads = DEFAULT_VALIDATOR_EXECUTOR_THREADS;
140140

141+
@Option(
142+
names = {"--Xvalidator-client-beacon-api-executor-threads"},
143+
paramLabel = "<INTEGER>",
144+
showDefaultValue = Visibility.ALWAYS,
145+
description = "Set the number of threads for the validator beacon node API executor",
146+
hidden = true,
147+
converter = OptionalIntConverter.class,
148+
arity = "1")
149+
private OptionalInt beaconApiExecutorThreads = OptionalInt.empty();
150+
151+
@Option(
152+
names = {"--Xvalidator-client-beacon-api-readiness-executor-threads"},
153+
paramLabel = "<INTEGER>",
154+
showDefaultValue = Visibility.ALWAYS,
155+
description =
156+
"Set the number of threads for the validator beacon node API readiness executor",
157+
hidden = true,
158+
converter = OptionalIntConverter.class,
159+
arity = "1")
160+
private OptionalInt beaconApiReadinessExecutorThreads = OptionalInt.empty();
161+
141162
@Option(
142163
names = {"--exit-when-no-validator-keys-enabled"},
143164
paramLabel = "<BOOLEAN>",
@@ -170,25 +191,26 @@ public class ValidatorOptions {
170191

171192
public void configure(final TekuConfiguration.Builder builder) {
172193
builder.validator(
173-
config -> {
174-
config
175-
.validatorKeystoreLockingEnabled(validatorKeystoreLockingEnabled)
176-
.validatorPerformanceTrackingMode(validatorPerformanceTrackingMode)
177-
.validatorExternalSignerSlashingProtectionEnabled(
178-
validatorExternalSignerSlashingProtectionEnabled)
179-
.isLocalSlashingProtectionSynchronizedModeEnabled(
180-
isLocalSlashingProtectionSynchronizedEnabled)
181-
.graffitiProvider(
182-
new FileBackedGraffitiProvider(
183-
Optional.ofNullable(graffiti), Optional.ofNullable(graffitiFile)))
184-
.clientGraffitiAppendFormat(clientGraffitiAppendFormat)
185-
.generateEarlyAttestations(generateEarlyAttestations)
186-
.doppelgangerDetectionEnabled(doppelgangerDetectionEnabled)
187-
.executorThreads(executorThreads)
188-
.exitWhenNoValidatorKeysEnabled(exitWhenNoValidatorKeysEnabled)
189-
.shutdownWhenValidatorSlashedEnabled(shutdownWhenValidatorSlashed);
190-
executorMaxQueueSize.ifPresent(config::executorMaxQueueSize);
191-
});
194+
config ->
195+
config
196+
.validatorKeystoreLockingEnabled(validatorKeystoreLockingEnabled)
197+
.validatorPerformanceTrackingMode(validatorPerformanceTrackingMode)
198+
.validatorExternalSignerSlashingProtectionEnabled(
199+
validatorExternalSignerSlashingProtectionEnabled)
200+
.isLocalSlashingProtectionSynchronizedModeEnabled(
201+
isLocalSlashingProtectionSynchronizedEnabled)
202+
.graffitiProvider(
203+
new FileBackedGraffitiProvider(
204+
Optional.ofNullable(graffiti), Optional.ofNullable(graffitiFile)))
205+
.clientGraffitiAppendFormat(clientGraffitiAppendFormat)
206+
.generateEarlyAttestations(generateEarlyAttestations)
207+
.doppelgangerDetectionEnabled(doppelgangerDetectionEnabled)
208+
.executorThreads(executorThreads)
209+
.exitWhenNoValidatorKeysEnabled(exitWhenNoValidatorKeysEnabled)
210+
.shutdownWhenValidatorSlashedEnabled(shutdownWhenValidatorSlashed)
211+
.executorMaxQueueSize(executorMaxQueueSize)
212+
.beaconApiExecutorThreads(beaconApiExecutorThreads)
213+
.beaconApiReadinessExecutorThreads(beaconApiReadinessExecutorThreads));
192214
validatorProposerOptions.configure(builder);
193215
validatorKeysOptions.configure(builder);
194216
}

teku/src/test/java/tech/pegasys/teku/cli/options/ValidatorOptionsTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,35 @@ public void shouldNotUseDvtSelectionsEndpointByDefault() {
251251
assertThat(config.validatorClient().getValidatorConfig().isDvtSelectionsEndpointEnabled())
252252
.isFalse();
253253
}
254+
255+
@Test
256+
public void validatorClientBeaconApiExecutorsEmptyByDefault() {
257+
final TekuConfiguration tekuConfiguration = getTekuConfigurationFromArguments();
258+
assertThat(
259+
tekuConfiguration.validatorClient().getValidatorConfig().getBeaconApiExecutorThreads())
260+
.isEmpty();
261+
assertThat(
262+
tekuConfiguration
263+
.validatorClient()
264+
.getValidatorConfig()
265+
.getBeaconApiReadinessExecutorThreads())
266+
.isEmpty();
267+
}
268+
269+
@Test
270+
public void validatorClientBeaconApiExecutorsCanBeSet() {
271+
final TekuConfiguration tekuConfiguration =
272+
getTekuConfigurationFromArguments(
273+
"--Xvalidator-client-beacon-api-executor-threads=2",
274+
"--Xvalidator-client-beacon-api-readiness-executor-threads=4");
275+
assertThat(
276+
tekuConfiguration.validatorClient().getValidatorConfig().getBeaconApiExecutorThreads())
277+
.hasValue(2);
278+
assertThat(
279+
tekuConfiguration
280+
.validatorClient()
281+
.getValidatorConfig()
282+
.getBeaconApiReadinessExecutorThreads())
283+
.hasValue(4);
284+
}
254285
}

validator/api/src/main/java/tech/pegasys/teku/validator/api/ValidatorConfig.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ public class ValidatorConfig {
110110

111111
private final int executorThreads;
112112

113+
private final OptionalInt beaconApiExecutorThreads;
114+
private final OptionalInt beaconApiReadinessExecutorThreads;
115+
113116
private final boolean isLocalSlashingProtectionSynchronizedModeEnabled;
114117
private final boolean dvtSelectionsEndpointEnabled;
115118
private final boolean attestationsV2ApisEnabled;
@@ -150,6 +153,8 @@ private ValidatorConfig(
150153
final Optional<BLSPublicKey> builderRegistrationPublicKeyOverride,
151154
final int executorMaxQueueSize,
152155
final int executorThreads,
156+
final OptionalInt beaconApiExecutorThreads,
157+
final OptionalInt beaconApiReadinessExecutorThreads,
153158
final Optional<String> sentryNodeConfigurationFile,
154159
final boolean isLocalSlashingProtectionSynchronizedModeEnabled,
155160
final boolean dvtSelectionsEndpointEnabled,
@@ -193,6 +198,8 @@ private ValidatorConfig(
193198
this.builderRegistrationPublicKeyOverride = builderRegistrationPublicKeyOverride;
194199
this.executorMaxQueueSize = executorMaxQueueSize;
195200
this.executorThreads = executorThreads;
201+
this.beaconApiExecutorThreads = beaconApiExecutorThreads;
202+
this.beaconApiReadinessExecutorThreads = beaconApiReadinessExecutorThreads;
196203
this.sentryNodeConfigurationFile = sentryNodeConfigurationFile;
197204
this.isLocalSlashingProtectionSynchronizedModeEnabled =
198205
isLocalSlashingProtectionSynchronizedModeEnabled;
@@ -346,6 +353,14 @@ public int getExecutorThreads() {
346353
return executorThreads;
347354
}
348355

356+
public OptionalInt getBeaconApiExecutorThreads() {
357+
return beaconApiExecutorThreads;
358+
}
359+
360+
public OptionalInt getBeaconApiReadinessExecutorThreads() {
361+
return beaconApiReadinessExecutorThreads;
362+
}
363+
349364
public Optional<String> getSentryNodeConfigurationFile() {
350365
return sentryNodeConfigurationFile;
351366
}
@@ -418,6 +433,8 @@ public static final class Builder {
418433
private Optional<UInt64> builderRegistrationTimestampOverride = Optional.empty();
419434
private Optional<BLSPublicKey> builderRegistrationPublicKeyOverride = Optional.empty();
420435
private OptionalInt executorMaxQueueSize = OptionalInt.empty();
436+
private OptionalInt beaconApiExecutorThreads = OptionalInt.empty();
437+
private OptionalInt beaconApiReadinessExecutorThreads = OptionalInt.empty();
421438
private Optional<String> sentryNodeConfigurationFile = Optional.empty();
422439
private int executorThreads = DEFAULT_VALIDATOR_EXECUTOR_THREADS;
423440
private boolean isLocalSlashingProtectionSynchronizedModeEnabled =
@@ -648,14 +665,25 @@ public Builder builderRegistrationPublicKeyOverride(
648665
return this;
649666
}
650667

651-
public Builder executorMaxQueueSize(final int executorMaxQueueSize) {
652-
this.executorMaxQueueSize = OptionalInt.of(executorMaxQueueSize);
668+
public Builder executorMaxQueueSize(final OptionalInt executorMaxQueueSize) {
669+
this.executorMaxQueueSize = executorMaxQueueSize;
670+
return this;
671+
}
672+
673+
public Builder beaconApiExecutorThreads(final OptionalInt beaconApiExecutorThreads) {
674+
this.beaconApiExecutorThreads = beaconApiExecutorThreads;
675+
return this;
676+
}
677+
678+
public Builder beaconApiReadinessExecutorThreads(
679+
final OptionalInt beaconApiReadinessExecutorThreads) {
680+
this.beaconApiReadinessExecutorThreads = beaconApiReadinessExecutorThreads;
653681
return this;
654682
}
655683

656684
public Builder executorMaxQueueSizeIfDefault(final int executorMaxQueueSize) {
657685
if (this.executorMaxQueueSize.isEmpty()) {
658-
this.executorMaxQueueSize(executorMaxQueueSize);
686+
this.executorMaxQueueSize = OptionalInt.of(executorMaxQueueSize);
659687
}
660688
return this;
661689
}
@@ -723,6 +751,8 @@ public ValidatorConfig build() {
723751
builderRegistrationPublicKeyOverride,
724752
executorMaxQueueSize.orElse(DEFAULT_EXECUTOR_MAX_QUEUE_SIZE),
725753
executorThreads,
754+
beaconApiExecutorThreads,
755+
beaconApiReadinessExecutorThreads,
726756
sentryNodeConfigurationFile,
727757
isLocalSlashingProtectionSynchronizedModeEnabled,
728758
dvtSelectionsEndpointEnabled,

validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteBeaconNodeApi.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ public static BeaconNodeApi create(
8484

8585
final int remoteNodeCount = failoverEndpoints.size() + 1;
8686

87-
final int apiMaxThreads =
88-
calculateAPIMaxThreads(
89-
remoteNodeCount, validatorConfig.isFailoversPublishSignedDutiesEnabled());
87+
final int apiMaxThreads = calculateAPIMaxThreads(remoteNodeCount, validatorConfig);
9088
final AsyncRunner asyncRunner =
9189
services.createAsyncRunner(
9290
"validatorBeaconAPI", apiMaxThreads, MAX_API_EXECUTOR_QUEUE_SIZE);
@@ -97,7 +95,8 @@ public static BeaconNodeApi create(
9795
} else {
9896
// Use a separate async runner for the readiness-related api calls, so that they do not
9997
// interfere with the critical path API calls.
100-
final int apiMaxReadinessThreads = calculateReadinessAPIMaxThreads(remoteNodeCount);
98+
final int apiMaxReadinessThreads =
99+
calculateReadinessAPIMaxThreads(remoteNodeCount, validatorConfig);
101100
readinessAsyncRunner =
102101
services.createAsyncRunner(
103102
"validatorBeaconAPIReadiness", apiMaxReadinessThreads, MAX_API_EXECUTOR_QUEUE_SIZE);
@@ -188,14 +187,23 @@ public static BeaconNodeApi create(
188187
}
189188

190189
public static int calculateAPIMaxThreads(
191-
final int remoteNodeCount, final boolean isFailoversPublishSignedDutiesEnabled) {
190+
final int remoteNodeCount, final ValidatorConfig validatorConfig) {
191+
if (validatorConfig.getBeaconApiExecutorThreads().isPresent()) {
192+
return validatorConfig.getBeaconApiExecutorThreads().getAsInt();
193+
}
192194
// Let's allow at least 4 parallel requests per remote node when publishing signed duties to
193195
// failovers, to reduce the risk of being affected by a slow remote node.
194-
final int remoteNodeCountMultiplier = isFailoversPublishSignedDutiesEnabled ? 4 : 2;
196+
final int remoteNodeCountMultiplier =
197+
validatorConfig.isFailoversPublishSignedDutiesEnabled() ? 4 : 2;
195198
return Math.max(5, remoteNodeCount * remoteNodeCountMultiplier);
196199
}
197200

198-
public static int calculateReadinessAPIMaxThreads(final int remoteNodeCount) {
201+
public static int calculateReadinessAPIMaxThreads(
202+
final int remoteNodeCount, final ValidatorConfig validatorConfig) {
203+
if (validatorConfig.getBeaconApiReadinessExecutorThreads().isPresent()) {
204+
return validatorConfig.getBeaconApiReadinessExecutorThreads().getAsInt();
205+
}
206+
199207
// we call two methods per remote node to check readiness, so we need at least 2 threads to call
200208
// them in parallel
201209
return remoteNodeCount * 2;

validator/remote/src/main/java/tech/pegasys/teku/validator/remote/sentry/SentryBeaconNodeApi.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,17 @@ public static BeaconNodeApi create(
8181

8282
final int apiMaxThreads =
8383
calculateAPIMaxThreads(
84-
dutiesProviderNodeConfig.getEndpointsAsURIs().size(),
85-
validatorConfig.isFailoversPublishSignedDutiesEnabled());
84+
dutiesProviderNodeConfig.getEndpointsAsURIs().size(), validatorConfig);
8685
final AsyncRunner asyncRunner =
8786
services.createAsyncRunner(
8887
"validatorBeaconAPI", apiMaxThreads, MAX_API_EXECUTOR_QUEUE_SIZE);
8988

90-
final int apiMaxReadinessThreads =
91-
calculateReadinessAPIMaxThreads(dutiesProviderNodeConfig.getEndpointsAsURIs().size());
89+
final int apiReadinessMaxThreads =
90+
calculateReadinessAPIMaxThreads(
91+
dutiesProviderNodeConfig.getEndpointsAsURIs().size(), validatorConfig);
9292
final AsyncRunner readinessAsyncRunner =
9393
services.createAsyncRunner(
94-
"validatorBeaconAPIReadiness", apiMaxReadinessThreads, MAX_API_EXECUTOR_QUEUE_SIZE);
94+
"validatorBeaconAPIReadiness", apiReadinessMaxThreads, MAX_API_EXECUTOR_QUEUE_SIZE);
9595

9696
final RemoteValidatorApiChannel dutiesProviderPrimaryValidatorApiChannel =
9797
createPrimaryValidatorApiChannel(

validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteBeaconNodeApiTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,22 @@
1414
package tech.pegasys.teku.validator.remote;
1515

1616
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
17+
import static org.mockito.ArgumentMatchers.anyInt;
18+
import static org.mockito.ArgumentMatchers.anyString;
1719
import static org.mockito.Mockito.mock;
20+
import static org.mockito.Mockito.times;
21+
import static org.mockito.Mockito.verify;
22+
import static org.mockito.Mockito.when;
23+
import static tech.pegasys.teku.validator.remote.RemoteBeaconNodeApi.MAX_API_EXECUTOR_QUEUE_SIZE;
1824

1925
import java.net.URI;
26+
import java.net.URISyntaxException;
2027
import java.util.List;
28+
import java.util.OptionalInt;
29+
import org.junit.jupiter.api.BeforeEach;
2130
import org.junit.jupiter.api.Test;
31+
import tech.pegasys.teku.infrastructure.events.EventChannels;
32+
import tech.pegasys.teku.infrastructure.metrics.StubMetricsSystem;
2233
import tech.pegasys.teku.service.serviceutils.ServiceConfig;
2334
import tech.pegasys.teku.spec.Spec;
2435
import tech.pegasys.teku.spec.TestSpecFactory;
@@ -27,9 +38,17 @@
2738
class RemoteBeaconNodeApiTest {
2839

2940
private final ServiceConfig serviceConfig = mock(ServiceConfig.class);
41+
private final EventChannels eventChannels = mock(EventChannels.class);
3042
private final ValidatorConfig validatorConfig = mock(ValidatorConfig.class);
43+
private final StubMetricsSystem metricsSystem = new StubMetricsSystem();
3144
private final Spec spec = TestSpecFactory.createMinimalAltair();
3245

46+
@BeforeEach
47+
void setUp() {
48+
when(serviceConfig.getEventChannels()).thenReturn(eventChannels);
49+
when(serviceConfig.getMetricsSystem()).thenReturn(metricsSystem);
50+
}
51+
3352
@Test
3453
void producesExceptionWhenInvalidUrlPassed() {
3554
assertThatThrownBy(
@@ -38,4 +57,65 @@ void producesExceptionWhenInvalidUrlPassed() {
3857
serviceConfig, validatorConfig, spec, List.of(new URI("notvalid"))))
3958
.hasMessageContaining("Failed to convert remote api endpoint");
4059
}
60+
61+
@Test
62+
void shouldConfigureBeaconApiPools_primaryOnly() throws URISyntaxException {
63+
RemoteBeaconNodeApi.create(
64+
serviceConfig, validatorConfig, spec, List.of(new URI("https://localhost")));
65+
66+
verify(serviceConfig).createAsyncRunner("validatorBeaconAPI", 5, MAX_API_EXECUTOR_QUEUE_SIZE);
67+
68+
verify(serviceConfig, times(1)).createAsyncRunner(anyString(), anyInt(), anyInt());
69+
}
70+
71+
@Test
72+
void shouldConfigureBeaconApiPools_primaryAndSecondary() throws URISyntaxException {
73+
RemoteBeaconNodeApi.create(
74+
serviceConfig,
75+
validatorConfig,
76+
spec,
77+
List.of(new URI("https://localhost"), new URI("https://secondary")));
78+
79+
verify(serviceConfig).createAsyncRunner("validatorBeaconAPI", 5, MAX_API_EXECUTOR_QUEUE_SIZE);
80+
verify(serviceConfig)
81+
.createAsyncRunner("validatorBeaconAPIReadiness", 4, MAX_API_EXECUTOR_QUEUE_SIZE);
82+
83+
verify(serviceConfig, times(2)).createAsyncRunner(anyString(), anyInt(), anyInt());
84+
}
85+
86+
@Test
87+
void shouldConfigureBeaconApiPools_primaryAndSecondaryWithFailoverPublish()
88+
throws URISyntaxException {
89+
when(validatorConfig.isFailoversPublishSignedDutiesEnabled()).thenReturn(true);
90+
91+
RemoteBeaconNodeApi.create(
92+
serviceConfig,
93+
validatorConfig,
94+
spec,
95+
List.of(new URI("https://localhost"), new URI("https://secondary")));
96+
97+
verify(serviceConfig).createAsyncRunner("validatorBeaconAPI", 8, MAX_API_EXECUTOR_QUEUE_SIZE);
98+
verify(serviceConfig)
99+
.createAsyncRunner("validatorBeaconAPIReadiness", 4, MAX_API_EXECUTOR_QUEUE_SIZE);
100+
101+
verify(serviceConfig, times(2)).createAsyncRunner(anyString(), anyInt(), anyInt());
102+
}
103+
104+
@Test
105+
void shouldConfigureBeaconApiPools_configOverride() throws URISyntaxException {
106+
when(validatorConfig.getBeaconApiExecutorThreads()).thenReturn(OptionalInt.of(2));
107+
when(validatorConfig.getBeaconApiReadinessExecutorThreads()).thenReturn(OptionalInt.of(4));
108+
109+
RemoteBeaconNodeApi.create(
110+
serviceConfig,
111+
validatorConfig,
112+
spec,
113+
List.of(new URI("https://localhost"), new URI("https://secondary")));
114+
115+
verify(serviceConfig).createAsyncRunner("validatorBeaconAPI", 2, MAX_API_EXECUTOR_QUEUE_SIZE);
116+
verify(serviceConfig)
117+
.createAsyncRunner("validatorBeaconAPIReadiness", 4, MAX_API_EXECUTOR_QUEUE_SIZE);
118+
119+
verify(serviceConfig, times(2)).createAsyncRunner(anyString(), anyInt(), anyInt());
120+
}
41121
}

0 commit comments

Comments
 (0)