Skip to content

Commit 1a00598

Browse files
committed
feat(price-server): support parsing accumul data
1 parent 0682cc9 commit 1a00598

File tree

4 files changed

+183
-11
lines changed

4 files changed

+183
-11
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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.8",
3+
"version": "3.1.0",
44
"description": "Webservice for retrieving prices from the Pyth oracle.",
55
"private": "true",
66
"main": "index.js",

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,59 @@ describe("Get VAA endpoint and Get VAA CCIP", () => {
508508
expect(ccipResp.status).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
509509
}
510510
);
511+
512+
test("vaaToPriceInfo works with accumulator update data", () => {
513+
// An update data taken from Hermes with the following price feed:
514+
// {
515+
// "id":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43",
516+
// "price":{"price":"2836040669135","conf":"3282830965","expo":-8,"publish_time":1692280808},
517+
// "ema_price":{"price":"2845324900000","conf":"3211773100","expo":-8,"publish_time":1692280808},
518+
// "metadata":{"slot":89783664,"emitter_chain":26,"price_service_receive_time":1692280809}
519+
// }
520+
const updateData = Buffer.from(
521+
"UE5BVQEAAAADuAEAAAADDQAsKPsmb7Vz7io3taJQKgoi1m/z0kqKgtpmlkv+ZuunX2Iegsf+8fuUtpHPLKgCWPU8PN2x9NyAZz5" +
522+
"BY9M3SWwJAALYlM0U7f2GFWfEjKwSJlHZ5sf+n6KXCocVC66ImS2o0TD0SBhTWcp0KdcuzR1rY1jfIHaFpVneroRLbTjNrk/WAA" +
523+
"MuAYxPVPf1DR30wYQo12Dbf+in3akTjhKERNQ+nPwRjxAyIQD+52LU3Rh2VL7nOIStMNTiBMaiWHywaPoXowWAAQbillhhX4MR+" +
524+
"7h81PfxHIbiXBmER4c5M7spilWKkROb+VXhrqnVJL162t9TdhYk56PDIhvXO1Tm/ldjVJw130y0AAk6qpccfsxDZEmVN8LI4z87" +
525+
"39Ni/kb+CB3yW2l2dWhKTjBeNanhK6TCCoNH/jRzWfrjrEk5zjNrUr82JwL4fR1OAQrYZescxbH26m8QHiH+RHzwlXpUKJgbHD5" +
526+
"NnWtB7oFb9AFM15jbjd4yIEBEtAlXPE0Q4j+X+DLnCtZbLSQiYNh5AQvz70LTbYry1lEExuUcO+IRJiysw5AFyqZ9Y1E//WKIqg" +
527+
"EysfcnHwoOxtDtAc5Z9sTUEYfPqQ1d27k3Yk0X7dvCAQ10cdG0qYHb+bQrYRIKKnb0aeCjkCs0HZQY2fXYmimyfTNfECclmPW9k" +
528+
"+CfOvW0JKuFxC1l11zJ3zjsgN/peA8BAQ5oIFQGjq9qmf5gegE1DjuzXsGksKao6nsjTXYIspCczCe2h5KNQ9l5hws11hauUKS2" +
529+
"0JoOYjHwxPD2x0adJKvkAQ+4UjVcZgVEQP8y3caqUDH81Ikcadz2bESpYg93dpnzZTH6A7Ue+RL34PTNx6cCRzukwQuhiStuyL1" +
530+
"WYEIrLI4nABAjGv3EBXjWaPLUj59OzVnGkzxkr6C4KDjMmpsYNzx7I2lp2iQV46TM78El8i9h7twiEDUOSdC5CmfQjRpkP72yAB" +
531+
"GVAQELUm2/SjkpF0O+/rVDgA/Y2/wMacD1ZDahdyvSNSFThn5NyRYA1JXGgIDxoYeAZgkr1gL1cjCLWiO+Bs9QARIiCvHfIkn2a" +
532+
"YhYHQq/u6cHB/2DxE3OgbCZyTv8OVO55hQDkJ1gDwAec+IJ4M5Od4OxWEu+OywhJT7zUmwZko9MAGTeJ+kAAAAAABrhAfrtrFhR" +
533+
"4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAAWllxAUFVV1YAAAAAAAVZ/XAAACcQ8Xfx5wQ+nj1rn6IeTUAy+VER1nUBAFU" +
534+
"A5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0MAAAKUUTJXzwAAAADDrAZ1////+AAAAABk3ifoAAAAAGTeJ+cAAAKWep" +
535+
"R2oAAAAAC/b8SsCasjFzENKvXWwOycuzCVaDWfm0IuuuesmamDKl2lNXss15orlNN+xHVNEEIIq7Xg8GRZGVLt43fkg7xli6EPQ" +
536+
"/Nyxl6SixiYteNt1uTTh4M1lQTUjPxKnkE5JEea4RnhOWgmSAWMf8ft4KgE7hvRifV1JP0rOsNgsOYFRbs6iDKW1qLpxgZLMAiO" +
537+
"clwS3Tjw2hj8sPfq1NHeVttsBEK5SIM14GjAuD/p2V0+NqHqMHxU/kfftg==",
538+
"base64"
539+
);
540+
541+
const priceInfo = RestAPI.vaaToPriceInfo(
542+
"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43",
543+
updateData
544+
);
545+
546+
expect(priceInfo).toBeDefined();
547+
expect(priceInfo?.priceFeed).toEqual(
548+
new PriceFeed({
549+
id: "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43",
550+
price: new Price({
551+
price: "2836040669135",
552+
conf: "3282830965",
553+
publishTime: 1692280808,
554+
expo: -8,
555+
}),
556+
emaPrice: new Price({
557+
price: "2845324900000",
558+
conf: "3211773100",
559+
publishTime: 1692280808,
560+
expo: -8,
561+
}),
562+
})
563+
);
564+
expect(priceInfo?.emitterChainId).toEqual(26);
565+
});
511566
});

