Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

removed requirement for block headers in voluntary exit #8461

Merged
merged 6 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,13 @@
import tech.pegasys.teku.cli.BeaconNodeCommand;
import tech.pegasys.teku.infrastructure.json.JsonUtil;
import tech.pegasys.teku.infrastructure.logging.LoggingConfigurator;
import tech.pegasys.teku.infrastructure.time.SystemTimeProvider;
import tech.pegasys.teku.infrastructure.time.TimeProvider;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.SpecMilestone;
import tech.pegasys.teku.spec.TestSpecFactory;
import tech.pegasys.teku.spec.config.SpecConfig;
import tech.pegasys.teku.spec.constants.Domain;
import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit;
import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers;
Expand Down Expand Up @@ -110,8 +114,6 @@ public class VoluntaryExitCommandTest {
@BeforeEach
public void setup(final ClientAndServer server) throws IOException {
this.mockBeaconServer = server;
configureSuccessfulHeadResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);
configureSuccessfulValidatorResponses(mockBeaconServer);
originalSystemIn = System.in;
originalSystemOut = System.out;
Expand Down Expand Up @@ -148,6 +150,7 @@ public void tearDown() {
@Test
public void shouldExitAllLoadedValidators() throws JsonProcessingException {
configureSuccessfulSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);
configureSuccessfulVoluntaryExitResponse(mockBeaconServer);

final List<String> args = getCommandArguments(true, false, List.of());
Expand All @@ -162,6 +165,7 @@ public void shouldExitAllLoadedValidators() throws JsonProcessingException {
public void shouldExitLoadedValidatorsUsingConfirmationMessage() throws JsonProcessingException {
setUserInput("yes");
configureSuccessfulSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);
configureSuccessfulVoluntaryExitResponse(mockBeaconServer);

final List<String> args = getCommandArguments(true, true, List.of());
Expand All @@ -175,6 +179,7 @@ public void shouldExitLoadedValidatorsUsingConfirmationMessage() throws JsonProc
@Test
public void shouldExitValidatorWithPubKeyFromKeyManagerOnly() throws JsonProcessingException {
configureSuccessfulSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);
configureSuccessfulVoluntaryExitResponse(mockBeaconServer);

final List<String> args =
Expand All @@ -193,6 +198,7 @@ public void shouldExitValidatorWithPubKeyFromKeyManagerOnly() throws JsonProcess
@Test
public void shouldAcceptNetworkOnCommandLine() {
configureSuccessfulVoluntaryExitResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);

// No beacon-api offered by spec, so would need to be loaded from local network option
final List<String> args =
Expand All @@ -210,6 +216,7 @@ public void shouldAcceptNetworkOnCommandLine() {
@Test
public void shouldReturnRejectedReasonWhenExitIsRejectedByBeaconNode() throws IOException {
configureRejectedVoluntaryExitResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);

final List<String> args =
getCommandArguments(
Expand Down Expand Up @@ -239,6 +246,7 @@ public void shouldFailToRunExitWithoutASpec() {
@Test
public void shouldExitValidatorWithPubKeyFromPathOnly() throws JsonProcessingException {
configureSuccessfulSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);
configureSuccessfulVoluntaryExitResponse(mockBeaconServer);

final List<String> args =
Expand All @@ -257,6 +265,7 @@ public void shouldExitValidatorWithPubKeyFromPathOnly() throws JsonProcessingExc
@Test
public void shouldSkipKeyManagerKeys() throws JsonProcessingException {
configureSuccessfulSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);
configureSuccessfulVoluntaryExitResponse(mockBeaconServer);

final List<String> args =
Expand All @@ -275,6 +284,7 @@ public void shouldSkipKeyManagerKeys() throws JsonProcessingException {
@Test
void shouldNotWarn_NotWithdrawableIfCapellaEnabled() throws JsonProcessingException {
configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella());
configureSuccessfulGenesisResponse(mockBeaconServer);

final List<String> args = getCommandArguments(false, true, List.of());
setUserInput("no");
Expand All @@ -290,6 +300,7 @@ void shouldNotWarn_NotWithdrawableIfCapellaEnabled() throws JsonProcessingExcept
@Test
void shouldGenerateExitWithoutSendingToNode(@TempDir final Path tempDir) throws IOException {
configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella());
configureSuccessfulGenesisResponse(mockBeaconServer);
final Path outputFolder = tempDir.resolve("out");
final List<String> args = new ArrayList<>();
args.addAll(commandArgs);
Expand Down Expand Up @@ -317,6 +328,8 @@ void shouldGenerateExitWithoutSendingToNode(@TempDir final Path tempDir) throws
@Test
void shouldFailIfSaveFolderCannotBeCreated(@TempDir final Path tempDir) throws IOException {
configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella());
configureSuccessfulGenesisResponse(mockBeaconServer);

final Path invalidOutputDestination = tempDir.resolve("testFile");
Files.writeString(invalidOutputDestination, "test");
final List<String> args = new ArrayList<>();
Expand All @@ -337,6 +350,8 @@ void shouldFailIfSaveFolderCannotBeCreated(@TempDir final Path tempDir) throws I
@DisabledOnOs(OS.WINDOWS) // can't set permissions on windows
void shouldFailIfSaveFolderHasInsufficientAccess(@TempDir final Path tempDir) throws IOException {
configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella());
configureSuccessfulGenesisResponse(mockBeaconServer);

final Path invalidOutputDestination = tempDir.resolve("testFile");
tempDir.toFile().mkdir();
tempDir.toFile().setWritable(false);
Expand All @@ -353,9 +368,29 @@ void shouldFailIfSaveFolderHasInsufficientAccess(@TempDir final Path tempDir) th
assertThat(stdErr.toString(UTF_8)).contains("Failed to store exit for a756543");
}

@Test
void shouldFailIfGenesisDataNotAvailableAndNoEpochSpecified() throws JsonProcessingException {
configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella());
final List<String> args =
getCommandArguments(false, true, List.of("--validator-public-keys", validatorPubKey1));
final int parseResult = beaconNodeCommand.parse(args.toArray(new String[0]));
assertThat(parseResult).isEqualTo(1);
assertThat(stdErr.toString(UTF_8)).contains("Could not calculate epoch from genesis data");
}

@Test
void shouldFailToGenerateExitWithoutBeaconNodeAvailable() {
final List<String> args =
List.of("voluntary-exit", "--validator-public-keys", validatorPubKey1);
final int parseResult = beaconNodeCommand.parse(args.toArray(new String[0]));
assertThat(parseResult).isEqualTo(1);
assertThat(stdErr.toString(UTF_8)).contains("Failed to connect to beacon node.");
}

@Test
void shouldExitFailureWithNoValidatorKeysFound() throws JsonProcessingException {
configureSuccessfulSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);

final List<String> args = commandArgs.subList(0, 5);
int parseResult = beaconNodeCommand.parse(args.toArray(new String[0]));
Expand All @@ -365,8 +400,9 @@ void shouldExitFailureWithNoValidatorKeysFound() throws JsonProcessingException
}

@Test
void shouldExitFailureFutureEpoch() throws JsonProcessingException {
void shouldExitFailureFutureEpoch() throws IOException {
configureSuccessfulSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);

final List<String> args = getCommandArguments(false, true, List.of("--epoch=1024"));
int parseResult = beaconNodeCommand.parse(args.toArray(new String[0]));
Expand All @@ -376,10 +412,33 @@ void shouldExitFailureFutureEpoch() throws JsonProcessingException {
.contains("The specified epoch 1024 is greater than current epoch");
}

@Test
void shouldCreateExitForFutureEpochIfOutputFolderDefined(@TempDir final Path tempDir)
throws IOException {
configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella());
configureSuccessfulGenesisResponse(mockBeaconServer);
final List<String> args = new ArrayList<>();
args.addAll(commandArgs);
args.addAll(
List.of(
"--epoch",
"1024",
"--validator-public-keys",
validatorPubKey1,
"--save-exits-path",
tempDir.toAbsolutePath().toString()));

beaconNodeCommand.parse(args.toArray(new String[0]));
final String outString = stdOut.toString(UTF_8);
assertThat(StringUtils.countMatches(outString, "Writing signed exit for")).isEqualTo(1);
}

@Test
void shouldUseCurrentForkDomainForSignatureBeforeDeneb() throws JsonProcessingException {
setUserInput("yes");
configureSuccessfulSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);

final Supplier<List<SignedVoluntaryExit>> exitsCapture =
configureSuccessfulVoluntaryExitResponseWithCapture(mockBeaconServer);

Expand All @@ -404,6 +463,8 @@ void shouldUseCurrentForkDomainForSignatureBeforeDeneb() throws JsonProcessingEx
void shouldUseCapellaForkDomainForSignatureAfterCapella() throws JsonProcessingException {
setUserInput("yes");
configureSuccessfulDenebSpecResponse(mockBeaconServer);
configureSuccessfulGenesisResponse(mockBeaconServer);

final Supplier<List<SignedVoluntaryExit>> exitsCapture =
configureSuccessfulVoluntaryExitResponseWithCapture(mockBeaconServer);

Expand Down Expand Up @@ -464,23 +525,21 @@ private void configureSuccessfulSpecResponse(
.respond(response().withStatusCode(200).withBody(getTestSpecJsonString(spec)));
}

private void configureSuccessfulHeadResponse(final ClientAndServer mockBeaconServer)
throws IOException {
final String testHead =
Resources.toString(
Resources.getResource("tech/pegasys/teku/cli/subcommand/voluntary-exit/head.json"),
UTF_8);
mockBeaconServer
.when(request().withPath("/eth/v1/beacon/headers/head"))
.respond(response().withStatusCode(200).withBody(testHead));
}
private void configureSuccessfulGenesisResponse(final ClientAndServer mockBeaconServer) {
final TimeProvider timeProvider = new SystemTimeProvider();
final SpecConfig config = spec.getGenesisSpec().getConfig();
final UInt64 genesisTime =
timeProvider
.getTimeInSeconds()
.minus(1020L * config.getSecondsPerSlot() * config.getSlotsPerEpoch());

private void configureSuccessfulGenesisResponse(final ClientAndServer mockBeaconServer)
throws IOException {
final String testHead =
Resources.toString(
Resources.getResource("tech/pegasys/teku/cli/subcommand/voluntary-exit/genesis.json"),
UTF_8);
String.format(
"{ \"data\": {\"genesis_time\": \"%s\","
+ "\"genesis_validators_root\": \"0xf03f804ff1c97ada13050eb617e66e88e1199c2ce1be0b6b27e36fafb8d3ee48\","
+ "\"genesis_fork_version\": \"0x00004105\"}}",
genesisTime);

mockBeaconServer
.when(request().withPath("/eth/v1/beacon/genesis"))
.respond(response().withStatusCode(200).withBody(testHead));
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
import tech.pegasys.teku.infrastructure.json.JsonUtil;
import tech.pegasys.teku.infrastructure.logging.SubCommandLogger;
import tech.pegasys.teku.infrastructure.logging.ValidatorLogger;
import tech.pegasys.teku.infrastructure.time.SystemTimeProvider;
import tech.pegasys.teku.infrastructure.time.TimeProvider;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.service.serviceutils.layout.DataDirLayout;
import tech.pegasys.teku.spec.Spec;
Expand Down Expand Up @@ -102,6 +104,7 @@ public class VoluntaryExitCommand implements Callable<Integer> {
private TekuConfiguration config;
private final MetricsSystem metricsSystem = new NoOpMetricsSystem();
private Spec spec;
private final TimeProvider timeProvider = new SystemTimeProvider();

static final String WITHDRAWALS_PERMANENT_MESASGE =
"These validators won't be able to be re-activated once this operation is complete.";
Expand Down Expand Up @@ -355,14 +358,12 @@ private String prettyExitMessage(
});
}

private Optional<UInt64> getEpoch() {
return apiClient
.getBlockHeader("head")
.map(response -> spec.computeEpochAtSlot(response.data.header.message.slot));
}

private Optional<Bytes32> getGenesisRoot() {
return typeDefClient.getGenesis().map(GenesisData::getGenesisValidatorsRoot);
private UInt64 getEpochFromGenesisData(final GenesisData genesisData) {
final UInt64 genesisTime = genesisData.getGenesisTime();
final UInt64 currentTime = timeProvider.getTimeInSeconds();
final UInt64 slot =
spec.getGenesisSpec().miscHelpers().computeSlotAtTime(genesisTime, currentTime);
return spec.computeEpochAtSlot(slot);
}

private void initialise() {
Expand Down Expand Up @@ -400,11 +401,12 @@ private void initialise() {
spec = SpecFactory.create(network);
}

validateOrDefaultEpoch();
final Optional<GenesisData> maybeGenesisData = typeDefClient.getGenesis();
validateOrDefaultEpoch(maybeGenesisData);
fork = spec.getForkSchedule().getFork(epoch);

// get genesis time
final Optional<Bytes32> maybeRoot = getGenesisRoot();
final Optional<Bytes32> maybeRoot = maybeGenesisData.map(GenesisData::getGenesisValidatorsRoot);
if (maybeRoot.isEmpty()) {
throw new InvalidConfigurationException(
"Unable to fetch genesis data, cannot generate an exit.");
Expand Down Expand Up @@ -462,13 +464,13 @@ private void initialise() {
}
}

private void validateOrDefaultEpoch() {
final Optional<UInt64> maybeEpoch = getEpoch();
private void validateOrDefaultEpoch(final Optional<GenesisData> maybeGenesisData) {
final Optional<UInt64> maybeEpoch = maybeGenesisData.map(this::getEpochFromGenesisData);

if (epoch == null) {
if (maybeEpoch.isEmpty()) {
throw new InvalidConfigurationException(
"Could not calculate epoch from latest block header, please specify --epoch");
"Could not calculate epoch from genesis data, please specify --epoch");
}
epoch = maybeEpoch.orElseThrow();
} else if (maybeEpoch.isPresent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import static java.util.Collections.emptyMap;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND;
import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_AGGREGATE;
import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_BLOCK_HEADER;
import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_GENESIS;
import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_PROPOSER_DUTIES;
import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_SYNC_COMMITTEE_CONTRIBUTION;
Expand Down Expand Up @@ -52,7 +51,6 @@
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.api.request.v1.validator.BeaconCommitteeSubscriptionRequest;
import tech.pegasys.teku.api.response.v1.beacon.GetBlockHeaderResponse;
import tech.pegasys.teku.api.response.v1.beacon.GetGenesisResponse;
import tech.pegasys.teku.api.response.v1.beacon.GetStateValidatorsResponse;
import tech.pegasys.teku.api.response.v1.beacon.PostDataFailureResponse;
Expand Down Expand Up @@ -96,15 +94,6 @@ public Optional<GetGenesisResponse> getGenesis() {
return get(GET_GENESIS, EMPTY_MAP, createHandler(GetGenesisResponse.class));
}

public Optional<GetBlockHeaderResponse> getBlockHeader(final String blockId) {
return get(
GET_BLOCK_HEADER,
Map.of("block_id", blockId),
EMPTY_MAP,
EMPTY_MAP,
createHandler(GetBlockHeaderResponse.class));
}

/**
* <a
* href="https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getStateValidators">GET
Expand Down