From b112d01a5d2813d7e3ea223bfd12618bec49648d Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Thu, 1 Dec 2022 22:04:47 -0800 Subject: [PATCH] feat: the web3.js getBlock APIs now accept `rewards` and `transactionDetails` config (#29000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add `transactionDetails` and `rewards` params to `getBlock` API of web3.js * Add the same content to the legacy call …because it's such a PITA to share config between two methods and not have Typedoc throw a fit. * Add tests to exercise block deserialization in the case that `transactionDetails` is `none` or `accounts` * Extract the annotated account key parser into a separate struct * Parse the `getBlock()` responses differently depending on the mode --- web3.js/src/connection.ts | 386 +++++++++++++++++++++++++----- web3.js/test/connection.test.ts | 404 ++++++++++++++++++++++++++++++++ 2 files changed, 727 insertions(+), 63 deletions(-) diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index 0cb3e11659..9a746f3073 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -23,6 +23,7 @@ import { import type {Struct} from 'superstruct'; import {Client as RpcWebSocketClient} from 'rpc-websockets'; import RpcClient from 'jayson/lib/client/browser'; +import {JSONRPCError} from 'jayson'; import {AgentManager} from './agent-manager'; import {EpochSchedule} from './epoch-schedule'; @@ -517,6 +518,18 @@ export type GetBalanceConfig = { export type GetBlockConfig = { /** The level of finality desired */ commitment?: Finality; + /** + * Whether to populate the rewards array. If parameter not provided, the default includes rewards. + */ + rewards?: boolean; + /** + * Level of transaction detail to return, either "full", "accounts", "signatures", or "none". If + * parameter not provided, the default detail level is "full". If "accounts" are requested, + * transaction details only include signatures and an annotated list of accounts in each + * transaction. Transaction metadata is limited to only: fee, err, pre_balances, post_balances, + * pre_token_balances, and post_token_balances. + */ + transactionDetails?: 'accounts' | 'full' | 'none' | 'signatures'; }; /** @@ -527,6 +540,18 @@ export type GetVersionedBlockConfig = { commitment?: Finality; /** The max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned */ maxSupportedTransactionVersion?: number; + /** + * Whether to populate the rewards array. If parameter not provided, the default includes rewards. + */ + rewards?: boolean; + /** + * Level of transaction detail to return, either "full", "accounts", "signatures", or "none". If + * parameter not provided, the default detail level is "full". If "accounts" are requested, + * transaction details only include signatures and an annotated list of accounts in each + * transaction. Transaction metadata is limited to only: fee, err, pre_balances, post_balances, + * pre_token_balances, and post_token_balances. + */ + transactionDetails?: 'accounts' | 'full' | 'none' | 'signatures'; }; /** @@ -1172,6 +1197,16 @@ export type BlockResponse = { blockTime: number | null; }; +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts` + */ +export type AccountsModeBlockResponse = VersionedAccountsModeBlockResponse; + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `none` + */ +export type NoneModeBlockResponse = VersionedNoneModeBlockResponse; + /** * A block with parsed transactions */ @@ -1208,6 +1243,33 @@ export type ParsedBlockResponse = { blockHeight: number | null; }; +/** + * A block with parsed transactions where the `transactionDetails` mode is `accounts` + */ +export type ParsedAccountsModeBlockResponse = Omit< + ParsedBlockResponse, + 'transactions' +> & { + transactions: Array< + Omit & { + transaction: Pick< + ParsedBlockResponse['transactions'][number]['transaction'], + 'signatures' + > & { + accountKeys: ParsedMessageAccount[]; + }; + } + >; +}; + +/** + * A block with parsed transactions where the `transactionDetails` mode is `none` + */ +export type ParsedNoneModeBlockResponse = Omit< + ParsedBlockResponse, + 'transactions' +>; + /** * A processed block fetched from the RPC API */ @@ -1247,6 +1309,33 @@ export type VersionedBlockResponse = { blockTime: number | null; }; +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts` + */ +export type VersionedAccountsModeBlockResponse = Omit< + VersionedBlockResponse, + 'transactions' +> & { + transactions: Array< + Omit & { + transaction: Pick< + VersionedBlockResponse['transactions'][number]['transaction'], + 'signatures' + > & { + accountKeys: ParsedMessageAccount[]; + }; + } + >; +}; + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `none` + */ +export type VersionedNoneModeBlockResponse = Omit< + VersionedBlockResponse, + 'transactions' +>; + /** * A confirmed block on the ledger * @@ -1980,6 +2069,18 @@ const ConfirmedTransactionResult = pick({ }), }); +const AnnotatedAccountKey = pick({ + pubkey: PublicKeyFromString, + signer: boolean(), + writable: boolean(), + source: optional(union([literal('transaction'), literal('lookupTable')])), +}); + +const ConfirmedTransactionAccountsModeResult = pick({ + accountKeys: array(AnnotatedAccountKey), + signatures: array(string()), +}); + const ParsedInstructionResult = pick({ parsed: unknown(), program: string(), @@ -2028,16 +2129,7 @@ const ParsedOrRawInstruction = coerce( const ParsedConfirmedTransactionResult = pick({ signatures: array(string()), message: pick({ - accountKeys: array( - pick({ - pubkey: PublicKeyFromString, - signer: boolean(), - writable: boolean(), - source: optional( - union([literal('transaction'), literal('lookupTable')]), - ), - }), - ), + accountKeys: array(AnnotatedAccountKey), instructions: array(ParsedOrRawInstruction), recentBlockhash: string(), addressTableLookups: optional(nullable(array(AddressTableLookupStruct))), @@ -2114,6 +2206,14 @@ const ParsedConfirmedTransactionMetaResult = pick({ const TransactionVersionStruct = union([literal(0), literal('legacy')]); +/** @internal */ +const RewardsResult = pick({ + pubkey: string(), + lamports: number(), + postBalance: nullable(number()), + rewardType: nullable(string()), +}); + /** * Expected JSON RPC response for the "getBlock" message */ @@ -2130,16 +2230,46 @@ const GetBlockRpcResult = jsonRpcResult( version: optional(TransactionVersionStruct), }), ), - rewards: optional( - array( - pick({ - pubkey: string(), - lamports: number(), - postBalance: nullable(number()), - rewardType: nullable(string()), - }), - ), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + +/** + * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `none` + */ +const GetNoneModeBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + +/** + * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts` + */ +const GetAccountsModeBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array( + pick({ + transaction: ConfirmedTransactionAccountsModeResult, + meta: nullable(ConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct), + }), ), + rewards: optional(array(RewardsResult)), blockTime: nullable(number()), blockHeight: nullable(number()), }), @@ -2162,16 +2292,46 @@ const GetParsedBlockRpcResult = jsonRpcResult( version: optional(TransactionVersionStruct), }), ), - rewards: optional( - array( - pick({ - pubkey: string(), - lamports: number(), - postBalance: nullable(number()), - rewardType: nullable(string()), - }), - ), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + +/** + * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts` + */ +const GetParsedAccountsModeBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array( + pick({ + transaction: ConfirmedTransactionAccountsModeResult, + meta: nullable(ParsedConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct), + }), ), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + +/** + * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `none` + */ +const GetParsedNoneModeBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + rewards: optional(array(RewardsResult)), blockTime: nullable(number()), blockHeight: nullable(number()), }), @@ -2195,16 +2355,7 @@ const GetConfirmedBlockRpcResult = jsonRpcResult( meta: nullable(ConfirmedTransactionMetaResult), }), ), - rewards: optional( - array( - pick({ - pubkey: string(), - lamports: number(), - postBalance: nullable(number()), - rewardType: nullable(string()), - }), - ), - ), + rewards: optional(array(RewardsResult)), blockTime: nullable(number()), }), ), @@ -4261,6 +4412,26 @@ export class Connection { rawConfig?: GetBlockConfig, ): Promise; + /** + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + // eslint-disable-next-line no-dupe-class-members + async getBlock( + slot: number, + rawConfig: GetBlockConfig & {transactionDetails: 'accounts'}, + ): Promise; + + /** + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + // eslint-disable-next-line no-dupe-class-members + async getBlock( + slot: number, + rawConfig: GetBlockConfig & {transactionDetails: 'none'}, + ): Promise; + /** * Fetch a processed block from the cluster. */ @@ -4270,6 +4441,18 @@ export class Connection { rawConfig?: GetVersionedBlockConfig, ): Promise; + // eslint-disable-next-line no-dupe-class-members + async getBlock( + slot: number, + rawConfig: GetVersionedBlockConfig & {transactionDetails: 'accounts'}, + ): Promise; + + // eslint-disable-next-line no-dupe-class-members + async getBlock( + slot: number, + rawConfig: GetVersionedBlockConfig & {transactionDetails: 'none'}, + ): Promise; + /** * Fetch a processed block from the cluster. */ @@ -4277,7 +4460,12 @@ export class Connection { async getBlock( slot: number, rawConfig?: GetVersionedBlockConfig, - ): Promise { + ): Promise< + | VersionedBlockResponse + | VersionedAccountsModeBlockResponse + | VersionedNoneModeBlockResponse + | null + > { const {commitment, config} = extractCommitmentFromConfig(rawConfig); const args = this._buildArgsAtLeastConfirmed( [slot], @@ -4286,26 +4474,54 @@ export class Connection { config, ); const unsafeRes = await this._rpcRequest('getBlock', args); - const res = create(unsafeRes, GetBlockRpcResult); - - if ('error' in res) { - throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); + try { + switch (config?.transactionDetails) { + case 'accounts': { + const res = create(unsafeRes, GetAccountsModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + case 'none': { + const res = create(unsafeRes, GetNoneModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + default: { + const res = create(unsafeRes, GetBlockRpcResult); + if ('error' in res) { + throw res.error; + } + const {result} = res; + return result + ? { + ...result, + transactions: result.transactions.map( + ({transaction, meta, version}) => ({ + meta, + transaction: { + ...transaction, + message: versionedMessageFromResponse( + version, + transaction.message, + ), + }, + version, + }), + ), + } + : null; + } + } + } catch (e) { + throw new SolanaJSONRPCError( + e as JSONRPCError, + 'failed to get confirmed block', + ); } - - const result = res.result; - if (!result) return result; - - return { - ...result, - transactions: result.transactions.map(({transaction, meta, version}) => ({ - meta, - transaction: { - ...transaction, - message: versionedMessageFromResponse(version, transaction.message), - }, - version, - })), - }; } /** @@ -4314,7 +4530,29 @@ export class Connection { async getParsedBlock( slot: number, rawConfig?: GetVersionedBlockConfig, - ): Promise { + ): Promise; + + // eslint-disable-next-line no-dupe-class-members + async getParsedBlock( + slot: number, + rawConfig: GetVersionedBlockConfig & {transactionDetails: 'accounts'}, + ): Promise; + + // eslint-disable-next-line no-dupe-class-members + async getParsedBlock( + slot: number, + rawConfig: GetVersionedBlockConfig & {transactionDetails: 'none'}, + ): Promise; + // eslint-disable-next-line no-dupe-class-members + async getParsedBlock( + slot: number, + rawConfig?: GetVersionedBlockConfig, + ): Promise< + | ParsedBlockResponse + | ParsedAccountsModeBlockResponse + | ParsedNoneModeBlockResponse + | null + > { const {commitment, config} = extractCommitmentFromConfig(rawConfig); const args = this._buildArgsAtLeastConfirmed( [slot], @@ -4323,11 +4561,33 @@ export class Connection { config, ); const unsafeRes = await this._rpcRequest('getBlock', args); - const res = create(unsafeRes, GetParsedBlockRpcResult); - if ('error' in res) { - throw new SolanaJSONRPCError(res.error, 'failed to get block'); + try { + switch (config?.transactionDetails) { + case 'accounts': { + const res = create(unsafeRes, GetParsedAccountsModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + case 'none': { + const res = create(unsafeRes, GetParsedNoneModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + default: { + const res = create(unsafeRes, GetParsedBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + } + } catch (e) { + throw new SolanaJSONRPCError(e as JSONRPCError, 'failed to get block'); } - return res.result; } /* diff --git a/web3.js/test/connection.test.ts b/web3.js/test/connection.test.ts index 1725628dc8..7d62990c91 100644 --- a/web3.js/test/connection.test.ts +++ b/web3.js/test/connection.test.ts @@ -3071,6 +3071,229 @@ describe('Connection', function () { }); } + describe('get parsed block', function () { + it('can deserialize a response when `transactionDetails` is `full`', async () => { + // Mock block with transaction, fetched using `"transactionDetails": "full"`. + await mockRpcResponse({ + method: 'getBlock', + params: [ + 1, + { + encoding: 'jsonParsed', + maxSupportedTransactionVersion: 0, + transactionDetails: 'full', + }, + ], + value: { + blockHeight: 0, + blockTime: 1614281964, + blockhash: '49d2UbduiZWjtR3Wvfv2t2QxmXvtZNWSPFRZxEDYAvQN', + parentSlot: 0, + previousBlockhash: 'mDd5yMLfuroS1JVZMHo2VZLTgKXXNBXrzPR5UkzFD4X', + transactions: [ + { + meta: { + err: null, + fee: 5000, + innerInstructions: [], + logMessages: [ + 'Program Vote111111111111111111111111111111111111111 invoke [1]', + 'Program Vote111111111111111111111111111111111111111 success', + ], + postBalances: [3712706991, 5765419239, 1169280, 143487360, 1], + postTokenBalances: [], + preBalances: [3712711991, 5765419239, 1169280, 143487360, 1], + preTokenBalances: [], + rewards: null, + status: {Ok: null}, + }, + transaction: { + message: { + accountKeys: [ + { + pubkey: '7v5fMKBqC9PuwjSdS9k9JU7efEXmq3bHTMF5fuSHnqrm', + signer: true, + source: 'transaction', + writable: true, + }, + { + pubkey: 'AhcvnNdppGEcgdpK5gfcaZnAWz4ct8V4n7De5QiLiuzG', + signer: false, + source: 'transaction', + writable: true, + }, + { + pubkey: 'SysvarC1ock11111111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + { + pubkey: 'SysvarS1otHashes111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + { + pubkey: 'Vote111111111111111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + ], + addressTableLookups: null, + instructions: [ + { + parsed: { + info: { + clockSysvar: + 'SysvarC1ock11111111111111111111111111111111', + slotHashesSysvar: + 'SysvarS1otHashes111111111111111111111111111', + vote: { + hash: '2gmQ8xMjZaXn63kr8qzPAUjQAHi7xCDjSibPdJxhVYMm', + slots: [164153060, 164153061], + timestamp: 1669845645, + }, + voteAccount: + 'AhcvnNdppGEcgdpK5gfcaZnAWz4ct8V4n7De5QiLiuzG', + voteAuthority: + '7v5fMKBqC9PuwjSdS9k9JU7efEXmq3bHTMF5fuSHnqrm', + }, + type: 'vote', + }, + program: 'vote', + programId: 'Vote111111111111111111111111111111111111111', + }, + ], + recentBlockhash: + 'GLqYrN6AQxCGtFTQywkPj2WN5tafC3KerBhW4QkmAyD4', + }, + signatures: [ + '5qDZ3nUUwp8VHFfAE5ydTQRULCoVLMGs16EprwdXsvyNCLe1NfckCkRE4BPi6wyEW9hXvG9iWU2prXfbM8SNPVEC', + ], + }, + version: 'legacy', + }, + ], + }, + }); + await expect( + connection.getParsedBlock(1, { + maxSupportedTransactionVersion: 0, + transactionDetails: 'full', + }), + ).not.to.eventually.be.rejected; + }); + + it('can deserialize a response when `transactionDetails` is `none`', async () => { + // Mock block with transaction, fetched using `"transactionDetails": "none"`. + await mockRpcResponse({ + method: 'getBlock', + params: [ + 1, + { + encoding: 'jsonParsed', + maxSupportedTransactionVersion: 0, + transactionDetails: 'none', + }, + ], + value: { + blockHeight: 0, + blockTime: 1614281964, + blockhash: '49d2UbduiZWjtR3Wvfv2t2QxmXvtZNWSPFRZxEDYAvQN', + parentSlot: 0, + previousBlockhash: 'mDd5yMLfuroS1JVZMHo2VZLTgKXXNBXrzPR5UkzFD4X', + }, + }); + await expect( + connection.getParsedBlock(1, { + maxSupportedTransactionVersion: 0, + transactionDetails: 'none', + }), + ).not.to.eventually.be.rejected; + }); + + it('can deserialize a response when `transactionDetails` is `accounts`', async () => { + // Mock block with transaction, fetched using `"transactionDetails": "accounts"`. + await mockRpcResponse({ + method: 'getBlock', + params: [ + 1, + { + encoding: 'jsonParsed', + maxSupportedTransactionVersion: 0, + transactionDetails: 'accounts', + }, + ], + value: { + blockHeight: 0, + blockTime: 1614281964, + blockhash: '49d2UbduiZWjtR3Wvfv2t2QxmXvtZNWSPFRZxEDYAvQN', + parentSlot: 0, + previousBlockhash: 'mDd5yMLfuroS1JVZMHo2VZLTgKXXNBXrzPR5UkzFD4X', + transactions: [ + { + meta: { + err: null, + fee: 5000, + postBalances: [18237691394, 26858640, 1169280, 143487360, 1], + postTokenBalances: [], + preBalances: [18237696394, 26858640, 1169280, 143487360, 1], + preTokenBalances: [], + status: {Ok: null}, + }, + transaction: { + accountKeys: [ + { + pubkey: '914RFshndUeZaNPjf8UWDCyo49ahQ1XQ2w9BnEMwpHKF', + signer: true, + source: 'transaction', + writable: true, + }, + { + pubkey: '4cCd4SGrMswhqboYBJ5AcCVvCjh5NtaeZNwWFJzsnUWY', + signer: false, + source: 'transaction', + writable: true, + }, + { + pubkey: 'SysvarC1ock11111111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + { + pubkey: 'SysvarS1otHashes111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + { + pubkey: 'Vote111111111111111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + ], + signatures: [ + '5ZDp1HfNZhNRHc75ncsiZ4sCq1fGJHMGf9u36M3foD5PMH4Xu5S4X2x7aryn4JinUdG11oSYCk7zxbNmLJzzqUft', + ], + }, + version: 'legacy', + }, + ], + }, + }); + await expect( + connection.getParsedBlock(1, { + maxSupportedTransactionVersion: 0, + transactionDetails: 'accounts', + }), + ).not.to.eventually.be.rejected; + }); + }); + describe('get block', function () { beforeEach(async function () { await mockRpcResponse({ @@ -3236,6 +3459,187 @@ describe('Connection', function () { `Block not available for slot ${Number.MAX_SAFE_INTEGER}`, ); }); + + it('can deserialize a response when `transactionDetails` is `full`', async () => { + // Mock block with transaction, fetched using `"transactionDetails": "full"`. + await mockRpcResponse({ + method: 'getBlock', + params: [ + 1, + { + maxSupportedTransactionVersion: 0, + transactionDetails: 'full', + }, + ], + value: { + blockHeight: 0, + blockTime: 1614281964, + blockhash: '49d2UbduiZWjtR3Wvfv2t2QxmXvtZNWSPFRZxEDYAvQN', + parentSlot: 0, + previousBlockhash: 'mDd5yMLfuroS1JVZMHo2VZLTgKXXNBXrzPR5UkzFD4X', + transactions: [ + { + meta: { + err: null, + fee: 5000, + innerInstructions: [], + loadedAddresses: {readonly: [], writable: []}, + logMessages: [ + 'Program Vote111111111111111111111111111111111111111 invoke [1]', + 'Program Vote111111111111111111111111111111111111111 success', + ], + postBalances: [12278161908, 39995373, 1169280, 143487360, 1], + postTokenBalances: [], + preBalances: [12278166908, 39995373, 1169280, 143487360, 1], + preTokenBalances: [], + rewards: null, + status: {Ok: null}, + }, + transaction: { + message: { + accountKeys: [ + 'FTWuJ2tqjecNizCSE66z4BD1tBHomG6DVffGUwRuWUkM', + 'H2z3pBT62ByS4jpqsiEMtgN3NUFEuZHiTvoKCFjqCtD6', + 'SysvarC1ock11111111111111111111111111111111', + 'SysvarS1otHashes111111111111111111111111111', + 'Vote111111111111111111111111111111111111111', + ], + header: { + numReadonlySignedAccounts: 0, + numReadonlyUnsignedAccounts: 3, + numRequiredSignatures: 1, + }, + instructions: [ + { + accounts: [1, 3, 2, 0], + data: '29z5mr1JoRmJYQ6zG7p2F3mu68pWTNw9q49Tu7KrSEgoS6Jh1LMPGUK3HXs1N3Dody3icCcXxu6xPYoXLWnUTafEGm3knK', + programIdIndex: 4, + }, + ], + recentBlockhash: + 'GLqYrN6AQxCGtFTQywkPj2WN5tafC3KerBhW4QkmAyD4', + }, + signatures: [ + '4SZofEnXEVzCYvzk16z6ScR6F3iNtZ3FsCC1PEWegpzvGwTJR6x9cDi8VHRmCFGC5XFs2yEFms3j36Mj7XVyHXbb', + ], + }, + version: 'legacy', + }, + ], + }, + }); + await expect( + connection.getBlock(1, { + maxSupportedTransactionVersion: 0, + transactionDetails: 'full', + }), + ).not.to.eventually.be.rejected; + }); + + it('can deserialize a response when `transactionDetails` is `none`', async () => { + // Mock block with transaction, fetched using `"transactionDetails": "none"`. + await mockRpcResponse({ + method: 'getBlock', + params: [ + 1, + { + maxSupportedTransactionVersion: 0, + transactionDetails: 'none', + }, + ], + value: { + blockHeight: 0, + blockTime: 1614281964, + blockhash: '49d2UbduiZWjtR3Wvfv2t2QxmXvtZNWSPFRZxEDYAvQN', + parentSlot: 0, + previousBlockhash: 'mDd5yMLfuroS1JVZMHo2VZLTgKXXNBXrzPR5UkzFD4X', + }, + }); + await expect( + connection.getBlock(1, { + maxSupportedTransactionVersion: 0, + transactionDetails: 'none', + }), + ).not.to.eventually.be.rejected; + }); + + it('can deserialize a response when `transactionDetails` is `accounts`', async () => { + // Mock block with transaction, fetched using `"transactionDetails": "accounts"`. + await mockRpcResponse({ + method: 'getBlock', + params: [ + 1, + { + maxSupportedTransactionVersion: 0, + transactionDetails: 'accounts', + }, + ], + value: { + blockHeight: 0, + blockTime: 1614281964, + blockhash: '49d2UbduiZWjtR3Wvfv2t2QxmXvtZNWSPFRZxEDYAvQN', + parentSlot: 0, + previousBlockhash: 'mDd5yMLfuroS1JVZMHo2VZLTgKXXNBXrzPR5UkzFD4X', + transactions: [ + { + meta: { + err: null, + fee: 5000, + postBalances: [2751549948, 11751747405, 1169280, 143487360, 1], + postTokenBalances: [], + preBalances: [2751554948, 11751747405, 1169280, 143487360, 1], + preTokenBalances: [], + status: {Ok: null}, + }, + transaction: { + accountKeys: [ + { + pubkey: 'D7hwgGRTr1vaCxzmfEKCaf56SPgBJmjHh6UXHG3p12bB', + signer: true, + source: 'transaction', + writable: true, + }, + { + pubkey: '8iLE53Y9k4sccy4gxrT936BHbhYS6J13kQT5vRXhXFMX', + signer: false, + source: 'transaction', + writable: true, + }, + { + pubkey: 'SysvarC1ock11111111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + { + pubkey: 'SysvarS1otHashes111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + { + pubkey: 'Vote111111111111111111111111111111111111111', + signer: false, + source: 'transaction', + writable: false, + }, + ], + signatures: [ + 'uNKj2ogn8ZRRjyVWXLC7sLRWpKQyMUomm66RXoDuWLXikPSJN8C7ZZK95j8S2bzcjwH6MvrXKSHtCWEURPpEXMB', + ], + }, + version: 'legacy', + }, + ], + }, + }); + await expect( + connection.getBlock(1, { + maxSupportedTransactionVersion: 0, + transactionDetails: 'accounts', + }), + ).not.to.eventually.be.rejected; + }); }); describe('get confirmed block', function () {