Skip to content

Commit

Permalink
feat: mithril proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
slowbackspace committed May 21, 2024
1 parent a2dc92e commit 7761607
Show file tree
Hide file tree
Showing 61 changed files with 454 additions and 179 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
'unicorn/no-process-exit': 0,
'unicorn/prefer-ternary': 0,
'unicorn/no-null': 'off',
'no-nested-ternary': 'off',
'unicorn/prevent-abbreviations': [
'error',
{
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"libc",
"milkomeda",
"mirs",
"mithril",
"mydomain",
"nutcoin",
"nutlink",
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/ret-npm-0.2.2-f5d3022812-774964bb41.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Proxy for Mithril Aggregator API
- config options `mithril.enabled`, `mithril.aggregator`, `mithril.snapshotCDN` (optional) and `mithril.allowedEndpoints` (optional)
- ENV var options `BLOCKFROST_MITHRIL_ENABLED`, `BLOCKFROST_MITHRIL_AGGREGATOR` and `BLOCKFROST_MITHRIL_SNAPSHOT_CDN`

### Changed
- Updated Fastify dependencies

## [2.0.2] - 2024-05-09

### Fixed
Expand Down
3 changes: 3 additions & 0 deletions config/development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ dbSync:
maxConnections: 10
network: "mainnet"
tokenRegistryUrl: "https://tokens.cardano.org"
mithril:
enabled: true
aggregator: "https://aggregator.release-mainnet.api.mithril.network/aggregator"
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,19 @@
"@blockfrost/blockfrost-utils": "2.8.0",
"@blockfrost/openapi": "0.1.63",
"@emurgo/cardano-serialization-lib-nodejs": "11.5.0",
"@fastify/cors": "^8.3.0",
"@fastify/postgres": "^5.2.0",
"@fastify/cors": "^9.0.1",
"@fastify/http-proxy": "^9.5.0",
"@fastify/postgres": "^5.2.2",
"@sentry/node": "^7.69.0",
"JSONStream": "^1.3.5",
"ajv": "^8.12.0",
"axios": "^1.5.0",
"config": "3.3.9",
"crc": "^4.3.2",
"cross-env": "^7.0.3",
"fastify": "4.23.2",
"fastify": "4.27.0",
"fastify-plugin": "^4.5.1",
"path-to-regexp": "^6.2.2",
"pg": "^8.11.3",
"pg-format": "^1.0.4",
"pino-pretty": "10.2.0",
Expand Down
7 changes: 7 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getConfig } from './config.js';
import { registerRoute } from './utils/common.js';
import { errorHandler, notFoundHandler } from './utils/error-handler.js';
import { createRequire } from 'module';
import { registerMithrilProxy } from './proxies/mithril.js';

const esmRequire = createRequire(import.meta.url);
const packageJson = esmRequire('../package.json');
Expand Down Expand Up @@ -58,6 +59,12 @@ const start = (options = {}): FastifyInstance => {
ssl: config.dbSync.ssl,
});

// proxies
if (config.mithril.enabled) {
console.log(`Mithril proxy enabled. Aggregator: ${config.mithril.aggregator}.`);
registerMithrilProxy(app);
}

// addresses
registerRoute(app, import('./routes/addresses/address/extended.js'));
registerRoute(app, import('./routes/addresses/address/index.js'));
Expand Down
59 changes: 57 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import config from 'config';

import { CARDANO_NETWORKS, Network } from './types/common.js';

const MITHRIL_ENDPOINT_ALLOWLIST_DEFAULT = [
'/',
'/epoch-settings',
'/certificate-pending',
'/certificates',
'/certificate/:certificate_hash',
'/artifact/snapshots',
'/artifact/snapshot/:digest',
'/artifact/snapshot/:digest/download',
'/artifact/mithril-stake-distributions',
'/artifact/cardano-transactions',
'/artifact/cardano-transaction/:hash',
'/proof/cardano-transaction',
'/signers/registered/:epoch',
'/signers/tickers',
];

