Choose unique VAA for query time if it exists (#853)
* hmm this way works but is complicated * choose unique vaa * update tests * lint * bump versions
This commit is contained in:
parent
af2d7b6e38
commit
64bce66383
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/price-service-server",
|
||||
"version": "3.0.4",
|
||||
"version": "3.0.5",
|
||||
"description": "Webservice for retrieving prices from the Pyth oracle.",
|
||||
"private": "true",
|
||||
"main": "index.js",
|
||||
|
|
|
@ -6,53 +6,73 @@ describe("VAA Cache works", () => {
|
|||
|
||||
expect(cache.get("a", 3)).toBeUndefined();
|
||||
|
||||
cache.set("a", 1, "a-1");
|
||||
cache.set("a", 1, 0, "a-1");
|
||||
|
||||
expect(cache.get("a", 3)).toBeUndefined();
|
||||
|
||||
cache.set("a", 4, "a-2");
|
||||
cache.set("a", 4, 3, "a-2");
|
||||
|
||||
expect(cache.get("a", 3)).toEqual<VaaConfig>({
|
||||
publishTime: 4,
|
||||
lastAttestedPublishTime: 3,
|
||||
vaa: "a-2",
|
||||
});
|
||||
|
||||
cache.set("a", 10, "a-3");
|
||||
cache.set("a", 10, 9, "a-3");
|
||||
cache.set("a", 10, 10, "a-4");
|
||||
cache.set("a", 10, 10, "a-5");
|
||||
cache.set("a", 10, 10, "a-6");
|
||||
cache.set("a", 11, 11, "a-7");
|
||||
|
||||
// Adding some elements with other keys to make sure
|
||||
// they are not stored separately.
|
||||
cache.set("b", 3, "b-1");
|
||||
cache.set("b", 7, "b-2");
|
||||
cache.set("b", 9, "b-3");
|
||||
cache.set("b", 3, 2, "b-1");
|
||||
cache.set("b", 7, 6, "b-2");
|
||||
cache.set("b", 9, 8, "b-3");
|
||||
|
||||
expect(cache.get("a", 3)).toEqual<VaaConfig>({
|
||||
publishTime: 4,
|
||||
lastAttestedPublishTime: 3,
|
||||
vaa: "a-2",
|
||||
});
|
||||
expect(cache.get("a", 4)).toEqual<VaaConfig>({
|
||||
publishTime: 4,
|
||||
lastAttestedPublishTime: 3,
|
||||
vaa: "a-2",
|
||||
});
|
||||
expect(cache.get("a", 5)).toEqual<VaaConfig>({
|
||||
publishTime: 10,
|
||||
lastAttestedPublishTime: 9,
|
||||
vaa: "a-3",
|
||||
});
|
||||
// There are multiple elements at 10, but we prefer to return the one with a lower lastAttestedPublishTime.
|
||||
expect(cache.get("a", 10)).toEqual<VaaConfig>({
|
||||
publishTime: 10,
|
||||
lastAttestedPublishTime: 9,
|
||||
vaa: "a-3",
|
||||
});
|
||||
// If the cache only contains elements where the lastAttestedPublishTime==publishTime, those will be returned.
|
||||
// Note that this behavior is undesirable (as this means we can return a noncanonical VAA for a query time);
|
||||
// this test simply documents it.
|
||||
expect(cache.get("a", 11)).toEqual<VaaConfig>({
|
||||
publishTime: 11,
|
||||
lastAttestedPublishTime: 11,
|
||||
vaa: "a-7",
|
||||
});
|
||||
|
||||
expect(cache.get("b", 3)).toEqual<VaaConfig>({
|
||||
publishTime: 3,
|
||||
lastAttestedPublishTime: 2,
|
||||
vaa: "b-1",
|
||||
});
|
||||
expect(cache.get("b", 4)).toEqual<VaaConfig>({
|
||||
publishTime: 7,
|
||||
lastAttestedPublishTime: 6,
|
||||
vaa: "b-2",
|
||||
});
|
||||
|
||||
// When no item item more recent than asked pubTime is asked it should return undefined
|
||||
expect(cache.get("a", 11)).toBeUndefined();
|
||||
expect(cache.get("a", 12)).toBeUndefined();
|
||||
expect(cache.get("b", 10)).toBeUndefined();
|
||||
|
||||
// When the asked pubTime is less than the first existing pubTime we are not sure that
|
||||
|
@ -68,17 +88,19 @@ describe("VAA Cache works", () => {
|
|||
// TTL of 500 seconds for the cache
|
||||
const cache = new VaaCache(500);
|
||||
|
||||
cache.set("a", 300, "a-1");
|
||||
cache.set("a", 700, "a-2");
|
||||
cache.set("a", 900, "a-3");
|
||||
cache.set("a", 300, 299, "a-1");
|
||||
cache.set("a", 700, 699, "a-2");
|
||||
cache.set("a", 900, 899, "a-3");
|
||||
|
||||
expect(cache.get("a", 300)).toEqual<VaaConfig>({
|
||||
publishTime: 300,
|
||||
lastAttestedPublishTime: 299,
|
||||
vaa: "a-1",
|
||||
});
|
||||
|
||||
expect(cache.get("a", 500)).toEqual<VaaConfig>({
|
||||
publishTime: 700,
|
||||
lastAttestedPublishTime: 699,
|
||||
vaa: "a-2",
|
||||
});
|
||||
|
||||
|
@ -98,12 +120,13 @@ describe("VAA Cache works", () => {
|
|||
const cache = new VaaCache(500, 100);
|
||||
cache.runRemoveExpiredValuesLoop();
|
||||
|
||||
cache.set("a", 300, "a-1");
|
||||
cache.set("a", 700, "a-2");
|
||||
cache.set("a", 900, "a-3");
|
||||
cache.set("a", 300, 299, "a-1");
|
||||
cache.set("a", 700, 699, "a-2");
|
||||
cache.set("a", 900, 899, "a-3");
|
||||
|
||||
expect(cache.get("a", 900)).toEqual<VaaConfig>({
|
||||
publishTime: 900,
|
||||
lastAttestedPublishTime: 899,
|
||||
vaa: "a-3",
|
||||
});
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import express, { Express } from "express";
|
|||
import { StatusCodes } from "http-status-codes";
|
||||
import request from "supertest";
|
||||
import { PriceInfo, PriceStore, VaaCache, VaaConfig } from "../listen";
|
||||
import { RestAPI } from "../rest";
|
||||
import { RestAPI, VaaResponse } from "../rest";
|
||||
|
||||
let priceInfo: PriceStore;
|
||||
let app: Express;
|
||||
|
@ -52,10 +52,18 @@ function dummyPriceInfoPair(
|
|||
vaa: Buffer.from(vaa, "hex"),
|
||||
emitterChainId: 0,
|
||||
priceServiceReceiveTime: 0,
|
||||
lastAttestedPublishTime: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Add some dummy data to the provided vaa cache.
|
||||
function addAbcdDataToCache(id: string, cache: VaaCache) {
|
||||
cache.set(id, 10, 9, "abcd10");
|
||||
cache.set(id, 20, 19, "abcd20");
|
||||
cache.set(id, 30, 29, "abcd30");
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
priceInfoMap = new Map<string, PriceInfo>([
|
||||
dummyPriceInfoPair(expandTo64Len("abcd"), 1, "a1b2c3d4"),
|
||||
|
@ -258,16 +266,14 @@ describe("Latest Vaa Bytes Endpoint", () => {
|
|||
describe("Get VAA endpoint and Get VAA CCIP", () => {
|
||||
test("When called with valid id and timestamp in the cache returns the correct answer", async () => {
|
||||
const id = expandTo64Len("abcd");
|
||||
vaasCache.set(id, 10, "abcd10");
|
||||
vaasCache.set(id, 20, "abcd20");
|
||||
vaasCache.set(id, 30, "abcd30");
|
||||
addAbcdDataToCache(id, vaasCache);
|
||||
|
||||
const resp = await request(app).get("/api/get_vaa").query({
|
||||
id,
|
||||
publish_time: 16,
|
||||
});
|
||||
expect(resp.status).toBe(StatusCodes.OK);
|
||||
expect(resp.body).toEqual<VaaConfig>({
|
||||
expect(resp.body).toEqual<VaaResponse>({
|
||||
vaa: "abcd20",
|
||||
publishTime: 20,
|
||||
});
|
||||
|
@ -286,9 +292,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
|
||||
test("When called with valid id with leading 0x and timestamp in the cache returns the correct answer", async () => {
|
||||
const id = expandTo64Len("abcd");
|
||||
vaasCache.set(id, 10, "abcd10");
|
||||
vaasCache.set(id, 20, "abcd20");
|
||||
vaasCache.set(id, 30, "abcd30");
|
||||
addAbcdDataToCache(id, vaasCache);
|
||||
|
||||
const resp = await request(app)
|
||||
.get("/api/get_vaa")
|
||||
|
@ -297,7 +301,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
publish_time: 16,
|
||||
});
|
||||
expect(resp.status).toBe(StatusCodes.OK);
|
||||
expect(resp.body).toEqual<VaaConfig>({
|
||||
expect(resp.body).toEqual<VaaResponse>({
|
||||
vaa: "abcd20",
|
||||
publishTime: 20,
|
||||
});
|
||||
|
@ -305,9 +309,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
|
||||
test("When called with target_chain, encodes resulting VAA in the right format", async () => {
|
||||
const id = expandTo64Len("abcd");
|
||||
vaasCache.set(id, 10, "abcd10");
|
||||
vaasCache.set(id, 20, "abcd20");
|
||||
vaasCache.set(id, 30, "abcd30");
|
||||
addAbcdDataToCache(id, vaasCache);
|
||||
|
||||
const resp = await request(app)
|
||||
.get("/api/get_vaa")
|
||||
|
@ -317,7 +319,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
target_chain: "evm",
|
||||
});
|
||||
expect(resp.status).toBe(StatusCodes.OK);
|
||||
expect(resp.body).toEqual<VaaConfig>({
|
||||
expect(resp.body).toEqual<VaaResponse>({
|
||||
vaa: "0x" + Buffer.from("abcd20", "base64").toString("hex"),
|
||||
publishTime: 20,
|
||||
});
|
||||
|
@ -346,9 +348,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
|
||||
test("When called with valid id and timestamp not in the cache without db returns vaa not found", async () => {
|
||||
const id = expandTo64Len("abcd");
|
||||
vaasCache.set(id, 10, "abcd10");
|
||||
vaasCache.set(id, 20, "abcd20");
|
||||
vaasCache.set(id, 30, "abcd30");
|
||||
addAbcdDataToCache(id, vaasCache);
|
||||
|
||||
const resp = await request(app)
|
||||
.get("/api/get_vaa")
|
||||
|
@ -396,9 +396,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
const appWithDb = await apiWithDb.createApp();
|
||||
|
||||
const id = expandTo64Len("abcd");
|
||||
vaasCache.set(id, 10, "abcd10");
|
||||
vaasCache.set(id, 20, "abcd20");
|
||||
vaasCache.set(id, 30, "abcd30");
|
||||
addAbcdDataToCache(id, vaasCache);
|
||||
|
||||
const resp = await request(appWithDb)
|
||||
.get("/api/get_vaa")
|
||||
|
@ -407,7 +405,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
publish_time: 5,
|
||||
});
|
||||
expect(resp.status).toBe(StatusCodes.OK);
|
||||
expect(resp.body).toEqual<VaaConfig>({
|
||||
expect(resp.body).toEqual<VaaResponse>({
|
||||
vaa: `pythnet${id}5`,
|
||||
publishTime: 5,
|
||||
});
|
||||
|
@ -451,9 +449,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
const appWithDb = await apiWithDb.createApp();
|
||||
|
||||
const id = expandTo64Len("abcd");
|
||||
vaasCache.set(id, 10, "abcd10");
|
||||
vaasCache.set(id, 20, "abcd20");
|
||||
vaasCache.set(id, 30, "abcd30");
|
||||
addAbcdDataToCache(id, vaasCache);
|
||||
|
||||
const resp = await request(appWithDb)
|
||||
.get("/api/get_vaa")
|
||||
|
@ -493,9 +489,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
|
|||
const appWithDb = await apiWithDb.createApp();
|
||||
|
||||
const id = expandTo64Len("abcd");
|
||||
vaasCache.set(id, 10, "abcd10");
|
||||
vaasCache.set(id, 20, "abcd20");
|
||||
vaasCache.set(id, 30, "abcd30");
|
||||
addAbcdDataToCache(id, vaasCache);
|
||||
|
||||
const resp = await request(appWithDb)
|
||||
.get("/api/get_vaa")
|
||||
|
|
|
@ -31,6 +31,7 @@ function dummyPriceInfo(id: HexString, vaa: HexString): PriceInfo {
|
|||
priceFeed: dummyPriceFeed(id),
|
||||
vaa: Buffer.from(vaa, "hex"),
|
||||
priceServiceReceiveTime: 4,
|
||||
lastAttestedPublishTime: -1,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ export type PriceInfo = {
|
|||
seqNum: number;
|
||||
publishTime: TimestampInSec;
|
||||
attestationTime: TimestampInSec;
|
||||
lastAttestedPublishTime: TimestampInSec;
|
||||
priceFeed: PriceFeed;
|
||||
emitterChainId: number;
|
||||
priceServiceReceiveTime: number;
|
||||
|
@ -45,6 +46,7 @@ export function createPriceInfo(
|
|||
vaa,
|
||||
publishTime: priceAttestation.publishTime,
|
||||
attestationTime: priceAttestation.attestationTime,
|
||||
lastAttestedPublishTime: priceAttestation.lastAttestedPublishTime,
|
||||
priceFeed,
|
||||
emitterChainId: emitterChain,
|
||||
priceServiceReceiveTime: Math.floor(new Date().getTime() / 1000),
|
||||
|
@ -78,6 +80,7 @@ type VaaKey = string;
|
|||
|
||||
export type VaaConfig = {
|
||||
publishTime: number;
|
||||
lastAttestedPublishTime: number;
|
||||
vaa: string;
|
||||
};
|
||||
|
||||
|
@ -95,11 +98,16 @@ export class VaaCache {
|
|||
this.cacheCleanupLoopInterval = cacheCleanupLoopInterval;
|
||||
}
|
||||
|
||||
set(key: VaaKey, publishTime: TimestampInSec, vaa: string): void {
|
||||
set(
|
||||
key: VaaKey,
|
||||
publishTime: TimestampInSec,
|
||||
lastAttestedPublishTime: TimestampInSec,
|
||||
vaa: string
|
||||
): void {
|
||||
if (this.cache.has(key)) {
|
||||
this.cache.get(key)!.push({ publishTime, vaa });
|
||||
this.cache.get(key)!.push({ publishTime, lastAttestedPublishTime, vaa });
|
||||
} else {
|
||||
this.cache.set(key, [{ publishTime, vaa }]);
|
||||
this.cache.set(key, [{ publishTime, lastAttestedPublishTime, vaa }]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +136,10 @@ export class VaaCache {
|
|||
|
||||
while (left <= right) {
|
||||
const middle = Math.floor((left + right) / 2);
|
||||
if (arr[middle].publishTime === publishTime) {
|
||||
if (
|
||||
arr[middle].publishTime === publishTime &&
|
||||
arr[middle].lastAttestedPublishTime < publishTime
|
||||
) {
|
||||
return arr[middle];
|
||||
} else if (arr[middle].publishTime < publishTime) {
|
||||
left = middle + 1;
|
||||
|
@ -368,6 +379,7 @@ export class Listener implements PriceStore {
|
|||
this.vaasCache.set(
|
||||
priceInfo.priceFeed.id,
|
||||
priceInfo.publishTime,
|
||||
priceInfo.lastAttestedPublishTime,
|
||||
priceInfo.vaa.toString("base64")
|
||||
);
|
||||
this.priceFeedVaaMap.set(key, priceInfo);
|
||||
|
|
|
@ -67,6 +67,11 @@ function asyncWrapper(
|
|||
};
|
||||
}
|
||||
|
||||
export type VaaResponse = {
|
||||
publishTime: number;
|
||||
vaa: string;
|
||||
};
|
||||
|
||||
export class RestAPI {
|
||||
private port: number;
|
||||
private priceFeedVaaInfo: PriceStore;
|
||||
|
@ -92,12 +97,18 @@ export class RestAPI {
|
|||
async getVaaWithDbLookup(
|
||||
priceFeedId: string,
|
||||
publishTime: TimestampInSec
|
||||
): Promise<VaaConfig | undefined> {
|
||||
): Promise<VaaResponse | undefined> {
|
||||
// Try to fetch the vaa from the local cache
|
||||
let vaa = this.priceFeedVaaInfo.getVaa(priceFeedId, publishTime);
|
||||
const vaaConfig = this.priceFeedVaaInfo.getVaa(priceFeedId, publishTime);
|
||||
let vaa: VaaResponse | undefined;
|
||||
|
||||
// if publishTime is older than cache ttl or vaa is not found, fetch from db
|
||||
if (vaa === undefined && this.dbApiEndpoint && this.dbApiCluster) {
|
||||
if (vaaConfig !== undefined) {
|
||||
vaa = {
|
||||
vaa: vaaConfig.vaa,
|
||||
publishTime: vaaConfig.publishTime,
|
||||
};
|
||||
} else if (vaa === undefined && this.dbApiEndpoint && this.dbApiCluster) {
|
||||
const priceFeedWithoutLeading0x = removeLeading0x(priceFeedId);
|
||||
|
||||
try {
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --passWithNoTests",
|
||||
"eject": "react-scripts eject",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/wormhole-attester-sdk",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Pyth Wormhole Attester SDk",
|
||||
"private": "true",
|
||||
"types": "lib/index.d.ts",
|
||||
|
|
|
@ -14,15 +14,15 @@ describe("Deserializing Batch Price Attestation works", () => {
|
|||
"50325748000300010001020003009D01010101010101010101010101010101010101010101010101010" +
|
||||
"10101010101FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE0000002B" +
|
||||
"AD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D00" +
|
||||
"000DEADBEEFFADE00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEFDEAD" +
|
||||
"BEEFFADEDEAF0202020202020202020202020202020202020202020202020202020202020202FDFDFDF" +
|
||||
"000DEADBEEFFADE00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0000" +
|
||||
"DEADBEEFFACE0202020202020202020202020202020202020202020202020202020202020202FDFDFDF" +
|
||||
"DFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFD0000002BAD2FEED70000000000" +
|
||||
"000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D00000DEADBEEFFADE000" +
|
||||
"00000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEFDEADBEEFFADEDEAF030303" +
|
||||
"00000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0000DEADBEEFFACE030303" +
|
||||
"0303030303030303030303030303030303030303030303030303030303FCFCFCFCFCFCFCFCFCFCFCFCF" +
|
||||
"CFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC0000002BAD2FEED70000000000000065FFFFFFFDFFFF" +
|
||||
"FFFFFFFFFFD6000000000000002A010001E14C0004E6D00000DEADBEEFFADE00000000DADEBEEF00000" +
|
||||
"000DEADBABE0000DEADFACEBEEF000000BADBADBEEFDEADBEEFFADEDEAF";
|
||||
"000DEADBABE0000DEADFACEBEEF000000BADBADBEEF0000DEADBEEFFACE";
|
||||
|
||||
const data = Buffer.from(fixture, "hex");
|
||||
const batchPriceAttestation = parseBatchPriceAttestation(data);
|
||||
|
@ -47,6 +47,7 @@ describe("Deserializing Batch Price Attestation works", () => {
|
|||
prevPublishTime: 0xdeadbabe,
|
||||
prevPrice: (0xdeadfacebeef).toString(),
|
||||
prevConf: (0xbadbadbeef).toString(),
|
||||
lastAttestedPublishTime: 0xdeadbeefface,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -70,6 +71,7 @@ describe("Price Attestation to Price Feed works", () => {
|
|||
prevPublishTime: 998,
|
||||
prevPrice: "101",
|
||||
prevConf: "6",
|
||||
lastAttestedPublishTime: 997,
|
||||
};
|
||||
|
||||
const priceFeed = priceAttestationToPriceFeed(priceAttestation);
|
||||
|
@ -109,6 +111,7 @@ describe("Price Attestation to Price Feed works", () => {
|
|||
prevPublishTime: 998,
|
||||
prevPrice: "101",
|
||||
prevConf: "6",
|
||||
lastAttestedPublishTime: 997,
|
||||
};
|
||||
|
||||
const priceFeed = priceAttestationToPriceFeed(priceAttestation);
|
||||
|
|
|
@ -34,6 +34,7 @@ export type PriceAttestation = {
|
|||
prevPublishTime: UnixTimestamp;
|
||||
prevPrice: string;
|
||||
prevConf: string;
|
||||
lastAttestedPublishTime: UnixTimestamp;
|
||||
};
|
||||
|
||||
export type BatchPriceAttestation = {
|
||||
|
@ -94,6 +95,9 @@ export function parsePriceAttestation(bytes: Buffer): PriceAttestation {
|
|||
const prevConf = bytes.readBigUint64BE(offset).toString();
|
||||
offset += 8;
|
||||
|
||||
const lastAttestedPublishTime = Number(bytes.readBigInt64BE(offset));
|
||||
offset += 8;
|
||||
|
||||
return {
|
||||
productId,
|
||||
priceId,
|
||||
|
@ -110,6 +114,7 @@ export function parsePriceAttestation(bytes: Buffer): PriceAttestation {
|
|||
prevPublishTime,
|
||||
prevPrice,
|
||||
prevConf,
|
||||
lastAttestedPublishTime,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue