Skip to content

Commit

Permalink
[wip]: Update blockhash timeout strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva committed Jan 22, 2025
1 parent 2849abb commit e082342
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 68 deletions.
9 changes: 3 additions & 6 deletions clients/js/src/createMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { getTransferSolInstruction } from '@solana-program/system';
import { Lamports, sendAndConfirmTransactionFactory } from '@solana/web3.js';
import { Lamports } from '@solana/web3.js';
import {
getAllocateInstruction,
getInitializeInstruction,
getWriteInstruction,
PROGRAM_METADATA_PROGRAM_ADDRESS,
} from './generated';
import {
getDefaultCreateMessage,
getDefaultInstructionPlanContext,
getPdaDetails,
InstructionPlan,
PdaDetails,
Expand All @@ -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 };
}

Expand Down
86 changes: 49 additions & 37 deletions clients/js/src/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -73,19 +80,18 @@ function getTimedCacheFunction<T>(
timeoutInMilliseconds: number
): () => Promise<T> {
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;
};
}
Expand Down Expand Up @@ -117,67 +123,73 @@ export type InstructionPlan =
| ParallelInstructionPlan
| MessageInstructionPlan;

export async function sendInstructionPlan(
plan: InstructionPlan,
type SendInstructionPlanContext = {
createMessage: () => Promise<
CompilableTransactionMessage & TransactionMessageWithBlockhashLifetime
>,
sendAndConfirm: ReturnType<typeof sendAndConfirmTransactionFactory>
>;
sendAndConfirm: ReturnType<typeof sendAndConfirmTransactionFactory>;
};

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');
}
}

async function sendSequentialInstructionPlan(
plan: SequentialInstructionPlan,
createMessage: Parameters<typeof sendInstructionPlan>[1],
sendAndConfirm: Parameters<typeof sendInstructionPlan>[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<typeof sendInstructionPlan>[1],
sendAndConfirm: Parameters<typeof sendInstructionPlan>[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<typeof sendInstructionPlan>[1],
sendAndConfirm: Parameters<typeof sendInstructionPlan>[2]
ctx: SendInstructionPlanContext
) {
await pipe(
await createMessage(),
await ctx.createMessage(),
(tx) => appendTransactionMessageInstructions(plan.instructions, tx),
(tx) => signAndSendTransaction(tx, sendAndConfirm)
(tx) => signAndSendTransaction(tx, ctx.sendAndConfirm)
);
}
8 changes: 2 additions & 6 deletions clients/js/src/updateMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
isTransactionSigner,
lamports,
Lamports,
sendAndConfirmTransactionFactory,
TransactionSigner,
} from '@solana/web3.js';
import {
Expand All @@ -14,7 +13,7 @@ import {
getWriteInstruction,
} from './generated';
import {
getDefaultCreateMessage,
getDefaultInstructionPlanContext,
getPdaDetails,
InstructionPlan,
MessageInstructionPlan,
Expand All @@ -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 };
}

Expand Down
14 changes: 4 additions & 10 deletions clients/js/src/uploadMetadata.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 };
}

Expand All @@ -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 };
}
9 changes: 0 additions & 9 deletions clients/js/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
Address,
assertAccountExists,
CompilableTransactionMessage,
EncodedAccount,
fetchEncodedAccount,
GetAccountInfoApi,
Expand All @@ -24,7 +23,6 @@ import {
SlotNotificationsApi,
Transaction,
TransactionSigner,
TransactionWithBlockhashLifetime,
unwrapOption,
} from '@solana/web3.js';
import {
Expand Down Expand Up @@ -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 = {
Expand Down

0 comments on commit e082342

Please sign in to comment.