asset-swapper: Punish latent RFQT makers

This commit is contained in:
F. Eugene Aumson 2020-09-10 16:23:37 -04:00
parent 7b0a1c3630
commit 1ce8a33937
No known key found for this signature in database
GPG Key ID: 23E6737B1374A24A
2 changed files with 58 additions and 4 deletions

View File

@ -11,6 +11,7 @@ import { constants } from '../constants';
import { MarketOperation, RfqtMakerAssetOfferings, RfqtRequestOpts } from '../types';
import { ONE_SECOND_MS } from './market_operation_utils/constants';
import { RfqMakerBlacklist } from './rfq_maker_blacklist';
// tslint:disable-next-line: custom-no-magic-numbers
const KEEP_ALIVE_TTL = 5 * 60 * ONE_SECOND_MS;
@ -20,6 +21,10 @@ export const quoteRequestorHttpClient: AxiosInstance = Axios.create({
httpsAgent: new HttpsAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
});
const MAKER_TIMEOUT_STREAK_LENGTH = 10;
const MAKER_TIMEOUT_BLACKLIST_DURATION_MINUTES = 10;
const rfqMakerBlacklist = new RfqMakerBlacklist(MAKER_TIMEOUT_STREAK_LENGTH, MAKER_TIMEOUT_BLACKLIST_DURATION_MINUTES);
/**
* Request quotes from RFQ-T providers
*/
@ -334,7 +339,10 @@ export class QuoteRequestor {
const result: Array<{ response: ResponseT; makerUri: string }> = [];
await Promise.all(
Object.keys(this._rfqtAssetOfferings).map(async url => {
if (this._makerSupportsPair(url, makerAssetData, takerAssetData)) {
if (
this._makerSupportsPair(url, makerAssetData, takerAssetData) &&
!rfqMakerBlacklist.isMakerBlacklisted(url)
) {
const requestParamsWithBigNumbers = {
takerAddress: options.takerAddress,
...inferQueryParams(marketOperation, makerAssetData, takerAssetData, assetFillAmount),
@ -354,6 +362,10 @@ export class QuoteRequestor {
const partialLogEntry = { url, quoteType, requestParams };
const timeBeforeAwait = Date.now();
const maxResponseTimeMs =
options.makerEndpointMaxResponseTimeMs === undefined
? constants.DEFAULT_RFQT_REQUEST_OPTS.makerEndpointMaxResponseTimeMs!
: options.makerEndpointMaxResponseTimeMs;
try {
const quotePath = (() => {
switch (quoteType) {
@ -368,8 +380,9 @@ export class QuoteRequestor {
const response = await quoteRequestorHttpClient.get<ResponseT>(`${url}/${quotePath}`, {
headers: { '0x-api-key': options.apiKey },
params: requestParams,
timeout: options.makerEndpointMaxResponseTimeMs,
timeout: maxResponseTimeMs,
});
const latencyMs = Date.now() - timeBeforeAwait;
this._infoLogger({
rfqtMakerInteraction: {
...partialLogEntry,
@ -378,12 +391,14 @@ export class QuoteRequestor {
apiKey: options.apiKey,
takerAddress: requestParams.takerAddress,
statusCode: response.status,
latencyMs: Date.now() - timeBeforeAwait,
latencyMs,
},
},
});
rfqMakerBlacklist.logTimeoutOrLackThereof(url, latencyMs > maxResponseTimeMs);
result.push({ response: response.data, makerUri: url });
} catch (err) {
const latencyMs = Date.now() - timeBeforeAwait;
this._infoLogger({
rfqtMakerInteraction: {
...partialLogEntry,
@ -392,10 +407,11 @@ export class QuoteRequestor {
apiKey: options.apiKey,
takerAddress: requestParams.takerAddress,
statusCode: err.response ? err.response.status : undefined,
latencyMs: Date.now() - timeBeforeAwait,
latencyMs,
},
},
});
rfqMakerBlacklist.logTimeoutOrLackThereof(url, latencyMs > maxResponseTimeMs);
this._warningLogger(
convertIfAxiosError(err),
`Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${url} for API key ${

View File

@ -0,0 +1,38 @@
/**
* Tracks a maker's history of timely responses, and manages whether a given
* maker should be avoided for being too latent.
*/
export class RfqMakerBlacklist {
// tslint:disable-next-line:custom-no-magic-numbers
private static readonly _msPerMinute = 1000 * 60;
private readonly _makerTimeoutStreakLength: { [makerUrl: string]: number } = {};
private readonly _makerBlacklistedUntilDate: { [makerUrl: string]: number } = {};
constructor(private readonly _blacklistDurationMinutes: number, private readonly _timeoutStreakThreshold: number) {}
public logTimeoutOrLackThereof(makerUrl: string, didTimeout: boolean): void {
if (!this._makerTimeoutStreakLength.hasOwnProperty(makerUrl)) {
this._makerTimeoutStreakLength[makerUrl] = 0;
}
if (didTimeout) {
this._makerTimeoutStreakLength[makerUrl] += 1;
if (this._makerTimeoutStreakLength[makerUrl] > this._timeoutStreakThreshold) {
this._makerBlacklistedUntilDate[makerUrl] =
Date.now() + this._blacklistDurationMinutes * RfqMakerBlacklist._msPerMinute;
this._makerTimeoutStreakLength[makerUrl] = 0;
}
} else {
this._makerTimeoutStreakLength[makerUrl] = 0;
}
}
public isMakerBlacklisted(makerUrl: string): boolean {
if (this._makerBlacklistedUntilDate.hasOwnProperty(makerUrl)) {
if (this._makerBlacklistedUntilDate[makerUrl] > Date.now()) {
delete this._makerBlacklistedUntilDate[makerUrl];
return false;
} else {
return true;
}
} else {
return false;
}
}
}