Skip to content

Commit

Permalink
feat: Refactor eth1 and eth2 routes (#1028)
Browse files Browse the repository at this point in the history
Refactor routes to separate classes to declutter Eth1Runner and Eth2Runner populateRoute methods
  • Loading branch information
usmansaleem authored Oct 17, 2024
1 parent da29ffb commit 9335457
Show file tree
Hide file tree
Showing 22 changed files with 859 additions and 440 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
*/
package tech.pegasys.web3signer.dsl.tls.support;

import static tech.pegasys.web3signer.core.Eth1Runner.createJsonDecoder;
import static tech.pegasys.web3signer.dsl.tls.support.CertificateHelpers.populateFingerprintFile;

import tech.pegasys.web3signer.core.service.jsonrpc.Eth1JsonDecoderFactory;
import tech.pegasys.web3signer.core.service.jsonrpc.JsonDecoder;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.HttpResponseFactory;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.JsonRpcErrorHandler;
Expand Down Expand Up @@ -81,7 +81,7 @@ public HttpServer create(
.setPassword(serverCert.getPassword()));

final Router router = Router.router(vertx);
final JsonDecoder jsonDecoder = createJsonDecoder();
final JsonDecoder jsonDecoder = Eth1JsonDecoderFactory.create();
final RequestMapper requestMapper = new RequestMapper(new MockBalanceReporter());
router
.route(HttpMethod.POST, "/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.io.StringWriter;
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;

import io.vertx.core.Vertx;
Expand Down Expand Up @@ -643,9 +644,9 @@ protected NoOpRunner(final BaseConfig baseConfig) {
public void run() {}

@Override
protected ArtifactSignerProvider createArtifactSignerProvider(
protected List<ArtifactSignerProvider> createArtifactSignerProvider(
final Vertx vertx, final MetricsSystem metricsSystem) {
return new DefaultArtifactSignerProvider(Collections::emptyList);
return List.of(new DefaultArtifactSignerProvider(Collections::emptyList));
}

@Override
Expand Down
12 changes: 7 additions & 5 deletions core/src/main/java/tech/pegasys/web3signer/core/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import tech.pegasys.web3signer.core.service.http.handlers.LogErrorHandler;
import tech.pegasys.web3signer.signing.ArtifactSignerProvider;

import java.util.List;

import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import org.hyperledger.besu.plugin.services.MetricsSystem;
Expand All @@ -24,19 +26,19 @@ public class Context {
private final MetricsSystem metricsSystem;
private final LogErrorHandler errorHandler;
private final Vertx vertx;
private final ArtifactSignerProvider artifactSignerProvider;
private final List<ArtifactSignerProvider> artifactSignerProviders;

public Context(
final Router router,
final MetricsSystem metricsSystem,
final LogErrorHandler errorHandler,
final Vertx vertx,
final ArtifactSignerProvider artifactSignerProvider) {
final List<ArtifactSignerProvider> artifactSignerProviders) {
this.router = router;
this.metricsSystem = metricsSystem;
this.errorHandler = errorHandler;
this.vertx = vertx;
this.artifactSignerProvider = artifactSignerProvider;
this.artifactSignerProviders = artifactSignerProviders;
}

public Router getRouter() {
Expand All @@ -55,7 +57,7 @@ public Vertx getVertx() {
return vertx;
}

public ArtifactSignerProvider getArtifactSignerProvider() {
return artifactSignerProvider;
public List<ArtifactSignerProvider> getArtifactSignerProviders() {
return artifactSignerProviders;
}
}
235 changes: 52 additions & 183 deletions core/src/main/java/tech/pegasys/web3signer/core/Eth1Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,19 @@
import static tech.pegasys.web3signer.core.config.HealthCheckNames.KEYS_CHECK_AWS_BULK_LOADING;
import static tech.pegasys.web3signer.core.config.HealthCheckNames.KEYS_CHECK_AZURE_BULK_LOADING;
import static tech.pegasys.web3signer.core.config.HealthCheckNames.KEYS_CHECK_V3_KEYSTORES_BULK_LOADING;
import static tech.pegasys.web3signer.signing.KeyType.SECP256K1;

import tech.pegasys.web3signer.core.config.BaseConfig;
import tech.pegasys.web3signer.core.config.Eth1Config;
import tech.pegasys.web3signer.core.service.DownstreamPathCalculator;
import tech.pegasys.web3signer.core.service.VertxRequestTransmitter;
import tech.pegasys.web3signer.core.service.VertxRequestTransmitterFactory;
import tech.pegasys.web3signer.core.service.http.handlers.LogErrorHandler;
import tech.pegasys.web3signer.core.service.http.handlers.signing.Eth1SignForIdentifierHandler;
import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier;
import tech.pegasys.web3signer.core.service.http.metrics.HttpApiMetrics;
import tech.pegasys.web3signer.core.service.jsonrpc.JsonDecoder;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.Eth1AccountsHandler;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.HttpResponseFactory;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.JsonRpcErrorHandler;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.JsonRpcHandler;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.PassThroughHandler;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.RequestMapper;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.internalresponse.EthSignResultProvider;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.internalresponse.EthSignTransactionResultProvider;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.internalresponse.EthSignTypedDataResultProvider;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.internalresponse.InternalResponseHandler;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.SendTransactionHandler;
import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.TransactionFactory;
import tech.pegasys.web3signer.core.routes.PublicKeysListRoute;
import tech.pegasys.web3signer.core.routes.ReloadRoute;
import tech.pegasys.web3signer.core.routes.eth1.Eth1SignRoute;
import tech.pegasys.web3signer.core.routes.eth1.JsonRpcRoute;
import tech.pegasys.web3signer.keystorage.azure.AzureKeyVault;
import tech.pegasys.web3signer.keystorage.common.MappedResults;
import tech.pegasys.web3signer.keystorage.hashicorp.HashicorpConnectionFactory;
import tech.pegasys.web3signer.signing.ArtifactSigner;
import tech.pegasys.web3signer.signing.ArtifactSignerProvider;
import tech.pegasys.web3signer.signing.EthSecpArtifactSigner;
import tech.pegasys.web3signer.signing.SecpArtifactSignature;
import tech.pegasys.web3signer.signing.bulkloading.SecpAwsBulkLoader;
import tech.pegasys.web3signer.signing.bulkloading.SecpAzureBulkLoader;
import tech.pegasys.web3signer.signing.bulkloading.SecpV3KeystoresBulkLoader;
Expand All @@ -69,29 +51,26 @@
import java.util.Collections;
import java.util.List;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.healthchecks.Status;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.ResponseContentTypeHandler;
import io.vertx.ext.web.impl.BlockingHandlerDecorator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hyperledger.besu.plugin.services.MetricsSystem;

/**
* Runner subclass for eth1 mode
*
* <p>eth1 mode uses two secp signer providers. For signing operations /api/v1/eth1/sign/, the
* secp256k1 primary key is used as identifier. For JSON-RPC call (eth_signTransactions etc.), the
* eth1 address is used as identifier. See createArtifactSignerProvider method.
*
* @see SecpArtifactSignerProviderAdapter that uses existing ArtifactSignerProvideer maps eth1
* address to primary key.
*/
public class Eth1Runner extends Runner {
public static final String PUBLIC_KEYS_PATH = "/api/v1/eth1/publicKeys";
public static final String ROOT_PATH = "/";
public static final String SIGN_PATH = "/api/v1/eth1/sign/:identifier";
private static final Logger LOG = LogManager.getLogger();
private final Eth1Config eth1Config;
private final HttpResponseFactory responseFactory = new HttpResponseFactory();

public Eth1Runner(final BaseConfig baseConfig, final Eth1Config eth1Config) {
super(baseConfig);
Expand All @@ -100,99 +79,49 @@ public Eth1Runner(final BaseConfig baseConfig, final Eth1Config eth1Config) {

@Override
protected void populateRouter(final Context context) {
final Router router = context.getRouter();
final LogErrorHandler errorHandler = context.getErrorHandler();
final ArtifactSignerProvider signerProvider = context.getArtifactSignerProvider();

addPublicKeysListHandler(router, signerProvider, PUBLIC_KEYS_PATH, context.getErrorHandler());
new PublicKeysListRoute(context, "eth1").register();
new Eth1SignRoute(context).register();
new ReloadRoute(context).register();
new JsonRpcRoute(context, eth1Config).register();
}

final SignerForIdentifier<SecpArtifactSignature> secpSigner =
new SignerForIdentifier<>(signerProvider, this::formatSecpSignature, SECP256K1);
router
.route(HttpMethod.POST, SIGN_PATH)
.handler(
new BlockingHandlerDecorator(
new Eth1SignForIdentifierHandler(
secpSigner,
new HttpApiMetrics(context.getMetricsSystem(), SECP256K1, signerProvider)),
false))
.failureHandler(errorHandler);
@Override
protected List<ArtifactSignerProvider> createArtifactSignerProvider(
final Vertx vertx, final MetricsSystem metricsSystem) {

final ArtifactSignerProvider signerProvider =
new DefaultArtifactSignerProvider(
() -> {
final List<ArtifactSigner> signers = new ArrayList<>();
final AzureKeyVaultFactory azureKeyVaultFactory = new AzureKeyVaultFactory();
final AzureHttpClientFactory azureHttpClientFactory = new AzureHttpClientFactory();
registerClose(azureKeyVaultFactory::close);
final AzureKeyVaultSignerFactory azureSignerFactory =
new AzureKeyVaultSignerFactory(azureKeyVaultFactory, azureHttpClientFactory);
final CachedAwsKmsClientFactory cachedAwsKmsClientFactory =
new CachedAwsKmsClientFactory(eth1Config.getAwsKmsClientCacheSize());
final AwsKmsSignerFactory awsKmsSignerFactory =
new AwsKmsSignerFactory(cachedAwsKmsClientFactory, true);
signers.addAll(
loadSignersFromKeyConfigFiles(
vertx, azureKeyVaultFactory, azureSignerFactory, awsKmsSignerFactory)
.getValues());
signers.addAll(
bulkLoadSigners(
azureKeyVaultFactory,
azureSignerFactory,
cachedAwsKmsClientFactory,
awsKmsSignerFactory)
.getValues());
return signers;
});

// uses eth1 address as identifier
final ArtifactSignerProvider secpArtifactSignerProvider =
new SecpArtifactSignerProviderAdapter(signerProvider);

loadSignerProvider(secpArtifactSignerProvider);

// The order of the elements in the list DO matter
addReloadHandler(
router, List.of(signerProvider, secpArtifactSignerProvider), context.getErrorHandler());

final DownstreamPathCalculator downstreamPathCalculator =
new DownstreamPathCalculator(eth1Config.getDownstreamHttpPath());

final WebClientOptions webClientOptions =
new WebClientOptionsFactory().createWebClientOptions(eth1Config);
final HttpClient downStreamConnection = context.getVertx().createHttpClient(webClientOptions);

final VertxRequestTransmitterFactory transmitterFactory =
responseBodyHandler ->
new VertxRequestTransmitter(
context.getVertx(),
downStreamConnection,
eth1Config.getDownstreamHttpRequestTimeout(),
downstreamPathCalculator,
responseBodyHandler);

final JsonDecoder jsonDecoder = createJsonDecoder();
final PassThroughHandler passThroughHandler =
new PassThroughHandler(transmitterFactory, jsonDecoder);

final RequestMapper requestMapper =
createRequestMapper(
transmitterFactory,
secpArtifactSignerProvider,
jsonDecoder,
eth1Config.getChainId().id());

router
.route(HttpMethod.POST, ROOT_PATH)
.produces(Runner.JSON)
.handler(ResponseContentTypeHandler.create())
.handler(BodyHandler.create())
.failureHandler(new JsonRpcErrorHandler(new HttpResponseFactory()))
.blockingHandler(new JsonRpcHandler(responseFactory, requestMapper, jsonDecoder), false);

router.route().handler(BodyHandler.create()).handler(passThroughHandler);
}

@Override
protected ArtifactSignerProvider createArtifactSignerProvider(
final Vertx vertx, final MetricsSystem metricsSystem) {
return new DefaultArtifactSignerProvider(
() -> {
final List<ArtifactSigner> signers = new ArrayList<>();
final AzureKeyVaultFactory azureKeyVaultFactory = new AzureKeyVaultFactory();
final AzureHttpClientFactory azureHttpClientFactory = new AzureHttpClientFactory();
registerClose(azureKeyVaultFactory::close);
final AzureKeyVaultSignerFactory azureSignerFactory =
new AzureKeyVaultSignerFactory(azureKeyVaultFactory, azureHttpClientFactory);
final CachedAwsKmsClientFactory cachedAwsKmsClientFactory =
new CachedAwsKmsClientFactory(eth1Config.getAwsKmsClientCacheSize());
final AwsKmsSignerFactory awsKmsSignerFactory =
new AwsKmsSignerFactory(cachedAwsKmsClientFactory, true);
signers.addAll(
loadSignersFromKeyConfigFiles(
vertx, azureKeyVaultFactory, azureSignerFactory, awsKmsSignerFactory)
.getValues());
signers.addAll(
bulkLoadSigners(
azureKeyVaultFactory,
azureSignerFactory,
cachedAwsKmsClientFactory,
awsKmsSignerFactory)
.getValues());
return signers;
});
// this order DO matter for reload handler
return List.of(signerProvider, secpArtifactSignerProvider);
}

private MappedResults<ArtifactSigner> loadSignersFromKeyConfigFiles(
Expand Down Expand Up @@ -309,66 +238,6 @@ private MappedResults<ArtifactSigner> bulkloadV3Keystores() {
return walletResults;
}

private String formatSecpSignature(final SecpArtifactSignature signature) {
return SecpArtifactSignature.toBytes(signature).toHexString();
}

public static JsonDecoder createJsonDecoder() {
// Force Transaction Deserialization to fail if missing expected properties
final ObjectMapper jsonObjectMapper = new ObjectMapper();
jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true);
jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true);

return new JsonDecoder(jsonObjectMapper);
}

private RequestMapper createRequestMapper(
final VertxRequestTransmitterFactory transmitterFactory,
final ArtifactSignerProvider signerProviderMappedToEth1Address,
final JsonDecoder jsonDecoder,
final long chainId) {
final PassThroughHandler defaultHandler =
new PassThroughHandler(transmitterFactory, jsonDecoder);
final SignerForIdentifier<SecpArtifactSignature> secpSigner =
new SignerForIdentifier<>(
signerProviderMappedToEth1Address, this::formatSecpSignature, SECP256K1);
final TransactionFactory transactionFactory =
new TransactionFactory(chainId, jsonDecoder, transmitterFactory);
final SendTransactionHandler sendTransactionHandler =
new SendTransactionHandler(chainId, transactionFactory, transmitterFactory, secpSigner);

final RequestMapper requestMapper = new RequestMapper(defaultHandler);
requestMapper.addHandler(
"eth_accounts",
new InternalResponseHandler<>(
responseFactory,
new Eth1AccountsHandler(signerProviderMappedToEth1Address::availableIdentifiers)));
requestMapper.addHandler(
"eth_sign",
new InternalResponseHandler<>(responseFactory, new EthSignResultProvider(secpSigner)));
requestMapper.addHandler(
"eth_signTypedData",
new InternalResponseHandler<>(
responseFactory, new EthSignTypedDataResultProvider(secpSigner)));
requestMapper.addHandler(
"eth_signTransaction",
new InternalResponseHandler<>(
responseFactory,
new EthSignTransactionResultProvider(chainId, secpSigner, jsonDecoder)));
requestMapper.addHandler("eth_sendTransaction", sendTransactionHandler);
requestMapper.addHandler("eea_sendTransaction", sendTransactionHandler);

return requestMapper;
}

private void loadSignerProvider(final ArtifactSignerProvider signerProvider) {
try {
signerProvider.load().get(); // wait for signers to get loaded ...
} catch (final Exception e) {
throw new InitializationException(e);
}
}

private void registerSignerLoadingHealthCheck(
final String name, final MappedResults<ArtifactSigner> result) {
super.registerHealthCheckProcedure(
Expand Down
Loading

0 comments on commit 9335457

Please sign in to comment.