Skip to content

Commit

Permalink
implement validator slashing in validator timing actions
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdi-aouadi committed Jan 24, 2024
1 parent ba251d3 commit fcb9e5c
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import tech.pegasys.teku.services.powchain.PowchainService;
import tech.pegasys.teku.validator.client.ValidatorClientService;
import tech.pegasys.teku.validator.client.slashingriskactions.DoppelgangerDetectionShutDown;
import tech.pegasys.teku.validator.client.slashingriskactions.SlashedValidatorShutDown;
import tech.pegasys.teku.validator.client.slashingriskactions.SlashingRiskAction;

public class BeaconNodeServiceController extends ServiceController {

Expand Down Expand Up @@ -52,9 +54,18 @@ public BeaconNodeServiceController(
tekuConfig.discovery().isDiscoveryEnabled()));
powchainService(tekuConfig, serviceConfig, maybeExecutionWeb3jClientProvider)
.ifPresent(services::add);

final Optional<SlashingRiskAction> maybeValidatorSlashedAction =
tekuConfig.validatorClient().getValidatorConfig().isShutdownWhenValidatorSlashedEnabled()
? Optional.of(new SlashedValidatorShutDown())
: Optional.empty();

services.add(
ValidatorClientService.create(
serviceConfig, tekuConfig.validatorClient(), new DoppelgangerDetectionShutDown()));
serviceConfig,
tekuConfig.validatorClient(),
new DoppelgangerDetectionShutDown(),
maybeValidatorSlashedAction));
}

