Skip to content

Commit 64bce66

Browse files
authored
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
1 parent af2d7b6 commit 64bce66

File tree

10 files changed

+101
-53
lines changed

10 files changed

+101
-53
lines changed

price_service/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/price-service-server",
3-
"version": "3.0.4",
3+
"version": "3.0.5",
44
"description": "Webservice for retrieving prices from the Pyth oracle.",
55
"private": "true",
66
"main": "index.js",

price_service/server/src/__tests__/listen.test.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,53 +6,73 @@ describe("VAA Cache works", () => {
66

77
expect(cache.get("a", 3)).toBeUndefined();
88

9-
cache.set("a", 1, "a-1");
9+
cache.set("a", 1, 0, "a-1");
1010

1111
expect(cache.get("a", 3)).toBeUndefined();
1212

13-
cache.set("a", 4, "a-2");
13+
cache.set("a", 4, 3, "a-2");
1414

1515
expect(cache.get("a", 3)).toEqual<VaaConfig>({
1616
publishTime: 4,
17+
lastAttestedPublishTime: 3,
1718
vaa: "a-2",
1819
});
1920

20-
cache.set("a", 10, "a-3");
21+
cache.set("a", 10, 9, "a-3");
22+
cache.set("a", 10, 10, "a-4");
23+
cache.set("a", 10, 10, "a-5");
24+
cache.set("a", 10, 10, "a-6");
25+
cache.set("a", 11, 11, "a-7");
2126

2227
// Adding some elements with other keys to make sure
2328
// they are not stored separately.
24-
cache.set("b", 3, "b-1");
25-
cache.set("b", 7, "b-2");
26-
cache.set("b", 9, "b-3");
29+
cache.set("b", 3, 2, "b-1");
30+
cache.set("b", 7, 6, "b-2");
31+
cache.set("b", 9, 8, "b-3");
2732

2833
expect(cache.get("a", 3)).toEqual<VaaConfig>({
2934
publishTime: 4,
35+
lastAttestedPublishTime: 3,
3036
vaa: "a-2",
3137
});
3238
expect(cache.get("a", 4)).toEqual<VaaConfig>({
3339
publishTime: 4,
40+
lastAttestedPublishTime: 3,
3441
vaa: "a-2",
3542
});
3643
expect(cache.get("a", 5)).toEqual<VaaConfig>({
3744
publishTime: 10,
45+
lastAttestedPublishTime: 9,
3846
vaa: "a-3",
3947
});
48+
// There are multiple elements at 10, but we prefer to return the one with a lower lastAttestedPublishTime.
4049
expect(cache.get("a", 10)).toEqual<VaaConfig>({
4150
publishTime: 10,
51+
lastAttestedPublishTime: 9,
4252
vaa: "a-3",
4353
});
54+
// If the cache only contains elements where the lastAttestedPublishTime==publishTime, those will be returned.
55+
// Note that this behavior is undesirable (as this means we can return a noncanonical VAA for a query time);
56+
// this test simply documents it.
57+
expect(cache.get("a", 11)).toEqual<VaaConfig>({
58+
publishTime: 11,
59+
lastAttestedPublishTime: 11,
60+
vaa: "a-7",
61+
});
4462

4563
expect(cache.get("b", 3)).toEqual<VaaConfig>({
4664
publishTime: 3,
65+
lastAttestedPublishTime: 2,
4766
vaa: "b-1",
4867
});
4968
expect(cache.get("b", 4)).toEqual<VaaConfig>({
5069
publishTime: 7,
70+
lastAttestedPublishTime: 6,
5171
vaa: "b-2",
5272
});
5373

5474
// When no item item more recent than asked pubTime is asked it should return undefined
55-
expect(cache.get("a", 11)).toBeUndefined();
75+
expect(cache.get("a", 12)).toBeUndefined();
5676
expect(cache.get("b", 10)).toBeUndefined();
5777

5878
// When the asked pubTime is less than the first existing pubTime we are not sure that
@@ -68,17 +88,19 @@ describe("VAA Cache works", () => {
6888
// TTL of 500 seconds for the cache
6989
const cache = new VaaCache(500);
7090

71-
cache.set("a", 300, "a-1");
72-
cache.set("a", 700, "a-2");
73-
cache.set("a", 900, "a-3");
91+
cache.set("a", 300, 299, "a-1");
92+
cache.set("a", 700, 699, "a-2");
93+
cache.set("a", 900, 899, "a-3");
7494

7595
expect(cache.get("a", 300)).toEqual<VaaConfig>({
7696
publishTime: 300,
97+
lastAttestedPublishTime: 299,
7798
vaa: "a-1",
7899
});
79100

80101
expect(cache.get("a", 500)).toEqual<VaaConfig>({
81102
publishTime: 700,
103+
lastAttestedPublishTime: 699,
82104
vaa: "a-2",
83105
});
84106

@@ -98,12 +120,13 @@ describe("VAA Cache works", () => {
98120
const cache = new VaaCache(500, 100);
99121
cache.runRemoveExpiredValuesLoop();
100122

101-
cache.set("a", 300, "a-1");
102-
cache.set("a", 700, "a-2");
103-
cache.set("a", 900, "a-3");
123+
cache.set("a", 300, 299, "a-1");
124+
cache.set("a", 700, 699, "a-2");
125+
cache.set("a", 900, 899, "a-3");
104126

105127
expect(cache.get("a", 900)).toEqual<VaaConfig>({
106128
publishTime: 900,
129+
lastAttestedPublishTime: 899,
107130
vaa: "a-3",
108131
});
109132

price_service/server/src/__tests__/rest.test.ts

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import express, { Express } from "express";
88
import { StatusCodes } from "http-status-codes";
99
import request from "supertest";
1010
import { PriceInfo, PriceStore, VaaCache, VaaConfig } from "../listen";
11-
import { RestAPI } from "../rest";
11+
import { RestAPI, VaaResponse } from "../rest";
1212

1313
let priceInfo: PriceStore;
1414
let app: Express;
@@ -52,10 +52,18 @@ function dummyPriceInfoPair(
5252
vaa: Buffer.from(vaa, "hex"),
5353
emitterChainId: 0,
5454
priceServiceReceiveTime: 0,
55+
lastAttestedPublishTime: 0,
5556
},
5657
];
5758
}
5859

60+
// Add some dummy data to the provided vaa cache.
61+
function addAbcdDataToCache(id: string, cache: VaaCache) {
62+
cache.set(id, 10, 9, "abcd10");
63+
cache.set(id, 20, 19, "abcd20");
64+
cache.set(id, 30, 29, "abcd30");
65+
}
66+
5967
beforeAll(async () => {
6068
priceInfoMap = new Map<string, PriceInfo>([
6169
dummyPriceInfoPair(expandTo64Len("abcd"), 1, "a1b2c3d4"),
@@ -258,16 +266,14 @@ describe("Latest Vaa Bytes Endpoint", () => {
258266
describe("Get VAA endpoint and Get VAA CCIP", () => {
259267
test("When called with valid id and timestamp in the cache returns the correct answer", async () => {
260268
const id = expandTo64Len("abcd");
261-
vaasCache.set(id, 10, "abcd10");
262-
vaasCache.set(id, 20, "abcd20");
263-
vaasCache.set(id, 30, "abcd30");
269+
addAbcdDataToCache(id, vaasCache);
264270

265271
const resp = await request(app).get("/api/get_vaa").query({
266272
id,
267273
publish_time: 16,
268274
});
269275
expect(resp.status).toBe(StatusCodes.OK);
270-
expect(resp.body).toEqual<VaaConfig>({
276+
expect(resp.body).toEqual<VaaResponse>({
271277
vaa: "abcd20",
272278
publishTime: 20,
273279
});
@@ -286,9 +292,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
286292

287293
test("When called with valid id with leading 0x and timestamp in the cache returns the correct answer", async () => {
288294
const id = expandTo64Len("abcd");
289-
vaasCache.set(id, 10, "abcd10");
290-
vaasCache.set(id, 20, "abcd20");
291-
vaasCache.set(id, 30, "abcd30");
295+
addAbcdDataToCache(id, vaasCache);
292296

293297
const resp = await request(app)
294298
.get("/api/get_vaa")
@@ -297,17 +301,15 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
297301
publish_time: 16,
298302
});
299303
expect(resp.status).toBe(StatusCodes.OK);
300-
expect(resp.body).toEqual<VaaConfig>({
304+
expect(resp.body).toEqual<VaaResponse>({
301305
vaa: "abcd20",
302306
publishTime: 20,
303307
});
304308
});
305309

306310
test("When called with target_chain, encodes resulting VAA in the right format", async () => {
307311
const id = expandTo64Len("abcd");
308-
vaasCache.set(id, 10, "abcd10");
309-
vaasCache.set(id, 20, "abcd20");
310-
vaasCache.set(id, 30, "abcd30");
312+
addAbcdDataToCache(id, vaasCache);
311313

312314
const resp = await request(app)
313315
.get("/api/get_vaa")
@@ -317,7 +319,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
317319
target_chain: "evm",
318320
});
319321
expect(resp.status).toBe(StatusCodes.OK);
320-
expect(resp.body).toEqual<VaaConfig>({
322+
expect(resp.body).toEqual<VaaResponse>({
321323
vaa: "0x" + Buffer.from("abcd20", "base64").toString("hex"),
322324
publishTime: 20,
323325
});
@@ -346,9 +348,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
346348

347349
test("When called with valid id and timestamp not in the cache without db returns vaa not found", async () => {
348350
const id = expandTo64Len("abcd");
349-
vaasCache.set(id, 10, "abcd10");
350-
vaasCache.set(id, 20, "abcd20");
351-
vaasCache.set(id, 30, "abcd30");
351+
addAbcdDataToCache(id, vaasCache);
352352

353353
const resp = await request(app)
354354
.get("/api/get_vaa")
@@ -396,9 +396,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
396396
const appWithDb = await apiWithDb.createApp();
397397

398398
const id = expandTo64Len("abcd");
399-
vaasCache.set(id, 10, "abcd10");
400-
vaasCache.set(id, 20, "abcd20");
401-
vaasCache.set(id, 30, "abcd30");
399+
addAbcdDataToCache(id, vaasCache);
402400

403401
const resp = await request(appWithDb)
404402
.get("/api/get_vaa")
@@ -407,7 +405,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
407405
publish_time: 5,
408406
});
409407
expect(resp.status).toBe(StatusCodes.OK);
410-
expect(resp.body).toEqual<VaaConfig>({
408+
expect(resp.body).toEqual<VaaResponse>({
411409
vaa: `pythnet${id}5`,
412410
publishTime: 5,
413411
});
@@ -451,9 +449,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
451449
const appWithDb = await apiWithDb.createApp();
452450

453451
const id = expandTo64Len("abcd");
454-
vaasCache.set(id, 10, "abcd10");
455-
vaasCache.set(id, 20, "abcd20");
456-
vaasCache.set(id, 30, "abcd30");
452+
addAbcdDataToCache(id, vaasCache);
457453

458454
const resp = await request(appWithDb)
459455
.get("/api/get_vaa")
@@ -493,9 +489,7 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
493489
const appWithDb = await apiWithDb.createApp();
494490

495491
const id = expandTo64Len("abcd");
496-
vaasCache.set(id, 10, "abcd10");
497-
vaasCache.set(id, 20, "abcd20");
498-
vaasCache.set(id, 30, "abcd30");
492+
addAbcdDataToCache(id, vaasCache);
499493

500494
const resp = await request(appWithDb)
501495
.get("/api/get_vaa")

price_service/server/src/__tests__/ws.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function dummyPriceInfo(id: HexString, vaa: HexString): PriceInfo {
3131
priceFeed: dummyPriceFeed(id),
3232
vaa: Buffer.from(vaa, "hex"),
3333
priceServiceReceiveTime: 4,
34+
lastAttestedPublishTime: -1,
3435
};
3536
}
3637

price_service/server/src/listen.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type PriceInfo = {
2828
seqNum: number;
2929
publishTime: TimestampInSec;
3030
attestationTime: TimestampInSec;
31+
lastAttestedPublishTime: TimestampInSec;
3132
priceFeed: PriceFeed;
3233
emitterChainId: number;
3334
priceServiceReceiveTime: number;
@@ -45,6 +46,7 @@ export function createPriceInfo(
4546
vaa,
4647
publishTime: priceAttestation.publishTime,
4748
attestationTime: priceAttestation.attestationTime,
49+
lastAttestedPublishTime: priceAttestation.lastAttestedPublishTime,
4850
priceFeed,
4951
emitterChainId: emitterChain,
5052
priceServiceReceiveTime: Math.floor(new Date().getTime() / 1000),
@@ -78,6 +80,7 @@ type VaaKey = string;
7880

7981
export type VaaConfig = {
8082
publishTime: number;
83+
lastAttestedPublishTime: number;
8184
vaa: string;
8285
};
8386

@@ -95,11 +98,16 @@ export class VaaCache {
9598
this.cacheCleanupLoopInterval = cacheCleanupLoopInterval;
9699
}
97100

98-
set(key: VaaKey, publishTime: TimestampInSec, vaa: string): void {
101+
set(
102+
key: VaaKey,
103+
publishTime: TimestampInSec,
104+
lastAttestedPublishTime: TimestampInSec,
105+
vaa: string
106+
): void {
99107
if (this.cache.has(key)) {
100-
this.cache.get(key)!.push({ publishTime, vaa });
108+
this.cache.get(key)!.push({ publishTime, lastAttestedPublishTime, vaa });
101109
} else {
102-
this.cache.set(key, [{ publishTime, vaa }]);
110+
this.cache.set(key, [{ publishTime, lastAttestedPublishTime, vaa }]);
103111
}
104112
}
105113

@@ -128,7 +136,10 @@ export class VaaCache {
128136

129137
while (left <= right) {
130138
const middle = Math.floor((left + right) / 2);
131-
if (arr[middle].publishTime === publishTime) {
139+
if (
140+
arr[middle].publishTime === publishTime &&
141+
arr[middle].lastAttestedPublishTime < publishTime
142+
) {
132143
return arr[middle];
133144
} else if (arr[middle].publishTime < publishTime) {
134145
left = middle + 1;
@@ -368,6 +379,7 @@ export class Listener implements PriceStore {
368379
this.vaasCache.set(
369380
priceInfo.priceFeed.id,
370381
priceInfo.publishTime,
382+
priceInfo.lastAttestedPublishTime,
371383
priceInfo.vaa.toString("base64")
372384
);
373385
this.priceFeedVaaMap.set(key, priceInfo);

price_service/server/src/rest.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ function asyncWrapper(
6767
};
6868
}
6969

70+
export type VaaResponse = {
71+
publishTime: number;
72+
vaa: string;
73+
};
74+
7075
export class RestAPI {
7176
private port: number;
7277
private priceFeedVaaInfo: PriceStore;
@@ -92,12 +97,18 @@ export class RestAPI {
9297
async getVaaWithDbLookup(
9398
priceFeedId: string,
9499
publishTime: TimestampInSec
95-
): Promise<VaaConfig | undefined> {
100+
): Promise<VaaResponse | undefined> {
96101
// Try to fetch the vaa from the local cache
97-
let vaa = this.priceFeedVaaInfo.getVaa(priceFeedId, publishTime);
102+
const vaaConfig = this.priceFeedVaaInfo.getVaa(priceFeedId, publishTime);
103+
let vaa: VaaResponse | undefined;
98104

99105
// if publishTime is older than cache ttl or vaa is not found, fetch from db
100-
if (vaa === undefined && this.dbApiEndpoint && this.dbApiCluster) {
106+
if (vaaConfig !== undefined) {
107+
vaa = {
108+
vaa: vaaConfig.vaa,
109+
publishTime: vaaConfig.publishTime,
110+
};
111+
} else if (vaa === undefined && this.dbApiEndpoint && this.dbApiCluster) {
101112
const priceFeedWithoutLeading0x = removeLeading0x(priceFeedId);
102113

103114
try {

target_chains/ethereum/examples/oracle_swap/app/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"scripts": {
2727
"start": "react-scripts start",
2828
"build": "react-scripts build",
29-
"test": "react-scripts test --passWithNoTests",
3029
"eject": "react-scripts eject",
3130
"format": "prettier --write src/"
3231
},

wormhole_attester/sdk/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/wormhole-attester-sdk",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "Pyth Wormhole Attester SDk",
55
"private": "true",
66
"types": "lib/index.d.ts",

0 commit comments

Comments
 (0)