Merge pull request #169 from drift-labs/master

Elasticache migration
This commit is contained in:
jack
2024-05-29 10:44:15 +10:00
committed by GitHub
15 changed files with 1760 additions and 340 deletions

View File

@@ -16,12 +16,12 @@ jobs:
node-version: '18.x' node-version: '18.x'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- name: Pull git modules
run: git submodule update --init --recursive
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
- name: Add specific version of sdk
run: yarn add @drift-labs/sdk@${{ github.event.client_payload.version }}
- name: Build after new dependency - name: Build after new dependency
run: yarn run build run: yarn run build

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "drift-common"]
path = drift-common
url = https://github.com/drift-labs/drift-common

View File

@@ -5,7 +5,15 @@ RUN npm install -g yarn
RUN npm install -g typescript RUN npm install -g typescript
WORKDIR /app WORKDIR /app
COPY drift-common /app/drift-common
COPY . . COPY . .
WORKDIR /app/drift-common/protocol/sdk
RUN yarn
RUN yarn build
WORKDIR /app/drift-common/common-ts
RUN yarn
RUN yarn build
WORKDIR /app
RUN yarn RUN yarn
RUN yarn build RUN yarn build

1
drift-common Submodule

Submodule drift-common added at 32e6ba9a38

View File

@@ -6,7 +6,8 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@coral-xyz/anchor": "^0.29.0", "@coral-xyz/anchor": "^0.29.0",
"@drift-labs/sdk": "2.83.0-beta.2", "@drift/common": "file:./drift-common/common-ts",
"@drift-labs/sdk": "file:./drift-common/protocol/sdk",
"@opentelemetry/api": "^1.1.0", "@opentelemetry/api": "^1.1.0",
"@opentelemetry/auto-instrumentations-node": "^0.31.1", "@opentelemetry/auto-instrumentations-node": "^0.31.1",
"@opentelemetry/exporter-prometheus": "^0.31.0", "@opentelemetry/exporter-prometheus": "^0.31.0",

View File

@@ -9,7 +9,8 @@ import {
groupL2, groupL2,
isVariant, isVariant,
} from '@drift-labs/sdk'; } from '@drift-labs/sdk';
import { RedisClient } from '../utils/redisClient'; import { RedisClient } from '@drift/common';
import { import {
SubscriberLookup, SubscriberLookup,
addMarketSlotToResponse, addMarketSlotToResponse,
@@ -136,6 +137,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
} }
getL2AndSendMsg(marketArgs: wsMarketArgs): void { getL2AndSendMsg(marketArgs: wsMarketArgs): void {
const clientPrefix = this.redisClient.forceGetClient().options.keyPrefix;
const grouping = marketArgs.grouping; const grouping = marketArgs.grouping;
const { marketName, ...l2FuncArgs } = marketArgs; const { marketName, ...l2FuncArgs } = marketArgs;
const l2 = this.getL2(l2FuncArgs); const l2 = this.getL2(l2FuncArgs);
@@ -261,25 +263,25 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
asks: l2Formatted.asks.slice(0, 5), asks: l2Formatted.asks.slice(0, 5),
}); });
this.redisClient.client.publish( this.redisClient.publish(
`orderbook_${marketType}_${marketArgs.marketIndex}`, `${clientPrefix}orderbook_${marketType}_${marketArgs.marketIndex}`,
JSON.stringify(l2Formatted) l2Formatted
); );
this.redisClient.client.set( this.redisClient.set(
`last_update_orderbook_${marketType}_${marketArgs.marketIndex}`, `last_update_orderbook_${marketType}_${marketArgs.marketIndex}`,
JSON.stringify(l2Formatted_depth100) l2Formatted_depth100
); );
this.redisClient.client.set( this.redisClient.set(
`last_update_orderbook_${marketType}_${marketArgs.marketIndex}_depth_100`, `last_update_orderbook_${marketType}_${marketArgs.marketIndex}_depth_100`,
JSON.stringify(l2Formatted_depth100) l2Formatted_depth100
); );
this.redisClient.client.set( this.redisClient.set(
`last_update_orderbook_${marketType}_${marketArgs.marketIndex}_depth_20`, `last_update_orderbook_${marketType}_${marketArgs.marketIndex}_depth_20`,
JSON.stringify(l2Formatted_depth20) l2Formatted_depth20
); );
this.redisClient.client.set( this.redisClient.set(
`last_update_orderbook_${marketType}_${marketArgs.marketIndex}_depth_5`, `last_update_orderbook_${marketType}_${marketArgs.marketIndex}_depth_5`,
JSON.stringify(l2Formatted_depth5) l2Formatted_depth5
); );
const oraclePriceData = const oraclePriceData =
@@ -306,9 +308,9 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
numMakers: 4, numMakers: 4,
}) })
.map((x) => x.toString()); .map((x) => x.toString());
this.redisClient.client.set( this.redisClient.set(
`last_update_orderbook_best_makers_${marketType}_${marketArgs.marketIndex}`, `last_update_orderbook_best_makers_${marketType}_${marketArgs.marketIndex}`,
JSON.stringify({ bids, asks, slot }) { bids, asks, slot }
); );
} }
@@ -361,9 +363,9 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
marketArgs.marketIndex marketArgs.marketIndex
); );
this.redisClient.client.set( this.redisClient.set(
`last_update_orderbook_l3_${marketType}_${marketArgs.marketIndex}`, `last_update_orderbook_l3_${marketType}_${marketArgs.marketIndex}`,
JSON.stringify(l3) l3
); );
} }
} }

View File

