diff --git a/indexer/packages/postgres/__tests__/stores/order-table.test.ts b/indexer/packages/postgres/__tests__/stores/order-table.test.ts index a5449405dff..9ac432f2915 100644 --- a/indexer/packages/postgres/__tests__/stores/order-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/order-table.test.ts @@ -254,6 +254,28 @@ describe('Order store', () => { goodTilBlock: null, }, ], + [ + 'goodTilBlockAfter', + 'goodTilBlock', + { + goodTilBlockAfter: '1', + }, + { + ...defaultOrder, + goodTilBlockTime: null, + }, + ], + [ + 'goodTilBlockTimeAfter', + 'goodTilBlockTime', + { + goodTilBlockTimeAfter: '2022-02-01T00:00:00.000Z', + }, + { + ...defaultOrderGoodTilBlockTime, + goodTilBlock: null, + }, + ], ])( 'Successfully finds all orders by %s, excludes orders with null %s', async ( diff --git a/indexer/packages/postgres/src/stores/order-table.ts b/indexer/packages/postgres/src/stores/order-table.ts index a197dee1a9f..43442fb3aa9 100644 --- a/indexer/packages/postgres/src/stores/order-table.ts +++ b/indexer/packages/postgres/src/stores/order-table.ts @@ -65,7 +65,9 @@ export async function findAll( reduceOnly, orderFlags, goodTilBlockBeforeOrAt, + goodTilBlockAfter, goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, clientMetadata, triggerPrice, page, @@ -166,6 +168,15 @@ export async function findAll( goodTilBlockBeforeOrAt, ); } + if (goodTilBlockAfter !== undefined) { + baseQuery = baseQuery.whereNotNull( + OrderColumns.goodTilBlock, + ).andWhere( + OrderColumns.goodTilBlock, + '>', + goodTilBlockAfter, + ); + } // If filtering by `goodTilBlockTime`, filter out all rows with NULL `goodTilBlockTime` if (goodTilBlockTimeBeforeOrAt !== undefined) { @@ -177,6 +188,15 @@ export async function findAll( goodTilBlockTimeBeforeOrAt, ); } + if (goodTilBlockTimeAfter !== undefined) { + baseQuery = baseQuery.whereNotNull( + OrderColumns.goodTilBlockTime, + ).andWhere( + OrderColumns.goodTilBlockTime, + '>', + goodTilBlockTimeAfter, + ); + } if (options.orderBy !== undefined) { for (const [column, order] of options.orderBy) { diff --git a/indexer/packages/postgres/src/types/query-types.ts b/indexer/packages/postgres/src/types/query-types.ts index 3b5cd025e2d..e3ac85d0812 100644 --- a/indexer/packages/postgres/src/types/query-types.ts +++ b/indexer/packages/postgres/src/types/query-types.ts @@ -58,7 +58,9 @@ export enum QueryableField { EFFECTIVE_BEFORE_OR_AT = 'effectiveBeforeOrAt', EFFECTIVE_BEFORE_OR_AT_HEIGHT = 'effectiveBeforeOrAtHeight', GOOD_TIL_BLOCK_BEFORE_OR_AT = 'goodTilBlockBeforeOrAt', + GOOD_TIL_BLOCK_AFTER = 'goodTilBlockAfter', GOOD_TIL_BLOCK_TIME_BEFORE_OR_AT = 'goodTilBlockTimeBeforeOrAt', + GOOD_TIL_BLOCK_TIME_AFTER = 'goodTilBlockTimeAfter', TICKER = 'ticker', RESOLUTION = 'resolution', FROM_ISO = 'fromISO', @@ -141,7 +143,9 @@ export interface OrderQueryConfig extends QueryConfig { [QueryableField.POST_ONLY]?: boolean, [QueryableField.REDUCE_ONLY]?: boolean, [QueryableField.GOOD_TIL_BLOCK_BEFORE_OR_AT]?: string, + [QueryableField.GOOD_TIL_BLOCK_AFTER]?: string, [QueryableField.GOOD_TIL_BLOCK_TIME_BEFORE_OR_AT]?: string, + [QueryableField.GOOD_TIL_BLOCK_TIME_AFTER]?: string, [QueryableField.ORDER_FLAGS]?: string, [QueryableField.CLIENT_METADATA]?: string, [QueryableField.TRIGGER_PRICE]?: string, diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/orders-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/orders-controller.test.ts index a43a419af91..643c2f80ea2 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/orders-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/orders-controller.test.ts @@ -409,7 +409,7 @@ describe('orders-controller#V4', () => { redisTestConstants.defaultRedisOrder, ], [ - 'goodTilBlock', + 'goodTilBlockBeforeOrAt', [ redisTestConstants.defaultRedisOrder, redisOrderWithDifferentMarket, @@ -424,7 +424,7 @@ describe('orders-controller#V4', () => { redisTestConstants.defaultRedisOrder, ], [ - 'goodTilBlock', + 'goodTilBlockBeforeOrAt with isolated market', [ redisTestConstants.defaultRedisOrder, { @@ -444,7 +444,42 @@ describe('orders-controller#V4', () => { redisTestConstants.defaultRedisOrder, ], [ - 'goodTilBlockTime', + 'goodTilBlockAfter', + [ + redisTestConstants.defaultRedisOrder, + redisOrderWithDifferentMarket, + redisTestConstants.defaultRedisOrderGoodTilBlockTime, + ], + { + ...defaultQueryParams, + goodTilBlockAfter: protocolTranslations.getGoodTilBlock( + redisOrderWithDifferentMarket.order!, + )! - 1, + }, + redisOrderWithDifferentMarket, + ], + [ + 'goodTilBlockAfter with isolated market', + [ + redisTestConstants.defaultRedisOrder, + { + ...redisTestConstants.isolatedMarketRedisOrder, + order: { + ...redisTestConstants.isolatedMarketOrder, + goodTilBlock: 1200, + }, + }, + ], + { + ...defaultQueryParams, + goodTilBlockAfter: protocolTranslations.getGoodTilBlock( + redisTestConstants.defaultRedisOrder.order!, + )! - 1, + }, + redisTestConstants.defaultRedisOrder, + ], + [ + 'goodTilBlockTimeBeforeOrAt', [ redisTestConstants.defaultRedisOrder, redisTestConstants.defaultRedisOrderGoodTilBlockTime, @@ -458,6 +493,19 @@ describe('orders-controller#V4', () => { }, redisTestConstants.defaultRedisOrderGoodTilBlockTime, ], + [ + 'goodTilBlockTimeAfter', + [ + redisTestConstants.defaultRedisOrder, + redisTestConstants.defaultRedisOrderGoodTilBlockTime, + newerRedisOrderGoodTilBlockTime, + ], + { + ...defaultQueryParams, + goodTilBlockTimeAfter: '2020-09-13T12:26:39.000Z', + }, + newerRedisOrderGoodTilBlockTime, + ], ])('Successfully filters redis order by %s', async ( _testName: string, redisOrders: RedisOrder[], diff --git a/indexer/services/comlink/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index 0aebabb2559..61c7d863145 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -2001,7 +2001,9 @@ fetch(`${baseURL}/orders?address=string&subaccountNumber=0.1`, |type|query|[OrderType](#schemaordertype)|false|none| |status|query|array[any]|false|none| |goodTilBlockBeforeOrAt|query|number(double)|false|none| +|goodTilBlockAfter|query|number(double)|false|none| |goodTilBlockTimeBeforeOrAt|query|[IsoString](#schemaisostring)|false|none| +|goodTilBlockTimeAfter|query|[IsoString](#schemaisostring)|false|none| |returnLatestOrders|query|boolean|false|none| #### Enumerated Values @@ -2195,7 +2197,9 @@ fetch(`${baseURL}/orders/parentSubaccountNumber?address=string&parentSubaccountN |type|query|[OrderType](#schemaordertype)|false|none| |status|query|array[any]|false|none| |goodTilBlockBeforeOrAt|query|number(double)|false|none| +|goodTilBlockAfter|query|number(double)|false|none| |goodTilBlockTimeBeforeOrAt|query|[IsoString](#schemaisostring)|false|none| +|goodTilBlockTimeAfter|query|[IsoString](#schemaisostring)|false|none| |returnLatestOrders|query|boolean|false|none| #### Enumerated Values diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index df26429665e..b1690af718a 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -2750,6 +2750,15 @@ "type": "number" } }, + { + "in": "query", + "name": "goodTilBlockAfter", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + }, { "in": "query", "name": "goodTilBlockTimeBeforeOrAt", @@ -2758,6 +2767,14 @@ "$ref": "#/components/schemas/IsoString" } }, + { + "in": "query", + "name": "goodTilBlockTimeAfter", + "required": false, + "schema": { + "$ref": "#/components/schemas/IsoString" + } + }, { "in": "query", "name": "returnLatestOrders", @@ -2859,6 +2876,15 @@ "type": "number" } }, + { + "in": "query", + "name": "goodTilBlockAfter", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + }, { "in": "query", "name": "goodTilBlockTimeBeforeOrAt", @@ -2867,6 +2893,14 @@ "$ref": "#/components/schemas/IsoString" } }, + { + "in": "query", + "name": "goodTilBlockTimeAfter", + "required": false, + "schema": { + "$ref": "#/components/schemas/IsoString" + } + }, { "in": "query", "name": "returnLatestOrders", diff --git a/indexer/services/comlink/src/controllers/api/v4/orders-controller.ts b/indexer/services/comlink/src/controllers/api/v4/orders-controller.ts index 90be17657f6..54c14e929eb 100644 --- a/indexer/services/comlink/src/controllers/api/v4/orders-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/orders-controller.ts @@ -84,7 +84,9 @@ async function listOrdersCommon( type?: OrderType, status?: APIOrderStatus[], goodTilBlockBeforeOrAt?: number, + goodTilBlockAfter?: number, goodTilBlockTimeBeforeOrAt?: IsoString, + goodTilBlockTimeAfter?: IsoString, returnLatestOrders?: boolean, ): Promise { let clobPairId: string | undefined; @@ -101,7 +103,9 @@ async function listOrdersCommon( side, type, goodTilBlockBeforeOrAt: goodTilBlockBeforeOrAt?.toString(), + goodTilBlockAfter: goodTilBlockAfter?.toString(), goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, }; if (!_.isEmpty(status)) { // BEST_EFFORT_OPENED status is not filtered out, because it's a minor optimization, @@ -124,7 +128,9 @@ async function listOrdersCommon( side, type, goodTilBlockBeforeOrAt, + goodTilBlockAfter, goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, ), OrderTable.findAll( orderQueryConfig, [], { @@ -207,7 +213,9 @@ class OrdersController extends Controller { @Query() type?: OrderType, @Query() status?: APIOrderStatus[], @Query() goodTilBlockBeforeOrAt?: number, + @Query() goodTilBlockAfter?: number, @Query() goodTilBlockTimeBeforeOrAt?: IsoString, + @Query() goodTilBlockTimeAfter?: IsoString, @Query() returnLatestOrders?: boolean, ): Promise { @@ -221,7 +229,9 @@ class OrdersController extends Controller { type, status, goodTilBlockBeforeOrAt, + goodTilBlockAfter, goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, returnLatestOrders, ); } @@ -236,7 +246,9 @@ class OrdersController extends Controller { @Query() type?: OrderType, @Query() status?: APIOrderStatus[], @Query() goodTilBlockBeforeOrAt?: number, + @Query() goodTilBlockAfter?: number, @Query() goodTilBlockTimeBeforeOrAt?: IsoString, + @Query() goodTilBlockTimeAfter?: IsoString, @Query() returnLatestOrders?: boolean, ): Promise { const childIdtoSubaccountNumber: Record = {}; @@ -254,7 +266,9 @@ class OrdersController extends Controller { type, status, goodTilBlockBeforeOrAt, + goodTilBlockAfter, goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, returnLatestOrders, ); } @@ -351,11 +365,23 @@ router.get( options: { gt: 0 }, }, }, + goodTilBlockAfter: { + in: 'query', + optional: true, + isInt: { + options: { gt: 0 }, + }, + }, goodTilBlockTimeBeforeOrAt: { in: 'query', optional: true, isISO8601: true, }, + goodTilBlockTimeAfter: { + in: 'query', + optional: true, + isISO8601: true, + }, returnLatestOrders: { in: 'query', isBoolean: true, @@ -378,7 +404,9 @@ router.get( type, status, goodTilBlockBeforeOrAt, + goodTilBlockAfter, goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, returnLatestOrders, }: ListOrderRequest = matchedData(req) as ListOrderRequest; @@ -396,7 +424,9 @@ router.get( type, status, goodTilBlockBeforeOrAt, + goodTilBlockAfter, goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, returnLatestOrders, ); @@ -489,7 +519,9 @@ router.get( type, status, goodTilBlockBeforeOrAt, + goodTilBlockAfter, goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, returnLatestOrders, }: ParentSubaccountListOrderRequest = matchedData(req) as ParentSubaccountListOrderRequest; @@ -507,7 +539,9 @@ router.get( type, status, goodTilBlockBeforeOrAt, + goodTilBlockAfter, goodTilBlockTimeBeforeOrAt, + goodTilBlockTimeAfter, returnLatestOrders, ); @@ -588,7 +622,9 @@ async function getRedisOrderMapForSubaccountIds( side?: OrderSide, type?: OrderType, goodTilBlockBeforeOrAt?: number, + goodTilBlockAfter?: number, goodTilBlockTimeBeforeOrAt?: IsoString, + goodTilBlockTimeAfter?: IsoString, ): Promise { if (type !== undefined && type !== OrderType.LIMIT) { // TODO(DEC-1458): Add support for advanced Orders @@ -633,10 +669,13 @@ async function getRedisOrderMapForSubaccountIds( if (goodTilBlockBeforeOrAt !== undefined && redisGoodTilBlock > goodTilBlockBeforeOrAt) { return false; } + if (goodTilBlockAfter !== undefined && redisGoodTilBlock <= goodTilBlockAfter) { + return false; + } } else { - // If `goodTilBlockBeforeOrAt` is defined as a filter, filter out all orders that don't have - // `goodTilBlock` defined - if (goodTilBlockBeforeOrAt !== undefined) { + // If `goodTilBlockBeforeOrAt` or `goodTilBlockAfter` is defined as a filter, filter out all + // orders that don't have `goodTilBlock` defined + if (goodTilBlockBeforeOrAt !== undefined || goodTilBlockAfter !== undefined) { return false; } } @@ -650,10 +689,15 @@ async function getRedisOrderMapForSubaccountIds( ) { return false; } + if (goodTilBlockTimeAfter !== undefined && + redisGoodTilBlockTimeDateObj <= DateTime.fromISO(goodTilBlockTimeAfter) + ) { + return false; + } } else { - if (goodTilBlockTimeBeforeOrAt !== undefined) { - // If `goodTilBlockTimeBeforeOrAt` is defined as a filter, filter out all orders that - // don't have `goodTilBlockTime` defined + if (goodTilBlockTimeBeforeOrAt !== undefined || goodTilBlockTimeAfter !== undefined) { + // If `goodTilBlockTimeBeforeOrAt` or `goodTilBlockTimeAfter` is defined as a filter, + // filter out all orders that don't have `goodTilBlockTime` defined return false; } } diff --git a/indexer/services/comlink/src/types.ts b/indexer/services/comlink/src/types.ts index 7796f0ffc28..acb137ac2fb 100644 --- a/indexer/services/comlink/src/types.ts +++ b/indexer/services/comlink/src/types.ts @@ -529,7 +529,9 @@ export interface ListOrderRequest extends SubaccountRequest, LimitRequest, Ticke type?: OrderType, status?: OrderStatus[], goodTilBlockBeforeOrAt?: number, + goodTilBlockAfter?: number, goodTilBlockTimeBeforeOrAt?: IsoString, + goodTilBlockTimeAfter?: IsoString, returnLatestOrders?: boolean, } @@ -539,7 +541,9 @@ export interface ParentSubaccountListOrderRequest type?: OrderType, status?: OrderStatus[], goodTilBlockBeforeOrAt?: number, + goodTilBlockAfter?: number, goodTilBlockTimeBeforeOrAt?: IsoString, + goodTilBlockTimeAfter?: IsoString, returnLatestOrders?: boolean, } diff --git a/indexer/services/socks/__tests__/lib/message-forwarder.test.ts b/indexer/services/socks/__tests__/lib/message-forwarder.test.ts index 7395153021b..791d80ea0f9 100644 --- a/indexer/services/socks/__tests__/lib/message-forwarder.test.ts +++ b/indexer/services/socks/__tests__/lib/message-forwarder.test.ts @@ -174,10 +174,10 @@ describe('message-forwarder', () => { childSubaccount2Messages[1], ]; - const mockAxiosResponse: Object = { a: 'b' }; + const mockAxiosResponse: Object[] = ['a', 'b']; const subaccountInitialMessage: Object = { ...mockAxiosResponse, - orders: mockAxiosResponse, + orders: mockAxiosResponse.concat(mockAxiosResponse), blockHeight: '2', }; diff --git a/indexer/services/socks/__tests__/lib/subscriptions.test.ts b/indexer/services/socks/__tests__/lib/subscriptions.test.ts index f23d1bb254e..02bf9ceea2d 100644 --- a/indexer/services/socks/__tests__/lib/subscriptions.test.ts +++ b/indexer/services/socks/__tests__/lib/subscriptions.test.ts @@ -58,7 +58,8 @@ describe('Subscriptions', () => { const initialResponseUrlPatterns: Record = { [Channel.V4_ACCOUNTS]: [ '/v4/addresses/.+/subaccountNumber/.+', - '/v4/orders?.+subaccountNumber.+OPEN,UNTRIGGERED,BEST_EFFORT_OPENED,BEST_EFFORT_CANCELED', + '/v4/orders?.+subaccountNumber.+OPEN,UNTRIGGERED,BEST_EFFORT_OPENED', + '/v4/orders?.+subaccountNumber.+BEST_EFFORT_CANCELED.+goodTilBlockAfter.+', ], [Channel.V4_CANDLES]: ['/v4/candles/perpetualMarkets/.+?resolution=.+'], [Channel.V4_MARKETS]: ['/v4/perpetualMarkets'], @@ -66,11 +67,12 @@ describe('Subscriptions', () => { [Channel.V4_TRADES]: ['/v4/trades/perpetualMarket/.+'], [Channel.V4_PARENT_ACCOUNTS]: [ '/v4/addresses/.+/parentSubaccountNumber/.+', - '/v4/orders/parentSubaccountNumber?.+parentSubaccountNumber.+OPEN,UNTRIGGERED,BEST_EFFORT_OPENED,BEST_EFFORT_CANCELED', + '/v4/orders/parentSubaccountNumber?.+parentSubaccountNumber.+OPEN,UNTRIGGERED,BEST_EFFORT_OPENED', + '/v4/orders/parentSubaccountNumber?.+parentSubaccountNumber.+BEST_EFFORT_CANCELED.+goodTilBlockAfter.+', ], [Channel.V4_BLOCK_HEIGHT]: ['v4/height'], }; - const initialMessage: Object = { a: 'b' }; + const initialMessage: Object = ['a', 'b']; const country: string = 'AR'; beforeAll(async () => { diff --git a/indexer/services/socks/src/lib/subscription.ts b/indexer/services/socks/src/lib/subscription.ts index fd797717b1b..cdfa9ebdce0 100644 --- a/indexer/services/socks/src/lib/subscription.ts +++ b/indexer/services/socks/src/lib/subscription.ts @@ -13,6 +13,7 @@ import { MAX_PARENT_SUBACCOUNTS, OrderStatus, perpetualMarketRefresher, + OrderFromDatabase, } from '@dydxprotocol-indexer/postgres'; import WebSocket from 'ws'; @@ -37,8 +38,8 @@ const VALID_ORDER_STATUS_FOR_INITIAL_SUBACCOUNT_RESPONSE: APIOrderStatus[] = [ OrderStatus.OPEN, OrderStatus.UNTRIGGERED, BestEffortOpenedStatus.BEST_EFFORT_OPENED, - OrderStatus.BEST_EFFORT_CANCELED, ]; + const VALID_ORDER_STATUS: string = VALID_ORDER_STATUS_FOR_INITIAL_SUBACCOUNT_RESPONSE.join(','); export class Subscriptions { @@ -553,10 +554,13 @@ export class Subscriptions { subaccountNumber: string, } = this.parseSubaccountChannelId(id); + const blockHeight: string = await blockHeightRefresher.getLatestBlockHeight(); + const numBlockHeight: number = parseInt(blockHeight, 10); + const [ subaccountsResponse, ordersResponse, - blockHeight, + currentBestEffortCanceledOrdersResponse, ]: [ string, string, @@ -581,12 +585,26 @@ export class Subscriptions { }, transformResponse: (res) => res, }), - blockHeightRefresher.getLatestBlockHeight(), + axiosRequest({ + method: RequestMethod.GET, + url: `${COMLINK_URL}/v4/orders?address=${address}&subaccountNumber=${subaccountNumber}&status=BEST_EFFORT_CANCELED&goodTilBlockAfter=$${Math.max(numBlockHeight - 20, 1)}`, + timeout: config.INITIAL_GET_TIMEOUT_MS, + headers: { + 'cf-ipcountry': country, + }, + transformResponse: (res) => res, + }), ]); + const orders: OrderFromDatabase[] = JSON.parse(ordersResponse); + const currentBestEffortCanceledOrders: OrderFromDatabase[] = JSON.parse( + currentBestEffortCanceledOrdersResponse, + ); + const allOrders: OrderFromDatabase[] = orders.concat(currentBestEffortCanceledOrders); + return JSON.stringify({ ...JSON.parse(subaccountsResponse), - orders: JSON.parse(ordersResponse), + orders: allOrders, blockHeight, }); } catch (error) { @@ -622,10 +640,13 @@ export class Subscriptions { subaccountNumber: string, } = this.parseSubaccountChannelId(id); + const blockHeight: string = await blockHeightRefresher.getLatestBlockHeight(); + const numBlockHeight: number = parseInt(blockHeight, 10); + const [ subaccountsResponse, ordersResponse, - blockHeight, + currentBestEffortCanceledOrdersResponse, ]: [ string, string, @@ -650,12 +671,26 @@ export class Subscriptions { }, transformResponse: (res) => res, }), - blockHeightRefresher.getLatestBlockHeight(), + axiosRequest({ + method: RequestMethod.GET, + url: `${COMLINK_URL}/v4/orders/parentSubaccountNumber?address=${address}&parentSubaccountNumber=${subaccountNumber}&status=BEST_EFFORT_CANCELED&goodTilBlockAfter=${Math.max(numBlockHeight - 20, 1)}`, + timeout: config.INITIAL_GET_TIMEOUT_MS, + headers: { + 'cf-ipcountry': country, + }, + transformResponse: (res) => res, + }), ]); + const orders: OrderFromDatabase[] = JSON.parse(ordersResponse); + const currentBestEffortCanceledOrders: OrderFromDatabase[] = JSON.parse( + currentBestEffortCanceledOrdersResponse, + ); + const allOrders: OrderFromDatabase[] = orders.concat(currentBestEffortCanceledOrders); + return JSON.stringify({ ...JSON.parse(subaccountsResponse), - orders: JSON.parse(ordersResponse), + orders: allOrders, blockHeight, }); } catch (error) {