From a43b7041be8d9be7ff90795eccaf8ab5406bcbf5 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 9 Nov 2023 19:00:55 +0000 Subject: [PATCH] refactor(experimental): revise transactions schema --- packages/rpc-graphql/README.md | 132 ++++++++---------- packages/rpc-graphql/src/context.ts | 71 +--------- packages/rpc-graphql/src/resolvers/block.ts | 30 ++++ .../rpc-graphql/src/resolvers/transaction.ts | 51 +++++++ .../rpc-graphql/src/schema/block/query.ts | 2 +- packages/rpc-graphql/src/schema/inputs.ts | 3 - .../src/schema/transaction/query.ts | 2 +- .../src/schema/transaction/types.ts | 118 ++++------------ 8 files changed, 173 insertions(+), 236 deletions(-) create mode 100644 packages/rpc-graphql/src/resolvers/block.ts create mode 100644 packages/rpc-graphql/src/resolvers/transaction.ts diff --git a/packages/rpc-graphql/README.md b/packages/rpc-graphql/README.md index 55684716965f..1d75bcc50af0 100644 --- a/packages/rpc-graphql/README.md +++ b/packages/rpc-graphql/README.md @@ -754,19 +754,17 @@ const source = ` ... on TransactionJsonParsed { transaction { message { - ... on TransactionMessageParsed { - accountKeys { - pubkey - signer - source - writable - } - instructions { - ... on PartiallyDecodedInstruction { - accounts - data - programId - } + accountKeys { + pubkey + signer + source + writable + } + instructions { + ... on PartiallyDecodedInstruction { + accounts + data + programId } } } @@ -829,16 +827,14 @@ const source = ` ... on TransactionJsonParsed { transaction { message { - ... on TransactionMessageParsed { - instructions { - ... on CreateAccountInstruction { - parsed { - info { - lamports - space - } - program + instructions { + ... on CreateAccountInstruction { + parsed { + info { + lamports + space } + program } } } @@ -934,57 +930,55 @@ const source = ` ... on TransactionJsonParsed { transaction { message { - ... on TransactionMessageParsed { - instructions { - ... on SplTokenTransferInstruction { - parsed { - info { - amount - authority { - address - lamports - } - destination { - ... on TokenAccount { - data { - address - mint { - ... on MintAccount { - data { - address - decimals - } + instructions { + ... on SplTokenTransferInstruction { + parsed { + info { + amount + authority { + address + lamports + } + destination { + ... on TokenAccount { + data { + address + mint { + ... on MintAccount { + data { + address + decimals } } - owner { - address - lamports - } + } + owner { + address + lamports } } } - source { - ... on TokenAccount { - data { - address - mint { - ... on MintAccount { - data { - address - decimals - } + } + source { + ... on TokenAccount { + data { + address + mint { + ... on MintAccount { + data { + address + decimals } } - owner { - address - lamports - } + } + owner { + address + lamports } } } } - program } + program } } } @@ -1080,16 +1074,14 @@ const source = ` ... on TransactionJsonParsed { transaction { message { - ... on TransactionMessageParsed { - instructions { - ... on CreateAccountInstruction { - parsed { - info { - lamports - space - } - program + instructions { + ... on CreateAccountInstruction { + parsed { + info { + lamports + space } + program } } } diff --git a/packages/rpc-graphql/src/context.ts b/packages/rpc-graphql/src/context.ts index b5c9697b9bdd..4c8f01d034eb 100644 --- a/packages/rpc-graphql/src/context.ts +++ b/packages/rpc-graphql/src/context.ts @@ -4,7 +4,9 @@ import { GraphQLResolveInfo } from 'graphql'; import { createGraphQLCache, GraphQLCache } from './cache'; import { resolveAccount } from './resolvers/account'; +import { resolveBlock } from './resolvers/block'; import { resolveProgramAccounts } from './resolvers/program-accounts'; +import { resolveTransaction } from './resolvers/transaction'; import { AccountQueryArgs } from './schema/account/query'; import { BlockQueryArgs } from './schema/block'; import { ProgramAccountsQueryArgs } from './schema/program-accounts'; @@ -19,75 +21,6 @@ export interface RpcGraphQLContext { rpc: Rpc; } -async function resolveBlock( - { slot, encoding = 'jsonParsed', ...config }: BlockQueryArgs, - cache: GraphQLCache, - rpc: Rpc -) { - const requestConfig = { encoding, ...config }; - - const cached = cache.get(slot, config); - if (cached !== null) { - return cached; - } - - const block = await rpc.getBlock(slot, requestConfig as Parameters[1]).send(); - - if (block === null) { - return null; - } - - cache.insert(slot, config, block); - - return block; -} - -async function resolveTransaction( - { signature, encoding = 'jsonParsed', ...config }: TransactionQueryArgs, - cache: GraphQLCache, - rpc: Rpc -) { - const requestConfig = { encoding, ...config }; - - const cached = cache.get(signature, requestConfig); - if (cached !== null) { - return cached; - } - - const transaction = await rpc - .getTransaction(signature, requestConfig as Parameters[1]) - .send(); - - if (transaction === null) { - return null; - } - - const [transactionData, responseEncoding, responseFormat] = Array.isArray(transaction.transaction) - ? encoding === 'jsonParsed' - ? [transaction.transaction[0], 'base64', 'unparsed'] - : [transaction.transaction[0], encoding, 'unparsed'] - : encoding === 'jsonParsed' - ? [transaction.transaction, encoding, 'parsed'] - : [transaction.transaction, encoding, 'unparsed']; - if (transaction.meta) { - // Ugly, but tells TypeScript what's happening - (transaction.meta as { format?: string } & { [key: string]: unknown })['format'] = responseFormat; - } - if (transactionData.message) { - // Ugly, but tells TypeScript what's happening - (transactionData.message as { format?: string } & { [key: string]: unknown })['format'] = responseFormat; - } - const queryResponse = { - ...transaction, - encoding: responseEncoding, - transaction: transactionData, - }; - - cache.insert(signature, requestConfig, queryResponse); - - return queryResponse; -} - export function createSolanaGraphQLContext(rpc: Rpc): RpcGraphQLContext { const cache = createGraphQLCache(); return { diff --git a/packages/rpc-graphql/src/resolvers/block.ts b/packages/rpc-graphql/src/resolvers/block.ts new file mode 100644 index 000000000000..068618912696 --- /dev/null +++ b/packages/rpc-graphql/src/resolvers/block.ts @@ -0,0 +1,30 @@ +import { SolanaRpcMethods } from '@solana/rpc-core'; +import { Rpc } from '@solana/rpc-transport/dist/types/json-rpc-types'; + +import { GraphQLCache } from '../cache'; +import { BlockQueryArgs } from '../schema/block'; + +export async function resolveBlock( + { slot, encoding = 'jsonParsed', ...config }: BlockQueryArgs, + cache: GraphQLCache, + rpc: Rpc +) { + const requestConfig = { encoding, ...config }; + + const cached = cache.get(slot, config); + if (cached !== null) { + return cached; + } + + const block = await rpc + .getBlock(slot, requestConfig as unknown as Parameters[1]) + .send(); + + if (block === null) { + return null; + } + + cache.insert(slot, config, block); + + return block; +} diff --git a/packages/rpc-graphql/src/resolvers/transaction.ts b/packages/rpc-graphql/src/resolvers/transaction.ts new file mode 100644 index 000000000000..b3eaffb99678 --- /dev/null +++ b/packages/rpc-graphql/src/resolvers/transaction.ts @@ -0,0 +1,51 @@ +import { SolanaRpcMethods } from '@solana/rpc-core'; +import { Rpc } from '@solana/rpc-transport/dist/types/json-rpc-types'; + +import { GraphQLCache } from '../cache'; +import { TransactionQueryArgs } from '../schema/transaction'; + +export async function resolveTransaction( + { signature, encoding = 'jsonParsed', ...config }: TransactionQueryArgs, + cache: GraphQLCache, + rpc: Rpc +) { + const requestConfig = { encoding, ...config }; + + const cached = cache.get(signature, requestConfig); + if (cached !== null) { + return cached; + } + + const transaction = await rpc + .getTransaction(signature, requestConfig as unknown as Parameters[1]) + .send(); + + if (transaction === null) { + return null; + } + + const [transactionData, responseEncoding, responseFormat] = Array.isArray(transaction.transaction) + ? encoding === 'jsonParsed' + ? [transaction.transaction[0], 'base64', 'unparsed'] + : [transaction.transaction[0], encoding, 'unparsed'] + : encoding === 'jsonParsed' + ? [transaction.transaction, encoding, 'parsed'] + : [transaction.transaction, encoding, 'unparsed']; + if (transaction.meta) { + // Ugly, but tells TypeScript what's happening + (transaction.meta as { format?: string } & { [key: string]: unknown })['format'] = responseFormat; + } + if (transactionData.message) { + // Ugly, but tells TypeScript what's happening + (transactionData.message as { format?: string } & { [key: string]: unknown })['format'] = responseFormat; + } + const queryResponse = { + ...transaction, + encoding: responseEncoding, + transaction: transactionData, + }; + + cache.insert(signature, requestConfig, queryResponse); + + return queryResponse; +} diff --git a/packages/rpc-graphql/src/schema/block/query.ts b/packages/rpc-graphql/src/schema/block/query.ts index 9e80ff34cb9c..8ef64013e1c3 100644 --- a/packages/rpc-graphql/src/schema/block/query.ts +++ b/packages/rpc-graphql/src/schema/block/query.ts @@ -10,7 +10,7 @@ import { blockInterface } from './types'; export type BlockQueryArgs = { slot: Slot; commitment?: Commitment; - encoding?: 'base58' | 'base64' | 'json' | 'jsonParsed'; + encoding?: 'base58' | 'base64' | 'jsonParsed'; maxSupportedTransactionVersion?: Exclude; rewards?: boolean; transactionDetails?: 'accounts' | 'full' | 'none' | 'signatures'; diff --git a/packages/rpc-graphql/src/schema/inputs.ts b/packages/rpc-graphql/src/schema/inputs.ts index 5373b1f97c9a..b11c6d42b100 100644 --- a/packages/rpc-graphql/src/schema/inputs.ts +++ b/packages/rpc-graphql/src/schema/inputs.ts @@ -125,9 +125,6 @@ export const transactionEncodingInputType = () => { base64: { value: 'base64', }, - json: { - value: 'json', - }, jsonParsed: { value: 'jsonParsed', }, diff --git a/packages/rpc-graphql/src/schema/transaction/query.ts b/packages/rpc-graphql/src/schema/transaction/query.ts index bdbc512f4682..70f5cc367efb 100644 --- a/packages/rpc-graphql/src/schema/transaction/query.ts +++ b/packages/rpc-graphql/src/schema/transaction/query.ts @@ -10,7 +10,7 @@ import { transactionInterface } from './types'; export type TransactionQueryArgs = { signature: Signature; commitment?: Commitment; - encoding?: 'base58' | 'base64' | 'json' | 'jsonParsed'; + encoding?: 'base58' | 'base64' | 'jsonParsed'; maxSupportedTransactionVersion?: Exclude; }; diff --git a/packages/rpc-graphql/src/schema/transaction/types.ts b/packages/rpc-graphql/src/schema/transaction/types.ts index 8ba4862349f0..45a30c9fe3c8 100644 --- a/packages/rpc-graphql/src/schema/transaction/types.ts +++ b/packages/rpc-graphql/src/schema/transaction/types.ts @@ -4249,111 +4249,47 @@ const transactionMetaParsed = () => { return memoisedTransactionMetaParsed; }; -let memoisedTransactionMessageInterfaceFields: object | undefined; /** - * The fields for the transaction message interface - */ -const transactionMessageInterfaceFields = () => { - if (!memoisedTransactionMessageInterfaceFields) - memoisedTransactionMessageInterfaceFields = { - addressTableLookups: list(type(addressTableLookup())), - format: string(), - header: object('TransactionJsonTransactionHeader', { - numReadonlySignedAccounts: number(), - numReadonlyUnsignedAccounts: number(), - numRequiredSignatures: number(), - }), - recentBlockhash: string(), - }; - return memoisedTransactionMessageInterfaceFields; -}; - -let memoisedTransactionMessageInterface: GraphQLInterfaceType | undefined; -/** - * Interface for a transaction message + * The standard transaction type, comprised of all the interfaces */ -const transactionMessageInterface = () => { - if (!memoisedTransactionMessageInterface) - memoisedTransactionMessageInterface = new GraphQLInterfaceType({ +let memoisedTransactionMessage: GraphQLObjectType | undefined; +const transactionMessage = () => { + if (!memoisedTransactionMessage) + memoisedTransactionMessage = new GraphQLObjectType({ + description: 'A transaction message', fields: { - ...transactionMessageInterfaceFields(), + accountKeys: list( + object('transactionMessageParsedAccountKey', { + pubkey: string(), + signer: boolean(), + source: string(), + writable: boolean(), + }) + ), + addressTableLookups: list(type(addressTableLookup())), + format: string(), + header: object('TransactionJsonTransactionHeader', { + numReadonlySignedAccounts: number(), + numReadonlyUnsignedAccounts: number(), + numRequiredSignatures: number(), + }), + instructions: list(type(parsedTransactionInstructionInterface())), + recentBlockhash: string(), }, name: 'TransactionMessage', - resolveType(message) { - if (message.format === 'parsed') { - return 'TransactionMessageParsed'; - } - return 'TransactionMessageUnparsed'; - }, }); - return memoisedTransactionMessageInterface; + return memoisedTransactionMessage; }; -/** - * Builds a transaction message type - * @param name The name of the transaction message type - * @param description The description of the transaction message type - * @param accountKeys The account keys of the transaction message type - * @param instructions The instructions of the transaction message type - * @returns The transaction message type as a GraphQL object - */ -const transactionMessageType = ( - name: string, - description: string, - accountKeys: { type: GraphQLList }, - instructions: { type: GraphQLList } -) => - new GraphQLObjectType({ - description, - fields: { - ...transactionMessageInterfaceFields(), - accountKeys, - instructions, - }, - interfaces: [transactionMessageInterface()], - name, - }); - -let memoisedTransactionMessageUnparsed: GraphQLObjectType | undefined; -const transactionMessageUnparsed = () => { - if (!memoisedTransactionMessageUnparsed) - memoisedTransactionMessageUnparsed = transactionMessageType( - 'TransactionMessageUnparsed', - 'Non-parsed transaction message', - list(string()), - list(type(transactionInstruction())) - ); - return memoisedTransactionMessageUnparsed; -}; - -let memoisedTransactionMessageParsed: GraphQLObjectType | undefined; -const transactionMessageParsed = () => { - if (!memoisedTransactionMessageParsed) - memoisedTransactionMessageParsed = transactionMessageType( - 'TransactionMessageParsed', - 'Parsed transaction message', - list( - object('transactionMessageParsedAccountKey', { - pubkey: string(), - signer: boolean(), - source: string(), - writable: boolean(), - }) - ), - list(type(parsedTransactionInstructionInterface())) - ); - return memoisedTransactionMessageParsed; -}; - -let memoisedTransactionTransaction: GraphQLObjectType | undefined; /** * The standard transaction type, comprised of all the interfaces */ +let memoisedTransactionTransaction: GraphQLObjectType | undefined; const transactionTransaction = () => { if (!memoisedTransactionTransaction) memoisedTransactionTransaction = new GraphQLObjectType({ fields: { - message: type(transactionMessageInterface()), + message: type(transactionMessage()), signatures: list(string()), }, name: 'TransactionTransaction', @@ -4485,8 +4421,6 @@ export const transactionTypes = () => { ...parsedInstructionsVote(), transactionMetaUnparsed(), transactionMetaParsed(), - transactionMessageUnparsed(), - transactionMessageParsed(), transactionBase58(), transactionBase64(), transactionJson(),