private Optional<PowchainService> powchainService(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,29 @@

package tech.pegasys.teku.services;

import java.util.Optional;
import tech.pegasys.teku.config.TekuConfiguration;
import tech.pegasys.teku.service.serviceutils.ServiceConfig;
import tech.pegasys.teku.validator.client.ValidatorClientService;
import tech.pegasys.teku.validator.client.slashingriskactions.DoppelgangerDetectionShutDown;
import tech.pegasys.teku.validator.client.slashingriskactions.SlashedValidatorShutDown;
import tech.pegasys.teku.validator.client.slashingriskactions.SlashingRiskAction;

public class ValidatorNodeServiceController extends ServiceController {

public ValidatorNodeServiceController(
final TekuConfiguration tekuConfig, final ServiceConfig serviceConfig) {

final Optional<SlashingRiskAction> maybeValidatorSlashedAction =
tekuConfig.validatorClient().getValidatorConfig().isShutdownWhenValidatorSlashedEnabled()
? Optional.of(new SlashedValidatorShutDown())
: Optional.empty();

this.services.add(
ValidatorClientService.create(
serviceConfig, tekuConfig.validatorClient(), new DoppelgangerDetectionShutDown()));
serviceConfig,
tekuConfig.validatorClient(),
new DoppelgangerDetectionShutDown(),
maybeValidatorSlashedAction));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,16 @@ public void shouldSetExitWhenNoValidatorKeysEnabled() {
@Test
public void shouldDefaultFalseShutdownWhenValidatorSlashedEnabled() {
final ValidatorConfig config =
getTekuConfigurationFromArguments().validatorClient().getValidatorConfig();
getTekuConfigurationFromArguments().validatorClient().getValidatorConfig();
assertThat(config.isShutdownWhenValidatorSlashedEnabled()).isFalse();
}

@Test
public void shouldSetShutdownWhenValidatorSlashedEnabled() {
final ValidatorConfig config =
getTekuConfigurationFromArguments("--Xshut-down-when-validator-slashed-enabled=true")
.validatorClient()
.getValidatorConfig();
getTekuConfigurationFromArguments("--Xshut-down-when-validator-slashed-enabled=true")
.validatorClient()
.getValidatorConfig();
assertThat(config.isShutdownWhenValidatorSlashedEnabled()).isTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public class ValidatorClientService extends Service {
private ValidatorIndexProvider validatorIndexProvider;
private Optional<DoppelgangerDetector> maybeDoppelgangerDetector = Optional.empty();
private final SlashingRiskAction doppelgangerDetectionAction;

private final Optional<SlashingRiskAction> maybeValidatorSlashedAction;
private Optional<RestApi> maybeValidatorRestApi = Optional.empty();
private final Optional<BeaconProposerPreparer> beaconProposerPreparer;
private final Optional<ValidatorRegistrator> validatorRegistrator;
Expand All @@ -115,7 +117,8 @@ private ValidatorClientService(
final Optional<ValidatorRegistrator> validatorRegistrator,
final Spec spec,
final MetricsSystem metricsSystem,
final SlashingRiskAction doppelgangerDetectionAction) {
final SlashingRiskAction doppelgangerDetectionAction,
final Optional<SlashingRiskAction> maybeValidatorSlashedAction) {
this.eventChannels = eventChannels;
this.validatorLoader = validatorLoader;
this.beaconNodeApi = beaconNodeApi;
Expand All @@ -127,12 +130,14 @@ private ValidatorClientService(
this.spec = spec;
this.metricsSystem = metricsSystem;
this.doppelgangerDetectionAction = doppelgangerDetectionAction;
this.maybeValidatorSlashedAction = maybeValidatorSlashedAction;
}

public static ValidatorClientService create(
final ServiceConfig services,
final ValidatorClientConfiguration config,
final SlashingRiskAction doppelgangerDetectionAction) {
final SlashingRiskAction doppelgangerDetectionAction,
final Optional<SlashingRiskAction> maybeValidatorSlashedAction) {
final EventChannels eventChannels = services.getEventChannels();
final ValidatorConfig validatorConfig = config.getValidatorConfig();

Expand Down Expand Up @@ -214,7 +219,8 @@ public static ValidatorClientService create(
validatorRegistrator,
config.getSpec(),
services.getMetricsSystem(),
doppelgangerDetectionAction);
doppelgangerDetectionAction,
maybeValidatorSlashedAction);

asyncRunner
.runAsync(
Expand Down Expand Up @@ -564,7 +570,11 @@ protected SafeFuture<?> doStart() {
eventChannels.subscribe(
ValidatorTimingChannel.class,
new ValidatorTimingActions(
validatorIndexProvider, validatorTimingChannels, spec, metricsSystem));
validatorIndexProvider,
validatorTimingChannels,
spec,
metricsSystem,
maybeValidatorSlashedAction));
validatorStatusProvider.start().ifExceptionGetsHereRaiseABug();
return beaconNodeApi.subscribeToEvents();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
Expand Down Expand Up @@ -99,6 +100,13 @@ private void logNewValidatorIndices(final Map<BLSPublicKey, Integer> knownValida
}
}

public Optional<BLSPublicKey> getPublicKey(final Integer index) {
return validatorIndicesByPublicKey.entrySet().stream()
.filter(entry -> entry.getValue().equals(index))
.findFirst()
.map(Map.Entry::getKey);
}

public boolean containsPublicKey(BLSPublicKey publicKey) {
return validatorIndicesByPublicKey.containsKey(publicKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@
package tech.pegasys.teku.validator.client;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.tuweni.bytes.Bytes32;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import tech.pegasys.teku.bls.BLSPublicKey;
import tech.pegasys.teku.infrastructure.metrics.SettableGauge;
import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing;
import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing;
import tech.pegasys.teku.validator.api.ValidatorTimingChannel;
import tech.pegasys.teku.validator.client.slashingriskactions.SlashingRiskAction;

public class ValidatorTimingActions implements ValidatorTimingChannel {
private final ValidatorIndexProvider validatorIndexProvider;
Expand All @@ -31,14 +37,18 @@ public class ValidatorTimingActions implements ValidatorTimingChannel {

private final SettableGauge validatorCurrentEpoch;

private final Optional<SlashingRiskAction> maybeValidatorSlashedAction;

public ValidatorTimingActions(
final ValidatorIndexProvider validatorIndexProvider,
final Collection<ValidatorTimingChannel> delegates,
final Spec spec,
final MetricsSystem metricsSystem) {
final MetricsSystem metricsSystem,
final Optional<SlashingRiskAction> maybeValidatorSlashedAction) {
this.validatorIndexProvider = validatorIndexProvider;
this.delegates = delegates;
this.spec = spec;
this.maybeValidatorSlashedAction = maybeValidatorSlashedAction;

this.validatorCurrentEpoch =
SettableGauge.create(
Expand Down Expand Up @@ -97,8 +107,30 @@ public void onAttestationAggregationDue(final UInt64 slot) {
}

@Override
public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {}
public void onAttesterSlashing(final AttesterSlashing attesterSlashing) {
delegates.forEach(delegates -> delegates.onAttesterSlashing(attesterSlashing));
maybeValidatorSlashedAction.ifPresent(
validatorSlashingAction -> {
final Set<UInt64> slashedIndices = attesterSlashing.getIntersectingValidatorIndices();
final List<BLSPublicKey> slashedPublicKeys =
slashedIndices.stream()
.map(slashedIndex -> validatorIndexProvider.getPublicKey(slashedIndex.intValue()))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
validatorSlashingAction.perform(slashedPublicKeys);
});
}

@Override
public void onProposerSlashing(final ProposerSlashing proposerSlashing) {}
public void onProposerSlashing(final ProposerSlashing proposerSlashing) {
delegates.forEach(delegates -> delegates.onProposerSlashing(proposerSlashing));
maybeValidatorSlashedAction.ifPresent(
validatorSlashedAction -> {
final UInt64 slashedIndex = proposerSlashing.getHeader1().getMessage().getProposerIndex();
validatorIndexProvider
.getPublicKey(slashedIndex.intValue())
.ifPresent(slashedPubKey -> maybeValidatorSlashedAction.get().perform(slashedPubKey));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public interface SlashingRiskAction {

void perform(final List<BLSPublicKey> doppelgangers);

default void perform(BLSPublicKey pubKey) {
perform(List.of(pubKey));
}

default void shutdown() {
System.exit(2);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
Expand Down Expand Up @@ -391,7 +392,7 @@ void shouldAddLocalKeysWhenDoppelgangerDetectionException()
verify(validatorLoader, times(1))
.addValidator(any(), eq(doppelgangerPassword), eq(doppelgangerPublicKey));
verify(doppelgangerDetector).performDoppelgangerDetection(Set.of(doppelgangerPublicKey));
verify(doppelgangerDetectionAction, never()).perform(any());
verify(doppelgangerDetectionAction, never()).perform(anyList());
logCaptor.assertErrorLog(
String.format(
"Failed to perform doppelganger detection for public keys %s",
Expand Down Expand Up @@ -485,7 +486,7 @@ void shouldAddExternalKeysWhenDoppelgangerDetectionException() throws MalformedU

verify(channel).onValidatorsAdded();
verify(doppelgangerDetector).performDoppelgangerDetection(Set.of(doppelgangerPublicKey));
verify(doppelgangerDetectionAction, never()).perform(any());
verify(doppelgangerDetectionAction, never()).perform(anyList());
verify(validatorLoader, times(1)).addExternalValidator(signerUrl, doppelgangerPublicKey);
logCaptor.assertErrorLog(
String.format(
Expand Down
Loading

0 comments on commit fcb9e5c

Please sign in to comment.