[price-service] Improve vaa validation (#814)
* [price-service/server] Improve Vaa Validation * [price-service/server] Improve performance * Update wormhole guardian version
This commit is contained in:
parent
f70f163f72
commit
e283b15d20
|
@ -56964,7 +56964,7 @@
|
|||
},
|
||||
"price_service/server": {
|
||||
"name": "@pythnetwork/price-service-server",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "^0.9.9",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
services:
|
||||
spy:
|
||||
# Find latest Guardian images in https://github.com/wormhole-foundation/wormhole/pkgs/container/guardiand
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.14.8.1
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.17.0
|
||||
command:
|
||||
- "spy"
|
||||
- "--nodeKey"
|
||||
|
@ -16,7 +16,7 @@ services:
|
|||
- "warn"
|
||||
price-service:
|
||||
# Find latest price service images https://gallery.ecr.aws/pyth-network/xc-server
|
||||
image: public.ecr.aws/pyth-network/xc-server:v3.0.0
|
||||
image: public.ecr.aws/pyth-network/xc-server:v3.0.3
|
||||
environment:
|
||||
SPY_SERVICE_HOST: "spy:7072"
|
||||
SPY_SERVICE_FILTERS: |
|
||||
|
@ -35,6 +35,7 @@ services:
|
|||
READINESS_SPY_SYNC_TIME_SECONDS: "20"
|
||||
READINESS_NUM_LOADED_SYMBOLS: "50"
|
||||
LOG_LEVEL: warning
|
||||
WORMHOLE_CLUSTER: mainnet
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
services:
|
||||
spy:
|
||||
# Find latest Guardian images in https://github.com/wormhole-foundation/wormhole/pkgs/container/guardiand
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.14.8.1
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.17.0
|
||||
command:
|
||||
- "spy"
|
||||
- "--nodeKey"
|
||||
|
@ -16,7 +16,7 @@ services:
|
|||
- "warn"
|
||||
price-service:
|
||||
# Find latest price service images https://gallery.ecr.aws/pyth-network/xc-server
|
||||
image: public.ecr.aws/pyth-network/xc-server:v3.0.0
|
||||
image: public.ecr.aws/pyth-network/xc-server:v3.0.3
|
||||
environment:
|
||||
SPY_SERVICE_HOST: "spy:7072"
|
||||
SPY_SERVICE_FILTERS: |
|
||||
|
@ -35,6 +35,7 @@ services:
|
|||
READINESS_SPY_SYNC_TIME_SECONDS: "20"
|
||||
READINESS_NUM_LOADED_SYMBOLS: "50"
|
||||
LOG_LEVEL: warning
|
||||
WORMHOLE_CLUSTER: testnet
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
|
|
|
@ -7,6 +7,8 @@ SPY_SERVICE_HOST=0.0.0.0:7072
|
|||
# testnet/mainnet pyth price_service deployment.
|
||||
SPY_SERVICE_FILTERS=[{"chain_id":1,"emitter_address":"71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b"}]
|
||||
|
||||
WORMHOLE_CLUSTER=localnet
|
||||
|
||||
# Number of seconds to sync with spy to be sure to have latest messages
|
||||
READINESS_SPY_SYNC_TIME_SECONDS=60
|
||||
READINESS_NUM_LOADED_SYMBOLS=5
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
services:
|
||||
spy:
|
||||
# Find latest Guardian images in https://github.com/wormhole-foundation/wormhole/pkgs/container/guardiand
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.14.8.1
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.17.0
|
||||
restart: on-failure
|
||||
command:
|
||||
- "spy"
|
||||
|
@ -17,7 +17,7 @@ services:
|
|||
- "warn"
|
||||
price-service:
|
||||
# Find latest price service images https://gallery.ecr.aws/pyth-network/xc-server
|
||||
image: public.ecr.aws/pyth-network/xc-server:v3.0.0
|
||||
image: public.ecr.aws/pyth-network/xc-server:v3.0.3
|
||||
restart: on-failure
|
||||
# Or alternatively use a locally built image
|
||||
# image: pyth_price_server
|
||||
|
@ -39,6 +39,7 @@ services:
|
|||
READINESS_SPY_SYNC_TIME_SECONDS: "20"
|
||||
READINESS_NUM_LOADED_SYMBOLS: "50"
|
||||
LOG_LEVEL: warning
|
||||
WORMHOLE_CLUSTER: mainnet
|
||||
DB_API_CLUSTER: pythnet
|
||||
REMOVE_EXPIRED_VALUES_INTERVAL_SECONDS: "60"
|
||||
CACHE_TTL_SECONDS: "300"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
services:
|
||||
spy:
|
||||
# Find latest Guardian images in https://github.com/wormhole-foundation/wormhole/pkgs/container/guardiand
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.14.8.1
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.17.0
|
||||
restart: on-failure
|
||||
command:
|
||||
- "spy"
|
||||
|
@ -17,7 +17,7 @@ services:
|
|||
- "warn"
|
||||
price-service:
|
||||
# Find latest price service images https://gallery.ecr.aws/pyth-network/xc-server
|
||||
image: public.ecr.aws/pyth-network/xc-server:v3.0.0
|
||||
image: public.ecr.aws/pyth-network/xc-server:v3.0.3
|
||||
restart: on-failure
|
||||
# Or alternatively use a locally built image
|
||||
# image: pyth_price_server
|
||||
|
@ -39,6 +39,7 @@ services:
|
|||
READINESS_SPY_SYNC_TIME_SECONDS: "20"
|
||||
READINESS_NUM_LOADED_SYMBOLS: "50"
|
||||
LOG_LEVEL: warning
|
||||
WORMHOLE_CLUSTER: testnet
|
||||
DB_API_CLUSTER: devnet
|
||||
REMOVE_EXPIRED_VALUES_INTERVAL_SECONDS: "60"
|
||||
CACHE_TTL_SECONDS: "300"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/price-service-server",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.3",
|
||||
"description": "Webservice for retrieving prices from the Pyth oracle.",
|
||||
"private": "true",
|
||||
"main": "index.js",
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Listener } from "./listen";
|
|||
import { initLogger } from "./logging";
|
||||
import { PromClient } from "./promClient";
|
||||
import { RestAPI } from "./rest";
|
||||
import { wormholeClusterFromString } from "./vaa";
|
||||
import { WebSocketAPI } from "./ws";
|
||||
|
||||
let configFile: string = ".env";
|
||||
|
@ -28,6 +29,7 @@ async function run() {
|
|||
{
|
||||
spyServiceHost: envOrErr("SPY_SERVICE_HOST"),
|
||||
filtersRaw: process.env.SPY_SERVICE_FILTERS,
|
||||
wormholeCluster: process.env.WORMHOLE_CLUSTER,
|
||||
readiness: {
|
||||
spySyncTimeSeconds: parseInt(
|
||||
envOrErr("READINESS_SPY_SYNC_TIME_SECONDS"),
|
||||
|
|
|
@ -17,10 +17,12 @@ import {
|
|||
PriceAttestation,
|
||||
} from "@pythnetwork/wormhole-attester-sdk";
|
||||
import { HexString, PriceFeed } from "@pythnetwork/price-service-sdk";
|
||||
import { ethers } from "ethers";
|
||||
import LRUCache from "lru-cache";
|
||||
import { DurationInSec, sleep, TimestampInSec } from "./helpers";
|
||||
import { logger } from "./logging";
|
||||
import { PromClient } from "./promClient";
|
||||
import { isValidVaa, WormholeCluster, wormholeClusterFromString } from "./vaa";
|
||||
|
||||
export type PriceInfo = {
|
||||
vaa: Buffer;
|
||||
|
@ -64,6 +66,7 @@ type ListenerReadinessConfig = {
|
|||
|
||||
type ListenerConfig = {
|
||||
spyServiceHost: string;
|
||||
wormholeCluster?: string;
|
||||
filtersRaw?: string;
|
||||
readiness: ListenerReadinessConfig;
|
||||
webApiEndpoint?: string;
|
||||
|
@ -171,6 +174,7 @@ export class Listener implements PriceStore {
|
|||
private updateCallbacks: ((priceInfo: PriceInfo) => any)[];
|
||||
private observedVaas: LRUCache<VaaKey, boolean>;
|
||||
private vaasCache: VaaCache;
|
||||
private wormholeCluster: WormholeCluster;
|
||||
|
||||
constructor(config: ListenerConfig, promClient?: PromClient) {
|
||||
this.promClient = promClient;
|
||||
|
@ -188,6 +192,11 @@ export class Listener implements PriceStore {
|
|||
config.cacheTtl,
|
||||
config.cacheCleanupLoopInterval
|
||||
);
|
||||
if (config.wormholeCluster !== undefined) {
|
||||
this.wormholeCluster = wormholeClusterFromString(config.wormholeCluster);
|
||||
} else {
|
||||
this.wormholeCluster = "mainnet";
|
||||
}
|
||||
}
|
||||
|
||||
private loadFilters(filtersRaw?: string) {
|
||||
|
@ -305,6 +314,11 @@ export class Listener implements PriceStore {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!isValidVaa(parsedVaa, this.wormholeCluster)) {
|
||||
logger.info("Ignoring an invalid VAA");
|
||||
return;
|
||||
}
|
||||
|
||||
let batchAttestation;
|
||||
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import { ParsedVaa } from "@certusone/wormhole-sdk";
|
||||
import { GuardianSet } from "@certusone/wormhole-spydk/lib/cjs/proto/publicrpc/v1/publicrpc";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
const WormholeClusters = ["localnet", "testnet", "mainnet"] as const;
|
||||
export type WormholeCluster = typeof WormholeClusters[number];
|
||||
|
||||
export function wormholeClusterFromString(s: string): WormholeCluster {
|
||||
if (WormholeClusters.includes(s as WormholeCluster)) {
|
||||
return s as WormholeCluster;
|
||||
}
|
||||
throw new Error(`Invalid wormhole cluster: ${s}`);
|
||||
}
|
||||
|
||||
const guardianSets: Record<WormholeCluster, GuardianSet> = {
|
||||
localnet: {
|
||||
index: 0,
|
||||
addresses: ["0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"],
|
||||
},
|
||||
testnet: {
|
||||
index: 0,
|
||||
addresses: ["0x13947Bd48b18E53fdAeEe77F3473391aC727C638"],
|
||||
},
|
||||
mainnet: {
|
||||
index: 3,
|
||||
addresses: [
|
||||
"0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5",
|
||||
"0xfF6CB952589BDE862c25Ef4392132fb9D4A42157",
|
||||
"0x114De8460193bdf3A2fCf81f86a09765F4762fD1",
|
||||
"0x107A0086b32d7A0977926A205131d8731D39cbEB",
|
||||
"0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2",
|
||||
"0x11b39756C042441BE6D8650b69b54EbE715E2343",
|
||||
"0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd",
|
||||
"0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20",
|
||||
"0x74a3bf913953D695260D88BC1aA25A4eeE363ef0",
|
||||
"0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e",
|
||||
"0xAF45Ced136b9D9e24903464AE889F5C8a723FC14",
|
||||
"0xf93124b7c738843CBB89E864c862c38cddCccF95",
|
||||
"0xD2CC37A4dc036a8D232b48f62cDD4731412f4890",
|
||||
"0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811",
|
||||
"0x71AA1BE1D36CaFE3867910F99C09e347899C19C3",
|
||||
"0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf",
|
||||
"0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8",
|
||||
"0x5E1487F35515d02A92753504a8D75471b9f49EdB",
|
||||
"0x6FbEBc898F403E4773E95feB15E80C9A99c8348d",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function isValidVaa(vaa: ParsedVaa, cluster: WormholeCluster): boolean {
|
||||
const currentGuardianSet = guardianSets[cluster];
|
||||
if (vaa.guardianSetIndex !== currentGuardianSet.index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const threshold = Math.ceil((currentGuardianSet.addresses.length * 2) / 3);
|
||||
if (vaa.guardianSignatures.length < threshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const digest = ethers.utils.keccak256(vaa.hash);
|
||||
|
||||
let validVaa = true;
|
||||
vaa.guardianSignatures.forEach((sig) => {
|
||||
if (
|
||||
ethers.utils.recoverAddress(digest, sig.signature) !==
|
||||
currentGuardianSet.addresses[sig.index]
|
||||
)
|
||||
validVaa = false;
|
||||
});
|
||||
|
||||
return validVaa;
|
||||
}
|
|
@ -53,7 +53,7 @@ spec:
|
|||
path: bigtable-key.json
|
||||
containers:
|
||||
- name: guardiand
|
||||
image: ghcr.io/certusone/guardiand:v2.8.9
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.17.0
|
||||
volumeMounts:
|
||||
- mountPath: /run/node
|
||||
name: node-rundir
|
||||
|
|
|
@ -62,6 +62,8 @@ spec:
|
|||
value: spy:7072
|
||||
- name: SPY_SERVICE_FILTERS
|
||||
value: '[{"chain_id":1,"emitter_address":"71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b"}]'
|
||||
- name: WORMHOLE_CLUSTER
|
||||
value: localnet
|
||||
- name: REST_PORT
|
||||
value: "4200"
|
||||
- name: PROM_PORT
|
||||
|
|
|
@ -35,7 +35,7 @@ spec:
|
|||
terminationGracePeriodSeconds: 0
|
||||
containers:
|
||||
- name: spy
|
||||
image: ghcr.io/certusone/guardiand:v2.8.9
|
||||
image: ghcr.io/wormhole-foundation/guardiand:v2.17.0
|
||||
command:
|
||||
- /guardiand
|
||||
- spy
|
||||
|
|
Loading…
Reference in New Issue