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, samplerGasLimit: 500e6,
ethGasStationUrl: ETH_GAS_STATION_API_URL, ethGasStationUrl: ETH_GAS_STATION_API_URL,
rfqt: { rfqt: {
takerApiKeyWhitelist: [], integratorsWhitelist: [],
makerAssetOfferings: {}, makerAssetOfferings: {},
txOriginBlacklist: new Set(), txOriginBlacklist: new Set(),
}, },

View File

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

View File

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

View File

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

View File

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

View File

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