-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce
InstructionPlan
executors in JS client (#4)
- Loading branch information
1 parent
f71c3af
commit c281352
Showing
8 changed files
with
320 additions
and
200 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
clients/js/src/instructionPlans/defaultInstructionPlanExecutor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { | ||
appendTransactionMessageInstructions, | ||
CompilableTransactionMessage, | ||
FullySignedTransaction, | ||
pipe, | ||
signTransactionMessageWithSigners, | ||
TransactionMessageWithBlockhashLifetime, | ||
TransactionMessageWithDurableNonceLifetime, | ||
TransactionWithLifetime, | ||
} from '@solana/web3.js'; | ||
import { MessageInstructionPlan } from './instructionPlan'; | ||
import { | ||
chunkParallelInstructionPlans, | ||
createInstructionPlanExecutor, | ||
InstructionPlanExecutor, | ||
} from './instructionPlanExecutor'; | ||
|
||
export type DefaultInstructionPlanExecutorConfig = Readonly<{ | ||
/** | ||
* When provided, chunks the plans inside a {@link ParallelInstructionPlan}. | ||
* Each chunk is executed sequentially but each plan within a chunk is | ||
* executed in parallel. | ||
*/ | ||
parallelChunkSize?: number; | ||
|
||
/** | ||
* If true _and_ if the transaction message contains an instruction | ||
* that sets the compute unit limit to any value, the executor will | ||
* simulate the transaction to determine the optimal compute unit limit | ||
* before updating the compute budget instruction with the computed value. | ||
*/ | ||
simulateComputeUnitLimit?: boolean; // TODO | ||
|
||
/** | ||
* Returns the default transaction message used to send transactions. | ||
* Any instructions inside a {@link MessageInstructionPlan} will be | ||
* appended to this message. | ||
*/ | ||
getDefaultMessage: (config?: { | ||
abortSignal?: AbortSignal; | ||
}) => Promise< | ||
CompilableTransactionMessage & | ||
( | ||
| TransactionMessageWithBlockhashLifetime | ||
| TransactionMessageWithDurableNonceLifetime | ||
) | ||
>; | ||
|
||
/** | ||
* Sends and confirms a constructed transaction. | ||
*/ | ||
sendAndConfirm: ( | ||
transaction: FullySignedTransaction & TransactionWithLifetime, | ||
config?: { abortSignal?: AbortSignal } | ||
) => Promise<void>; | ||
}>; | ||
|
||
export function getDefaultInstructionPlanExecutor( | ||
config: DefaultInstructionPlanExecutorConfig | ||
): InstructionPlanExecutor { | ||
const { | ||
getDefaultMessage, | ||
parallelChunkSize: chunkSize, | ||
sendAndConfirm, | ||
} = config; | ||
|
||
return async (plan, config) => { | ||
const handleMessage = async (plan: MessageInstructionPlan) => { | ||
const tx = await pipe( | ||
await getDefaultMessage(config), | ||
(tx) => appendTransactionMessageInstructions(plan.instructions, tx), | ||
(tx) => signTransactionMessageWithSigners(tx) | ||
); | ||
await sendAndConfirm(tx, config); | ||
}; | ||
|
||
const executor = pipe(createInstructionPlanExecutor(handleMessage), (e) => | ||
chunkSize ? chunkParallelInstructionPlans(e, chunkSize) : e | ||
); | ||
|
||
return await executor(plan, config); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './defaultInstructionPlanExecutor'; | ||
export * from './instructionPlan'; | ||
export * from './instructionPlanExecutor'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { | ||
appendTransactionMessageInstructions, | ||
BaseTransactionMessage, | ||
IInstruction, | ||
} from '@solana/web3.js'; | ||
|
||
export type InstructionPlan = | ||
| SequentialInstructionPlan | ||
| ParallelInstructionPlan | ||
| MessageInstructionPlan; | ||
|
||
export type SequentialInstructionPlan = Readonly<{ | ||
kind: 'sequential'; | ||
plans: InstructionPlan[]; | ||
}>; | ||
|
||
export type ParallelInstructionPlan = Readonly<{ | ||
kind: 'parallel'; | ||
plans: InstructionPlan[]; | ||
}>; | ||
|
||
export type MessageInstructionPlan< | ||
TInstructions extends IInstruction[] = IInstruction[], | ||
> = Readonly<{ | ||
kind: 'message'; | ||
instructions: TInstructions; | ||
}>; | ||
|
||
export function getTransactionMessageFromPlan< | ||
TTransactionMessage extends BaseTransactionMessage = BaseTransactionMessage, | ||
>( | ||
defaultMessage: TTransactionMessage, | ||
plan: MessageInstructionPlan | ||
): TTransactionMessage { | ||
return appendTransactionMessageInstructions( | ||
plan.instructions, | ||
defaultMessage | ||
); | ||
} |
69 changes: 69 additions & 0 deletions
69
clients/js/src/instructionPlans/instructionPlanExecutor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { | ||
InstructionPlan, | ||
MessageInstructionPlan, | ||
ParallelInstructionPlan, | ||
} from './instructionPlan'; | ||
|
||
export type InstructionPlanExecutor = ( | ||
plan: InstructionPlan, | ||
config?: { abortSignal?: AbortSignal } | ||
) => Promise<void>; | ||
|
||
export function createInstructionPlanExecutor( | ||
handleMessage: ( | ||
plan: MessageInstructionPlan, | ||
config?: { abortSignal?: AbortSignal } | ||
) => Promise<void> | ||
): InstructionPlanExecutor { | ||
return async function self(plan, config) { | ||
switch (plan.kind) { | ||
case 'sequential': | ||
for (const subPlan of plan.plans) { | ||
await self(subPlan, config); | ||
} | ||
break; | ||
case 'parallel': | ||
await Promise.all(plan.plans.map((subPlan) => self(subPlan, config))); | ||
break; | ||
case 'message': | ||
return await handleMessage(plan, config); | ||
default: | ||
throw new Error('Unsupported instruction plan'); | ||
} | ||
}; | ||
} | ||
|
||
export function chunkParallelInstructionPlans( | ||
executor: InstructionPlanExecutor, | ||
chunkSize: number | ||
): InstructionPlanExecutor { | ||
const chunkPlan = (plan: ParallelInstructionPlan) => { | ||
return plan.plans | ||
.reduce( | ||
(chunks, subPlan) => { | ||
const lastChunk = chunks[chunks.length - 1]; | ||
if (lastChunk && lastChunk.length < chunkSize) { | ||
lastChunk.push(subPlan); | ||
} else { | ||
chunks.push([subPlan]); | ||
} | ||
return chunks; | ||
}, | ||
[[]] as InstructionPlan[][] | ||
) | ||
.map((plans) => ({ kind: 'parallel', plans }) as ParallelInstructionPlan); | ||
}; | ||
return async function self(plan, config) { | ||
switch (plan.kind) { | ||
case 'sequential': | ||
return await self(plan, config); | ||
case 'parallel': | ||
for (const chunk of chunkPlan(plan)) { | ||
await executor(chunk, config); | ||
} | ||
break; | ||
default: | ||
return await executor(plan, config); | ||
} | ||
}; | ||
} |
Oops, something went wrong.