Skip to content

Commit

Permalink
feat: Commit boost API - Get Public Keys (#1031)
Browse files Browse the repository at this point in the history
* `--commit-boost-api-enabled=<true|false>`. To enable commit boost API
* `--proxy-keystores-path`. Path to the directory that will read and store encrypted proxy keys in v4 (BLS) and v3 (SECP) formats.
* `--proxy-keystores-password-file`. The path to file that contains password to encrypt and decrypt proxy keystores.
* Implement route and handlers for `/signer/v1/get_pubkeys`
* Load proxy signers from local directories
* refactoring DefaultArtifactSignerProvider and unit test
  • Loading branch information
usmansaleem authored Oct 24, 2024
1 parent 9335457 commit 68db6b7
Show file tree
Hide file tree
Showing 17 changed files with 672 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Features Added
- Java 21 for build and runtime. [#995](https://github.com/Consensys/web3signer/pull/995)
- Electra fork support. [#1020](https://github.com/Consensys/web3signer/pull/1020) and [#1023](https://github.com/Consensys/web3signer/pull/1023)
- Commit boost API - Get Public Keys. [#1031](https://github.com/Consensys/web3signer/pull/1031)

### Bugs fixed
- Override protobuf-java to 3.25.5 which is a transitive dependency from google-cloud-secretmanager. It fixes CVE-2024-7254.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2022 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.web3signer.commandline.config;

import static tech.pegasys.web3signer.commandline.DefaultCommandValues.PATH_FORMAT_HELP;

import tech.pegasys.web3signer.signing.config.KeystoresParameters;

import java.nio.file.Path;

import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.Spec;

public class PicoCommitBoostApiParameters implements KeystoresParameters {
@Spec private CommandSpec commandSpec; // injected by picocli

@CommandLine.Option(
names = {"--commit-boost-api-enabled"},
paramLabel = "<BOOL>",
description = "Enable the commit boost API (default: ${DEFAULT-VALUE}).",
arity = "1")
private boolean isCommitBoostApiEnabled = false;

@Option(
names = {"--proxy-keystores-path"},
description =
"The path to a writeable directory to store v3 and v4 proxy keystores for commit boost API.",
paramLabel = PATH_FORMAT_HELP)
private Path keystoresPath;

@Option(
names = {"--proxy-keystores-password-file"},
description =
"The path to the password file used to encrypt/decrypt proxy keystores for commit boost API.",
paramLabel = PATH_FORMAT_HELP)
private Path keystoresPasswordFile;

@Override
public Path getKeystoresPath() {
return keystoresPath;
}

@Override
public Path getKeystoresPasswordsPath() {
return null;
}

@Override
public Path getKeystoresPasswordFile() {
return keystoresPasswordFile;
}

@Override
public boolean isEnabled() {
return isCommitBoostApiEnabled;
}

public void validateParameters() throws ParameterException {
if (!isCommitBoostApiEnabled) {
return;
}

if (keystoresPath == null) {
throw new ParameterException(
commandSpec.commandLine(),
"Commit boost API is enabled, but --proxy-keystores-path not set");
}

if (keystoresPasswordFile == null) {
throw new ParameterException(
commandSpec.commandLine(),
"Commit boost API is enabled, but --proxy-keystores-password-file not set");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import tech.pegasys.web3signer.commandline.PicoCliEth2AzureKeyVaultParameters;
import tech.pegasys.web3signer.commandline.PicoCliGcpSecretManagerParameters;
import tech.pegasys.web3signer.commandline.PicoCliSlashingProtectionParameters;
import tech.pegasys.web3signer.commandline.config.PicoCommitBoostApiParameters;
import tech.pegasys.web3signer.commandline.config.PicoKeystoresParameters;
import tech.pegasys.web3signer.common.config.AwsAuthenticationMode;
import tech.pegasys.web3signer.core.Eth2Runner;
Expand Down Expand Up @@ -164,6 +165,7 @@ private static class NetworkCliCompletionCandidates extends ArrayList<String> {
@Mixin private PicoKeystoresParameters keystoreParameters;
@Mixin private PicoCliAwsSecretsManagerParameters awsSecretsManagerParameters;
@Mixin private PicoCliGcpSecretManagerParameters gcpSecretManagerParameters;
@Mixin private PicoCommitBoostApiParameters commitBoostApiParameters;
private tech.pegasys.teku.spec.Spec eth2Spec;

public Eth2SubCommand() {
Expand All @@ -183,7 +185,8 @@ public Runner createRunner() {
gcpSecretManagerParameters,
eth2Spec,
isKeyManagerApiEnabled,
signingExtEnabled);
signingExtEnabled,
commitBoostApiParameters);
}

private void logNetworkSpecInformation() {
Expand Down Expand Up @@ -261,6 +264,7 @@ protected void validateArgs() {
validateKeystoreParameters(keystoreParameters);
validateAwsSecretsManageParameters();
validateGcpSecretManagerParameters();
commitBoostApiParameters.validateParameters();
}

private void validateGcpSecretManagerParameters() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

import io.vertx.core.Vertx;
Expand Down Expand Up @@ -612,6 +613,47 @@ void vertxWorkerPoolSizeParsesSuccessfully() {
assertThat(mockEth2SubCommand.getConfig().getVertxWorkerPoolSize()).isEqualTo(40);
}

@Test
void commitBoostApiEnabledWithoutKeystorePathFailsToParse() {
String cmdline = validBaseCommandOptions();
cmdline += "eth2 --slashing-protection-enabled=false --commit-boost-api-enabled=true";

parser.registerSubCommands(new MockEth2SubCommand());
final int result = parser.parseCommandLine(cmdline.split(" "));

assertThat(result).isNotZero();
assertThat(commandError.toString())
.contains(
"Error parsing parameters: Commit boost API is enabled, but --proxy-keystores-path not set");
}

@Test
void commitBoostApiEnabledWithoutKeystorePasswordFileFailsToParse() {
String cmdline = validBaseCommandOptions();
cmdline +=
"eth2 --slashing-protection-enabled=false --commit-boost-api-enabled=true --proxy-keystores-path=./keystore";

parser.registerSubCommands(new MockEth2SubCommand());
final int result = parser.parseCommandLine(cmdline.split(" "));

assertThat(result).isNotZero();
assertThat(commandError.toString())
.contains(
"Error parsing parameters: Commit boost API is enabled, but --proxy-keystores-password-file not set");
}

@Test
void commitBoostApiEnabledWithKeystorePathAndKeystorePasswordFileParsesSuccessfully() {
String cmdline = validBaseCommandOptions();
cmdline +=
"eth2 --slashing-protection-enabled=false --commit-boost-api-enabled=true --proxy-keystores-path=./keystore --proxy-keystores-password-file=./password";

parser.registerSubCommands(new MockEth2SubCommand());
final int result = parser.parseCommandLine(cmdline.split(" "));

assertThat(result).isZero();
}

private <T> void missingOptionalParameterIsValidAndMeetsDefault(
final String paramToRemove, final Supplier<T> actualValueGetter, final T expectedValue) {

Expand Down Expand Up @@ -646,7 +688,7 @@ public void run() {}
@Override
protected List<ArtifactSignerProvider> createArtifactSignerProvider(
final Vertx vertx, final MetricsSystem metricsSystem) {
return List.of(new DefaultArtifactSignerProvider(Collections::emptyList));
return List.of(new DefaultArtifactSignerProvider(Collections::emptyList, Optional.empty()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
Expand Down Expand Up @@ -114,7 +115,8 @@ protected List<ArtifactSignerProvider> createArtifactSignerProvider(
awsKmsSignerFactory)
.getValues());
return signers;
});
},
Optional.empty());

// uses eth1 address as identifier
final ArtifactSignerProvider secpArtifactSignerProvider =
Expand Down
12 changes: 10 additions & 2 deletions core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import tech.pegasys.web3signer.core.config.BaseConfig;
import tech.pegasys.web3signer.core.routes.PublicKeysListRoute;
import tech.pegasys.web3signer.core.routes.ReloadRoute;
import tech.pegasys.web3signer.core.routes.eth2.CommitBoostPublicKeysRoute;
import tech.pegasys.web3signer.core.routes.eth2.Eth2SignExtensionRoute;
import tech.pegasys.web3signer.core.routes.eth2.Eth2SignRoute;
import tech.pegasys.web3signer.core.routes.eth2.HighWatermarkRoute;
Expand Down Expand Up @@ -88,6 +89,7 @@ public class Eth2Runner extends Runner {
private final Spec eth2Spec;
private final boolean isKeyManagerApiEnabled;
private final boolean signingExtEnabled;
private final KeystoresParameters commitBoostApiParameters;

public Eth2Runner(
final BaseConfig baseConfig,
Expand All @@ -98,7 +100,8 @@ public Eth2Runner(
final GcpSecretManagerParameters gcpSecretManagerParameters,
final Spec eth2Spec,
final boolean isKeyManagerApiEnabled,
final boolean signingExtEnabled) {
final boolean signingExtEnabled,
final KeystoresParameters commitBoostApiParameters) {
super(baseConfig);
this.slashingProtectionContext = createSlashingProtection(slashingProtectionParameters);
this.azureKeyVaultParameters = azureKeyVaultParameters;
Expand All @@ -110,6 +113,7 @@ public Eth2Runner(
this.awsVaultParameters = awsVaultParameters;
this.gcpSecretManagerParameters = gcpSecretManagerParameters;
this.signingExtEnabled = signingExtEnabled;
this.commitBoostApiParameters = commitBoostApiParameters;
}

private Optional<SlashingProtectionContext> createSlashingProtection(
Expand Down Expand Up @@ -137,6 +141,9 @@ public void populateRouter(final Context context) {
if (isKeyManagerApiEnabled) {
new KeyManagerApiRoute(context, baseConfig, slashingProtectionContext).register();
}
if (commitBoostApiParameters.isEnabled()) {
new CommitBoostPublicKeysRoute(context).register();
}
}

@Override
Expand Down Expand Up @@ -166,7 +173,8 @@ protected List<ArtifactSignerProvider> createArtifactSignerProvider(

return signers;
}
}));
},
Optional.of(commitBoostApiParameters)));
}

private MappedResults<ArtifactSigner> loadSignersFromKeyConfigFiles(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2024 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.web3signer.core.routes.eth2;

import tech.pegasys.web3signer.core.Context;
import tech.pegasys.web3signer.core.routes.Web3SignerRoute;
import tech.pegasys.web3signer.core.service.http.handlers.commitboost.CommitBoostPublicKeysHandler;

import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.impl.BlockingHandlerDecorator;

public class CommitBoostPublicKeysRoute implements Web3SignerRoute {
private static final String PATH = "/signer/v1/get_pubkeys";
private final Context context;

public CommitBoostPublicKeysRoute(final Context context) {
this.context = context;
}

@Override
public void register() {
context
.getRouter()
.route(HttpMethod.GET, PATH)
.produces(JSON_HEADER)
.handler(
new BlockingHandlerDecorator(
new CommitBoostPublicKeysHandler(context.getArtifactSignerProviders()), false))
.failureHandler(context.getErrorHandler())
.failureHandler(
ctx -> {
final int statusCode = ctx.statusCode();
if (statusCode == 500) {
ctx.response()
.setStatusCode(statusCode)
.end(
new JsonObject()
.put("code", statusCode)
.put("message", "Internal Server Error")
.encode());
} else {
ctx.next(); // go to global failure handler
}
});
}
}
Loading

0 comments on commit 68db6b7

Please sign in to comment.