From e082342ce63cecc1093e8e879910c7dbfb328da5 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Wed, 22 Jan 2025 18:49:36 +0100 Subject: [PATCH] [wip]: Update blockhash timeout strategy --- clients/js/src/createMetadata.ts | 9 ++-- clients/js/src/internals.ts | 86 ++++++++++++++++++-------------- clients/js/src/updateMetadata.ts | 8 +-- clients/js/src/uploadMetadata.ts | 14 ++---- clients/js/src/utils.ts | 9 ---- 5 files changed, 58 insertions(+), 68 deletions(-) diff --git a/clients/js/src/createMetadata.ts b/clients/js/src/createMetadata.ts index 77f39f6..0c55e10 100644 --- a/clients/js/src/createMetadata.ts +++ b/clients/js/src/createMetadata.ts @@ -1,5 +1,5 @@ import { getTransferSolInstruction } from '@solana-program/system'; -import { Lamports, sendAndConfirmTransactionFactory } from '@solana/web3.js'; +import { Lamports } from '@solana/web3.js'; import { getAllocateInstruction, getInitializeInstruction, @@ -7,7 +7,7 @@ import { PROGRAM_METADATA_PROGRAM_ADDRESS, } from './generated'; import { - getDefaultCreateMessage, + getDefaultInstructionPlanContext, getPdaDetails, InstructionPlan, PdaDetails, @@ -24,10 +24,7 @@ export async function createMetadata( const pdaDetails = await getPdaDetails(input); const extendedInput = { ...input, ...pdaDetails }; const plan = await getCreateMetadataInstructions(extendedInput); - const createMessage = - input.createMessage ?? getDefaultCreateMessage(input.rpc, input.payer); - const sendAndConfirm = sendAndConfirmTransactionFactory(input); - await sendInstructionPlan(plan, createMessage, sendAndConfirm); + await sendInstructionPlan(plan, getDefaultInstructionPlanContext(input)); return { metadata: extendedInput.metadata }; } diff --git a/clients/js/src/internals.ts b/clients/js/src/internals.ts index fdbc67f..b47dd9e 100644 --- a/clients/js/src/internals.ts +++ b/clients/js/src/internals.ts @@ -5,14 +5,21 @@ import { CompilableTransactionMessage, createTransactionMessage, GetAccountInfoApi, + GetEpochInfoApi, GetLatestBlockhashApi, + GetMinimumBalanceForRentExemptionApi, + GetSignatureStatusesApi, IInstruction, pipe, Rpc, + RpcSubscriptions, sendAndConfirmTransactionFactory, + SendTransactionApi, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, + SignatureNotificationsApi, signTransactionMessageWithSigners, + SlotNotificationsApi, TransactionMessageWithBlockhashLifetime, TransactionSigner, } from '@solana/web3.js'; @@ -73,19 +80,18 @@ function getTimedCacheFunction( timeoutInMilliseconds: number ): () => Promise { let cache: T | null = null; - let cacheExpiryTimer: NodeJS.Timeout | null = null; + let lastFetchTime = 0; return async () => { + const currentTime = Date.now(); + // Cache hit. - if (cache && cacheExpiryTimer) { + if (cache && currentTime - lastFetchTime < timeoutInMilliseconds) { return cache; } // Cache miss. cache = await fn(); - cacheExpiryTimer = setTimeout(() => { - cache = null; - cacheExpiryTimer = null; - }, timeoutInMilliseconds); + lastFetchTime = currentTime; return cache; }; } @@ -117,32 +123,43 @@ export type InstructionPlan = | ParallelInstructionPlan | MessageInstructionPlan; -export async function sendInstructionPlan( - plan: InstructionPlan, +type SendInstructionPlanContext = { createMessage: () => Promise< CompilableTransactionMessage & TransactionMessageWithBlockhashLifetime - >, - sendAndConfirm: ReturnType + >; + sendAndConfirm: ReturnType; +}; + +export function getDefaultInstructionPlanContext(input: { + rpc: Rpc< + GetLatestBlockhashApi & + GetEpochInfoApi & + GetSignatureStatusesApi & + SendTransactionApi & + GetMinimumBalanceForRentExemptionApi + >; + rpcSubscriptions: RpcSubscriptions< + SignatureNotificationsApi & SlotNotificationsApi + >; + payer: TransactionSigner; +}): SendInstructionPlanContext { + return { + createMessage: getDefaultCreateMessage(input.rpc, input.payer), + sendAndConfirm: sendAndConfirmTransactionFactory(input), + }; +} + +export async function sendInstructionPlan( + plan: InstructionPlan, + ctx: SendInstructionPlanContext ) { switch (plan.kind) { case 'sequential': - return await sendSequentialInstructionPlan( - plan, - createMessage, - sendAndConfirm - ); + return await sendSequentialInstructionPlan(plan, ctx); case 'parallel': - return await sendParallelInstructionPlan( - plan, - createMessage, - sendAndConfirm - ); + return await sendParallelInstructionPlan(plan, ctx); case 'message': - return await sendMessageInstructionPlan( - plan, - createMessage, - sendAndConfirm - ); + return await sendMessageInstructionPlan(plan, ctx); default: throw new Error('Unsupported instruction plan'); } @@ -150,34 +167,29 @@ export async function sendInstructionPlan( async function sendSequentialInstructionPlan( plan: SequentialInstructionPlan, - createMessage: Parameters[1], - sendAndConfirm: Parameters[2] + ctx: SendInstructionPlanContext ) { for (const subPlan of plan.plans) { - await sendInstructionPlan(subPlan, createMessage, sendAndConfirm); + await sendInstructionPlan(subPlan, ctx); } } async function sendParallelInstructionPlan( plan: ParallelInstructionPlan, - createMessage: Parameters[1], - sendAndConfirm: Parameters[2] + ctx: SendInstructionPlanContext ) { await Promise.all( - plan.plans.map((subPlan) => - sendInstructionPlan(subPlan, createMessage, sendAndConfirm) - ) + plan.plans.map((subPlan) => sendInstructionPlan(subPlan, ctx)) ); } async function sendMessageInstructionPlan( plan: MessageInstructionPlan, - createMessage: Parameters[1], - sendAndConfirm: Parameters[2] + ctx: SendInstructionPlanContext ) { await pipe( - await createMessage(), + await ctx.createMessage(), (tx) => appendTransactionMessageInstructions(plan.instructions, tx), - (tx) => signAndSendTransaction(tx, sendAndConfirm) + (tx) => signAndSendTransaction(tx, ctx.sendAndConfirm) ); } diff --git a/clients/js/src/updateMetadata.ts b/clients/js/src/updateMetadata.ts index cfe1b3b..1603fd2 100644 --- a/clients/js/src/updateMetadata.ts +++ b/clients/js/src/updateMetadata.ts @@ -4,7 +4,6 @@ import { isTransactionSigner, lamports, Lamports, - sendAndConfirmTransactionFactory, TransactionSigner, } from '@solana/web3.js'; import { @@ -14,7 +13,7 @@ import { getWriteInstruction, } from './generated'; import { - getDefaultCreateMessage, + getDefaultInstructionPlanContext, getPdaDetails, InstructionPlan, MessageInstructionPlan, @@ -40,10 +39,7 @@ export async function updateMetadata( ...pdaDetails, }; const plan = await getUpdateMetadataInstructions(extendedInput); - const createMessage = - input.createMessage ?? getDefaultCreateMessage(input.rpc, input.payer); - const sendAndConfirm = sendAndConfirmTransactionFactory(input); - await sendInstructionPlan(plan, createMessage, sendAndConfirm); + await sendInstructionPlan(plan, getDefaultInstructionPlanContext(input)); return { metadata: extendedInput.metadata }; } diff --git a/clients/js/src/uploadMetadata.ts b/clients/js/src/uploadMetadata.ts index 7183492..a4d7709 100644 --- a/clients/js/src/uploadMetadata.ts +++ b/clients/js/src/uploadMetadata.ts @@ -1,8 +1,7 @@ -import { sendAndConfirmTransactionFactory } from '@solana/web3.js'; import { getCreateMetadataInstructions } from './createMetadata'; import { fetchMaybeMetadata } from './generated'; import { - getDefaultCreateMessage, + getDefaultInstructionPlanContext, getPdaDetails, sendInstructionPlan, } from './internals'; @@ -16,14 +15,12 @@ export async function uploadMetadata(input: MetadataInput) { pdaDetails.metadata ); const extendedInput = { ...input, ...pdaDetails }; + const sendContext = getDefaultInstructionPlanContext(input); // Create metadata if it doesn't exist. if (!metadataAccount.exists) { const plan = await getCreateMetadataInstructions(extendedInput); - const createMessage = - input.createMessage ?? getDefaultCreateMessage(input.rpc, input.payer); - const sendAndConfirm = sendAndConfirmTransactionFactory(input); - await sendInstructionPlan(plan, createMessage, sendAndConfirm); + await sendInstructionPlan(plan, sendContext); return { metadata: extendedInput.metadata }; } @@ -35,9 +32,6 @@ export async function uploadMetadata(input: MetadataInput) { ...extendedInput, currentDataLength: BigInt(metadataAccount.data.data.length), }); - const createMessage = - input.createMessage ?? getDefaultCreateMessage(input.rpc, input.payer); - const sendAndConfirm = sendAndConfirmTransactionFactory(input); - await sendInstructionPlan(plan, createMessage, sendAndConfirm); + await sendInstructionPlan(plan, sendContext); return { metadata: extendedInput.metadata }; } diff --git a/clients/js/src/utils.ts b/clients/js/src/utils.ts index 78730aa..f355d0e 100644 --- a/clients/js/src/utils.ts +++ b/clients/js/src/utils.ts @@ -1,7 +1,6 @@ import { Address, assertAccountExists, - CompilableTransactionMessage, EncodedAccount, fetchEncodedAccount, GetAccountInfoApi, @@ -24,7 +23,6 @@ import { SlotNotificationsApi, Transaction, TransactionSigner, - TransactionWithBlockhashLifetime, unwrapOption, } from '@solana/web3.js'; import { @@ -84,13 +82,6 @@ export type MetadataInput = { * Defaults to `false`. */ extractLastTransaction?: boolean; // TODO: use this. - /** - * The function to use when creating new transaction messages. - * Defaults to using transaction message V0 using the latest blockhash. - */ - createMessage?: () => Promise< - CompilableTransactionMessage & TransactionWithBlockhashLifetime - >; // TODO: use this. }; export type MetadataResponse = {