add /debug to dlobPublisher
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -142,4 +142,8 @@ src/**.js.map
|
|||||||
lib
|
lib
|
||||||
src/playground.ts
|
src/playground.ts
|
||||||
|
|
||||||
*dump.rdb
|
# redis files
|
||||||
|
*.rdb
|
||||||
|
*.log
|
||||||
|
*.confg
|
||||||
|
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -18,6 +18,12 @@ This is the backend server that provides a REST API for the drift [DLOB](https:/
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
The build dependencies
|
||||||
|
```
|
||||||
|
git submodule update --init
|
||||||
|
bash build_all.sh
|
||||||
|
```
|
||||||
|
|
||||||
First set the necessary environment variables:
|
First set the necessary environment variables:
|
||||||
```
|
```
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
@@ -66,18 +72,28 @@ To run the websocket server, a Redis cache is required, and the following enviro
|
|||||||
* `REDIS_PASSWORDS`
|
* `REDIS_PASSWORDS`
|
||||||
* `REDIS_PORTS`
|
* `REDIS_PORTS`
|
||||||
|
|
||||||
In one terminal, run:
|
In the first terminal, start the redis cluster:
|
||||||
|
```
|
||||||
|
bash redisCluster.sh start
|
||||||
|
bash redisCluster.sh create
|
||||||
|
```
|
||||||
|
|
||||||
|
In second terminal, run:
|
||||||
```
|
```
|
||||||
yarn run dlob-publisher
|
yarn run dlob-publisher
|
||||||
```
|
```
|
||||||
|
|
||||||
In a second terminal, run:
|
In a third terminal, run:
|
||||||
```
|
```
|
||||||
yarn run ws-manager
|
yarn run ws-manager
|
||||||
```
|
```
|
||||||
|
|
||||||
Then connect to the ws server at ws://127.0.0.1:3000
|
Then connect to the ws server at ws://127.0.0.1:3000
|
||||||
|
|
||||||
|
When you're done, stop the redis cluster:
|
||||||
|
```
|
||||||
|
bash redisCluster.sh stop
|
||||||
|
```
|
||||||
|
|
||||||
# Run the example client
|
# Run the example client
|
||||||
|
|
||||||
|
|||||||
142
redisCluster.sh
Normal file
142
redisCluster.sh
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
CLUSTER_HOST=127.0.0.1
|
||||||
|
PORT=6378
|
||||||
|
TIMEOUT=2000
|
||||||
|
NODES=6
|
||||||
|
REPLICAS=1
|
||||||
|
PROTECTED_MODE=yes
|
||||||
|
ADDITIONAL_OPTIONS=""
|
||||||
|
|
||||||
|
# You may want to put the above config parameters into config.sh in order to
|
||||||
|
# override the defaults without modifying this script.
|
||||||
|
|
||||||
|
if [ -a config.sh ]
|
||||||
|
then
|
||||||
|
source "config.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Computed vars
|
||||||
|
ENDPORT=$((PORT+NODES))
|
||||||
|
|
||||||
|
if [ "$1" == "start" ]
|
||||||
|
then
|
||||||
|
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||||
|
PORT=$((PORT+1))
|
||||||
|
echo "Starting $PORT"
|
||||||
|
redis-server --port $PORT --protected-mode $PROTECTED_MODE --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --appenddirname appendonlydir-${PORT} --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes ${ADDITIONAL_OPTIONS}
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "create" ]
|
||||||
|
then
|
||||||
|
HOSTS=""
|
||||||
|
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||||
|
PORT=$((PORT+1))
|
||||||
|
HOSTS="$HOSTS $CLUSTER_HOST:$PORT"
|
||||||
|
done
|
||||||
|
OPT_ARG=""
|
||||||
|
if [ "$2" == "-f" ]; then
|
||||||
|
OPT_ARG="--cluster-yes"
|
||||||
|
fi
|
||||||
|
redis-cli --cluster create $HOSTS --cluster-replicas $REPLICAS $OPT_ARG
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "stop" ]
|
||||||
|
then
|
||||||
|
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||||
|
PORT=$((PORT+1))
|
||||||
|
echo "Stopping $PORT"
|
||||||
|
redis-cli -p $PORT shutdown nosave
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "restart" ]
|
||||||
|
then
|
||||||
|
OLD_PORT=$PORT
|
||||||
|
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||||
|
PORT=$((PORT+1))
|
||||||
|
echo "Stopping $PORT"
|
||||||
|
redis-cli -p $PORT shutdown nosave
|
||||||
|
done
|
||||||
|
PORT=$OLD_PORT
|
||||||
|
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||||
|
PORT=$((PORT+1))
|
||||||
|
echo "Starting $PORT"
|
||||||
|
redis-server --port $PORT --protected-mode $PROTECTED_MODE --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --appenddirname appendonlydir-${PORT} --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes ${ADDITIONAL_OPTIONS}
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "watch" ]
|
||||||
|
then
|
||||||
|
PORT=$((PORT+1))
|
||||||
|
while [ 1 ]; do
|
||||||
|
clear
|
||||||
|
date
|
||||||
|
redis-cli -p $PORT cluster nodes | head -30
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "tail" ]
|
||||||
|
then
|
||||||
|
INSTANCE=$2
|
||||||
|
PORT=$((PORT+INSTANCE))
|
||||||
|
tail -f ${PORT}.log
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "tailall" ]
|
||||||
|
then
|
||||||
|
tail -f *.log
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "call" ]
|
||||||
|
then
|
||||||
|
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||||
|
PORT=$((PORT+1))
|
||||||
|
redis-cli -p $PORT $2 $3 $4 $5 $6 $7 $8 $9
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "clean" ]
|
||||||
|
then
|
||||||
|
echo "Cleaning *.log"
|
||||||
|
rm -rf *.log
|
||||||
|
echo "Cleaning appendonlydir-*"
|
||||||
|
rm -rf appendonlydir-*
|
||||||
|
echo "Cleaning dump-*.rdb"
|
||||||
|
rm -rf dump-*.rdb
|
||||||
|
echo "Cleaning nodes-*.conf"
|
||||||
|
rm -rf nodes-*.conf
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "clean-logs" ]
|
||||||
|
then
|
||||||
|
echo "Cleaning *.log"
|
||||||
|
rm -rf *.log
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Usage: $0 [start|create|stop|restart|watch|tail|tailall|clean|clean-logs|call]"
|
||||||
|
echo "start -- Launch Redis Cluster instances."
|
||||||
|
echo "create [-f] -- Create a cluster using redis-cli --cluster create."
|
||||||
|
echo "stop -- Stop Redis Cluster instances."
|
||||||
|
echo "restart -- Restart Redis Cluster instances."
|
||||||
|
echo "watch -- Show CLUSTER NODES output (first 30 lines) of first node."
|
||||||
|
echo "tail <id> -- Run tail -f of instance at base port + ID."
|
||||||
|
echo "tailall -- Run tail -f for all the log files at once."
|
||||||
|
echo "clean -- Remove all instances data, logs, configs."
|
||||||
|
echo "clean-logs -- Remove just instances logs."
|
||||||
|
echo "call <cmd> -- Call a command (up to 7 arguments) on all nodes."
|
||||||
@@ -247,7 +247,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
|
|||||||
l2Formatted['marketSlot'] !== lastMarketSlotAndTime.slot
|
l2Formatted['marketSlot'] !== lastMarketSlotAndTime.slot
|
||||||
) {
|
) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`Updating market slot for ${marketArgs.marketName} with slot ${l2Formatted['marketSlot']}`
|
`Updating market slot for ${marketArgs.marketName} from ${lastMarketSlotAndTime.slot} -> ${l2Formatted['marketSlot']}`
|
||||||
);
|
);
|
||||||
this.lastMarketSlotMap
|
this.lastMarketSlotMap
|
||||||
.get(marketArgs.marketType)
|
.get(marketArgs.marketType)
|
||||||
@@ -262,6 +262,13 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
|
|||||||
asks: l2Formatted.asks.slice(0, 100),
|
asks: l2Formatted.asks.slice(0, 100),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`bbo: ${l2Formatted['marketName']}`,
|
||||||
|
(+l2Formatted.bids[0].price / 1e6).toFixed(4),
|
||||||
|
'//',
|
||||||
|
(+l2Formatted.asks[0].price / 1e6).toFixed(4)
|
||||||
|
);
|
||||||
|
|
||||||
this.redisClient.publish(
|
this.redisClient.publish(
|
||||||
`${clientPrefix}orderbook_${marketType}_${marketArgs.marketIndex}`,
|
`${clientPrefix}orderbook_${marketType}_${marketArgs.marketIndex}`,
|
||||||
l2Formatted
|
l2Formatted
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
PerpMarketConfig,
|
PerpMarketConfig,
|
||||||
SpotMarketConfig,
|
SpotMarketConfig,
|
||||||
PhoenixSubscriber,
|
PhoenixSubscriber,
|
||||||
|
MarketType,
|
||||||
|
OraclePriceData,
|
||||||
} from '@drift-labs/sdk';
|
} from '@drift-labs/sdk';
|
||||||
import { RedisClient, RedisClientPrefix } from '@drift/common/clients';
|
import { RedisClient, RedisClientPrefix } from '@drift/common/clients';
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ import { logger, setLogLevel } from '../utils/logger';
|
|||||||
import {
|
import {
|
||||||
SubscriberLookup,
|
SubscriberLookup,
|
||||||
getOpenbookSubscriber,
|
getOpenbookSubscriber,
|
||||||
|
l2WithBNToStrings,
|
||||||
parsePositiveIntArray,
|
parsePositiveIntArray,
|
||||||
sleep,
|
sleep,
|
||||||
} from '../utils/utils';
|
} from '../utils/utils';
|
||||||
@@ -40,7 +43,7 @@ import {
|
|||||||
} from '../dlobProvider';
|
} from '../dlobProvider';
|
||||||
import FEATURE_FLAGS from '../utils/featureFlags';
|
import FEATURE_FLAGS from '../utils/featureFlags';
|
||||||
import { GeyserOrderSubscriber } from '../grpc/OrderSubscriberGRPC';
|
import { GeyserOrderSubscriber } from '../grpc/OrderSubscriberGRPC';
|
||||||
import express from 'express';
|
import express, { Response, Request } from 'express';
|
||||||
import { handleHealthCheck } from '../core/metrics';
|
import { handleHealthCheck } from '../core/metrics';
|
||||||
import { setGlobalDispatcher, Agent } from 'undici';
|
import { setGlobalDispatcher, Agent } from 'undici';
|
||||||
|
|
||||||
@@ -453,6 +456,50 @@ const main = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDebug = async (req: Request, res: Response) => {
|
||||||
|
const marketIndex = +req.query.marketIndex;
|
||||||
|
let marketType: MarketType = MarketType.PERP;
|
||||||
|
let oraclePriceData: OraclePriceData;
|
||||||
|
if (req.query.marketType === 'spot') {
|
||||||
|
marketType = MarketType.SPOT;
|
||||||
|
oraclePriceData = driftClient.getOracleDataForSpotMarket(marketIndex);
|
||||||
|
} else {
|
||||||
|
oraclePriceData = driftClient.getOracleDataForPerpMarket(marketIndex);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const slot = slotSource.getSlot();
|
||||||
|
const dlob = await dlobProvider.getDLOB(slot);
|
||||||
|
const l2 = dlob.getL2({
|
||||||
|
marketIndex,
|
||||||
|
marketType,
|
||||||
|
depth: 5,
|
||||||
|
slot,
|
||||||
|
oraclePriceData,
|
||||||
|
});
|
||||||
|
const l3 = dlob.getL3({
|
||||||
|
marketIndex,
|
||||||
|
marketType,
|
||||||
|
slot,
|
||||||
|
oraclePriceData,
|
||||||
|
});
|
||||||
|
const state = {
|
||||||
|
dlobSize: dlobProvider.size(),
|
||||||
|
slot,
|
||||||
|
markets: {
|
||||||
|
perp: perpMarketInfos,
|
||||||
|
spot: spotMarketInfos,
|
||||||
|
},
|
||||||
|
l2: l2WithBNToStrings(l2),
|
||||||
|
l3,
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(state);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).json({ error: e.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
app.get('/debug', handleDebug);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/health',
|
'/health',
|
||||||
handleHealthCheck(WS_FALLBACK_FETCH_INTERVAL, dlobProvider)
|
handleHealthCheck(WS_FALLBACK_FETCH_INTERVAL, dlobProvider)
|
||||||
|
|||||||
@@ -37,6 +37,21 @@ export const l2WithBNToStrings = (l2: L2OrderBook): any => {
|
|||||||
return l2;
|
return l2;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const l3WithBNToStrings = (l3: L3OrderBook): any => {
|
||||||
|
for (const key of Object.keys(l3)) {
|
||||||
|
for (const idx in l3[key]) {
|
||||||
|
const level = l3[key][idx];
|
||||||
|
l3[key][idx] = {
|
||||||
|
price: level.price.toString(),
|
||||||
|
size: level.size.toString(),
|
||||||
|
maker: level.maker.toBase58(),
|
||||||
|
orderId: level.orderId.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l3;
|
||||||
|
};
|
||||||
|
|
||||||
export function sleep(ms: number): Promise<void> {
|
export function sleep(ms: number): Promise<void> {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@@ -460,11 +460,12 @@
|
|||||||
kuler "^2.0.0"
|
kuler "^2.0.0"
|
||||||
|
|
||||||
"@drift-labs/sdk@file:./drift-common/protocol/sdk", "@drift-labs/sdk@file:drift-common/protocol/sdk":
|
"@drift-labs/sdk@file:./drift-common/protocol/sdk", "@drift-labs/sdk@file:drift-common/protocol/sdk":
|
||||||
version "2.96.0-beta.25"
|
version "2.103.0-beta.8"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@coral-xyz/anchor" "0.28.0"
|
"@coral-xyz/anchor" "0.28.0"
|
||||||
"@coral-xyz/anchor-30" "npm:@coral-xyz/anchor@0.30.1"
|
"@coral-xyz/anchor-30" "npm:@coral-xyz/anchor@0.30.1"
|
||||||
"@ellipsis-labs/phoenix-sdk" "^1.4.2"
|
"@ellipsis-labs/phoenix-sdk" "^1.4.2"
|
||||||
|
"@grpc/grpc-js" "^1.8.0"
|
||||||
"@openbook-dex/openbook-v2" "0.2.10"
|
"@openbook-dex/openbook-v2" "0.2.10"
|
||||||
"@project-serum/serum" "^0.13.38"
|
"@project-serum/serum" "^0.13.38"
|
||||||
"@pythnetwork/client" "2.5.3"
|
"@pythnetwork/client" "2.5.3"
|
||||||
@@ -473,18 +474,20 @@
|
|||||||
"@solana/spl-token" "0.3.7"
|
"@solana/spl-token" "0.3.7"
|
||||||
"@solana/web3.js" "1.92.3"
|
"@solana/web3.js" "1.92.3"
|
||||||
"@switchboard-xyz/on-demand" "1.2.42"
|
"@switchboard-xyz/on-demand" "1.2.42"
|
||||||
|
"@triton-one/yellowstone-grpc" "0.6.0"
|
||||||
anchor-bankrun "^0.3.0"
|
anchor-bankrun "^0.3.0"
|
||||||
node-cache "^5.1.2"
|
node-cache "^5.1.2"
|
||||||
rpc-websockets "7.5.1"
|
rpc-websockets "7.5.1"
|
||||||
solana-bankrun "^0.3.0"
|
solana-bankrun "^0.3.0"
|
||||||
strict-event-emitter-types "^2.0.0"
|
strict-event-emitter-types "^2.0.0"
|
||||||
|
tweetnacl "1.0.3"
|
||||||
uuid "^8.3.2"
|
uuid "^8.3.2"
|
||||||
zstddec "^0.1.0"
|
zstddec "^0.1.0"
|
||||||
|
|
||||||
"@drift/common@file:./drift-common/common-ts":
|
"@drift/common@file:./drift-common/common-ts":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@drift-labs/sdk" "file:../../Library/Caches/Yarn/v6/npm-@drift-common-1.0.0-79940387-1595-4bdc-b73c-2bd48440a03a-1729802406625/node_modules/@drift/protocol/sdk"
|
"@drift-labs/sdk" "file:../../Library/Caches/Yarn/v6/npm-@drift-common-1.0.0-2478b7b0-73c2-48c2-8cdc-077c34db7294-1733168269147/node_modules/@drift/protocol/sdk"
|
||||||
"@jest/globals" "^29.3.1"
|
"@jest/globals" "^29.3.1"
|
||||||
"@slack/web-api" "^6.4.0"
|
"@slack/web-api" "^6.4.0"
|
||||||
"@solana/spl-token" "^0.3.8"
|
"@solana/spl-token" "^0.3.8"
|
||||||
@@ -2140,6 +2143,13 @@
|
|||||||
js-yaml "^4.1.0"
|
js-yaml "^4.1.0"
|
||||||
protobufjs "^7.2.6"
|
protobufjs "^7.2.6"
|
||||||
|
|
||||||
|
"@triton-one/yellowstone-grpc@0.6.0":
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@triton-one/yellowstone-grpc/-/yellowstone-grpc-0.6.0.tgz#9e6376cec8a42284c23dc195df2c3423c87c4f27"
|
||||||
|
integrity sha512-rgdZM2N3U9/d/QKOI5PP+9rSHUl2oSI5Uwzvuss8y/mtTaHFjbOMpXpQXviIeDkusOa+mef4wLYrbjEZCwTXiw==
|
||||||
|
dependencies:
|
||||||
|
"@grpc/grpc-js" "^1.8.0"
|
||||||
|
|
||||||
"@triton-one/yellowstone-grpc@^0.3.0":
|
"@triton-one/yellowstone-grpc@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@triton-one/yellowstone-grpc/-/yellowstone-grpc-0.3.0.tgz#2318b98b9ee80871e4ee8f9259f3d8673bf2c576"
|
resolved "https://registry.yarnpkg.com/@triton-one/yellowstone-grpc/-/yellowstone-grpc-0.3.0.tgz#2318b98b9ee80871e4ee8f9259f3d8673bf2c576"
|
||||||
@@ -6307,7 +6317,7 @@ tsutils@^3.21.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.8.1"
|
tslib "^1.8.1"
|
||||||
|
|
||||||
tweetnacl@^1.0.3:
|
tweetnacl@1.0.3, tweetnacl@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
|
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
|
||||||
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
|
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
|
||||||
|
|||||||
Reference in New Issue
Block a user