From b846fc3c843f447d7de5925c0869a3c0732c1631 Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Tue, 6 Sep 2022 00:02:57 +0800 Subject: [PATCH] add new latest_price_info endpoint (#264) * add new latest_price_info endpoint * add verbose query param to latest_price_feeds * add back logging statement for VAA * trigger CI --- .../price-service/src/__tests__/rest.test.ts | 1 + third_party/pyth/price-service/src/listen.ts | 18 ++++---- third_party/pyth/price-service/src/rest.ts | 43 ++++++++++++------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/third_party/pyth/price-service/src/__tests__/rest.test.ts b/third_party/pyth/price-service/src/__tests__/rest.test.ts index 1e45b098db..d8ec44ac63 100644 --- a/third_party/pyth/price-service/src/__tests__/rest.test.ts +++ b/third_party/pyth/price-service/src/__tests__/rest.test.ts @@ -43,6 +43,7 @@ function dummyPriceInfoPair( attestationTime: 0, seqNum, vaaBytes: Buffer.from(vaa, "hex").toString("binary"), + emitterChainId: 0, }, ]; } diff --git a/third_party/pyth/price-service/src/listen.ts b/third_party/pyth/price-service/src/listen.ts index 96f0b9e5a5..34c00ede47 100644 --- a/third_party/pyth/price-service/src/listen.ts +++ b/third_party/pyth/price-service/src/listen.ts @@ -1,36 +1,37 @@ import { ChainId, hexToUint8Array, - uint8ArrayToHex, + uint8ArrayToHex } from "@certusone/wormhole-sdk"; import { createSpyRPCServiceClient, - subscribeSignedVAA, + subscribeSignedVAA } from "@certusone/wormhole-spydk"; import { importCoreWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; -import { envOrErr, sleep, TimestampInSec } from "./helpers"; -import { PromClient } from "./promClient"; import { getBatchSummary, parseBatchPriceAttestation, - priceAttestationToPriceFeed, + priceAttestationToPriceFeed } from "@certusone/p2w-sdk"; -import { ClientReadableStream } from "@grpc/grpc-js"; import { FilterEntry, - SubscribeSignedVAAResponse, + SubscribeSignedVAAResponse } from "@certusone/wormhole-spydk/lib/cjs/proto/spy/v1/spy"; -import { logger } from "./logging"; +import { ClientReadableStream } from "@grpc/grpc-js"; import { HexString, PriceFeed } from "@pythnetwork/pyth-sdk-js"; +import { sleep, TimestampInSec } from "./helpers"; +import { logger } from "./logging"; +import { PromClient } from "./promClient"; export type PriceInfo = { vaaBytes: string; seqNum: number; attestationTime: TimestampInSec; priceFeed: PriceFeed; + emitterChainId: number; }; export interface PriceStore { @@ -196,6 +197,7 @@ export class Listener implements PriceStore { vaaBytes: vaaBytes, attestationTime: priceAttestation.attestationTime, priceFeed, + emitterChainId: parsedVAA.emitter_chain, }); for (let callback of this.updateCallbacks) { diff --git a/third_party/pyth/price-service/src/rest.ts b/third_party/pyth/price-service/src/rest.ts index d19731ad05..3080077d58 100644 --- a/third_party/pyth/price-service/src/rest.ts +++ b/third_party/pyth/price-service/src/rest.ts @@ -1,15 +1,14 @@ -import express, { Express } from "express"; import cors from "cors"; +import express, { NextFunction, Request, Response } from "express"; +import { Joi, schema, validate, ValidationError } from "express-validation"; +import { Server } from "http"; +import { StatusCodes } from "http-status-codes"; import morgan from "morgan"; import responseTime from "response-time"; -import { Request, Response, NextFunction } from "express"; +import { DurationInMs, DurationInSec } from "./helpers"; import { PriceStore } from "./listen"; import { logger } from "./logging"; import { PromClient } from "./promClient"; -import { DurationInMs, DurationInSec } from "./helpers"; -import { StatusCodes } from "http-status-codes"; -import { validate, ValidationError, Joi, schema } from "express-validation"; -import { Server } from "http"; const MORGAN_LOG_FORMAT = ':remote-addr - :remote-user ":method :url HTTP/:http-version"' + @@ -135,6 +134,7 @@ export class RestAPI { ids: Joi.array() .items(Joi.string().regex(/^(0x)?[a-f0-9]{64}$/)) .required(), + verbose: Joi.boolean(), }).required(), }; app.get( @@ -142,6 +142,8 @@ export class RestAPI { validate(latestPriceFeedsInputSchema), (req: Request, res: Response) => { let priceIds = req.query.ids as string[]; + // verbose is optional, default to false + let verbose = req.query.verbose === "true"; let responseJson = []; @@ -167,7 +169,18 @@ export class RestAPI { freshness ); - responseJson.push(latestPriceInfo.priceFeed.toJson()); + if (verbose) { + responseJson.push({ + ...latestPriceInfo.priceFeed.toJson(), + metadata: { + emitter_chain: latestPriceInfo.emitterChainId, + attestation_time: latestPriceInfo.attestationTime, + sequence_number: latestPriceInfo.seqNum, + }, + }); + } else { + responseJson.push(latestPriceInfo.priceFeed.toJson()); + } } if (notFoundIds.length > 0) { @@ -180,18 +193,16 @@ export class RestAPI { endpoints.push( "api/latest_price_feeds?ids[]=&ids[]=&.." ); - - app.get( - "/api/price_feed_ids", - (req: Request, res: Response) => { - const availableIds = this.priceFeedVaaInfo.getPriceIds(); - res.json([...availableIds]); - } - ); endpoints.push( - "api/price_feed_ids" + "api/latest_price_feeds?ids[]=&ids[]=&..&verbose=true" ); + app.get("/api/price_feed_ids", (req: Request, res: Response) => { + const availableIds = this.priceFeedVaaInfo.getPriceIds(); + res.json([...availableIds]); + }); + endpoints.push("api/price_feed_ids"); + app.get("/ready", (_, res: Response) => { if (this.isReady!()) { res.sendStatus(StatusCodes.OK);