From f79f56ed07d18d2720bfe9bea4d5b8a018eb0e4c Mon Sep 17 00:00:00 2001 From: guibescos <59208140+guibescos@users.noreply.github.com> Date: Thu, 28 Nov 2024 18:08:12 +0000 Subject: [PATCH] limonade: bump express-relay sdk and remove direct http checks (#264) * do something * send requests concurrently * add-rule * check * pr comments --- .eslintrc.js | 6 ++ scripts/limonade/package-lock.json | 12 +-- scripts/limonade/package.json | 4 +- scripts/limonade/src/index.ts | 161 ++++++++++++----------------- 4 files changed, 78 insertions(+), 105 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fca472f3..e2ef57cd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,5 +2,11 @@ module.exports = { root: true, parser: "@typescript-eslint/parser", plugins: ["@typescript-eslint"], + parserOptions: { + project: "./tsconfig.json", + }, + rules: { + "@typescript-eslint/no-misused-promises": "error", + }, extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], }; diff --git a/scripts/limonade/package-lock.json b/scripts/limonade/package-lock.json index d1beb59a..5d4c2fad 100644 --- a/scripts/limonade/package-lock.json +++ b/scripts/limonade/package-lock.json @@ -1,18 +1,18 @@ { "name": "limonade", - "version": "0.2.3", + "version": "0.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "limonade", - "version": "0.2.3", + "version": "0.2.4", "license": "APACHE-2.0", "dependencies": { "@coral-xyz/anchor": "^0.30.1", "@coral-xyz/anchor-cli": "^0.30.1", "@kamino-finance/limo-sdk": "^0.7.1", - "@pythnetwork/express-relay-js": "^0.15.0", + "@pythnetwork/express-relay-js": "^0.15.1", "@solana/web3.js": "^1.95.3", "axios": "^1.7.3", "bs58": "^6.0.0", @@ -617,9 +617,9 @@ } }, "node_modules/@pythnetwork/express-relay-js": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/express-relay-js/-/express-relay-js-0.15.0.tgz", - "integrity": "sha512-1MMzfkhNE9gOuVFccHxHLM9COlVKKcm0omeWKLQmvVc9+MQnICv1edT1czZhQ3IKAEOuycYZyLr19RmrGjYd3Q==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@pythnetwork/express-relay-js/-/express-relay-js-0.15.1.tgz", + "integrity": "sha512-k6AXTWVOqrp01wOK5zAKFXglF4lVljdrva/MA/zIWL9PLDtflP3ahcPtaP4aMeKot2yPk9PGYGnYvzC3AcGARA==", "license": "Apache-2.0", "dependencies": { "@coral-xyz/anchor": "^0.30.1", diff --git a/scripts/limonade/package.json b/scripts/limonade/package.json index 3d89aea4..12a8155f 100644 --- a/scripts/limonade/package.json +++ b/scripts/limonade/package.json @@ -1,6 +1,6 @@ { "name": "limonade", - "version": "0.2.3", + "version": "0.2.4", "description": "This script is used to submit new opportunities fetched from the limo program to the express relay.", "main": "index.js", "scripts": { @@ -22,7 +22,7 @@ "@coral-xyz/anchor": "^0.30.1", "@coral-xyz/anchor-cli": "^0.30.1", "@kamino-finance/limo-sdk": "^0.7.1", - "@pythnetwork/express-relay-js": "^0.15.0", + "@pythnetwork/express-relay-js": "^0.15.1", "@solana/web3.js": "^1.95.3", "axios": "^1.7.3", "bs58": "^6.0.0", diff --git a/scripts/limonade/src/index.ts b/scripts/limonade/src/index.ts index f1cd0c2c..c7f31e4c 100644 --- a/scripts/limonade/src/index.ts +++ b/scripts/limonade/src/index.ts @@ -10,6 +10,7 @@ import yargs from "yargs"; import { ChainType, Client, + ClientError, OpportunityCreate, } from "@pythnetwork/express-relay-js"; import { getPdaAuthority } from "@kamino-finance/limo-sdk/dist/utils"; @@ -46,6 +47,11 @@ const argv = yargs(hideBin(process.argv)) type: "string", demandOption: true, }) + .option("number-of-concurrent-submissions", { + description: "Number of concurrent submissions to the express relay server", + type: "number", + default: 100, + }) .help() .alias("help", "h") .parseSync(); @@ -54,6 +60,7 @@ async function run() { const connection = new Connection(argv.rpcEndpoint); const globalConfig = new PublicKey(argv.globalConfig); + const numberOfConcurrentSubmissions = argv.numberOfConcurrentSubmissions; const filters: GetProgramAccountsFilter[] = [ { memcmp: { @@ -72,6 +79,16 @@ async function run() { console.log("Listening for program account changes"); const client = new Client({ baseUrl: argv.endpoint, apiKey: argv.apiKey }); + const handleSubmitError = (e: unknown) => { + if ( + !( + e instanceof ClientError && + e.message.includes("Same opportunity is submitted recently") + ) + ) { + console.error("Failed to submit opportunity", e); + } + }; const submitExistingOpportunities = async () => { const response = await connection.getProgramAccounts(limoId, { commitment: "confirmed", @@ -100,55 +117,63 @@ async function run() { ); console.log("Resubmitting opportunities", payloads.length); - for (const payload of payloads) { - try { - await client.submitOpportunity(payload); - } catch (e) { - console.error(e); - } + for (let i = 0; i < payloads.length; i += numberOfConcurrentSubmissions) { + const batch = payloads.slice(i, i + numberOfConcurrentSubmissions); + await Promise.all( + batch.map(async (payload) => { + try { + await client.submitOpportunity(payload); + } catch (e) { + handleSubmitError(e); + } + }) + ); } }; connection.onProgramAccountChange( limoId, - async (info, context) => { - const order = Order.decode(info.accountInfo.data); - if (order.remainingInputAmount.toNumber() === 0) { - const router = getPdaAuthority(limoId, globalConfig); + (info, context) => { + async function handleUpdate() { + const order = Order.decode(info.accountInfo.data); + if (order.remainingInputAmount.toNumber() === 0) { + const router = getPdaAuthority(limoId, globalConfig); + + try { + await client.removeOpportunity({ + chainType: ChainType.SVM, + program: "limo", + chainId: argv.chainId, + permissionAccount: info.accountId, + router, + }); + } catch (e) { + console.error("Failed to remove opportunity", e); + } + return; + } + console.log( + "Fetched order with address:", + info.accountId.toBase58(), + "slot:", + context.slot + ); + + const payload: OpportunityCreate = { + program: "limo", + chainId: argv.chainId, + slot: context.slot, + order: { state: order, address: info.accountId }, + }; try { - await client.removeOpportunity({ - chainType: ChainType.SVM, - program: "limo", - chainId: argv.chainId, - permissionAccount: info.accountId, - router, - }); + await client.submitOpportunity(payload); + lastChange[info.accountId.toBase58()] = Date.now(); } catch (e) { - console.error("Failed to remove opportunity", e); + handleSubmitError(e); } - return; - } - console.log( - "Fetched order with address:", - info.accountId.toBase58(), - "slot:", - context.slot - ); - - const payload: OpportunityCreate = { - program: "limo", - chainId: argv.chainId, - slot: context.slot, - order: { state: order, address: info.accountId }, - }; - - try { - await client.submitOpportunity(payload); - lastChange[info.accountId.toBase58()] = Date.now(); - } catch (e) { - console.error("Failed to submit opportunity", e); } + handleUpdate().catch(console.error); }, { commitment: "processed", @@ -165,65 +190,7 @@ async function run() { } }; - const RPC_HEALTH_CHECK_SECONDS_THRESHOLD = 300; - const checkRpcHealth = async () => { - //eslint-disable-next-line no-constant-condition - while (true) { - try { - const slot = await connection.getSlot("finalized"); - const blockTime = await connection.getBlockTime(slot); - const timeNow = Date.now() / 1000; - if (blockTime === null) { - console.error( - `Health Error (RPC endpoint): unable to poll block time for slot ${slot}` - ); - } else if (blockTime < timeNow - RPC_HEALTH_CHECK_SECONDS_THRESHOLD) { - console.error( - `Health Error (RPC endpoint): block time is stale by ${ - timeNow - blockTime - } seconds` - ); - } - } catch (e) { - if (e instanceof Error) { - if (!e.message.includes("Block not available for slot")) { - console.error("Health Error (RPC endpoint), failure to fetch: ", e); - } - } else { - console.error("Health Error (RPC endpoint), failure to fetch: ", e); - } - } - // Wait for 10 seconds before rechecking - await new Promise((resolve) => setTimeout(resolve, 10 * 1000)); - } - }; - - const urlExpressRelayHealth = new URL("/live", argv.endpoint); - const checkExpressRelayHealth = async () => { - //eslint-disable-next-line no-constant-condition - while (true) { - try { - const responseHealth = await fetch(urlExpressRelayHealth); - if (responseHealth.status !== 200) { - console.error( - "Health Error (Express Relay endpoint): ", - responseHealth - ); - } - } catch (e) { - console.error( - "Health Error (Express Relay endpoint), failure to fetch: ", - e - ); - } - // Wait for 10 seconds before rechecking - await new Promise((resolve) => setTimeout(resolve, 10 * 1000)); - } - }; - resubmitOpportunities().catch(console.error); - checkRpcHealth().catch(console.error); - checkExpressRelayHealth().catch(console.error); } run();