diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index 0f9b788649..e55f4f4c53 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -504,8 +504,15 @@ export type TokenBalance = { /** * Metadata for a parsed confirmed transaction on the ledger + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link ParsedTransactionMeta} instead. */ -export type ParsedConfirmedTransactionMeta = { +export type ParsedConfirmedTransactionMeta = ParsedTransactionMeta; + +/** + * Metadata for a parsed transaction on the ledger + */ +export type ParsedTransactionMeta = { /** The fee charged for processing the transaction */ fee: number; /** An array of cross program invoked parsed instructions */ @@ -644,14 +651,21 @@ export type ParsedTransaction = { /** * A parsed and confirmed transaction on the ledger + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link ParsedTransactionWithMeta} instead. */ -export type ParsedConfirmedTransaction = { +export type ParsedConfirmedTransaction = ParsedTransactionWithMeta; + +/** + * A parsed transaction on the ledger with meta + */ +export type ParsedTransactionWithMeta = { /** The slot during which the transaction was processed */ slot: number; /** The details of the transaction */ transaction: ParsedTransaction; /** Metadata produced from the transaction */ - meta: ParsedConfirmedTransactionMeta | null; + meta: ParsedTransactionMeta | null; /** The unix timestamp of when the transaction was processed */ blockTime?: number | null; }; @@ -720,9 +734,9 @@ export type ConfirmedBlock = { }; /** - * A ConfirmedBlock on the ledger with signatures only + * A Block on the ledger with signatures only */ -export type ConfirmedBlockSignatures = { +export type BlockSignatures = { /** Blockhash of this block */ blockhash: Blockhash; /** Blockhash of this block's parent */ @@ -1494,8 +1508,41 @@ const ParsedConfirmedTransactionMetaResult = pick({ postTokenBalances: optional(nullable(array(TokenBalanceResult))), }); +/** + * Expected JSON RPC response for the "getBlock" message + */ +const GetBlockRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array( + pick({ + transaction: ConfirmedTransactionResult, + meta: nullable(ConfirmedTransactionMetaResult), + }), + ), + rewards: optional( + array( + pick({ + pubkey: string(), + lamports: number(), + postBalance: nullable(number()), + rewardType: nullable(string()), + }), + ), + ), + blockTime: nullable(number()), + blockHeight: nullable(number()), + }), + ), +); + /** * Expected JSON RPC response for the "getConfirmedBlock" message + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link GetBlockRpcResult} instead. */ const GetConfirmedBlockRpcResult = jsonRpcResult( nullable( @@ -1525,9 +1572,9 @@ const GetConfirmedBlockRpcResult = jsonRpcResult( ); /** - * Expected JSON RPC response for the "getConfirmedBlockSignatures" message + * Expected JSON RPC response for the "getBlock" message */ -const GetConfirmedBlockSignaturesRpcResult = jsonRpcResult( +const GetBlockSignaturesRpcResult = jsonRpcResult( nullable( pick({ blockhash: string(), @@ -1540,9 +1587,9 @@ const GetConfirmedBlockSignaturesRpcResult = jsonRpcResult( ); /** - * Expected JSON RPC response for the "getConfirmedTransaction" message + * Expected JSON RPC response for the "getTransaction" message */ -const GetConfirmedTransactionRpcResult = jsonRpcResult( +const GetTransactionRpcResult = jsonRpcResult( nullable( pick({ slot: number(), @@ -1554,9 +1601,9 @@ const GetConfirmedTransactionRpcResult = jsonRpcResult( ); /** - * Expected JSON RPC response for the "getConfirmedTransaction" message + * Expected parsed JSON RPC response for the "getTransaction" message */ -const GetParsedConfirmedTransactionRpcResult = jsonRpcResult( +const GetParsedTransactionRpcResult = jsonRpcResult( nullable( pick({ slot: number(), @@ -1569,6 +1616,8 @@ const GetParsedConfirmedTransactionRpcResult = jsonRpcResult( /** * Expected JSON RPC response for the "getRecentBlockhash" message + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link GetLatestBlockhashRpcResult} instead. */ const GetRecentBlockhashAndContextRpcResult = jsonRpcResultAndContext( pick({ @@ -1579,6 +1628,16 @@ const GetRecentBlockhashAndContextRpcResult = jsonRpcResultAndContext( }), ); +/** + * Expected JSON RPC response for the "getLatestBlockhash" message + */ +const GetLatestBlockhashRpcResult = jsonRpcResultAndContext( + pick({ + blockhash: string(), + lastValidBlockHeight: number(), + }), +); + const PerfSampleResult = pick({ slot: number(), numTransactions: number(), @@ -2962,6 +3021,8 @@ export class Connection { /** * Fetch a recent blockhash from the cluster, return with context * @return {Promise>} + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link getLatestBlockhash} instead. */ async getRecentBlockhashAndContext( commitment?: Commitment, @@ -3001,6 +3062,8 @@ export class Connection { /** * Fetch the fee calculator for a recent blockhash from the cluster, return with context + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link getFeeForMessage} instead. */ async getFeeCalculatorForBlockhash( blockhash: Blockhash, @@ -3047,6 +3110,8 @@ export class Connection { /** * Fetch a recent blockhash from the cluster * @return {Promise<{blockhash: Blockhash, feeCalculator: FeeCalculator}>} + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link getLatestBlockhash} instead. */ async getRecentBlockhash( commitment?: Commitment, @@ -3059,6 +3124,39 @@ export class Connection { } } + /** + * Fetch the latest blockhash from the cluster + * @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>} + */ + async getLatestBlockhash( + commitment?: Commitment, + ): Promise<{blockhash: Blockhash; lastValidBlockHeight: number}> { + try { + const res = await this.getLatestBlockhashAndContext(commitment); + return res.value; + } catch (e) { + throw new Error('failed to get recent blockhash: ' + e); + } + } + + /** + * Fetch the latest blockhash from the cluster + * @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>} + */ + async getLatestBlockhashAndContext( + commitment?: Commitment, + ): Promise< + RpcResponseAndContext<{blockhash: Blockhash; lastValidBlockHeight: number}> + > { + const args = this._buildArgs([], commitment); + const unsafeRes = await this._rpcRequest('getLatestBlockhash', args); + const res = create(unsafeRes, GetLatestBlockhashRpcResult); + if ('error' in res) { + throw new Error('failed to get latest blockhash: ' + res.error.message); + } + return res.result; + } + /** * Fetch the node version */ @@ -3094,8 +3192,8 @@ export class Connection { [slot], opts && opts.commitment, ); - const unsafeRes = await this._rpcRequest('getConfirmedBlock', args); - const res = create(unsafeRes, GetConfirmedBlockRpcResult); + const unsafeRes = await this._rpcRequest('getBlock', args); + const res = create(unsafeRes, GetBlockRpcResult); if ('error' in res) { throw new Error('failed to get confirmed block: ' + res.error.message); @@ -3120,7 +3218,7 @@ export class Connection { } /** - * Fetch a processed transaction from the cluster. + * Fetch a confirmed or finalized transaction from the cluster. */ async getTransaction( signature: string, @@ -3130,12 +3228,10 @@ export class Connection { [signature], opts && opts.commitment, ); - const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); - const res = create(unsafeRes, GetConfirmedTransactionRpcResult); + const unsafeRes = await this._rpcRequest('getTransaction', args); + const res = create(unsafeRes, GetTransactionRpcResult); if ('error' in res) { - throw new Error( - 'failed to get confirmed transaction: ' + res.error.message, - ); + throw new Error('failed to get transaction: ' + res.error.message); } const result = res.result; @@ -3150,6 +3246,57 @@ export class Connection { }; } + /** + * Fetch parsed transaction details for a confirmed or finalized transaction + */ + async getParsedTransaction( + signature: TransactionSignature, + commitment?: Finality, + ): Promise { + const args = this._buildArgsAtLeastConfirmed( + [signature], + commitment, + 'jsonParsed', + ); + const unsafeRes = await this._rpcRequest('getTransaction', args); + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new Error('failed to get transaction: ' + res.error.message); + } + return res.result; + } + + /** + * Fetch parsed transaction details for a batch of confirmed transactions + */ + async getParsedTransactions( + signatures: TransactionSignature[], + commitment?: Finality, + ): Promise<(ParsedConfirmedTransaction | null)[]> { + const batch = signatures.map(signature => { + const args = this._buildArgsAtLeastConfirmed( + [signature], + commitment, + 'jsonParsed', + ); + return { + methodName: 'getTransaction', + args, + }; + }); + + const unsafeRes = await this._rpcBatchRequest(batch); + const res = unsafeRes.map((unsafeRes: any) => { + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new Error('failed to get transactions: ' + res.error.message); + } + return res.result; + }); + + return res; + } + /** * Fetch a list of Transactions and transaction statuses from the cluster * for a confirmed block. @@ -3160,14 +3307,36 @@ export class Connection { slot: number, commitment?: Finality, ): Promise { - const result = await this.getBlock(slot, {commitment}); + const args = this._buildArgsAtLeastConfirmed([slot], commitment); + const unsafeRes = await this._rpcRequest('getConfirmedBlock', args); + const res = create(unsafeRes, GetConfirmedBlockRpcResult); + + if ('error' in res) { + throw new Error('failed to get confirmed block: ' + res.error.message); + } + + const result = res.result; if (!result) { throw new Error('Confirmed block ' + slot + ' not found'); } - return { + const block = { ...result, transactions: result.transactions.map(({transaction, meta}) => { + const message = new Message(transaction.message); + return { + meta, + transaction: { + ...transaction, + message, + }, + }; + }), + }; + + return { + ...block, + transactions: block.transactions.map(({transaction, meta}) => { return { meta, transaction: Transaction.populate( @@ -3191,7 +3360,7 @@ export class Connection { endSlot !== undefined ? [startSlot, endSlot] : [startSlot], commitment, ); - const unsafeRes = await this._rpcRequest('getConfirmedBlocks', args); + const unsafeRes = await this._rpcRequest('getBlocks', args); const res = create(unsafeRes, jsonRpcResult(array(number()))); if ('error' in res) { throw new Error('failed to get blocks: ' + res.error.message); @@ -3199,13 +3368,43 @@ export class Connection { return res.result; } + /** + * Fetch a list of Signatures from the cluster for a block, excluding rewards + */ + async getBlockSignatures( + slot: number, + commitment?: Finality, + ): Promise { + const args = this._buildArgsAtLeastConfirmed( + [slot], + commitment, + undefined, + { + transactionDetails: 'signatures', + rewards: false, + }, + ); + const unsafeRes = await this._rpcRequest('getBlock', args); + const res = create(unsafeRes, GetBlockSignaturesRpcResult); + if ('error' in res) { + throw new Error('failed to get block: ' + res.error.message); + } + const result = res.result; + if (!result) { + throw new Error('Block ' + slot + ' not found'); + } + return result; + } + /** * Fetch a list of Signatures from the cluster for a confirmed block, excluding rewards + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link getBlockSignatures} instead. */ async getConfirmedBlockSignatures( slot: number, commitment?: Finality, - ): Promise { + ): Promise { const args = this._buildArgsAtLeastConfirmed( [slot], commitment, @@ -3216,7 +3415,7 @@ export class Connection { }, ); const unsafeRes = await this._rpcRequest('getConfirmedBlock', args); - const res = create(unsafeRes, GetConfirmedBlockSignaturesRpcResult); + const res = create(unsafeRes, GetBlockSignaturesRpcResult); if ('error' in res) { throw new Error('failed to get confirmed block: ' + res.error.message); } @@ -3229,14 +3428,25 @@ export class Connection { /** * Fetch a transaction details for a confirmed transaction + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link getTransaction} instead. */ async getConfirmedTransaction( signature: TransactionSignature, commitment?: Finality, ): Promise { - const result = await this.getTransaction(signature, {commitment}); + const args = this._buildArgsAtLeastConfirmed([signature], commitment); + const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); + const res = create(unsafeRes, GetTransactionRpcResult); + if ('error' in res) { + throw new Error('failed to get transaction: ' + res.error.message); + } + + const result = res.result; if (!result) return result; - const {message, signatures} = result.transaction; + + const message = new Message(result.transaction.message); + const signatures = result.transaction.signatures; return { ...result, transaction: Transaction.populate(message, signatures), @@ -3245,6 +3455,8 @@ export class Connection { /** * Fetch parsed transaction details for a confirmed transaction + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link getParsedTransaction} instead. */ async getParsedConfirmedTransaction( signature: TransactionSignature, @@ -3256,7 +3468,7 @@ export class Connection { 'jsonParsed', ); const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); - const res = create(unsafeRes, GetParsedConfirmedTransactionRpcResult); + const res = create(unsafeRes, GetParsedTransactionRpcResult); if ('error' in res) { throw new Error( 'failed to get confirmed transaction: ' + res.error.message, @@ -3267,6 +3479,8 @@ export class Connection { /** * Fetch parsed transaction details for a batch of confirmed transactions + * + * @deprecated Deprecated since Solana v1.8.0. Please use {@link getParsedTransactions} instead. */ async getParsedConfirmedTransactions( signatures: TransactionSignature[], @@ -3286,7 +3500,7 @@ export class Connection { const unsafeRes = await this._rpcBatchRequest(batch); const res = unsafeRes.map((unsafeRes: any) => { - const res = create(unsafeRes, GetParsedConfirmedTransactionRpcResult); + const res = create(unsafeRes, GetParsedTransactionRpcResult); if ('error' in res) { throw new Error( 'failed to get confirmed transactions: ' + res.error.message, diff --git a/web3.js/test/connection.test.ts b/web3.js/test/connection.test.ts index 4303295be3..e0da0568c7 100644 --- a/web3.js/test/connection.test.ts +++ b/web3.js/test/connection.test.ts @@ -1501,9 +1501,10 @@ describe('Connection', () => { } await mockRpcResponse({ - method: 'getConfirmedBlock', + method: 'getBlock', params: [1], value: { + blockHeight: 0, blockTime: 1614281964, blockhash: '57zQNBZBEiHsCZFqsaY6h176ioXy5MsSLmcvHkEyaLGy', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', @@ -1552,18 +1553,18 @@ describe('Connection', () => { // Find a block that has a transaction, usually Block 1 let slot = 0; - let confirmedTransaction: string | undefined; - while (!confirmedTransaction) { + let transaction: string | undefined; + while (!transaction) { slot++; const block = await connection.getBlock(slot); if (block && block.transactions.length > 0) { - confirmedTransaction = block.transactions[0].transaction.signatures[0]; + transaction = block.transactions[0].transaction.signatures[0]; } } await mockRpcResponse({ - method: 'getConfirmedTransaction', - params: [confirmedTransaction], + method: 'getTransaction', + params: [transaction], value: { slot, transaction: { @@ -1604,7 +1605,7 @@ describe('Connection', () => { }, }); - const result = await connection.getTransaction(confirmedTransaction); + const result = await connection.getTransaction(transaction); if (!result) { expect(result).to.be.ok; @@ -1612,7 +1613,7 @@ describe('Connection', () => { } const resultSignature = result.transaction.signatures[0]; - expect(resultSignature).to.eq(confirmedTransaction); + expect(resultSignature).to.eq(transaction); const newAddress = Keypair.generate().publicKey; const recentSignature = await helpers.airdrop({ @@ -1622,7 +1623,7 @@ describe('Connection', () => { }); await mockRpcResponse({ - method: 'getConfirmedTransaction', + method: 'getTransaction', params: [recentSignature], value: null, }); @@ -1896,9 +1897,10 @@ describe('Connection', () => { } await mockRpcResponse({ - method: 'getConfirmedBlock', + method: 'getBlock', params: [0], value: { + blockHeight: 0, blockTime: 1614281964, blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', @@ -1921,9 +1923,10 @@ describe('Connection', () => { expect(block0.parentSlot).to.eq(0); await mockRpcResponse({ - method: 'getConfirmedBlock', + method: 'getBlock', params: [1], value: { + blockHeight: 0, blockTime: 1614281964, blockhash: '57zQNBZBEiHsCZFqsaY6h176ioXy5MsSLmcvHkEyaLGy', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', @@ -1985,7 +1988,7 @@ describe('Connection', () => { } await mockRpcResponse({ - method: 'getConfirmedBlock', + method: 'getBlock', params: [Number.MAX_SAFE_INTEGER], error: { message: `Block not available for slot ${Number.MAX_SAFE_INTEGER}`, @@ -2109,7 +2112,7 @@ describe('Connection', () => { it('get blocks between two slots', async () => { await mockRpcResponse({ - method: 'getConfirmedBlocks', + method: 'getBlocks', params: [0, 10], value: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], }); @@ -2129,7 +2132,7 @@ describe('Connection', () => { it('get blocks from starting slot', async () => { await mockRpcResponse({ - method: 'getConfirmedBlocks', + method: 'getBlocks', params: [0], value: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, @@ -2155,7 +2158,7 @@ describe('Connection', () => { expect(blocks).to.contain(latestSlot); }); - it('get confirmed block signatures', async () => { + it('get block signatures', async () => { await mockRpcResponse({ method: 'getSlot', params: [], @@ -2167,7 +2170,7 @@ describe('Connection', () => { } await mockRpcResponse({ - method: 'getConfirmedBlock', + method: 'getBlock', params: [ 0, { @@ -2176,6 +2179,7 @@ describe('Connection', () => { }, ], value: { + blockHeight: 0, blockTime: 1614281964, blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', @@ -2185,7 +2189,7 @@ describe('Connection', () => { }); // Block 0 never has any transactions in test validator - const block0 = await connection.getConfirmedBlockSignatures(0); + const block0 = await connection.getBlockSignatures(0); const blockhash0 = block0.blockhash; expect(block0.signatures).to.have.length(0); expect(blockhash0).not.to.be.null; @@ -2194,7 +2198,7 @@ describe('Connection', () => { expect(block0).to.not.have.property('rewards'); await mockRpcResponse({ - method: 'getConfirmedBlock', + method: 'getBlock', params: [ 1, { @@ -2203,6 +2207,7 @@ describe('Connection', () => { }, ], value: { + blockHeight: 1, blockTime: 1614281964, blockhash: '57zQNBZBEiHsCZFqsaY6h176ioXy5MsSLmcvHkEyaLGy', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', @@ -2217,7 +2222,7 @@ describe('Connection', () => { // Find a block that has a transaction, usually Block 1 let x = 1; while (x < 10) { - const block1 = await connection.getConfirmedBlockSignatures(x); + const block1 = await connection.getBlockSignatures(x); if (block1.signatures.length >= 1) { expect(block1.previousBlockhash).to.eq(blockhash0); expect(block1.blockhash).not.to.be.null; @@ -2230,14 +2235,14 @@ describe('Connection', () => { } await mockRpcResponse({ - method: 'getConfirmedBlock', + method: 'getBlock', params: [Number.MAX_SAFE_INTEGER], error: { message: `Block not available for slot ${Number.MAX_SAFE_INTEGER}`, }, }); await expect( - connection.getConfirmedBlockSignatures(Number.MAX_SAFE_INTEGER), + connection.getBlockSignatures(Number.MAX_SAFE_INTEGER), ).to.be.rejectedWith( `Block not available for slot ${Number.MAX_SAFE_INTEGER}`, ); @@ -2255,6 +2260,18 @@ describe('Connection', () => { } }); + it('get latest blockhash', async () => { + const commitments: Commitment[] = ['processed', 'confirmed', 'finalized']; + for (const commitment of commitments) { + const {blockhash, lastValidBlockHeight} = await helpers.latestBlockhash({ + connection, + commitment, + }); + expect(bs58.decode(blockhash)).to.have.length(32); + expect(lastValidBlockHeight).to.be.at.least(0); + } + }); + it('get fee calculator', async () => { const {blockhash} = await helpers.recentBlockhash({connection}); await mockRpcResponse({ @@ -2282,7 +2299,7 @@ describe('Connection', () => { const accountFrom = Keypair.generate(); const accountTo = Keypair.generate(); - const {blockhash} = await helpers.recentBlockhash({connection}); + const {blockhash} = await helpers.latestBlockhash({connection}); const transaction = new Transaction({ feePayer: accountFrom.publicKey, @@ -2972,7 +2989,7 @@ describe('Connection', () => { }); const recentBlockhash = await ( - await helpers.recentBlockhash({connection}) + await helpers.latestBlockhash({connection}) ).blockhash; const message = new Message({ accountKeys: [ diff --git a/web3.js/test/mocks/rpc-http.ts b/web3.js/test/mocks/rpc-http.ts index b4bb8de725..9998978b4a 100644 --- a/web3.js/test/mocks/rpc-http.ts +++ b/web3.js/test/mocks/rpc-http.ts @@ -104,6 +104,32 @@ export const mockRpcResponse = async ({ ); }; +const latestBlockhash = async ({ + connection, + commitment, +}: { + connection: Connection; + commitment?: Commitment; +}) => { + const blockhash = uniqueBlockhash(); + const params = []; + if (commitment) { + params.push({commitment}); + } + + await mockRpcResponse({ + method: 'getLatestBlockhash', + params, + value: { + blockhash, + lastValidBlockHeight: 3090, + }, + withContext: true, + }); + + return await connection.getLatestBlockhash(commitment); +}; + const recentBlockhash = async ({ connection, commitment, @@ -145,7 +171,7 @@ const processTransaction = async ({ commitment: Commitment; err?: any; }) => { - const blockhash = (await recentBlockhash({connection})).blockhash; + const blockhash = (await latestBlockhash({connection})).blockhash; transaction.recentBlockhash = blockhash; transaction.sign(...signers); @@ -211,4 +237,5 @@ export const helpers = { airdrop, processTransaction, recentBlockhash, + latestBlockhash, };