Skip to content

Commit

Permalink
Add origin header in request and cors in Launchpad API (#288)
Browse files Browse the repository at this point in the history
* add origin header in request and cors

* Add origin to any request coming from brain backend

* Removed unnecessary comment

* Fix tests

* Improvement for beaconcha test

* More resilient beaconcha test

---------

Co-authored-by: dappnodedev <diego@dappnode.io>
  • Loading branch information
pablomendezroyo and dappnodedev authored Feb 6, 2024
1 parent 079bb6a commit 11dce58
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 148 deletions.
6 changes: 3 additions & 3 deletions packages/brain/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ export const signerApi = new Web3SignerApi({
baseUrl: signerUrl,
authToken: token,
host,
});
export const beaconchaApi = new BeaconchaApi({ baseUrl: beaconchaUrl });
}, network);
export const beaconchaApi = new BeaconchaApi({ baseUrl: beaconchaUrl }, network);
export const validatorApi = new ValidatorApi({
baseUrl: validatorUrl,
authToken: token,
tlsCert,
});
}, network);
export const beaconchainApi = new Beaconchain(
{ baseUrl: beaconchainUrl },
network
Expand Down
2 changes: 1 addition & 1 deletion packages/brain/src/modules/apiClients/beaconcha/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class BeaconchaApi extends StandardApi {
const endpoint = `/api/v1/validator/${pubkeys.join(",")}`;

try {
return (await this.request("GET", endpoint)) as BeaconchaGetResponse;
return (await this.request({ method: "GET", endpoint })) as BeaconchaGetResponse;
} catch (e) {
e.message += "Error on getting indexes for validator public keys";
throw e;
Expand Down
44 changes: 22 additions & 22 deletions packages/brain/src/modules/apiClients/beaconchain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class Beaconchain extends StandardApi {
private beaconchainEndpoint = "/eth/v1/beacon";

constructor(apiParams: ApiParams, network: Network) {
super(apiParams);
super(apiParams, network);
this.SLOTS_PER_EPOCH = network === "gnosis" ? 16 : 32;
}

Expand All @@ -30,11 +30,11 @@ export class Beaconchain extends StandardApi {
postVoluntaryExitsRequest: BeaconchainPoolVoluntaryExitsPostRequest;
}): Promise<void> {
try {
await this.request(
"POST",
path.join(this.beaconchainEndpoint, "pool", "voluntary_exits"),
JSON.stringify(postVoluntaryExitsRequest)
);
await this.request({
method: "POST",
endpoint: path.join(this.beaconchainEndpoint, "pool", "voluntary_exits"),
body: JSON.stringify(postVoluntaryExitsRequest)
});
} catch (e) {
e.message += `Error posting (POST) voluntary exits to beaconchain. `;
throw e;
Expand All @@ -47,10 +47,10 @@ export class Beaconchain extends StandardApi {
*/
public async getGenesis(): Promise<BeaconchainGenesisGetResponse> {
try {
return (await this.request(
"GET",
path.join(this.beaconchainEndpoint, "genesis")
)) as BeaconchainGenesisGetResponse;
return (await this.request({
method: "GET",
endpoint: path.join(this.beaconchainEndpoint, "genesis")
})) as BeaconchainGenesisGetResponse;
} catch (e) {
e.message += `Error getting (GET) genesis from beaconchain. `;
throw e;
Expand All @@ -68,10 +68,10 @@ export class Beaconchain extends StandardApi {
state_id: string;
}): Promise<BeaconchainForkFromStateGetResponse> {
try {
return (await this.request(
"GET",
path.join(this.beaconchainEndpoint, "states", state_id, "fork")
)) as BeaconchainForkFromStateGetResponse;
return (await this.request({
method: "GET",
endpoint: path.join(this.beaconchainEndpoint, "states", state_id, "fork")
})) as BeaconchainForkFromStateGetResponse;
} catch (e) {
e.message += `Error getting (GET) fork from beaconchain. `;
throw e;
Expand All @@ -92,16 +92,16 @@ export class Beaconchain extends StandardApi {
pubkey: string;
}): Promise<BeaconchainValidatorFromStateGetResponse> {
try {
return (await this.request(
"GET",
path.join(
return (await this.request({
method: "GET",
endpoint: path.join(
this.beaconchainEndpoint,
"states",
state,
"validators",
pubkey
)
)) as BeaconchainValidatorFromStateGetResponse;
})) as BeaconchainValidatorFromStateGetResponse;
} catch (e) {
e.message += `Error getting (GET) validator from beaconchain. `;
throw e;
Expand All @@ -128,10 +128,10 @@ export class Beaconchain extends StandardApi {
block_id: string;
}): Promise<BeaconchainBlockHeaderGetResponse> {
try {
return (await this.request(
"GET",
path.join(this.beaconchainEndpoint, "headers", block_id)
)) as BeaconchainBlockHeaderGetResponse;
return (await this.request({
method: "GET",
endpoint: path.join(this.beaconchainEndpoint, "headers", block_id)
})) as BeaconchainBlockHeaderGetResponse;
} catch (e) {
e.message += `Error getting (GET) block header from beaconchain. `;
throw e;
Expand Down
23 changes: 19 additions & 4 deletions packages/brain/src/modules/apiClients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import {
ApiParams,
AllowedMethods,
ErrnoException,
Network,
} from "@stakingbrain/common";
import { ApiError } from "./error.js";
import logger from "../logger/index.js";

export class StandardApi {
private useTls = false;
private requestOptions: https.RequestOptions;
protected network: Network;

constructor(apiParams: ApiParams) {
constructor(apiParams: ApiParams, network: Network) {
const urlOptions = new URL(apiParams.baseUrl + (apiParams.apiPath || ""));

this.network = network;

this.requestOptions = {
hostname: urlOptions.hostname,
port: urlOptions.port,
Expand Down Expand Up @@ -46,13 +50,19 @@ export class StandardApi {
return `${protocol}//${hostname}:${port || 80}`;
}

protected async request(
protected async request({
method,
endpoint,
body,
setOrigin = false
}: {
method: AllowedMethods,
endpoint: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body?: any
body?: any,
setOrigin?: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> {
}): Promise<any> {
let req: http.ClientRequest;
this.requestOptions.method = method;
this.requestOptions.path = endpoint;
Expand All @@ -62,6 +72,11 @@ export class StandardApi {
req = https.request(this.requestOptions);
} else req = http.request(this.requestOptions);

if (setOrigin)
req.setHeader("Origin", this.network === "mainnet"
? "http://brain.web3signer.dappnode"
: `http://brain.web3signer-${this.network}.dappnode`);

if (body) {
req.setHeader("Content-Length", Buffer.byteLength(body));
req.write(body);
Expand Down
54 changes: 27 additions & 27 deletions packages/brain/src/modules/apiClients/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ export class ValidatorApi extends StandardApi {
publicKey: string
): Promise<ValidatorGetFeeResponse> {
try {
return (await this.request(
"GET",
path.join(
return (await this.request({
method: "GET",
endpoint: path.join(
this.feeRecipientEndpoint,
prefix0xPubkey(publicKey),
"feerecipient"
)
)) as ValidatorGetFeeResponse;
})) as ValidatorGetFeeResponse;
} catch (e) {
e.message += `Error getting (GET) fee recipient for pubkey ${publicKey} from validator. `;
throw e;
Expand All @@ -54,15 +54,15 @@ export class ValidatorApi extends StandardApi {
publicKey: string
): Promise<void> {
try {
await this.request(
"POST",
path.join(
await this.request({
method: "POST",
endpoint: path.join(
this.feeRecipientEndpoint,
prefix0xPubkey(publicKey),
"feerecipient"
),
JSON.stringify({ ethaddress: newFeeRecipient })
);
body: JSON.stringify({ ethaddress: newFeeRecipient })
});
} catch (e) {
e.message += `Error setting (POST) fee recipient for pubkey ${publicKey} to ${newFeeRecipient} on validator. `;
throw e;
Expand All @@ -75,14 +75,14 @@ export class ValidatorApi extends StandardApi {
*/
public async deleteFeeRecipient(publicKey: string): Promise<void> {
try {
await this.request(
"DELETE",
path.join(
await this.request({
method: "DELETE",
endpoint: path.join(
this.feeRecipientEndpoint,
prefix0xPubkey(publicKey),
"feerecipient"
)
);
});
} catch (e) {
e.message += `Error deleting (DELETE) fee recipient for pubkey ${publicKey} from validator. `;
throw e;
Expand All @@ -95,10 +95,10 @@ export class ValidatorApi extends StandardApi {
*/
public async getRemoteKeys(): Promise<ValidatorGetRemoteKeysResponse> {
try {
return (await this.request(
"GET",
this.remoteKeymanagerEndpoint
)) as ValidatorGetRemoteKeysResponse;
return (await this.request({
method: "GET",
endpoint: this.remoteKeymanagerEndpoint
})) as ValidatorGetRemoteKeysResponse;
} catch (e) {
e.message += `Error getting (GET) remote keys from validator. `;
throw e;
Expand All @@ -116,11 +116,11 @@ export class ValidatorApi extends StandardApi {
remoteKeys.remote_keys = remoteKeys.remote_keys.map((k) => {
return { pubkey: prefix0xPubkey(k.pubkey), url: k.url };
});
return (await this.request(
"POST",
this.remoteKeymanagerEndpoint,
JSON.stringify(remoteKeys)
)) as ValidatorPostRemoteKeysResponse;
return (await this.request({
method: "POST",
endpoint: this.remoteKeymanagerEndpoint,
body: JSON.stringify(remoteKeys)
})) as ValidatorPostRemoteKeysResponse;
} catch (e) {
e.message += `Error posting (POST) remote keys to validator. `;
throw e;
Expand All @@ -137,11 +137,11 @@ export class ValidatorApi extends StandardApi {
try {
// Make sure all pubkeys are prefixed with 0x
pubkeys.pubkeys = pubkeys.pubkeys.map((k) => prefix0xPubkey(k));
return (await this.request(
"DELETE",
this.remoteKeymanagerEndpoint,
JSON.stringify(pubkeys)
)) as ValidatorDeleteRemoteKeysResponse;
return (await this.request({
method: "DELETE",
endpoint: this.remoteKeymanagerEndpoint,
body: JSON.stringify(pubkeys)
})) as ValidatorDeleteRemoteKeysResponse;
} catch (e) {
e.message += `Error deleting (DELETE) remote keys from validator. `;
throw e;
Expand Down
51 changes: 28 additions & 23 deletions packages/brain/src/modules/apiClients/web3signer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ export class Web3SignerApi extends StandardApi {
pubkey: string;
}): Promise<Web3SignerPostSignvoluntaryexitResponse> {
try {
return await this.request(
"POST",
path.join(this.signEndpoint, pubkey),
JSON.stringify(signerVoluntaryExitRequest)
);
return await this.request({
method: "POST",
endpoint: path.join(this.signEndpoint, pubkey),
body: JSON.stringify(signerVoluntaryExitRequest),
setOrigin: true,
});
} catch (e) {
e.message += `Error signing (POST) voluntary exit for validator index ${signerVoluntaryExitRequest.voluntary_exit.validator_index}. `;
throw e;
Expand All @@ -66,11 +67,12 @@ export class Web3SignerApi extends StandardApi {
): Promise<Web3signerPostResponse> {
try {
// IMPORTANT: do not edit the keystore data, it must be exactly as it was received from the remote signer
return (await this.request(
"POST",
this.localKeymanagerEndpoint,
JSON.stringify(postRequest)
)) as Web3signerPostResponse;
return (await this.request({
method: "POST",
endpoint: this.localKeymanagerEndpoint,
body: JSON.stringify(postRequest),
setOrigin: true,
})) as Web3signerPostResponse;
} catch (e) {
e.message += `Error importing (POST) keystores to remote signer. `;
throw e;
Expand All @@ -92,11 +94,12 @@ export class Web3SignerApi extends StandardApi {
const data = JSON.stringify({
pubkeys: deleteRequest.pubkeys,
});
return (await this.request(
"DELETE",
this.localKeymanagerEndpoint,
data
)) as Web3signerDeleteResponse;
return (await this.request({
method: "DELETE",
endpoint: this.localKeymanagerEndpoint,
body: data,
setOrigin: true,
})) as Web3signerDeleteResponse;
} catch (e) {
e.message += `Error deleting (DELETE) keystores from remote signer. `;
throw e;
Expand All @@ -109,10 +112,11 @@ export class Web3SignerApi extends StandardApi {
*/
public async getKeystores(): Promise<Web3signerGetResponse> {
try {
return (await this.request(
"GET",
this.localKeymanagerEndpoint
)) as Web3signerGetResponse;
return (await this.request({
method: "GET",
endpoint: this.localKeymanagerEndpoint,
setOrigin: true,
})) as Web3signerGetResponse;
} catch (e) {
e.message += `Error getting (GET) keystores from remote signer. `;
throw e;
Expand All @@ -125,10 +129,11 @@ export class Web3SignerApi extends StandardApi {
*/
public async getStatus(): Promise<Web3signerHealthcheckResponse> {
try {
return (await this.request(
"GET",
this.serverStatusEndpoint
)) as Web3signerHealthcheckResponse;
return (await this.request({
method: "GET",
endpoint: this.serverStatusEndpoint,
setOrigin: true,
})) as Web3signerHealthcheckResponse;
} catch (e) {
e.message += `Error getting (GET) server status. Is Web3Signer running? `;
throw e;
Expand Down
11 changes: 11 additions & 0 deletions packages/brain/src/modules/apiServers/launchpad/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import express from "express";
import cors from "cors";
import { tags as availableTags, Tag } from "@stakingbrain/common";
import logger from "../../logger/index.js";
import http from "node:http";
Expand All @@ -9,6 +10,16 @@ export function startLaunchpadApi(): http.Server {
const app = express();
const server = new http.Server(app);
app.use(express.json());
app.use(
cors({
origin: [
"http://rocketpool-testnet.public.dappnode", // TODO: deprecate after holesky published
"http://rocketpool.dappnode", // Mainnet
"http://stader-testnet.dappnode", // Testnet
"http://stader.dappnode", // Mainnet
],
})
);
app.post("/eth/v1/keystores", async (req, res) => {
const { keystores, passwords, slashingProtection, tags, feeRecipients } =
req.body;
Expand Down
Loading

0 comments on commit 11dce58

Please sign in to comment.