From 1f71eae438615a82906007faaf3f039b101ae14f Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 25 Jun 2024 13:11:00 +0200 Subject: [PATCH 1/7] refactor(appts/price_pusher): fix warnings --- apps/price_pusher/src/sui/sui.ts | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/apps/price_pusher/src/sui/sui.ts b/apps/price_pusher/src/sui/sui.ts index 2e341c9832..d6ae489431 100644 --- a/apps/price_pusher/src/sui/sui.ts +++ b/apps/price_pusher/src/sui/sui.ts @@ -112,12 +112,6 @@ export class SuiPricePusher implements IPricePusher { private readonly provider: SuiClient, private logger: Logger, private priceServiceConnection: PriceServiceConnection, - private pythPackageId: string, - private pythStateId: string, - private wormholePackageId: string, - private wormholeStateId: string, - endpoint: string, - keypair: Ed25519Keypair, private gasBudget: number, private gasPool: SuiObjectRef[], private pythClient: SuiPythClient @@ -180,14 +174,6 @@ export class SuiPricePusher implements IPricePusher { } const provider = new SuiClient({ url: endpoint }); - const pythPackageId = await SuiPricePusher.getPackageId( - provider, - pythStateId - ); - const wormholePackageId = await SuiPricePusher.getPackageId( - provider, - wormholeStateId - ); const gasPool = await SuiPricePusher.initializeGasPool( keypair, @@ -208,12 +194,6 @@ export class SuiPricePusher implements IPricePusher { provider, logger, priceServiceConnection, - pythPackageId, - pythStateId, - wormholePackageId, - wormholeStateId, - endpoint, - keypair, gasBudget, gasPool, pythClient @@ -337,7 +317,7 @@ export class SuiPricePusher implements IPricePusher { ignoreGasObjects: string[], logger: Logger ): Promise { - const signerAddress = await signer.toSuiAddress(); + const signerAddress = signer.toSuiAddress(); if (ignoreGasObjects.length > 0) { logger.info( From 5548e145da74d03bebb54b9429939a9601aefdb7 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 25 Jun 2024 13:20:42 +0200 Subject: [PATCH 2/7] refactor(apps/price_pusher): crash on rpc issues --- apps/price_pusher/src/aptos/aptos.ts | 53 +++++++++----------- apps/price_pusher/src/injective/injective.ts | 3 +- apps/price_pusher/src/solana/solana.ts | 7 ++- apps/price_pusher/src/sui/sui.ts | 9 ++-- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/apps/price_pusher/src/aptos/aptos.ts b/apps/price_pusher/src/aptos/aptos.ts index 732793d5d2..612b2eeb28 100644 --- a/apps/price_pusher/src/aptos/aptos.ts +++ b/apps/price_pusher/src/aptos/aptos.ts @@ -23,14 +23,14 @@ export class AptosPriceListener extends ChainPriceListener { } async getOnChainPriceInfo(priceId: string): Promise { - try { - const client = new AptosClient(this.endpoint); + const client = new AptosClient(this.endpoint); - const res = await client.getAccountResource( - this.pythModule, - `${this.pythModule}::state::LatestPriceInfo` - ); + const res = await client.getAccountResource( + this.pythModule, + `${this.pythModule}::state::LatestPriceInfo` + ); + try { // This depends upon the pyth contract storage on Aptos and should not be undefined. // If undefined, there has been some change and we would need to update accordingly. const handle = (res.data as any).info.handle; @@ -134,29 +134,26 @@ export class AptosPricePusher implements IPricePusher { return; } - try { - const account = AptosAccount.fromDerivePath( - APTOS_ACCOUNT_HD_PATH, - this.mnemonic - ); - const client = new AptosClient(this.endpoint); - - const sequenceNumber = await this.tryGetNextSequenceNumber( - client, - account - ); - const rawTx = await client.generateTransaction( - account.address(), - { - function: `${this.pythContractAddress}::pyth::update_price_feeds_with_funder`, - type_arguments: [], - arguments: [priceFeedUpdateData], - }, - { - sequence_number: sequenceNumber.toFixed(), - } - ); + const account = AptosAccount.fromDerivePath( + APTOS_ACCOUNT_HD_PATH, + this.mnemonic + ); + const client = new AptosClient(this.endpoint); + + const sequenceNumber = await this.tryGetNextSequenceNumber(client, account); + const rawTx = await client.generateTransaction( + account.address(), + { + function: `${this.pythContractAddress}::pyth::update_price_feeds_with_funder`, + type_arguments: [], + arguments: [priceFeedUpdateData], + }, + { + sequence_number: sequenceNumber.toFixed(), + } + ); + try { const signedTx = await client.signTransaction(account, rawTx); const pendingTx = await client.submitTransaction(signedTx); diff --git a/apps/price_pusher/src/injective/injective.ts b/apps/price_pusher/src/injective/injective.ts index d648131841..e7cf94e438 100644 --- a/apps/price_pusher/src/injective/injective.ts +++ b/apps/price_pusher/src/injective/injective.ts @@ -232,7 +232,8 @@ export class InjectivePricePusher implements IPricePusher { updateFeeQueryResponse = JSON.parse(json); } catch (err) { this.logger.error(err, "Error fetching update fee"); - return; + // Throwing an error because it is likely an RPC issue + throw err; } try { diff --git a/apps/price_pusher/src/solana/solana.ts b/apps/price_pusher/src/solana/solana.ts index 911d3e24ef..873e5b706e 100644 --- a/apps/price_pusher/src/solana/solana.ts +++ b/apps/price_pusher/src/solana/solana.ts @@ -103,10 +103,13 @@ export class SolanaPricePusher implements IPricePusher { this.pythSolanaReceiver.connection, this.pythSolanaReceiver.wallet ); - this.logger.info({ signatures }, "updatePriceFeed successful"); + this.logger.info( + { signatures }, + "broadcasted updatePriceFeed transactions" + ); } catch (err: any) { this.logger.error(err, "updatePriceFeed failed"); - return; + throw err; } } } diff --git a/apps/price_pusher/src/sui/sui.ts b/apps/price_pusher/src/sui/sui.ts index d6ae489431..4566c31a25 100644 --- a/apps/price_pusher/src/sui/sui.ts +++ b/apps/price_pusher/src/sui/sui.ts @@ -363,8 +363,7 @@ export class SuiPricePusher implements IPricePusher { } // Attempt to refresh the version of the provided object reference to point to the current version - // of the object. Return the provided object reference if an error occurs or the object could not - // be retrieved. + // of the object. Throws an error if the object cannot be refreshed. private static async tryRefreshObjectReference( provider: SuiClient, ref: SuiObjectRef @@ -378,10 +377,10 @@ export class SuiPricePusher implements IPricePusher { version: objectResponse.data!.version, }; } else { - return ref; + throw new Error("Failed to refresh object reference"); } - } catch (error) { - return ref; + } catch (err) { + throw err; } } From 1fa60fad4a0f2beaab9c20b0c95c1bcbbbdf27bd Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 25 Jun 2024 13:44:27 +0200 Subject: [PATCH 3/7] refactor(apps/price_pusher): crash on stale hermes data --- apps/price_pusher/src/pyth-price-listener.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/price_pusher/src/pyth-price-listener.ts b/apps/price_pusher/src/pyth-price-listener.ts index c69a98077e..2a01a36b5e 100644 --- a/apps/price_pusher/src/pyth-price-listener.ts +++ b/apps/price_pusher/src/pyth-price-listener.ts @@ -6,12 +6,15 @@ import { import { PriceInfo, IPriceListener, PriceItem } from "./interface"; import { Logger } from "pino"; +type TimestampInMs = number & { readonly _: unique symbol }; + export class PythPriceListener implements IPriceListener { private connection: PriceServiceConnection; private priceIds: HexString[]; private priceIdToAlias: Map; private latestPriceInfo: Map; private logger: Logger; + private lastUpdated: TimestampInMs | undefined; constructor( connection: PriceServiceConnection, @@ -46,6 +49,17 @@ export class PythPriceListener implements IPriceListener { publishTime: latestAvailablePrice.publishTime, }); }); + + // Check health of the price feeds 5 second. If the price feeds are not updating + // for more than 30s, throw an error. + setInterval(() => { + if ( + this.lastUpdated === undefined || + this.lastUpdated < Date.now() - 30 * 1000 + ) { + throw new Error("Hermes Price feeds are not updating."); + } + }, 5000); } private onNewPriceFeed(priceFeed: PriceFeed) { @@ -68,6 +82,7 @@ export class PythPriceListener implements IPriceListener { }; this.latestPriceInfo.set(priceFeed.id, priceInfo); + this.lastUpdated = Date.now() as TimestampInMs; } getLatestPriceInfo(priceId: string): PriceInfo | undefined { From 59a41ebb97c801f1e9afc238d5049936b5fc6013 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 25 Jun 2024 13:46:55 +0200 Subject: [PATCH 4/7] fix: run linter --- apps/price_pusher/src/sui/sui.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/apps/price_pusher/src/sui/sui.ts b/apps/price_pusher/src/sui/sui.ts index 4566c31a25..0d3a373baf 100644 --- a/apps/price_pusher/src/sui/sui.ts +++ b/apps/price_pusher/src/sui/sui.ts @@ -368,19 +368,15 @@ export class SuiPricePusher implements IPricePusher { provider: SuiClient, ref: SuiObjectRef ): Promise { - try { - const objectResponse = await provider.getObject({ id: ref.objectId }); - if (objectResponse.data !== undefined) { - return { - digest: objectResponse.data!.digest, - objectId: objectResponse.data!.objectId, - version: objectResponse.data!.version, - }; - } else { - throw new Error("Failed to refresh object reference"); - } - } catch (err) { - throw err; + const objectResponse = await provider.getObject({ id: ref.objectId }); + if (objectResponse.data !== undefined) { + return { + digest: objectResponse.data!.digest, + objectId: objectResponse.data!.objectId, + version: objectResponse.data!.version, + }; + } else { + throw new Error("Failed to refresh object reference"); } } From 4c1a946c939ab05dfea2ad63ca1a254efb74c3ed Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 25 Jun 2024 13:47:31 +0200 Subject: [PATCH 5/7] chore: bump version --- apps/price_pusher/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/price_pusher/package.json b/apps/price_pusher/package.json index 64a7448638..0b4bed567c 100644 --- a/apps/price_pusher/package.json +++ b/apps/price_pusher/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/price-pusher", - "version": "7.0.0-alpha", + "version": "7.0.0", "description": "Pyth Price Pusher", "homepage": "https://pyth.network", "main": "lib/index.js", From 638468086d1f72a8ad62f03b192905b0f141f95c Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 25 Jun 2024 14:37:40 +0200 Subject: [PATCH 6/7] fix: do not crash on sendtx failure in solana --- apps/price_pusher/src/solana/solana.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/price_pusher/src/solana/solana.ts b/apps/price_pusher/src/solana/solana.ts index 873e5b706e..1e677e84bc 100644 --- a/apps/price_pusher/src/solana/solana.ts +++ b/apps/price_pusher/src/solana/solana.ts @@ -28,6 +28,25 @@ export class SolanaPriceListener extends ChainPriceListener { super(config.pollingFrequency, priceItems); } + // Checking the health of the Solana connection by checking the last block time + // and ensuring it is not older than 30 seconds. + private async checkHealth() { + const slot = await this.pythSolanaReceiver.connection.getSlot(); + const blockTime = await this.pythSolanaReceiver.connection.getBlockTime( + slot + ); + if (blockTime !== undefined || blockTime < Date.now() / 1000 - 30) { + throw new Error("Solana connection is unhealthy"); + } + } + + async start() { + // Frequently check the RPC connection to ensure it is healthy + setInterval(() => this.checkHealth.bind(this), 5000); + + await super.start(); + } + async getOnChainPriceInfo(priceId: string): Promise { try { const priceFeedAccount = @@ -103,13 +122,9 @@ export class SolanaPricePusher implements IPricePusher { this.pythSolanaReceiver.connection, this.pythSolanaReceiver.wallet ); - this.logger.info( - { signatures }, - "broadcasted updatePriceFeed transactions" - ); + this.logger.info({ signatures }, "updatePriceFeed successful"); } catch (err: any) { this.logger.error(err, "updatePriceFeed failed"); - throw err; } } } From 8de8d28de20ca3967bce7e9e90fbf6714ca5f893 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 25 Jun 2024 19:05:13 +0200 Subject: [PATCH 7/7] fix: address issues raised in review --- apps/price_pusher/src/solana/solana.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/price_pusher/src/solana/solana.ts b/apps/price_pusher/src/solana/solana.ts index 1e677e84bc..efb56ce42a 100644 --- a/apps/price_pusher/src/solana/solana.ts +++ b/apps/price_pusher/src/solana/solana.ts @@ -35,14 +35,14 @@ export class SolanaPriceListener extends ChainPriceListener { const blockTime = await this.pythSolanaReceiver.connection.getBlockTime( slot ); - if (blockTime !== undefined || blockTime < Date.now() / 1000 - 30) { + if (blockTime === null || blockTime < Date.now() / 1000 - 30) { throw new Error("Solana connection is unhealthy"); } } async start() { // Frequently check the RPC connection to ensure it is healthy - setInterval(() => this.checkHealth.bind(this), 5000); + setInterval(this.checkHealth.bind(this), 5000); await super.start(); } @@ -125,6 +125,7 @@ export class SolanaPricePusher implements IPricePusher { this.logger.info({ signatures }, "updatePriceFeed successful"); } catch (err: any) { this.logger.error(err, "updatePriceFeed failed"); + return; } } }