From c6ec88600cd0ad2d3697554526a7c4e39a81170b Mon Sep 17 00:00:00 2001 From: guibescos <59208140+guibescos@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:47:14 +0000 Subject: [PATCH] feat: add dynamic jito tips (#2113) * add dynamic jito tips * uncomment * go * using jito tip * go * bump * cap it * go * go * test * disable health check * go --- apps/price_pusher/package.json | 2 +- apps/price_pusher/src/solana/command.ts | 16 +++++- apps/price_pusher/src/solana/solana.ts | 67 +++++++++++++++++++------ 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/apps/price_pusher/package.json b/apps/price_pusher/package.json index 5b98e86706..bbc8a5e516 100644 --- a/apps/price_pusher/package.json +++ b/apps/price_pusher/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/price-pusher", - "version": "8.1.0", + "version": "8.2.0", "description": "Pyth Price Pusher", "homepage": "https://pyth.network", "main": "lib/index.js", diff --git a/apps/price_pusher/src/solana/command.ts b/apps/price_pusher/src/solana/command.ts index edefd404be..8d4e1dcb3d 100644 --- a/apps/price_pusher/src/solana/command.ts +++ b/apps/price_pusher/src/solana/command.ts @@ -11,7 +11,7 @@ import { import { Controller } from "../controller"; import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver"; import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; -import { Keypair, Connection } from "@solana/web3.js"; +import { Keypair, Connection, LAMPORTS_PER_SOL } from "@solana/web3.js"; import fs from "fs"; import { PublicKey } from "@solana/web3.js"; import { @@ -61,6 +61,16 @@ export default { type: "number", optional: true, } as Options, + "dynamic-jito-tips": { + description: "Use dynamic jito tips", + type: "boolean", + default: false, + } as Options, + "max-jito-tip-lamports": { + description: "Maximum jito tip lamports", + type: "number", + default: LAMPORTS_PER_SOL / 100, + } as Options, "jito-bundle-size": { description: "Number of transactions in each bundle", type: "number", @@ -94,6 +104,8 @@ export default { jitoEndpoint, jitoKeypairFile, jitoTipLamports, + dynamicJitoTips, + maxJitoTipLamports, jitoBundleSize, updatesPerJitoBundle, logLevel, @@ -148,6 +160,8 @@ export default { logger.child({ module: "SolanaPricePusherJito" }), shardId, jitoTipLamports, + dynamicJitoTips, + maxJitoTipLamports, jitoClient, jitoBundleSize, updatesPerJitoBundle diff --git a/apps/price_pusher/src/solana/solana.ts b/apps/price_pusher/src/solana/solana.ts index 49d686a81b..cab4899a20 100644 --- a/apps/price_pusher/src/solana/solana.ts +++ b/apps/price_pusher/src/solana/solana.ts @@ -14,6 +14,7 @@ import { import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher"; import { sliceAccumulatorUpdateData } from "@pythnetwork/price-service-sdk"; import { Logger } from "pino"; +import { LAMPORTS_PER_SOL } from "@solana/web3.js"; const HEALTH_CHECK_TIMEOUT_SECONDS = 60; @@ -34,21 +35,24 @@ export class SolanaPriceListener extends ChainPriceListener { // and ensuring it is not older than 30 seconds. private async checkHealth() { const slot = await this.pythSolanaReceiver.connection.getSlot("finalized"); - const blockTime = await this.pythSolanaReceiver.connection.getBlockTime( - slot - ); - if ( - blockTime === null || - blockTime < Date.now() / 1000 - HEALTH_CHECK_TIMEOUT_SECONDS - ) { - if (blockTime !== null) { - this.logger.info( - `Solana connection is behind by ${ - Date.now() / 1000 - blockTime - } seconds` - ); + try { + const blockTime = await this.pythSolanaReceiver.connection.getBlockTime( + slot + ); + if ( + blockTime === null || + blockTime < Date.now() / 1000 - HEALTH_CHECK_TIMEOUT_SECONDS + ) { + if (blockTime !== null) { + this.logger.info( + `Solana connection is behind by ${ + Date.now() / 1000 - blockTime + } seconds` + ); + } } - throw new Error("Solana connection is unhealthy"); + } catch (err) { + this.logger.error({ err }, "checkHealth failed"); } } @@ -155,17 +159,48 @@ export class SolanaPricePusherJito implements IPricePusher { private priceServiceConnection: PriceServiceConnection, private logger: Logger, private shardId: number, - private jitoTipLamports: number, + private defaultJitoTipLamports: number, + private dynamicJitoTips: boolean, + private maxJitoTipLamports: number, private searcherClient: SearcherClient, private jitoBundleSize: number, private updatesPerJitoBundle: number ) {} + async getRecentJitoTipLamports(): Promise { + try { + const response = await fetch( + "http://bundles-api-rest.jito.wtf/api/v1/bundles/tip_floor" + ); + if (!response.ok) { + this.logger.error( + { status: response.status, statusText: response.statusText }, + "getRecentJitoTips http request failed" + ); + return undefined; + } + const data = await response.json(); + return Math.floor( + Number(data[0].landed_tips_25th_percentile) * LAMPORTS_PER_SOL + ); + } catch (err: any) { + this.logger.error({ err }, "getRecentJitoTips failed"); + return undefined; + } + } + async updatePriceFeed( priceIds: string[], // eslint-disable-next-line @typescript-eslint/no-unused-vars _pubTimesToPush: number[] ): Promise { + const jitoTip = this.dynamicJitoTips + ? (await this.getRecentJitoTipLamports()) ?? this.defaultJitoTipLamports + : this.defaultJitoTipLamports; + + const cappedJitoTip = Math.min(jitoTip, this.maxJitoTipLamports); + this.logger.info({ cappedJitoTip }, "using jito tip of"); + let priceFeedUpdateData: string[]; try { priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas( @@ -192,7 +227,7 @@ export class SolanaPricePusherJito implements IPricePusher { ); const transactions = await transactionBuilder.buildVersionedTransactions({ - jitoTipLamports: this.jitoTipLamports, + jitoTipLamports: cappedJitoTip, tightComputeBudget: true, jitoBundleSize: this.jitoBundleSize, });