@@ -21,6 +21,7 @@ import {
PhoenixSubscriber, PhoenixSubscriber,
BulkAccountLoader, BulkAccountLoader,
} from '@drift-labs/sdk'; } from '@drift-labs/sdk';
import { RedisClient, RedisClientPrefix } from '@drift/common';
import { logger, setLogLevel } from './utils/logger'; import { logger, setLogLevel } from './utils/logger';
@@ -43,10 +44,11 @@ import {
normalizeBatchQueryParams, normalizeBatchQueryParams,
sleep, sleep,
validateDlobQuery, validateDlobQuery,
getAccountFromId,
getRawAccountFromId,
} from './utils/utils'; } from './utils/utils';
import FEATURE_FLAGS from './utils/featureFlags'; import FEATURE_FLAGS from './utils/featureFlags';
import { getDLOBProviderFromOrderSubscriber } from './dlobProvider'; import { getDLOBProviderFromOrderSubscriber } from './dlobProvider';
import { RedisClient } from './utils/redisClient';
import { setGlobalDispatcher, Agent } from 'undici'; import { setGlobalDispatcher, Agent } from 'undici';
setGlobalDispatcher( setGlobalDispatcher(
@@ -58,35 +60,8 @@ setGlobalDispatcher(
require('dotenv').config(); require('dotenv').config();
// Reading in Redis env vars // Reading in Redis env vars
const REDIS_HOSTS = process.env.REDIS_HOSTS?.replace(/^\[|\]$/g, '') const REDIS_CLIENTS = [RedisClientPrefix.DLOB, RedisClientPrefix.DLOB_HELIUS];
.split(',') console.log('Redis Clients:', REDIS_CLIENTS);
.map((host) => host.trim()) || ['localhost'];
const REDIS_PORTS = process.env.REDIS_PORTS?.replace(/^\[|\]$/g, '')
.split(',')
.map((port) => parseInt(port.trim(), 10)) || [6379];
const REDIS_PASSWORDS_ENV = process.env.REDIS_PASSWORDS || "['']";
let REDIS_PASSWORDS;
if (REDIS_PASSWORDS_ENV.trim() === "['']") {
REDIS_PASSWORDS = [undefined];
} else {
REDIS_PASSWORDS = REDIS_PASSWORDS_ENV.replace(/^\[|\]$/g, '')
.split(/\s*,\s*/)
.map((pwd) => pwd.replace(/(^'|'$)/g, '').trim())
.map((pwd) => (pwd === '' ? undefined : pwd));
}
console.log('Redis Hosts:', REDIS_HOSTS);
console.log('Redis Ports:', REDIS_PORTS);
console.log('Redis Passwords:', REDIS_PASSWORDS);
if (
REDIS_PORTS.length !== REDIS_PASSWORDS.length ||
REDIS_PORTS.length !== REDIS_HOSTS.length
) {
throw 'REDIS_HOSTS and REDIS_PASSWORDS and REDIS_PORTS must be the same length';
}
const driftEnv = (process.env.ENV || 'devnet') as DriftEnv; const driftEnv = (process.env.ENV || 'devnet') as DriftEnv;
const commitHash = process.env.COMMIT; const commitHash = process.env.COMMIT;
@@ -180,7 +155,7 @@ const initializeAllMarketSubscribers = async (driftClient: DriftClient) => {
const bulkAccountLoader = new BulkAccountLoader( const bulkAccountLoader = new BulkAccountLoader(
driftClient.connection, driftClient.connection,
stateCommitment, stateCommitment,
2_000 5_000
); );
const phoenixSubscriber = new PhoenixSubscriber({ const phoenixSubscriber = new PhoenixSubscriber({
connection: driftClient.connection, connection: driftClient.connection,
@@ -333,16 +308,10 @@ const main = async (): Promise<void> => {
> = new Map(); > = new Map();
if (useRedis) { if (useRedis) {
logger.info('Connecting to redis'); logger.info('Connecting to redis');
for (let i = 0; i < REDIS_HOSTS.length; i++) { for (let i = 0; i < REDIS_CLIENTS.length; i++) {
redisClients.push( redisClients.push(new RedisClient({ prefix: REDIS_CLIENTS[i] }));
new RedisClient(
REDIS_HOSTS[i],
REDIS_PORTS[i].toString(),
REDIS_PASSWORDS[i] || undefined
)
);
await redisClients[i].connect();
} }
for (let i = 0; i < sdkConfig.SPOT_MARKETS.length; i++) { for (let i = 0; i < sdkConfig.SPOT_MARKETS.length; i++) {
spotMarketRedisMap.set(sdkConfig.SPOT_MARKETS[i].marketIndex, { spotMarketRedisMap.set(sdkConfig.SPOT_MARKETS[i].marketIndex, {
client: redisClients[0], client: redisClients[0],
@@ -361,6 +330,8 @@ const main = async (): Promise<void> => {
} }
} }
const userMapClient = new RedisClient({ prefix: RedisClientPrefix.USER_MAP });
function canRotate(marketType: MarketType, marketIndex: number) { function canRotate(marketType: MarketType, marketIndex: number) {
if (isVariant(marketType, 'spot')) { if (isVariant(marketType, 'spot')) {
const state = spotMarketRedisMap.get(marketIndex); const state = spotMarketRedisMap.get(marketIndex);
@@ -444,15 +415,22 @@ const main = async (): Promise<void> => {
try { try {
const { marketIndex, marketType } = req.query; const { marketIndex, marketType } = req.query;
const fees = await redisClients[ const fees = await redisClients
parseInt(process.env.HELIUS_REDIS_HOST_INDEX) ?? 0 .find((client) => {
].client.get(`priorityFees_${marketType}_${marketIndex}`); return (
client.forceGetClient().options.keyPrefix ===
RedisClientPrefix.DLOB_HELIUS
);
})
.getRaw(`priorityFees_${marketType}_${marketIndex}`);
if (fees) { if (fees) {
res.status(200).json({ res.status(200).json({
...JSON.parse(fees), ...JSON.parse(fees),
marketType, marketType,
marketIndex, marketIndex,
}); });
return; return;
} else { } else {
res.writeHead(404); res.writeHead(404);
@@ -483,11 +461,16 @@ const main = async (): Promise<void> => {
const fees = await Promise.all( const fees = await Promise.all(
normedParams.map(async (normedParam) => { normedParams.map(async (normedParam) => {
const fees = await redisClients[ const fees = await redisClients
parseInt(process.env.HELIUS_REDIS_HOST_INDEX) ?? 0 .find(
].client.get( (client) =>
client.forceGetClient().options.keyPrefix ===
RedisClientPrefix.DLOB_HELIUS
)
.getRaw(
`priorityFees_${normedParam['marketType']}_${normedParam['marketIndex']}` `priorityFees_${normedParam['marketType']}_${normedParam['marketIndex']}`
); );
return { return {
...JSON.parse(fees), ...JSON.parse(fees),
marketType: normedParam['marketType'], marketType: normedParam['marketType'],
@@ -516,6 +499,7 @@ const main = async (): Promise<void> => {
marketType, marketType,
side, // bid or ask side, // bid or ask
limit, limit,
includeAccounts,
} = req.query; } = req.query;
const { normedMarketType, normedMarketIndex, error } = validateDlobQuery( const { normedMarketType, normedMarketIndex, error } = validateDlobQuery(
@@ -550,12 +534,17 @@ const main = async (): Promise<void> => {
normedLimit = 4; normedLimit = 4;
} }
let accountFlag = false;
if (includeAccounts) {
accountFlag = (includeAccounts as string).toLowerCase() === 'true';
}
let topMakers: string[]; let topMakers: string[];
if (useRedis) { if (useRedis) {
const redisClient = isVariant(normedMarketType, 'perp') const redisClient = isVariant(normedMarketType, 'perp')
? perpMarketRedisMap.get(normedMarketIndex).client ? perpMarketRedisMap.get(normedMarketIndex).client
: spotMarketRedisMap.get(normedMarketIndex).client; : spotMarketRedisMap.get(normedMarketIndex).client;
const redisResponse = await redisClient.client.get( const redisResponse = await redisClient.getRaw(
`last_update_orderbook_best_makers_${getVariant( `last_update_orderbook_best_makers_${getVariant(
normedMarketType normedMarketType
)}_${marketIndex}` )}_${marketIndex}`
@@ -582,6 +571,16 @@ const main = async (): Promise<void> => {
path: req.baseUrl + req.path, path: req.baseUrl + req.path,
}); });
res.writeHead(200); res.writeHead(200);
if (accountFlag) {
const topAccounts = await getRawAccountFromId(
userMapClient,
topMakers
);
res.end(JSON.stringify(topAccounts));
return;
}
res.end(JSON.stringify(topMakers)); res.end(JSON.stringify(topMakers));
return; return;
} }
@@ -636,6 +635,13 @@ const main = async (): Promise<void> => {
path: req.baseUrl + req.path, path: req.baseUrl + req.path,
}); });
res.writeHead(200); res.writeHead(200);
if (accountFlag) {
const topAccounts = await getAccountFromId(userMapClient, topMakers);
res.end(JSON.stringify(topAccounts));
return;
}
res.end(JSON.stringify(topMakers)); res.end(JSON.stringify(topMakers));
} catch (err) { } catch (err) {
next(err); next(err);
@@ -678,15 +684,15 @@ const main = async (): Promise<void> => {
let redisL2: string; let redisL2: string;
const redisClient = perpMarketRedisMap.get(normedMarketIndex).client; const redisClient = perpMarketRedisMap.get(normedMarketIndex).client;
if (parseInt(adjustedDepth as string) === 5) { if (parseInt(adjustedDepth as string) === 5) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_5` `last_update_orderbook_perp_${normedMarketIndex}_depth_5`
); );
} else if (parseInt(adjustedDepth as string) === 20) { } else if (parseInt(adjustedDepth as string) === 20) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_20` `last_update_orderbook_perp_${normedMarketIndex}_depth_20`
); );
} else if (parseInt(adjustedDepth as string) === 100) { } else if (parseInt(adjustedDepth as string) === 100) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_100` `last_update_orderbook_perp_${normedMarketIndex}_depth_100`
); );
} }
@@ -711,15 +717,15 @@ const main = async (): Promise<void> => {
let redisL2: string; let redisL2: string;
const redisClient = spotMarketRedisMap.get(normedMarketIndex).client; const redisClient = spotMarketRedisMap.get(normedMarketIndex).client;
if (parseInt(adjustedDepth as string) === 5) { if (parseInt(adjustedDepth as string) === 5) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_5` `last_update_orderbook_spot_${normedMarketIndex}_depth_5`
); );
} else if (parseInt(adjustedDepth as string) === 20) { } else if (parseInt(adjustedDepth as string) === 20) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_20` `last_update_orderbook_spot_${normedMarketIndex}_depth_20`
); );
} else if (parseInt(adjustedDepth as string) === 100) { } else if (parseInt(adjustedDepth as string) === 100) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_100` `last_update_orderbook_spot_${normedMarketIndex}_depth_100`
); );
} }
@@ -851,15 +857,15 @@ const main = async (): Promise<void> => {
const redisClient = const redisClient =
perpMarketRedisMap.get(normedMarketIndex).client; perpMarketRedisMap.get(normedMarketIndex).client;
if (parseInt(adjustedDepth as string) === 5) { if (parseInt(adjustedDepth as string) === 5) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_5` `last_update_orderbook_perp_${normedMarketIndex}_depth_5`
); );
} else if (parseInt(adjustedDepth as string) === 20) { } else if (parseInt(adjustedDepth as string) === 20) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_20` `last_update_orderbook_perp_${normedMarketIndex}_depth_20`
); );
} else if (parseInt(adjustedDepth as string) === 100) { } else if (parseInt(adjustedDepth as string) === 100) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_100` `last_update_orderbook_perp_${normedMarketIndex}_depth_100`
); );
} }
@@ -887,15 +893,15 @@ const main = async (): Promise<void> => {
const redisClient = const redisClient =
spotMarketRedisMap.get(normedMarketIndex).client; spotMarketRedisMap.get(normedMarketIndex).client;
if (parseInt(adjustedDepth as string) === 5) { if (parseInt(adjustedDepth as string) === 5) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_5` `last_update_orderbook_spot_${normedMarketIndex}_depth_5`
); );
} else if (parseInt(adjustedDepth as string) === 20) { } else if (parseInt(adjustedDepth as string) === 20) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_20` `last_update_orderbook_spot_${normedMarketIndex}_depth_20`
); );
} else if (parseInt(adjustedDepth as string) === 100) { } else if (parseInt(adjustedDepth as string) === 100) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_100` `last_update_orderbook_spot_${normedMarketIndex}_depth_100`
); );
} }
@@ -993,7 +999,7 @@ const main = async (): Promise<void> => {
const redisClient = ( const redisClient = (
marketTypeStr === 'spot' ? spotMarketRedisMap : perpMarketRedisMap marketTypeStr === 'spot' ? spotMarketRedisMap : perpMarketRedisMap
).get(normedMarketIndex).client; ).get(normedMarketIndex).client;
const redisL3 = await redisClient.client.get( const redisL3 = await redisClient.getRaw(
`last_update_orderbook_l3_${marketTypeStr}_${normedMarketIndex}` `last_update_orderbook_l3_${marketTypeStr}_${normedMarketIndex}`
); );
if ( if (

View File

@@ -19,6 +19,7 @@ import {
SpotMarketConfig, SpotMarketConfig,
PhoenixSubscriber, PhoenixSubscriber,
} from '@drift-labs/sdk'; } from '@drift-labs/sdk';
import { RedisClient, RedisClientPrefix } from '@drift/common';
import { logger, setLogLevel } from '../utils/logger'; import { logger, setLogLevel } from '../utils/logger';
import { import {
@@ -31,7 +32,6 @@ import {
DLOBSubscriberIO, DLOBSubscriberIO,
wsMarketInfo, wsMarketInfo,
} from '../dlob-subscriber/DLOBSubscriberIO'; } from '../dlob-subscriber/DLOBSubscriberIO';
import { RedisClient } from '../utils/redisClient';
import { import {
DLOBProvider, DLOBProvider,
getDLOBProviderFromGrpcOrderSubscriber, getDLOBProviderFromGrpcOrderSubscriber,
@@ -54,9 +54,8 @@ require('dotenv').config();
const stateCommitment: Commitment = 'confirmed'; const stateCommitment: Commitment = 'confirmed';
const driftEnv = (process.env.ENV || 'devnet') as DriftEnv; const driftEnv = (process.env.ENV || 'devnet') as DriftEnv;
const commitHash = process.env.COMMIT; const commitHash = process.env.COMMIT;
const REDIS_HOST = process.env.REDIS_HOST || 'localhost';
const REDIS_PORT = process.env.REDIS_PORT || '6379'; const REDIS_CLIENT = process.env.REDIS_CLIENT || 'DLOB';
const REDIS_PASSWORD = process.env.REDIS_PASSWORD;
// Set up express for health checks // Set up express for health checks
const app = express(); const app = express();
@@ -275,6 +274,11 @@ const main = async () => {
const wallet = new Wallet(new Keypair()); const wallet = new Wallet(new Keypair());
const clearingHousePublicKey = new PublicKey(sdkConfig.DRIFT_PROGRAM_ID); const clearingHousePublicKey = new PublicKey(sdkConfig.DRIFT_PROGRAM_ID);
const redisClient = new RedisClient({
prefix: RedisClientPrefix[REDIS_CLIENT],
});
await redisClient.connect();
const connection = new Connection(endpoint, { const connection = new Connection(endpoint, {
wsEndpoint: wsEndpoint, wsEndpoint: wsEndpoint,
commitment: stateCommitment, commitment: stateCommitment,
@@ -407,9 +411,6 @@ const main = async () => {
await dlobProvider.subscribe(); await dlobProvider.subscribe();
const redisClient = new RedisClient(REDIS_HOST, REDIS_PORT, REDIS_PASSWORD);
await redisClient.connect();
const dlobSubscriber = new DLOBSubscriberIO({ const dlobSubscriber = new DLOBSubscriberIO({
driftClient, driftClient,
env: driftEnv, env: driftEnv,

View File

@@ -10,12 +10,12 @@ import {
BulkAccountLoader, BulkAccountLoader,
getMarketsAndOraclesForSubscription, getMarketsAndOraclesForSubscription,
} from '@drift-labs/sdk'; } from '@drift-labs/sdk';
import { RedisClient, RedisClientPrefix } from '@drift/common';
import { logger, setLogLevel } from '../utils/logger'; import { logger, setLogLevel } from '../utils/logger';
import { sleep } from '../utils/utils'; import { sleep } from '../utils/utils';
import express from 'express'; import express from 'express';
// import { handleHealthCheck } from '../core/metrics'; // import { handleHealthCheck } from '../core/metrics';
import { RedisClient } from '../utils/redisClient';
import { setGlobalDispatcher, Agent } from 'undici'; import { setGlobalDispatcher, Agent } from 'undici';
setGlobalDispatcher( setGlobalDispatcher(
@@ -28,10 +28,7 @@ require('dotenv').config();
const stateCommitment: Commitment = 'confirmed'; const stateCommitment: Commitment = 'confirmed';
const driftEnv = (process.env.ENV || 'devnet') as DriftEnv; const driftEnv = (process.env.ENV || 'devnet') as DriftEnv;
const commitHash = process.env.COMMIT; const commitHash = process.env.COMMIT;
const REDIS_HOST = process.env.REDIS_HOST || 'localhost'; const redisClientPrefix = RedisClientPrefix.DLOB_HELIUS;
const REDIS_PORT = process.env.REDIS_PORT || '6379';
const REDIS_PASSWORD = process.env.REDIS_PASSWORD;
// Set up express for health checks // Set up express for health checks
const app = express(); const app = express();
@@ -144,25 +141,25 @@ class PriorityFeeSubscriber {
dataPerp.forEach((result: any) => { dataPerp.forEach((result: any) => {
const marketIndex = parseInt(result['id']); const marketIndex = parseInt(result['id']);
this.redisClient.client.publish( this.redisClient.publish(
`priorityFees_perp_${marketIndex}`, `${redisClientPrefix}priorityFees_perp_${marketIndex}`,
JSON.stringify(result.result['priorityFeeLevels']) result.result['priorityFeeLevels']
); );
this.redisClient.client.set( this.redisClient.set(
`priorityFees_perp_${marketIndex}`, `priorityFees_perp_${marketIndex}`,
JSON.stringify(result.result['priorityFeeLevels']) result.result['priorityFeeLevels']
); );
}); });
dataSpot.forEach((result: any) => { dataSpot.forEach((result: any) => {
const marketIndex = parseInt(result['id']) - 100; const marketIndex = parseInt(result['id']) - 100;
this.redisClient.client.publish( this.redisClient.publish(
`priorityFees_spot_${marketIndex}`, `${redisClientPrefix}priorityFees_spot_${marketIndex}`,
JSON.stringify(result.result['priorityFeeLevels']) result.result['priorityFeeLevels']
); );
this.redisClient.client.set( this.redisClient.set(
`priorityFees_spot_${marketIndex}`, `priorityFees_spot_${marketIndex}`,
JSON.stringify(result.result['priorityFeeLevels']) result.result['priorityFeeLevels']
); );
}); });
} }
@@ -174,6 +171,11 @@ const main = async () => {
commitment: stateCommitment, commitment: stateCommitment,
}); });
const redisClient = new RedisClient({
prefix: redisClientPrefix,
});
await redisClient.connect();
const { perpMarketIndexes, spotMarketIndexes, oracleInfos } = const { perpMarketIndexes, spotMarketIndexes, oracleInfos } =
getMarketsAndOraclesForSubscription(sdkConfig.ENV); getMarketsAndOraclesForSubscription(sdkConfig.ENV);
@@ -216,9 +218,6 @@ const main = async () => {
}); });
} }
const redisClient = new RedisClient(REDIS_HOST, REDIS_PORT, REDIS_PASSWORD);
await redisClient.connect();
const priorityFeeSubscriber = new PriorityFeeSubscriber({ const priorityFeeSubscriber = new PriorityFeeSubscriber({
endpoint, endpoint,
perpMarketPubkeys, perpMarketPubkeys,

View File

@@ -20,10 +20,10 @@ import {
OrderActionRecord, OrderActionRecord,
Event, Event,
} from '@drift-labs/sdk'; } from '@drift-labs/sdk';
import { RedisClient, RedisClientPrefix } from '@drift/common';
import { logger, setLogLevel } from '../utils/logger'; import { logger, setLogLevel } from '../utils/logger';
import { sleep } from '../utils/utils'; import { sleep } from '../utils/utils';
import { RedisClient } from '../utils/redisClient';
import { fromEvent, filter, map } from 'rxjs'; import { fromEvent, filter, map } from 'rxjs';
import { setGlobalDispatcher, Agent } from 'undici'; import { setGlobalDispatcher, Agent } from 'undici';
@@ -36,10 +36,7 @@ setGlobalDispatcher(
require('dotenv').config(); require('dotenv').config();
const driftEnv = (process.env.ENV || 'devnet') as DriftEnv; const driftEnv = (process.env.ENV || 'devnet') as DriftEnv;
const commitHash = process.env.COMMIT; const commitHash = process.env.COMMIT;
const REDIS_HOST = process.env.REDIS_HOST || 'localhost'; const redisClientPrefix = RedisClientPrefix.DLOB;
const REDIS_PORT = process.env.REDIS_PORT || '6379';
const REDIS_PASSWORD = process.env.REDIS_PASSWORD;
//@ts-ignore //@ts-ignore
const sdkConfig = initialize({ env: process.env.ENV }); const sdkConfig = initialize({ env: process.env.ENV });
@@ -77,6 +74,9 @@ const main = async () => {
env: driftEnv, env: driftEnv,
}); });
const redisClient = new RedisClient({ prefix: redisClientPrefix });
await redisClient.connect();
const slotSubscriber = new SlotSubscriber(connection, {}); const slotSubscriber = new SlotSubscriber(connection, {});
const lamportsBalance = await connection.getBalance(wallet.publicKey); const lamportsBalance = await connection.getBalance(wallet.publicKey);
@@ -93,9 +93,6 @@ const main = async () => {
await slotSubscriber.subscribe(); await slotSubscriber.subscribe();
const redisClient = new RedisClient(REDIS_HOST, REDIS_PORT, REDIS_PASSWORD);
await redisClient.connect();
const eventSubscriber = new EventSubscriber(connection, driftClient.program, { const eventSubscriber = new EventSubscriber(connection, driftClient.program, {
maxTx: 8192, maxTx: 8192,
maxEventsPerType: 4096, maxEventsPerType: 4096,
@@ -189,9 +186,9 @@ const main = async () => {
}) })
) )
.subscribe((fillEvent) => { .subscribe((fillEvent) => {
redisClient.client.publish( redisClient.publish(
`trades_${fillEvent.marketType}_${fillEvent.marketIndex}`, `${redisClientPrefix}trades_${fillEvent.marketType}_${fillEvent.marketIndex}`,
JSON.stringify(fillEvent) fillEvent
); );
}); });

View File

@@ -16,7 +16,6 @@ import {
} from './core/metrics'; } from './core/metrics';
import { handleResponseTime } from './core/middleware'; import { handleResponseTime } from './core/middleware';
import { errorHandler, normalizeBatchQueryParams, sleep } from './utils/utils'; import { errorHandler, normalizeBatchQueryParams, sleep } from './utils/utils';
import { RedisClient } from './utils/redisClient';
import { import {
DriftEnv, DriftEnv,
MarketType, MarketType,
@@ -26,36 +25,13 @@ import {
isVariant, isVariant,
initialize, initialize,
} from '@drift-labs/sdk'; } from '@drift-labs/sdk';
import { RedisClient, RedisClientPrefix } from '@drift/common';
require('dotenv').config(); require('dotenv').config();
// Reading in Redis env vars // Reading in Redis env vars
const REDIS_HOSTS_ENV = (process.env.REDIS_HOSTS as string) || 'localhost'; const REDIS_CLIENTS = [RedisClientPrefix.DLOB, RedisClientPrefix.DLOB_HELIUS];
const REDIS_HOSTS = REDIS_HOSTS_ENV.includes(',') console.log('Redis Clients:', REDIS_CLIENTS);
? REDIS_HOSTS_ENV.trim()
.replace(/^\[|\]$/g, '')
.split(/\s*,\s*/)
: [REDIS_HOSTS_ENV];
const REDIS_PASSWORDS_ENV = (process.env.REDIS_PASSWORDS as string) || '';
const REDIS_PASSWORDS = REDIS_PASSWORDS_ENV.includes(',')
? REDIS_PASSWORDS_ENV.trim()
.replace(/^\[|\]$/g, '')
.split(/\s*,\s*/)
: [REDIS_PASSWORDS_ENV];
const REDIS_PORTS_ENV = (process.env.REDIS_PORTS as string) || '6379';
const REDIS_PORTS = REDIS_PORTS_ENV.includes(',')
? REDIS_PORTS_ENV.trim()
.replace(/^\[|\]$/g, '')
.split(/\s*,\s*/)
: [REDIS_PORTS_ENV];
if (
REDIS_PORTS.length !== REDIS_PASSWORDS.length ||
REDIS_PORTS.length !== REDIS_HOSTS.length
) {
throw 'REDIS_HOSTS and REDIS_PASSWORDS and REDIS_PORTS must be the same length';
}
const driftEnv = (process.env.ENV || 'devnet') as DriftEnv; const driftEnv = (process.env.ENV || 'devnet') as DriftEnv;
const commitHash = process.env.COMMIT; const commitHash = process.env.COMMIT;
@@ -145,14 +121,9 @@ const main = async () => {
number, number,
{ client: RedisClient; clientIndex: number } { client: RedisClient; clientIndex: number }
> = new Map(); > = new Map();
for (let i = 0; i < REDIS_HOSTS.length; i++) { for (let i = 0; i < REDIS_CLIENTS.length; i++) {
redisClients.push( const prefix = RedisClientPrefix[REDIS_CLIENTS[i]];
new RedisClient( redisClients.push(new RedisClient({ prefix }));
REDIS_HOSTS[i],
REDIS_PORTS[i],
REDIS_PASSWORDS[i] || undefined
)
);
await redisClients[i].connect(); await redisClients[i].connect();
} }
for (let i = 0; i < sdkConfig.SPOT_MARKETS.length; i++) { for (let i = 0; i < sdkConfig.SPOT_MARKETS.length; i++) {
@@ -245,15 +216,15 @@ const main = async () => {
let redisL2: string; let redisL2: string;
const redisClient = perpMarketRedisMap.get(normedMarketIndex).client; const redisClient = perpMarketRedisMap.get(normedMarketIndex).client;
if (parseInt(adjustedDepth as string) === 5) { if (parseInt(adjustedDepth as string) === 5) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_5` `last_update_orderbook_perp_${normedMarketIndex}_depth_5`
); );
} else if (parseInt(adjustedDepth as string) === 20) { } else if (parseInt(adjustedDepth as string) === 20) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_20` `last_update_orderbook_perp_${normedMarketIndex}_depth_20`
); );
} else if (parseInt(adjustedDepth as string) === 100) { } else if (parseInt(adjustedDepth as string) === 100) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_100` `last_update_orderbook_perp_${normedMarketIndex}_depth_100`
); );
} }
@@ -287,15 +258,15 @@ const main = async () => {
let redisL2: string; let redisL2: string;
const redisClient = spotMarketRedisMap.get(normedMarketIndex).client; const redisClient = spotMarketRedisMap.get(normedMarketIndex).client;
if (parseInt(adjustedDepth as string) === 5) { if (parseInt(adjustedDepth as string) === 5) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_5` `last_update_orderbook_spot_${normedMarketIndex}_depth_5`
); );
} else if (parseInt(adjustedDepth as string) === 20) { } else if (parseInt(adjustedDepth as string) === 20) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_20` `last_update_orderbook_spot_${normedMarketIndex}_depth_20`
); );
} else if (parseInt(adjustedDepth as string) === 100) { } else if (parseInt(adjustedDepth as string) === 100) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_100` `last_update_orderbook_spot_${normedMarketIndex}_depth_100`
); );
} }
@@ -410,15 +381,15 @@ const main = async () => {
const redisClient = const redisClient =
perpMarketRedisMap.get(normedMarketIndex).client; perpMarketRedisMap.get(normedMarketIndex).client;
if (parseInt(adjustedDepth as string) === 5) { if (parseInt(adjustedDepth as string) === 5) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_5` `last_update_orderbook_perp_${normedMarketIndex}_depth_5`
); );
} else if (parseInt(adjustedDepth as string) === 20) { } else if (parseInt(adjustedDepth as string) === 20) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_20` `last_update_orderbook_perp_${normedMarketIndex}_depth_20`
); );
} else if (parseInt(adjustedDepth as string) === 100) { } else if (parseInt(adjustedDepth as string) === 100) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_perp_${normedMarketIndex}_depth_100` `last_update_orderbook_perp_${normedMarketIndex}_depth_100`
); );
} }
@@ -455,15 +426,15 @@ const main = async () => {
const redisClient = const redisClient =
spotMarketRedisMap.get(normedMarketIndex).client; spotMarketRedisMap.get(normedMarketIndex).client;
if (parseInt(adjustedDepth as string) === 5) { if (parseInt(adjustedDepth as string) === 5) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_5` `last_update_orderbook_spot_${normedMarketIndex}_depth_5`
); );
} else if (parseInt(adjustedDepth as string) === 20) { } else if (parseInt(adjustedDepth as string) === 20) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_20` `last_update_orderbook_spot_${normedMarketIndex}_depth_20`
); );
} else if (parseInt(adjustedDepth as string) === 100) { } else if (parseInt(adjustedDepth as string) === 100) {
redisL2 = await redisClient.client.get( redisL2 = await redisClient.getRaw(
`last_update_orderbook_spot_${normedMarketIndex}_depth_100` `last_update_orderbook_spot_${normedMarketIndex}_depth_100`
); );
} }

View File

@@ -1,124 +0,0 @@
import Redis, { RedisOptions } from 'ioredis';
import { sleep } from './utils';
export const getRedisClient = (
host?: string,
port?: string,
password?: string,
db?: number,
opts?: RedisOptions
): Redis => {
if (host && port) {
console.log(`Connecting to configured redis:: ${host}:${port}`);
const redisClient = new Redis({
host: host,
port: parseInt(port, 10),
password: password,
...(opts ?? {}),
retryStrategy: (times) => {
const delay = Math.min(times * 1000, 10000);
console.log(
`Reconnecting to Redis in ${delay}ms... (retries: ${times})`
);
return delay;
},
reconnectOnError: (err) => {
const targetError = 'ECONNREFUSED';
if (err.message.includes(targetError)) {
console.log(
`Redis error: ${targetError}. Attempting to reconnect...`
);
return true;
}
return false;
},
maxRetriesPerRequest: null, // unlimited retries
});
redisClient.on('connect', () => {
console.log('Connected to Redis.');
});
redisClient.on('error', (err) => {
console.error('Redis error:', err);
});
redisClient.on('reconnecting', () => {
console.log('Reconnecting to Redis...');
});
return redisClient;
}
console.log(`Using default redis`);
return new Redis({ db });
};
/**
* Wrapper around the redis client.
*
* You can hover over the underlying redis client methods for explanations of the methods, but will also include links to DOCS for some important concepts below:
*
* zRange, zRangeByScore etc.:
* - All of the "z" methods are methods that use sorted sets.
* - Sorted sets are explained here : https://redis.io/docs/data-types/sorted-sets/
*/
export class RedisClient {
public client: Redis;
connectionPromise: Promise<void>;
constructor(
host?: string,
port?: string,
password?: string,
db?: number,
opts?: RedisOptions
) {
this.client = getRedisClient(host, port, password, db, opts);
}
/**
* Should avoid using this unless necessary.
* @returns
*/
public forceGetClient() {
return this.client;
}
public get connected() {
return this?.client?.status === 'ready';
}
async connect() {
if (this.client.status === 'ready' || this.client.status === 'connect') {
return;
}
if (this.client.status === 'connecting') {
await sleep(100);
return this.connect(); // recursive call to check again
}
try {
await this.client.connect();
} catch (e) {
console.error(e);
}
}
disconnect() {
this.client.disconnect();
}
private assertConnected() {
if (!this.connected) {
throw 'Redis client not connected';
}
}
private getPrefixForMetrics(key: string) {
return key.split(':')?.[0];
}
}

View File

@@ -11,8 +11,11 @@ import {
SerumSubscriber, SerumSubscriber,
SpotMarketConfig, SpotMarketConfig,
SpotMarkets, SpotMarkets,
decodeUser,
isVariant, isVariant,
} from '@drift-labs/sdk'; } from '@drift-labs/sdk';
import { RedisClient } from '@drift/common';
import { logger } from './logger'; import { logger } from './logger';
import { NextFunction, Request, Response } from 'express'; import { NextFunction, Request, Response } from 'express';
import FEATURE_FLAGS from './featureFlags'; import FEATURE_FLAGS from './featureFlags';
@@ -338,6 +341,55 @@ export const validateDlobQuery = (
}; };
}; };
export const getAccountFromId = async (
userMapClient: RedisClient,
topMakers: string[]
) => {
return Promise.all(
topMakers.map(async (userAccountPubKey) => {
const userAccountEncoded = await userMapClient.getRaw(userAccountPubKey);
if (userAccountEncoded) {
return {
userAccountPubKey,
account: decodeUser(
Buffer.from(userAccountEncoded.split('::')[1], 'base64')
),
};
}
return {
userAccountPubKey,
account: null,
};
})
).then((results) => results.filter((user) => !!user));
};
export const getRawAccountFromId = async (
userMapClient: RedisClient,
topMakers: string[]
): Promise<
{
userAccountPubKey: string;
accountBase64: string;
}[]
> => {
return Promise.all(
topMakers.map(async (userAccountPubKey) => {
const userAccountEncoded = await userMapClient.getRaw(userAccountPubKey);
if (userAccountEncoded) {
return {
userAccountPubKey,
accountBase64: userAccountEncoded.split('::')[1],
};
}
return {
userAccountPubKey,
accountBase64: null,
};
})
).then((results) => results.filter((user) => !!user));
};
export function errorHandler( export function errorHandler(
err: Error, err: Error,
_req: Request, _req: Request,

View File

@@ -4,9 +4,9 @@ import * as http from 'http';
import compression from 'compression'; import compression from 'compression';
import { WebSocket, WebSocketServer } from 'ws'; import { WebSocket, WebSocketServer } from 'ws';
import { sleep } from './utils/utils'; import { sleep } from './utils/utils';
import { RedisClient } from './utils/redisClient';
import { register, Gauge } from 'prom-client'; import { register, Gauge } from 'prom-client';
import { DriftEnv, PerpMarkets, SpotMarkets } from '@drift-labs/sdk'; import { DriftEnv, PerpMarkets, SpotMarkets } from '@drift-labs/sdk';
import { RedisClient, RedisClientPrefix } from '@drift/common';
// Set up env constants // Set up env constants
require('dotenv').config(); require('dotenv').config();
@@ -29,17 +29,18 @@ const wss = new WebSocketServer({
perMessageDeflate: true, perMessageDeflate: true,
}); });
const REDIS_HOST = process.env.REDIS_HOST || 'localhost';
const REDIS_PORT = process.env.REDIS_PORT || '6379';
const WS_PORT = process.env.WS_PORT || '3000'; const WS_PORT = process.env.WS_PORT || '3000';
console.log(`WS LISTENER PORT : ${WS_PORT}`); console.log(`WS LISTENER PORT : ${WS_PORT}`);
const REDIS_PASSWORD = process.env.REDIS_PASSWORD;
const MAX_BUFFERED_AMOUNT = 300000; const MAX_BUFFERED_AMOUNT = 300000;
const CHANNEL_PREFIX = RedisClientPrefix.DLOB;
const CHANNEL_PREFIX_HELIUS = RedisClientPrefix.DLOB_HELIUS;
const safeGetRawChannelFromMessage = (message: any): string => { const sanitiseChannelForClient = (channel: string): string => {
return message?.channel; return channel
.replace(CHANNEL_PREFIX, '')
.replace(CHANNEL_PREFIX_HELIUS, '');
}; };
const getRedisChannelFromMessage = (message: any): string => { const getRedisChannelFromMessage = (message: any): string => {
@@ -67,11 +68,11 @@ const getRedisChannelFromMessage = (message: any): string => {
switch (channel.toLowerCase()) { switch (channel.toLowerCase()) {
case 'trades': case 'trades':
return `trades_${marketType}_${marketIndex}`; return `${CHANNEL_PREFIX}trades_${marketType}_${marketIndex}`;
case 'orderbook': case 'orderbook':
return `orderbook_${marketType}_${marketIndex}`; return `${CHANNEL_PREFIX}orderbook_${marketType}_${marketIndex}`;
case 'priorityfees': case 'priorityfees':
return `priorityFees_${marketType}_${marketIndex}`; return `${CHANNEL_PREFIX_HELIUS}priorityFees_${marketType}_${marketIndex}`;
case undefined: case undefined:
default: default:
throw new Error('Bad channel specified'); throw new Error('Bad channel specified');
@@ -79,12 +80,8 @@ const getRedisChannelFromMessage = (message: any): string => {
}; };
async function main() { async function main() {
const redisClient = new RedisClient(REDIS_HOST, REDIS_PORT, REDIS_PASSWORD); const redisClient = new RedisClient({});
const lastMessageRetriever = new RedisClient( const lastMessageRetriever = new RedisClient({ prefix: CHANNEL_PREFIX });
REDIS_HOST,
REDIS_PORT,
REDIS_PASSWORD
);
await redisClient.connect(); await redisClient.connect();
await lastMessageRetriever.connect(); await lastMessageRetriever.connect();
@@ -92,17 +89,17 @@ async function main() {
const channelSubscribers = new Map<string, Set<WebSocket>>(); const channelSubscribers = new Map<string, Set<WebSocket>>();
const subscribedChannels = new Set<string>(); const subscribedChannels = new Set<string>();
redisClient.client.on('connect', () => { redisClient.forceGetClient().on('connect', () => {
subscribedChannels.forEach(async (channel) => { subscribedChannels.forEach(async (channel) => {
try { try {
await redisClient.client.subscribe(channel); await redisClient.subscribe(channel);
} catch (error) { } catch (error) {
console.error(`Error subscribing to ${channel}:`, error); console.error(`Error subscribing to ${channel}:`, error);
} }
}); });
}); });
redisClient.client.on('message', (subscribedChannel, message) => { redisClient.forceGetClient().on('message', (subscribedChannel, message) => {
const subscribers = channelSubscribers.get(subscribedChannel); const subscribers = channelSubscribers.get(subscribedChannel);
if (subscribers) { if (subscribers) {
subscribers.forEach((ws) => { subscribers.forEach((ws) => {
@@ -111,13 +108,16 @@ async function main() {
ws.bufferedAmount < MAX_BUFFERED_AMOUNT ws.bufferedAmount < MAX_BUFFERED_AMOUNT
) )
ws.send( ws.send(
JSON.stringify({ channel: subscribedChannel, data: message }) JSON.stringify({
channel: sanitiseChannelForClient(subscribedChannel),
data: message,
})
); );
}); });
} }
}); });
redisClient.client.on('error', (error) => { redisClient.forceGetClient().on('error', (error) => {
console.error('Redis client error:', error); console.error('Redis client error:', error);
}); });
@@ -141,7 +141,7 @@ async function main() {
try { try {
redisChannel = getRedisChannelFromMessage(parsedMessage); redisChannel = getRedisChannelFromMessage(parsedMessage);
} catch (error) { } catch (error) {
const requestChannel = safeGetRawChannelFromMessage(parsedMessage); const requestChannel = sanitiseChannelForClient(parsedMessage?.channel);
if (requestChannel) { if (requestChannel) {
ws.send( ws.send(
JSON.stringify({ JSON.stringify({
@@ -162,7 +162,7 @@ async function main() {
if (!subscribedChannels.has(redisChannel)) { if (!subscribedChannels.has(redisChannel)) {
console.log('Trying to subscribe to channel', redisChannel); console.log('Trying to subscribe to channel', redisChannel);
redisClient.client redisClient
.subscribe(redisChannel) .subscribe(redisChannel)
.then(() => { .then(() => {
subscribedChannels.add(redisChannel); subscribedChannels.add(redisChannel);
@@ -190,11 +190,13 @@ async function main() {
); );
// Fetch and send last message // Fetch and send last message
if (redisChannel.includes('orderbook')) { if (redisChannel.includes('orderbook')) {
const lastUpdateChannel = `last_update_${redisChannel}`; const lastUpdateChannel = `last_update_${redisChannel}`.replace(
const lastMessage = await lastMessageRetriever.client.get( CHANNEL_PREFIX,
''
);
const lastMessage = await lastMessageRetriever.getRaw(
lastUpdateChannel lastUpdateChannel
); );
if (lastMessage) { if (lastMessage) {
ws.send( ws.send(
JSON.stringify({ JSON.stringify({
@@ -212,7 +214,7 @@ async function main() {
try { try {
redisChannel = getRedisChannelFromMessage(parsedMessage); redisChannel = getRedisChannelFromMessage(parsedMessage);
} catch (error) { } catch (error) {
const requestChannel = safeGetRawChannelFromMessage(parsedMessage); const requestChannel = sanitiseChannelForClient(parsedMessage?.channel);
if (requestChannel) { if (requestChannel) {
console.log('Error unsubscribing from channel:', error.message); console.log('Error unsubscribing from channel:', error.message);
ws.send( ws.send(
@@ -270,7 +272,7 @@ async function main() {
clearInterval(bufferInterval); clearInterval(bufferInterval);
channelSubscribers.forEach((subscribers, channel) => { channelSubscribers.forEach((subscribers, channel) => {
if (subscribers.delete(ws) && subscribers.size === 0) { if (subscribers.delete(ws) && subscribers.size === 0) {
redisClient.client.unsubscribe(channel); redisClient.unsubscribe(channel);
channelSubscribers.delete(channel); channelSubscribers.delete(channel);
subscribedChannels.delete(channel); subscribedChannels.delete(channel);
} }

1549
yarn.lock

File diff suppressed because it is too large Load Diff