[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:
Ali Behjati 2023-05-17 20:20:17 +02:00 committed by GitHub
parent f70f163f72
commit e283b15d20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 109 additions and 12 deletions

2
package-lock.json generated
View File

@ -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",

View File

@ -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:
[

View File

@ -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:
[

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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",

View File

@ -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"),

View File

@ -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 {

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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