price_service/server/src/rest.ts

Lines changed: 126 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
import { HexString } from "@pythnetwork/price-service-sdk";
1+
import { HexString, Price, PriceFeed } from "@pythnetwork/price-service-sdk";
22
import cors from "cors";
33
import express, { NextFunction, Request, Response } from "express";
44
import { Joi, schema, validate, ValidationError } from "express-validation";
55
import { Server } from "http";
66
import { StatusCodes } from "http-status-codes";
77
import morgan from "morgan";
88
import fetch from "node-fetch";
9-
import {
10-
parseBatchPriceAttestation,
11-
priceAttestationToPriceFeed,
12-
} from "@pythnetwork/wormhole-attester-sdk";
9+
import { parseBatchPriceAttestation } from "@pythnetwork/wormhole-attester-sdk";
1310
import { removeLeading0x, TimestampInSec } from "./helpers";
14-
import { createPriceInfo, PriceInfo, PriceStore, VaaConfig } from "./listen";
11+
import { createPriceInfo, PriceInfo, PriceStore } from "./listen";
1512
import { logger } from "./logging";
1613
import { PromClient } from "./promClient";
1714
import { retry } from "ts-retry-promise";
@@ -21,7 +18,6 @@ import {
2118
TargetChain,
2219
validTargetChains,
2320
defaultTargetChain,
24-
VaaEncoding,
2521
encodeVaaForChain,
2622
} from "./encoding";
2723

@@ -136,7 +132,128 @@ export class RestAPI {
136132
return vaa;
137133
}
138134

