Skip to content

Commit

Permalink
TRCL-2587 find the best indexer, with the same logic to find best nod…
Browse files Browse the repository at this point in the history
…e url (#22)
  • Loading branch information
johnqh authored Sep 5, 2023
1 parent 9099e53 commit da212d6
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 19 deletions.
77 changes: 70 additions & 7 deletions v4-client-js/__native__/__ios__/v4-native-client.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion v4-client-js/__native__/__ios__/v4-native-client.js.map

This file was deleted.

33 changes: 31 additions & 2 deletions v4-client-js/examples/optimal_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
} from '../src/clients/constants';
import { NetworkOptimizer } from '../src/network_optimizer';

async function test(): Promise<void> {
async function testNodes(): Promise<void> {
// all valid endpoints
try {
const optimizer = new NetworkOptimizer();
Expand Down Expand Up @@ -48,4 +48,33 @@ async function test(): Promise<void> {
}
}

test().catch(console.log);
async function testIndexers(): Promise<void> {
// all valid endpoints
try {
const optimizer = new NetworkOptimizer();
const endpoints = [
'https://indexer.v4testnet2.dydx.exchange',
];
const optimal = await optimizer.findOptimalIndexer(endpoints);
console.log(optimal);
} catch (error) {
console.log(error.message);
}

// all invalid endpoints

try {
const optimizer = new NetworkOptimizer();
const endpoints = [
'https://example.com',
'https://example.org',
];
const optimal = await optimizer.findOptimalIndexer(endpoints);
console.log(optimal);
} catch (error) {
console.log(error.message);
}
}

testNodes().catch(console.log);
testIndexers().catch(console.log);
2 changes: 1 addition & 1 deletion v4-client-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dydxprotocol/v4-client-js",
"version": "0.32.0",
"version": "0.32.1",
"description": "General client library for the new dYdX system (v4 decentralized)",
"main": "build/src/index.js",
"scripts": {
Expand Down
24 changes: 24 additions & 0 deletions v4-client-js/src/clients/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,3 +862,27 @@ export async function getOptimalNode(endpointUrlsAsJson: string): Promise<string
return wrappedError(error);
}
}

export async function getOptimalIndexer(endpointUrlsAsJson: string): Promise<string> {
/*
param:
endpointUrlsAsJson:
{
"endpointUrls": [
"https://api.example.org"
]
}
*/
try {
const param = JSON.parse(endpointUrlsAsJson);
const endpointUrls = param.endpointUrls;
const networkOptimizer = new NetworkOptimizer();
const optimalUrl = await networkOptimizer.findOptimalIndexer(endpointUrls);
const url = {
url: optimalUrl,
};
return encodeJson(url);
} catch (error) {
return wrappedError(error);
}
}
60 changes: 52 additions & 8 deletions v4-client-js/src/network_optimizer.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { Block } from '@cosmjs/stargate';

import { IndexerClient } from './clients/indexer-client';
import { ValidatorClient } from './clients/validator-client';
import { encodeJson } from './lib/helpers';
import { ValidatorConfig } from './types';
import { IndexerConfig, ValidatorConfig } from './types';

class PingResponse {
public readonly block: Block;
public readonly height: number;
public readonly responseTime: Date;
public endpoint?: string;

constructor(
block: Block,
height: number,
) {
this.block = block;
this.height = height;
this.responseTime = new Date();
}
}
Expand All @@ -32,6 +31,15 @@ export class NetworkOptimizer {
)).filter(isTruthy);
}

private indexerClients(
endpointUrls: string[],
): IndexerClient[] {
return endpointUrls.map((endpointUrl) => new IndexerClient(
// socket is not used for finding optimal indexer, but required as a parameter to the config
new IndexerConfig(endpointUrl, endpointUrl.replace('https://', 'wss://').replace('http://', 'ws://')),
)).filter(isTruthy);
}

async findOptimalNode(endpointUrls: string[], chainId: string): Promise<string> {
if (endpointUrls.length === 0) {
const errorResponse = {
Expand All @@ -46,10 +54,46 @@ export class NetworkOptimizer {
clients
.map(async (client) => {
const block = await client.get.latestBlock();
const response = new PingResponse(block);
const response = new PingResponse(block.header.height);
return {
endpoint: client.config.restEndpoint,
height: response.height,
time: response.responseTime.getTime(),
};
})
.map((promise) => promise.catch((_) => undefined)),
)).filter(isTruthy);

if (responses.length === 0) {
throw new Error('Could not connect to endpoints');
}
const maxHeight = Math.max(...responses.map(({ height }) => height));
return responses
// Only consider nodes at `maxHeight`
.filter(({ height }) => height === maxHeight)
// Return the endpoint with the fastest response time
.sort((a, b) => a.time - b.time)[0]
.endpoint;
}

async findOptimalIndexer(endpointUrls: string[]): Promise<string> {
if (endpointUrls.length === 0) {
const errorResponse = {
error: {
message: 'No URL provided',
},
};
return encodeJson(errorResponse);
}
const clients = this.indexerClients(endpointUrls);
const responses = (await Promise.all(
clients
.map(async (client) => {
const block = await client.markets.getHeight();
const response = new PingResponse(+block.height);
return {
endpoint: client.config.restEndpoint,
height: response.block.header.height,
height: response.height,
time: response.responseTime.getTime(),
};
})
Expand Down

0 comments on commit da212d6

Please sign in to comment.