Skip to content

Commit

Permalink
feat: Agave v2 RPC: replace getRecentBlockhash with `getLatestBlock…
Browse files Browse the repository at this point in the history
…hash` (#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
  • Loading branch information
buffalojoec authored Dec 16, 2024
1 parent a805cb9 commit 8ea27fc
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 78 deletions.
44 changes: 23 additions & 21 deletions src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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,
},
};
}

/**
Expand Down
125 changes: 74 additions & 51 deletions test/connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
);
}
});
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
);
}
});
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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);
}
});
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -4620,14 +4615,42 @@ 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({
connection,
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.',
);
}
});

Expand Down
41 changes: 35 additions & 6 deletions test/mocks/rpc-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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<Object> = [];
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,
Expand All @@ -160,13 +190,11 @@ const recentBlockhash = async ({
}

await mockRpcResponse({
method: 'getRecentBlockhash',
method: 'getLatestBlockhash',
params,
value: {
blockhash,
feeCalculator: {
lamportsPerSignature: 42,
},
lastValidBlockHeight: 100,
},
withContext: true,
});
Expand Down Expand Up @@ -267,7 +295,8 @@ const airdrop = async ({

export const helpers = {
airdrop,
getFeeForMessage,
latestBlockhash,
processTransaction,
recentBlockhash,
latestBlockhash,
};

0 comments on commit 8ea27fc

Please sign in to comment.