Эх сурвалжийг харах

[price-service] Improve vaa validation (#814)

* [price-service/server] Improve Vaa Validation

* [price-service/server] Improve performance

* Update wormhole guardian version
Ali Behjati 2 жил өмнө
parent
commit
e283b15d20

+ 1 - 1
package-lock.json

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

+ 3 - 2
price_pusher/docker-compose.mainnet.sample.yaml

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

+ 3 - 2
price_pusher/docker-compose.testnet.sample.yaml

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

+ 2 - 0
price_service/server/.env.sample

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

+ 3 - 2
price_service/server/docker-compose.mainnet.yaml

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

+ 3 - 2
price_service/server/docker-compose.testnet.yaml

@@ -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 - 1
price_service/server/package.json

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

+ 2 - 0
price_service/server/src/index.ts

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

+ 14 - 0
price_service/server/src/listen.ts

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

+ 73 - 0
price_service/server/src/vaa.ts

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

+ 1 - 1
tilt_devnet/k8s/node.yaml

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

+ 2 - 0
tilt_devnet/k8s/pyth-price-server.yaml

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

+ 1 - 1
tilt_devnet/k8s/spy.yaml

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