From 623047bdd0809b964309ab5eef382ca25e1c2b4e Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Sat, 24 May 2025 19:54:48 +0200 Subject: [PATCH 1/3] fix(apps/price_pusher): ignore invalid price updates on push when a feed is not found, the code throws an error which is typically good but often a feed is removed and that results in the entire push failing. this change adds the flag to ignore invalid price updates, which allows the push to continue. --- apps/price_pusher/package.json | 2 +- apps/price_pusher/src/aptos/aptos.ts | 1 + apps/price_pusher/src/fuel/fuel.ts | 1 + apps/price_pusher/src/injective/injective.ts | 1 + apps/price_pusher/src/near/near.ts | 1 + apps/price_pusher/src/solana/solana.ts | 1 + apps/price_pusher/src/sui/sui.ts | 1 + apps/price_pusher/src/ton/ton.ts | 1 + 8 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/price_pusher/package.json b/apps/price_pusher/package.json index a29842140d..ae9f213d9a 100644 --- a/apps/price_pusher/package.json +++ b/apps/price_pusher/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/price-pusher", - "version": "9.3.3", + "version": "9.3.4", "description": "Pyth Price Pusher", "homepage": "https://pyth.network", "main": "lib/index.js", diff --git a/apps/price_pusher/src/aptos/aptos.ts b/apps/price_pusher/src/aptos/aptos.ts index fe95a5f73d..ad3d738c75 100644 --- a/apps/price_pusher/src/aptos/aptos.ts +++ b/apps/price_pusher/src/aptos/aptos.ts @@ -109,6 +109,7 @@ export class AptosPricePusher implements IPricePusher { async getPriceFeedsUpdateData(priceIds: string[]): Promise { const response = await this.hermesClient.getLatestPriceUpdates(priceIds, { encoding: "base64", + ignoreInvalidPriceIds: true, }); return response.binary.data.map((data) => Array.from(Buffer.from(data, "base64")), diff --git a/apps/price_pusher/src/fuel/fuel.ts b/apps/price_pusher/src/fuel/fuel.ts index 0d6edb9f1a..3efe3aeea4 100644 --- a/apps/price_pusher/src/fuel/fuel.ts +++ b/apps/price_pusher/src/fuel/fuel.ts @@ -101,6 +101,7 @@ export class FuelPricePusher implements IPricePusher { try { const response = await this.hermesClient.getLatestPriceUpdates(priceIds, { encoding: "base64", + ignoreInvalidPriceIds: true, }); priceFeedUpdateData = response.binary.data; } catch (err: any) { diff --git a/apps/price_pusher/src/injective/injective.ts b/apps/price_pusher/src/injective/injective.ts index 23ebb0b820..8ef45375ad 100644 --- a/apps/price_pusher/src/injective/injective.ts +++ b/apps/price_pusher/src/injective/injective.ts @@ -301,6 +301,7 @@ export class InjectivePricePusher implements IPricePusher { try { const response = await this.hermesClient.getLatestPriceUpdates(priceIds, { encoding: "base64", + ignoreInvalidPriceIds: true, }); const vaas = response.binary.data; diff --git a/apps/price_pusher/src/near/near.ts b/apps/price_pusher/src/near/near.ts index 41a3000557..3bc39c173d 100644 --- a/apps/price_pusher/src/near/near.ts +++ b/apps/price_pusher/src/near/near.ts @@ -131,6 +131,7 @@ export class NearPricePusher implements IPricePusher { ): Promise { const response = await this.hermesClient.getLatestPriceUpdates(priceIds, { encoding: "base64", + ignoreInvalidPriceIds: true, }); return response.binary.data; } diff --git a/apps/price_pusher/src/solana/solana.ts b/apps/price_pusher/src/solana/solana.ts index e907283496..1213490cc2 100644 --- a/apps/price_pusher/src/solana/solana.ts +++ b/apps/price_pusher/src/solana/solana.ts @@ -118,6 +118,7 @@ export class SolanaPricePusher implements IPricePusher { shuffledPriceIds, { encoding: "base64", + ignoreInvalidPriceIds: true, }, ); priceFeedUpdateData = response.binary.data; diff --git a/apps/price_pusher/src/sui/sui.ts b/apps/price_pusher/src/sui/sui.ts index 96c6541d37..2ba1644806 100644 --- a/apps/price_pusher/src/sui/sui.ts +++ b/apps/price_pusher/src/sui/sui.ts @@ -225,6 +225,7 @@ export class SuiPricePusher implements IPricePusher { priceIdChunk, { encoding: "base64", + ignoreInvalidPriceIds: true, }, ); if (response.binary.data.length !== 1) { diff --git a/apps/price_pusher/src/ton/ton.ts b/apps/price_pusher/src/ton/ton.ts index d527ba3c66..4ecb681de8 100644 --- a/apps/price_pusher/src/ton/ton.ts +++ b/apps/price_pusher/src/ton/ton.ts @@ -98,6 +98,7 @@ export class TonPricePusher implements IPricePusher { try { const response = await this.hermesClient.getLatestPriceUpdates(priceIds, { encoding: "base64", + ignoreInvalidPriceIds: true, }); priceFeedUpdateData = response.binary.data; } catch (err: any) { From 9fb34e903dff251c2d084b97cdcc54f0c9f6ca36 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Sat, 24 May 2025 20:15:49 +0200 Subject: [PATCH 2/3] fix(apps/price_pusher): better price feed listener error handling --- apps/price_pusher/src/pyth-price-listener.ts | 33 +++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/price_pusher/src/pyth-price-listener.ts b/apps/price_pusher/src/pyth-price-listener.ts index 6a70a3c7a3..b3757b1c8a 100644 --- a/apps/price_pusher/src/pyth-price-listener.ts +++ b/apps/price_pusher/src/pyth-price-listener.ts @@ -5,6 +5,7 @@ import { } from "@pythnetwork/hermes-client"; import { PriceInfo, IPriceListener, PriceItem } from "./interface"; import { Logger } from "pino"; +import { sleep } from "./utils"; type TimestampInMs = number & { readonly _: unique symbol }; @@ -34,6 +35,24 @@ export class PythPriceListener implements IPriceListener { // This method should be awaited on and once it finishes it has the latest value // for the given price feeds (if they exist). async start() { + this.startListening(); + + // Store health check interval reference + this.healthCheckInterval = setInterval(() => { + if ( + this.lastUpdated === undefined || + this.lastUpdated < Date.now() - 30 * 1000 + ) { + throw new Error("Hermes Price feeds are not updating."); + } + }, 5000); + } + + async startListening() { + this.logger.info( + `Starting to listen for price updates from Hermes for ${this.priceIds.length} price feeds.`, + ); + const eventSource = await this.hermesClient.getPriceUpdatesStream( this.priceIds, { @@ -71,20 +90,12 @@ export class PythPriceListener implements IPriceListener { }); }; - eventSource.onerror = (error: Event) => { + eventSource.onerror = async (error: Event) => { console.error("Error receiving updates from Hermes:", error); eventSource.close(); + await sleep(5000); // Wait a bit before trying to reconnect + this.startListening(); // Attempt to restart the listener }; - - // Store health check interval reference - this.healthCheckInterval = setInterval(() => { - if ( - this.lastUpdated === undefined || - this.lastUpdated < Date.now() - 30 * 1000 - ) { - throw new Error("Hermes Price feeds are not updating."); - } - }, 5000); } getLatestPriceInfo(priceId: HexString): PriceInfo | undefined { From 3a6836903a75cdd555d720dd3dce1bc7833b1ee0 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Mon, 26 May 2025 11:19:15 +0200 Subject: [PATCH 3/3] fix(apps/price_pusher): do not push if source price is undefined --- apps/price_pusher/src/price-config.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/price_pusher/src/price-config.ts b/apps/price_pusher/src/price-config.ts index 3a1679c94c..af4f7bc500 100644 --- a/apps/price_pusher/src/price-config.ts +++ b/apps/price_pusher/src/price-config.ts @@ -96,9 +96,12 @@ export function shouldUpdate( ): UpdateCondition { const priceId = priceConfig.id; - // There is no price to update the target with. + // There is no price to update the target with. So we should not update it. if (sourceLatestPrice === undefined) { - return UpdateCondition.YES; + logger.info( + `${priceConfig.alias} (${priceId}) is not available on the source network. Ignoring it.`, + ); + return UpdateCondition.NO; } // It means that price never existed there. So we should push the latest price feed. @@ -140,7 +143,7 @@ export function shouldUpdate( }%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?) OR ` + `Confidence ratio: ${confidenceRatioPct.toFixed(5)}% (< ${ priceConfig.confidenceRatio - }%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?)`, + }%? / early: < ${priceConfig.earlyUpdateConfidenceRatio}%?)`, ); if (