feat: jito pusher (#1444)

* feat: jito script

* Go

* Go

* Checkpoint

* Checkpoint

* Rename

* Make tip account random

* Go

* Jito pusher

* Go

* lint

* Lint

* Bump
This commit is contained in:
guibescos 2024-04-11 19:14:10 +01:00 committed by GitHub
parent 0aeae8ca40
commit c727195e9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 169 additions and 15 deletions

6
package-lock.json generated
View File

@ -56725,7 +56725,7 @@
},
"price_pusher": {
"name": "@pythnetwork/price-pusher",
"version": "6.4.3",
"version": "6.5.0",
"license": "Apache-2.0",
"dependencies": {
"@injectivelabs/sdk-ts": "1.10.72",
@ -56736,6 +56736,7 @@
"@pythnetwork/pyth-sui-js": "*",
"@truffle/hdwallet-provider": "^2.1.3",
"aptos": "^1.8.5",
"jito-ts": "^3.0.1",
"joi": "^17.6.0",
"near-api-js": "^3.0.2",
"web3": "^1.8.1",
@ -69382,6 +69383,7 @@
"aptos": "^1.8.5",
"eslint": "^8.13.0",
"jest": "^29.7.0",
"jito-ts": "^3.0.1",
"joi": "^17.6.0",
"near-api-js": "^3.0.2",
"prettier": "^2.6.2",
@ -71551,7 +71553,7 @@
"bs58": "^5.0.0",
"eslint": "^8.13.0",
"jest": "^29.4.0",
"jito-ts": "*",
"jito-ts": "^3.0.1",
"prettier": "^2.6.2",
"quicktype": "^23.0.76",
"ts-jest": "^29.0.5",

View File

@ -1,6 +1,6 @@
{
"name": "@pythnetwork/price-pusher",
"version": "6.4.3",
"version": "6.5.0",
"description": "Pyth Price Pusher",
"homepage": "https://pyth.network",
"main": "lib/index.js",
@ -52,13 +52,14 @@
},
"dependencies": {
"@injectivelabs/sdk-ts": "1.10.72",
"@pythnetwork/pyth-solana-receiver": "*",
"@mysten/sui.js": "^0.49.1",
"@pythnetwork/price-service-client": "*",
"@pythnetwork/pyth-sdk-solidity": "*",
"@pythnetwork/pyth-solana-receiver": "*",
"@pythnetwork/pyth-sui-js": "*",
"@truffle/hdwallet-provider": "^2.1.3",
"aptos": "^1.8.5",
"jito-ts": "^3.0.1",
"joi": "^17.6.0",
"near-api-js": "^3.0.2",
"web3": "^1.8.1",

View File

@ -3,13 +3,21 @@ import * as options from "../options";
import { readPriceConfigFile } from "../price-config";
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
import { PythPriceListener } from "../pyth-price-listener";
import { SolanaPriceListener, SolanaPricePusher } from "./solana";
import {
SolanaPriceListener,
SolanaPricePusher,
SolanaPricePusherJito,
} from "./solana";
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 fs from "fs";
import { PublicKey } from "@solana/web3.js";
import {
SearcherClient,
searcherClient,
} from "jito-ts/dist/sdk/block-engine/searcher";
export default {
command: "solana",
@ -35,6 +43,27 @@ export default {
type: "number",
default: 50000,
} as Options,
"jito-endpoint": {
description: "Jito endpoint",
type: "string",
optional: true,
} as Options,
"jito-keypair-file": {
description:
"Path to the jito keypair file (need for grpc authentication)",
type: "string",
optional: true,
} as Options,
"jito-tip-lamports": {
description: "Lamports to tip the jito builder",
type: "number",
optional: true,
} as Options,
"jito-bundle-size": {
description: "Number of transactions in each bundle",
type: "number",
default: 2,
} as Options,
...options.priceConfigFile,
...options.priceServiceEndpoint,
...options.pythContractAddress,
@ -52,6 +81,10 @@ export default {
pythContractAddress,
pushingFrequency,
pollingFrequency,
jitoEndpoint,
jitoKeypairFile,
jitoTipLamports,
jitoBundleSize,
} = argv;
const priceConfigs = readPriceConfigFile(priceConfigFile);
@ -89,12 +122,32 @@ export default {
pushOracleProgramId: new PublicKey(pythContractAddress),
});
const solanaPricePusher = new SolanaPricePusher(
pythSolanaReceiver,
priceServiceConnection,
shardId,
computeUnitPriceMicroLamports
);
let solanaPricePusher;
if (jitoTipLamports) {
const jitoKeypair = Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(fs.readFileSync(jitoKeypairFile, "ascii")))
);
const jitoClient = searcherClient(jitoEndpoint, jitoKeypair);
solanaPricePusher = new SolanaPricePusherJito(
pythSolanaReceiver,
priceServiceConnection,
shardId,
jitoTipLamports,
jitoClient,
jitoBundleSize
);
onBundleResult(jitoClient);
} else {
solanaPricePusher = new SolanaPricePusher(
pythSolanaReceiver,
priceServiceConnection,
shardId,
computeUnitPriceMicroLamports
);
}
const solanaPriceListener = new SolanaPriceListener(
pythSolanaReceiver,
shardId,
@ -113,3 +166,12 @@ export default {
controller.start();
},
};
export const onBundleResult = (c: SearcherClient) => {
c.onBundleResult(
() => undefined,
(e) => {
console.log("Error in bundle result: ", e);
}
);
};

View File

@ -7,7 +7,11 @@ import {
} from "../interface";
import { DurationInSeconds } from "../utils";
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
import { sendTransactions } from "@pythnetwork/solana-utils";
import {
sendTransactions,
sendTransactionsJito,
} from "@pythnetwork/solana-utils";
import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher";
export class SolanaPriceListener extends ChainPriceListener {
constructor(
@ -110,3 +114,74 @@ export class SolanaPricePusher implements IPricePusher {
}
}
}
export class SolanaPricePusherJito implements IPricePusher {
constructor(
private pythSolanaReceiver: PythSolanaReceiver,
private priceServiceConnection: PriceServiceConnection,
private shardId: number,
private jitoTipLamports: number,
private searcherClient: SearcherClient,
private jitoBundleSize: number
) {}
async updatePriceFeed(
priceIds: string[],
pubTimesToPush: number[]
): Promise<void> {
let priceFeedUpdateData;
try {
priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas(
priceIds
);
} catch (e: any) {
console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
return;
}
const transactionBuilder = this.pythSolanaReceiver.newTransactionBuilder({
closeUpdateAccounts: false,
});
await transactionBuilder.addUpdatePriceFeed(
priceFeedUpdateData,
this.shardId
);
const transactions = await transactionBuilder.buildVersionedTransactions({
jitoTipLamports: this.jitoTipLamports,
tightComputeBudget: true,
jitoBundleSize: this.jitoBundleSize,
});
const firstSignature = await sendTransactionsJito(
transactions.slice(0, this.jitoBundleSize),
this.searcherClient,
this.pythSolanaReceiver.wallet
);
const blockhashResult =
await this.pythSolanaReceiver.connection.getLatestBlockhashAndContext({
commitment: "confirmed",
});
await this.pythSolanaReceiver.connection.confirmTransaction(
{
signature: firstSignature,
blockhash: blockhashResult.value.blockhash,
lastValidBlockHeight: blockhashResult.value.lastValidBlockHeight,
},
"confirmed"
);
for (
let i = this.jitoBundleSize;
i < transactions.length;
i += this.jitoBundleSize
) {
await sendTransactionsJito(
transactions.slice(i, i + 2),
this.searcherClient,
this.pythSolanaReceiver.wallet
);
}
}
}

View File

@ -1,5 +1,6 @@
import { Wallet } from "@coral-xyz/anchor";
import { PublicKey, Signer, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";
import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher";
import { Bundle } from "jito-ts/dist/sdk/block-engine/types";
@ -26,7 +27,7 @@ export async function sendTransactionsJito(
}[],
searcherClient: SearcherClient,
wallet: Wallet
) {
): Promise<string> {
const signedTransactions = [];
for (const transaction of transactions) {
@ -41,6 +42,12 @@ export async function sendTransactionsJito(
signedTransactions.push(tx);
}
const firstTransactionSignature = bs58.encode(
signedTransactions[0].signatures[0]
);
const bundle = new Bundle(signedTransactions, 2);
await searcherClient.sendBundle(bundle);
return firstTransactionSignature;
}

View File

@ -45,6 +45,7 @@ export type PriorityFeeConfig = {
computeUnitPriceMicroLamports?: number;
tightComputeBudget?: boolean;
jitoTipLamports?: number;
jitoBundleSize?: number;
};
/**
@ -225,6 +226,9 @@ export class TransactionBuilder {
await this.connection.getLatestBlockhash({ commitment: "confirmed" })
).blockhash;
const jitoBundleSize =
args.jitoBundleSize || this.transactionInstructions.length;
return this.transactionInstructions.map(
({ instructions, signers, computeUnits }, index) => {
const instructionsWithComputeBudget: TransactionInstruction[] = [
@ -247,7 +251,7 @@ export class TransactionBuilder {
}
if (
args.jitoTipLamports &&
index == this.transactionInstructions.length - 1
index % jitoBundleSize === jitoBundleSize - 1
) {
instructionsWithComputeBudget.push(
SystemProgram.transfer({
@ -280,6 +284,9 @@ export class TransactionBuilder {
buildLegacyTransactions(
args: PriorityFeeConfig
): { tx: Transaction; signers: Signer[] }[] {
const jitoBundleSize =
args.jitoBundleSize || this.transactionInstructions.length;
return this.transactionInstructions.map(
({ instructions, signers, computeUnits }, index) => {
const instructionsWithComputeBudget: TransactionInstruction[] = [
@ -302,7 +309,7 @@ export class TransactionBuilder {
}
if (
args.jitoTipLamports &&
index == this.transactionInstructions.length - 1
index % jitoBundleSize === jitoBundleSize - 1
) {
instructionsWithComputeBudget.push(
SystemProgram.transfer({