export const loadConfig = () => {
// server
const listenAddress =
Expand Down Expand Up @@ -41,12 +57,44 @@ export const loadConfig = () => {
const network = process.env.BLOCKFROST_CONFIG_NETWORK ?? config.get('network');

if (!network || !CARDANO_NETWORKS.includes(network)) {
throw new Error('Invalid network in the config.');
throw new Error('Invalid network configuration.');
}
// token registry
const tokenRegistryUrl =
process.env.BLOCKFROST_CONFIG_TOKEN_REGISTRY_URL ?? config.get('tokenRegistryUrl');

// Mithril
let mithrilEnabled = config.has('mithril.enabled')
? config.get<boolean>('mithril.enabled')
: false;

let mithrilAggregator =
process.env.BLOCKFROST_MITHRIL_AGGREGATOR ?? config.has('mithril.aggregator')
? config.get<string>('mithril.aggregator')
: undefined;

const mithrilSnapshotCDN =
process.env.BLOCKFROST_MITHRIL_SNAPSHOT_CDN ?? config.has('mithril.snapshotCDN')
? config.get<string>('mithril.snapshotCDN')
: undefined;

const mithrilAllowedEndpoints = config.has('mithril.mithrilAllowedEndpoints')
? config.get<string[]>('mithril.mithrilAllowedEndpoints')
: MITHRIL_ENDPOINT_ALLOWLIST_DEFAULT;

// ENV vars override config
if (process.env.BLOCKFROST_MITHRIL_ENABLED) {
mithrilEnabled = process.env.BLOCKFROST_MITHRIL_ENABLED === 'true';
}

if (process.env.BLOCKFROST_MITHRIL_AGGREGATOR) {
mithrilAggregator = process.env.BLOCKFROST_MITHRIL_AGGREGATOR;
}

if (mithrilEnabled && !mithrilAggregator) {
throw new Error('Invalid Mithril Aggregator configuration');
}

return {
server: {
listenAddress,
Expand All @@ -65,11 +113,18 @@ export const loadConfig = () => {
},
network: network as Network,
tokenRegistryUrl,
mithril: {
enabled: mithrilEnabled,
aggregator: mithrilAggregator as string,
snapshotCDN: mithrilSnapshotCDN,
allowedEndpoints: mithrilAllowedEndpoints,
},
};
};

export const mainConfig = loadConfig();

// Use this function to load config to allow easier mocking in unit tests
export const getConfig = () => {
return mainConfig;
};
81 changes: 81 additions & 0 deletions src/proxies/mithril.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import fastifyHttpProxy from '@fastify/http-proxy';
import { FastifyInstance, FastifyReply } from 'fastify';
import { getConfig } from '../config.js';
import { pathToRegexp } from 'path-to-regexp';
import { handle404 } from '../utils/error-handler.js';

const config = getConfig();

export const matchUrlToEndpoint = (requestUrl: string, allowedEndpointPatterns: string[]) => {
for (const allowedEndpointPattern of allowedEndpointPatterns) {
const regexp = pathToRegexp(allowedEndpointPattern);
const match = requestUrl.match(regexp);

if (match) {
return true;
}
}

return false;
};

export const registerMithrilProxy = (app: FastifyInstance) => {
app.register(fastifyHttpProxy, {
upstream: config.mithril.aggregator,
prefix: '/mithril',
proxyPayloads: false,
http: {
requestOptions: {
timeout: 30_000,
},
},
preHandler: async (request, reply) => {
// strip /mithril and query params
const url = request.url.replace('/mithril', '').split('?')[0];
const allowedEndpoints = config.mithril.allowedEndpoints;

console.log('url', url);
const match = matchUrlToEndpoint(url, allowedEndpoints);

console.log('match', match);

if (!match) {
return reply.code(400).send({
error: 'Bad Request',
message: 'Invalid path. Please check https://docs.blockfrost.io/',
status_code: 400,
});
}
},
replyOptions: {
onResponse: async (_request, reply, response) => {
const isErrorResponse = reply.statusCode >= 400;

if (isErrorResponse) {
// error response returned from the proxy can originate from:
// 1) backend, 2) varnish, 3) nginx, 4) whatever
// If the error is not in blockfrost format (e.g. html errors from nginx or varnish) then return generic 500
// Otherwise forward the original response
// const errorBody = await convertStreamToString(response);
if (reply.statusCode === 404) {
return handle404(reply as FastifyReply);
} else if (reply.statusCode === 412) {
return reply.code(412).send({
error: 'Api Version mismatch',
message: 'Invalid path. Please check https://docs.blockfrost.io/',
status_code: 400,
});
} else {
return reply.code(500).send({
error: 'Internal Server Error',
message: 'Internal Server Error',
status_code: 500,
});
}
}

return reply.send(response);
},
},
});
};
Loading

0 comments on commit 7761607

Please sign in to comment.