139-
vaaToPriceInfo(priceFeedId: string, vaa: Buffer): PriceInfo | undefined {
135+
// Extract the price info from an Accumulator update. This is a temporary solution until hermes adoption
136+
// to maintain backward compatibility when the db migrates to the new update format.
137+
static extractPriceInfoFromAccumulatorUpdate(
138+
priceFeedId: string,
139+
updateData: Buffer
140+
): PriceInfo | undefined {
141+
let offset = 0;
142+
offset += 4; // magic
143+
offset += 1; // major version
144+
offset += 1; // minor version
145+
146+
const trailingHeaderSize = updateData.readUint8(offset);
147+
offset += 1 + trailingHeaderSize;
148+
149+
const updateType = updateData.readUint8(offset);
150+
offset += 1;
151+
152+
// There is a single update type of 0 for now.
153+
if (updateType !== 0) {
154+
logger.error(`Invalid accumulator update type: ${updateType}`);
155+
return undefined;
156+
}
157+
158+
const vaaLength = updateData.readUint16BE(offset);
159+
offset += 2;
160+
161+
const vaaBuffer = updateData.slice(offset, offset + vaaLength);
162+
const vaa = parseVaa(vaaBuffer);
163+
offset += vaaLength;
164+
165+
const numUpdates = updateData.readUint8(offset);
166+
offset += 1;
167+
168+
// Iterate through the updates to find the price info with the given id
169+
for (let i = 0; i < numUpdates; i++) {
170+
const messageLength = updateData.readUint16BE(offset);
171+
offset += 2;
172+
173+
const message = updateData.slice(offset, offset + messageLength);
174+
offset += messageLength;
175+
176+
const proofLength = updateData.readUint8(offset);
177+
offset += 1;
178+
179+
// ignore proofs
180+
offset += proofLength;
181+
182+
// Checket whether the message is a price feed update
183+
// from the given price id and if so, extract the price info
184+
let messageOffset = 0;
185+
const messageType = message.readUint8(messageOffset);
186+
messageOffset += 1;
187+
188+
// MessageType of 0 is a price feed update
189+
if (messageType !== 0) {
190+
continue;
191+
}
192+
193+
const priceId = message
194+
.slice(messageOffset, messageOffset + 32)
195+
.toString("hex");
196+
messageOffset += 32;
197+
198+
if (priceId !== priceFeedId) {
199+
continue;
200+
}
201+
202+
const price = message.readBigInt64BE(messageOffset);
203+
messageOffset += 8;
204+
const conf = message.readBigUint64BE(messageOffset);
205+
messageOffset += 8;
206+
const expo = message.readInt32BE(messageOffset);
207+
messageOffset += 4;
208+
const publishTime = message.readBigInt64BE(messageOffset);
209+
messageOffset += 8;
210+
const prevPublishTime = message.readBigInt64BE(messageOffset);
211+
messageOffset += 8;
212+
const emaPrice = message.readBigInt64BE(messageOffset);
213+
messageOffset += 8;
214+
const emaConf = message.readBigUint64BE(messageOffset);
215+
216+
return {
217+
priceFeed: new PriceFeed({
218+
id: priceFeedId,
219+
price: new Price({
220+
price: price.toString(),
221+
conf: conf.toString(),
222+
expo,
223+
publishTime: Number(publishTime),
224+
}),
225+
emaPrice: new Price({
226+
price: emaPrice.toString(),
227+
conf: emaConf.toString(),
228+
expo,
229+
publishTime: Number(publishTime),
230+
}),
231+
}),
232+
publishTime: Number(publishTime),
233+
vaa: vaaBuffer,
234+
seqNum: Number(vaa.sequence),
235+
emitterChainId: vaa.emitterChain,
236+
// These are not available in the accumulator update format
237+
// but are required by the PriceInfo type.
238+
attestationTime: Number(publishTime),
239+
lastAttestedPublishTime: Number(prevPublishTime),
240+
priceServiceReceiveTime: Number(publishTime),
241+
};
242+
}
243+
244+
return undefined;
245+
}
246+
247+
static vaaToPriceInfo(
248+
priceFeedId: string,
249+
vaa: Buffer
250+
): PriceInfo | undefined {
251+
// Vaa could be the update data from the db with the Accumulator format.
252+
const ACCUMULATOR_MAGIC = "504e4155";
253+
if (vaa.slice(0, 4).toString("hex") === ACCUMULATOR_MAGIC) {
254+
return RestAPI.extractPriceInfoFromAccumulatorUpdate(priceFeedId, vaa);
255+
}
256+
140257
const parsedVaa = parseVaa(vaa);
141258

142259
let batchAttestation;
@@ -454,7 +571,7 @@ export class RestAPI {
454571
throw RestException.VaaNotFound();
455572
}
456573

457-
const priceInfo = this.vaaToPriceInfo(
574+
const priceInfo = RestAPI.vaaToPriceInfo(
458575
priceFeedId,
459576
Buffer.from(vaa.vaa, "base64")
460577
);

0 commit comments

Comments
 (0)