From 8ea27fcefe938eaec464c41bde15ac44c6821469 Mon Sep 17 00:00:00 2001 From: Joe C Date: Mon, 16 Dec 2024 13:34:29 +0900 Subject: [PATCH] feat: Agave v2 RPC: replace `getRecentBlockhash` with `getLatestBlockhash` (#3419) * feat: agave v2 rpc: replace `getConfirmedBlock` with `getBlock` * feat: agave v2 rpc: replace `getConfirmedTransaction` with `getTransaction` * feat: agave v2 rpc: replace `getRecentBlockhash` with `getLatestBlockhash` * protect against json stringify --- src/connection.ts | 44 +++++++------- test/connection.test.ts | 125 ++++++++++++++++++++++++---------------- test/mocks/rpc-http.ts | 41 +++++++++++-- 3 files changed, 132 insertions(+), 78 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 70e4091e9a29..776201941efd 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -2599,20 +2599,6 @@ const GetParsedTransactionRpcResult = jsonRpcResult( ), ); -/** - * Expected JSON RPC response for the "getRecentBlockhash" message - * - * @deprecated Deprecated since RPC v1.8.0. Please use {@link GetLatestBlockhashRpcResult} instead. - */ -const GetRecentBlockhashAndContextRpcResult = jsonRpcResultAndContext( - pick({ - blockhash: string(), - feeCalculator: pick({ - lamportsPerSignature: number(), - }), - }), -); - /** * Expected JSON RPC response for the "getLatestBlockhash" message */ @@ -4567,13 +4553,29 @@ export class Connection { feeCalculator: FeeCalculator; }> > { - const args = this._buildArgs([], commitment); - const unsafeRes = await this._rpcRequest('getRecentBlockhash', args); - const res = create(unsafeRes, GetRecentBlockhashAndContextRpcResult); - if ('error' in res) { - throw new SolanaJSONRPCError(res.error, 'failed to get recent blockhash'); - } - return res.result; + const { + context, + value: {blockhash}, + } = await this.getLatestBlockhashAndContext(commitment); + const feeCalculator = { + get lamportsPerSignature(): number { + throw new Error( + 'The capability to fetch `lamportsPerSignature` using the `getRecentBlockhash` API is ' + + 'no longer offered by the network. Use the `getFeeForMessage` API to obtain the fee ' + + 'for a given message.', + ); + }, + toJSON() { + return {}; + }, + }; + return { + context, + value: { + blockhash, + feeCalculator, + }, + }; } /** diff --git a/test/connection.test.ts b/test/connection.test.ts index 6a8c86b9bbb3..081f9001d0b3 100644 --- a/test/connection.test.ts +++ b/test/connection.test.ts @@ -417,30 +417,38 @@ describe('Connection', function () { }); } - { - await helpers.airdrop({ - connection, - address: account1.publicKey, - amount: 0.5 * LAMPORTS_PER_SOL, - }); + await helpers.airdrop({ + connection, + address: account1.publicKey, + amount: 0.5 * LAMPORTS_PER_SOL, + }); - const transaction = new Transaction().add( - SystemProgram.assign({ - accountPubkey: account1.publicKey, - programId: programId.publicKey, - }), - ); + const {blockhash, lastValidBlockHeight} = await helpers.latestBlockhash({ + connection, + }); + const feePayer = account1.publicKey; - await helpers.processTransaction({ - connection, - transaction, - signers: [account1], - commitment: 'confirmed', - }); - } + const transaction = new Transaction({ + blockhash, + lastValidBlockHeight, + feePayer, + }).add( + SystemProgram.assign({ + accountPubkey: account1.publicKey, + programId: programId.publicKey, + }), + ); - const feeCalculator = (await helpers.recentBlockhash({connection})) - .feeCalculator; + const message = transaction._compile(); + const fee = + (await helpers.getFeeForMessage({connection, message})).value ?? 0; + + await helpers.processTransaction({ + connection, + transaction, + signers: [account1], + commitment: 'confirmed', + }); { await mockRpcResponse({ @@ -454,7 +462,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -465,8 +473,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: 0.5 * LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -485,13 +492,11 @@ describe('Connection', function () { expect(programAccounts).to.have.length(2); programAccounts.forEach(function (keyedAccount) { if (keyedAccount.pubkey.equals(account0.publicKey)) { - expect(keyedAccount.account.lamports).to.eq( - LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, - ); + expect(keyedAccount.account.lamports).to.eq(LAMPORTS_PER_SOL - fee); } else { expect(keyedAccount.pubkey).to.eql(account1.publicKey); expect(keyedAccount.account.lamports).to.eq( - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + 0.5 * LAMPORTS_PER_SOL - fee, ); } }); @@ -509,7 +514,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -520,8 +525,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: 0.5 * LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -538,13 +542,11 @@ describe('Connection', function () { expect(programAccounts).to.have.length(2); programAccounts.forEach(function (keyedAccount) { if (keyedAccount.pubkey.equals(account0.publicKey)) { - expect(keyedAccount.account.lamports).to.eq( - LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, - ); + expect(keyedAccount.account.lamports).to.eq(LAMPORTS_PER_SOL - fee); } else { expect(keyedAccount.pubkey).to.eql(account1.publicKey); expect(keyedAccount.account.lamports).to.eq( - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + 0.5 * LAMPORTS_PER_SOL - fee, ); } }); @@ -570,7 +572,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -581,8 +583,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: 0.5 * LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -652,7 +653,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -663,8 +664,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: 0.5 * LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -684,14 +684,10 @@ describe('Connection', function () { programAccounts.forEach(function (element) { if (element.pubkey.equals(account0.publicKey)) { - expect(element.account.lamports).to.eq( - LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, - ); + expect(element.account.lamports).to.eq(LAMPORTS_PER_SOL - fee); } else { expect(element.pubkey).to.eql(account1.publicKey); - expect(element.account.lamports).to.eq( - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, - ); + expect(element.account.lamports).to.eq(0.5 * LAMPORTS_PER_SOL - fee); } }); } @@ -746,7 +742,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -757,8 +753,7 @@ describe('Connection', function () { account: { data: ['', 'base64'], executable: false, - lamports: - 0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature, + lamports: 0.5 * LAMPORTS_PER_SOL - fee, owner: programId.publicKey.toBase58(), rentEpoch: 20, space: 0, @@ -4620,6 +4615,30 @@ describe('Connection', function () { }); it('get recent blockhash', async () => { + const commitments: Commitment[] = ['processed', 'confirmed', 'finalized']; + for (const commitment of commitments) { + const {blockhash} = await helpers.recentBlockhash({ + connection, + commitment, + }); + expect(bs58.decode(blockhash)).to.have.length(32); + } + }); + + it('get recent blockhash can stringify', async () => { + const commitments: Commitment[] = ['processed', 'confirmed', 'finalized']; + for (const commitment of commitments) { + const response = await helpers.recentBlockhash({ + connection, + commitment, + }); + expect(JSON.stringify(response)).to.match( + /{"blockhash":"\w+","feeCalculator":{}}/, + ); + } + }); + + it('get recent blockhash LPS field throws', async () => { const commitments: Commitment[] = ['processed', 'confirmed', 'finalized']; for (const commitment of commitments) { const {blockhash, feeCalculator} = await helpers.recentBlockhash({ @@ -4627,7 +4646,11 @@ describe('Connection', function () { commitment, }); expect(bs58.decode(blockhash)).to.have.length(32); - expect(feeCalculator.lamportsPerSignature).to.be.at.least(0); + // Accessing the blockhash field works fine. + // When attempting to access `lamportsPerSignature`, throws. + expect(() => feeCalculator.lamportsPerSignature).to.throw( + 'The capability to fetch `lamportsPerSignature` using the `getRecentBlockhash` API is no longer offered by the network. Use the `getFeeForMessage` API to obtain the fee for a given message.', + ); } }); diff --git a/test/mocks/rpc-http.ts b/test/mocks/rpc-http.ts index dae8fdd41ff8..2088ccc23991 100644 --- a/test/mocks/rpc-http.ts +++ b/test/mocks/rpc-http.ts @@ -3,7 +3,13 @@ import BN from 'bn.js'; import * as mockttp from 'mockttp'; import {mockRpcMessage} from './rpc-websocket'; -import {Connection, PublicKey, Transaction, Signer} from '../../src'; +import { + Connection, + PublicKey, + Transaction, + Signer, + VersionedMessage, +} from '../../src'; import invariant from '../../src/utils/assert'; import type {Commitment, HttpHeaders, RpcParams} from '../../src/connection'; @@ -146,6 +152,30 @@ const latestBlockhash = async ({ return await connection.getLatestBlockhash(commitment); }; +const getFeeForMessage = async ({ + connection, + commitment, + message, +}: { + connection: Connection; + commitment?: Commitment; + message: VersionedMessage; +}) => { + const params: Array = []; + if (commitment) { + params.push({commitment}); + } + + await mockRpcResponse({ + method: 'getFeeForMessage', + params, + value: 42, + withContext: true, + }); + + return await connection.getFeeForMessage(message, commitment); +}; + const recentBlockhash = async ({ connection, commitment, @@ -160,13 +190,11 @@ const recentBlockhash = async ({ } await mockRpcResponse({ - method: 'getRecentBlockhash', + method: 'getLatestBlockhash', params, value: { blockhash, - feeCalculator: { - lamportsPerSignature: 42, - }, + lastValidBlockHeight: 100, }, withContext: true, }); @@ -267,7 +295,8 @@ const airdrop = async ({ export const helpers = { airdrop, + getFeeForMessage, + latestBlockhash, processTransaction, recentBlockhash, - latestBlockhash, };