Refactor integrator ID and add Prometheus metrics

This commit is contained in:
Daniel Pyrathon
2021-09-13 16:26:40 -04:00
parent 0479bb5fe1
commit d314655444
6 changed files with 89 additions and 29 deletions

View File

@@ -50,7 +50,7 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
samplerGasLimit: 500e6,
ethGasStationUrl: ETH_GAS_STATION_API_URL,
rfqt: {
takerApiKeyWhitelist: [],
integratorsWhitelist: [],
makerAssetOfferings: {},
txOriginBlacklist: new Set(),
},

View File

@@ -75,6 +75,7 @@ export class SwapQuoter {
private readonly _marketOperationUtils: MarketOperationUtils;
private readonly _rfqtOptions?: SwapQuoterRfqOpts;
private readonly _quoteRequestorHttpClient: AxiosInstance;
private readonly _integratorIdsSet: Set<string>;
/**
* Instantiates a new SwapQuoter instance
@@ -164,6 +165,9 @@ export class SwapQuoter {
httpsAgent: new HttpsAgent({ keepAlive: true, timeout: KEEP_ALIVE_TTL }),
...(rfqt ? rfqt.axiosInstanceOpts : {}),
});
const integratorIds = this._rfqtOptions?.integratorsWhitelist.map(integrator => integrator.integratorId) || [];
this._integratorIdsSet = new Set(integratorIds);
}
public async getBatchMarketBuySwapQuoteAsync(
@@ -414,12 +418,11 @@ export class SwapQuoter {
return isOpenOrder && !willOrderExpire && isFeeTypeAllowed;
}; // tslint:disable-line:semicolon
private _isApiKeyWhitelisted(apiKey: string | undefined): boolean {
private _isIntegrationKeyWhitelisted(apiKey: string | undefined): boolean {
if (!apiKey) {
return false;
}
const whitelistedApiKeys = this._rfqtOptions ? this._rfqtOptions.takerApiKeyWhitelist : [];
return whitelistedApiKeys.includes(apiKey);
return this._integratorIdsSet.has(apiKey);
}
private _isTxOriginBlacklisted(txOrigin: string | undefined): boolean {
@@ -438,7 +441,7 @@ export class SwapQuoter {
return rfqt;
}
// tslint:disable-next-line: boolean-naming
const { apiKey, nativeExclusivelyRFQ, intentOnFilling, txOrigin } = rfqt;
const { integrator, nativeExclusivelyRFQ, intentOnFilling, txOrigin } = rfqt;
// If RFQ-T is enabled and `nativeExclusivelyRFQ` is set, then `ERC20BridgeSource.Native` should
// never be excluded.
if (nativeExclusivelyRFQ === true && !sourceFilters.isAllowed(ERC20BridgeSource.Native)) {
@@ -446,11 +449,11 @@ export class SwapQuoter {
}
// If an API key was provided, but the key is not whitelisted, raise a warning and disable RFQ
if (!this._isApiKeyWhitelisted(apiKey)) {
if (!this._isIntegrationKeyWhitelisted(integrator.integratorId)) {
if (this._rfqtOptions && this._rfqtOptions.warningLogger) {
this._rfqtOptions.warningLogger(
{
apiKey,
...integrator,
},
'Attempt at using an RFQ API key that is not whitelisted. Disabling RFQ for the request lifetime.',
);
@@ -474,7 +477,7 @@ export class SwapQuoter {
// Otherwise check other RFQ options
if (
intentOnFilling && // The requestor is asking for a firm quote
this._isApiKeyWhitelisted(apiKey) && // A valid API key was provided
this._isIntegrationKeyWhitelisted(integrator.integratorId) && // A valid API key was provided
sourceFilters.isAllowed(ERC20BridgeSource.Native) // Native liquidity is not excluded
) {
if (!txOrigin || txOrigin === constants.NULL_ADDRESS) {

View File

@@ -243,7 +243,7 @@ export interface RfqmRequestOptions extends RfqRequestOpts {
export interface RfqRequestOpts {
takerAddress: string;
txOrigin: string;
apiKey: string;
integrator: Integrator
apiKeyWhitelist?: string[];
intentOnFilling: boolean;
isIndicative?: boolean;
@@ -294,8 +294,13 @@ export interface RfqFirmQuoteValidator {
getRfqtTakerFillableAmountsAsync(quotes: RfqOrder[]): Promise<BigNumber[]>;
}
export interface Integrator {
integratorId: string;
label: string;
}
export interface SwapQuoterRfqOpts {
takerApiKeyWhitelist: string[];
integratorsWhitelist: Integrator[]
makerAssetOfferings: RfqMakerAssetOfferings;
txOriginBlacklist: Set<string>;
altRfqCreds?: {

View File

@@ -61,6 +61,20 @@ export interface MetricsProxy {
* @param expirationTimeSeconds the expiration time in seconds
*/
incrementFillRatioWarningCounter(isLastLook: boolean, maker: string): void;
logRfqMakerInteraction(interaction: {
isLastLook: boolean,
integrator: {
integratorId: string;
label: string;
};
url: string;
quoteType: 'firm' | 'indicative';
statusCode: number;
latencyMs: number;
included: boolean;
}): void;
}
/**
@@ -472,18 +486,28 @@ export class QuoteRequestor {
try {
if (typedMakerUrl.pairType === RfqPairType.Standard) {
const response = await this._quoteRequestorHttpClient.get(`${typedMakerUrl.url}/${quotePath}`, {
headers: { '0x-api-key': options.apiKey },
headers: { '0x-api-key': options.integrator.integratorId },
params: requestParams,
timeout: timeoutMs,
cancelToken: cancelTokenSource.token,
});
const latencyMs = Date.now() - timeBeforeAwait;
const {isLastLook, integrator} = options;
this._metrics?.logRfqMakerInteraction({
isLastLook: isLastLook || false,
url: typedMakerUrl.url,
quoteType,
statusCode: response.status,
latencyMs,
included: true,
integrator,
});
this._infoLogger({
rfqtMakerInteraction: {
...partialLogEntry,
response: {
included: true,
apiKey: options.apiKey,
apiKey: options.integrator.integratorId,
takerAddress: requestParams.takerAddress,
txOrigin: requestParams.txOrigin,
statusCode: response.status,
@@ -501,7 +525,7 @@ export class QuoteRequestor {
typedMakerUrl.url,
this._altRfqCreds.altRfqApiKey,
this._altRfqCreds.altRfqProfile,
options.apiKey,
options.integrator.integratorId,
quoteType === 'firm' ? AltQuoteModel.Firm : AltQuoteModel.Indicative,
makerToken,
takerToken,
@@ -519,7 +543,7 @@ export class QuoteRequestor {
...partialLogEntry,
response: {
included: true,
apiKey: options.apiKey,
apiKey: options.integrator.integratorId,
takerAddress: requestParams.takerAddress,
txOrigin: requestParams.txOrigin,
statusCode: quote.status,
@@ -538,7 +562,7 @@ export class QuoteRequestor {
...partialLogEntry,
response: {
included: false,
apiKey: options.apiKey,
apiKey: options.integrator.integratorId,
takerAddress: requestParams.takerAddress,
txOrigin: requestParams.txOrigin,
statusCode: err.response ? err.response.status : undefined,
@@ -549,7 +573,7 @@ export class QuoteRequestor {
rfqMakerBlacklist.logTimeoutOrLackThereof(typedMakerUrl.url, latencyMs >= timeoutMs);
this._warningLogger(
convertIfAxiosError(err),
`Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${typedMakerUrl.url} for API key ${options.apiKey} for taker address ${options.takerAddress} and tx origin ${options.txOrigin}`,
`Failed to get RFQ-T ${quoteType} quote from market maker endpoint ${typedMakerUrl.url} for API key ${options.integrator.integratorId} (${options.integrator.label}) for taker address ${options.takerAddress} and tx origin ${options.txOrigin}`,
);
return;
}

View File

@@ -16,7 +16,7 @@ import * as _ from 'lodash';
import * as TypeMoq from 'typemoq';
import { MarketOperation, QuoteRequestor, RfqRequestOpts, SignedNativeOrder } from '../src';
import { NativeOrderWithFillableAmounts } from '../src/types';
import { Integrator, NativeOrderWithFillableAmounts } from '../src/types';
import { MarketOperationUtils } from '../src/utils/market_operation_utils/';
import {
BUY_SOURCE_FILTER_BY_CHAIN_ID,
@@ -62,6 +62,10 @@ const SELL_SOURCES = SELL_SOURCE_FILTER_BY_CHAIN_ID[ChainId.Mainnet].sources;
const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = { default: [] };
const SIGNATURE = { v: 1, r: NULL_BYTES, s: NULL_BYTES, signatureType: SignatureType.EthSign };
const FOO_INTEGRATOR: Integrator = {
integratorId: 'foo',
label: 'foo',
};
/**
* gets the orders required for a market sell operation by (potentially) merging native orders with
@@ -745,7 +749,7 @@ describe('MarketOperationUtils tests', () => {
feeSchedule,
rfqt: {
isIndicative: false,
apiKey: 'foo',
integrator: FOO_INTEGRATOR,
takerAddress: randomAddress(),
txOrigin: randomAddress(),
intentOnFilling: true,
@@ -790,7 +794,7 @@ describe('MarketOperationUtils tests', () => {
...DEFAULT_OPTS,
rfqt: {
isIndicative: false,
apiKey: 'foo',
integrator: FOO_INTEGRATOR,
takerAddress: randomAddress(),
intentOnFilling: true,
txOrigin: randomAddress(),
@@ -837,7 +841,7 @@ describe('MarketOperationUtils tests', () => {
...DEFAULT_OPTS,
rfqt: {
isIndicative: true,
apiKey: 'foo',
integrator: FOO_INTEGRATOR,
takerAddress: randomAddress(),
txOrigin: randomAddress(),
intentOnFilling: true,
@@ -896,7 +900,10 @@ describe('MarketOperationUtils tests', () => {
...DEFAULT_OPTS,
rfqt: {
isIndicative: false,
apiKey: 'foo',
integrator: {
integratorId: 'foo',
label: 'foo',
},
takerAddress: randomAddress(),
intentOnFilling: true,
txOrigin: randomAddress(),
@@ -954,7 +961,7 @@ describe('MarketOperationUtils tests', () => {
...DEFAULT_OPTS,
rfqt: {
isIndicative: false,
apiKey: 'foo',
integrator: FOO_INTEGRATOR,
takerAddress: randomAddress(),
txOrigin: randomAddress(),
intentOnFilling: true,

View File

@@ -240,7 +240,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell,
undefined,
{
apiKey,
integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
@@ -435,7 +438,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell,
undefined,
{
apiKey,
integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
@@ -551,7 +557,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell,
undefined,
{
apiKey,
integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
@@ -675,7 +684,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell,
undefined,
{
apiKey,
integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
@@ -762,7 +774,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Sell,
undefined,
{
apiKey,
integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
@@ -823,7 +838,10 @@ describe('QuoteRequestor', async () => {
MarketOperation.Buy,
undefined,
{
apiKey,
integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
@@ -1088,7 +1106,10 @@ describe('QuoteRequestor', async () => {
altScenario.requestedOperation,
undefined,
{
apiKey,
integrator: {
integratorId: apiKey,
label: 'foo',
},
takerAddress,
txOrigin,
intentOnFilling: true,