feat: add filters to getProgramAccounts and getParsedProgramAccounts (#16448)

* feat: add filters to getProgramAccounts and getParsedProgramAccounts

* fix: documentation edits

* fix: make connection interface match existing interface
This commit is contained in:
Josh
2021-04-16 10:18:19 -07:00
committed by GitHub
parent c63a208488
commit 7e3db1dedb
2 changed files with 334 additions and 4 deletions

View File

@ -1368,6 +1368,63 @@ export type StakeActivationData = {
inactive: number; inactive: number;
}; };
/**
* Data slice argument for getProgramAccounts
*/
export type DataSlice = {
/** offset of data slice */
offset: number;
/** length of data slice */
length: number;
};
/**
* Memory comparison filter for getProgramAccounts
*/
export type MemcmpFilter = {
memcmp: {
/** offset into program account data to start comparison */
offset: number;
/** data to match, as base-58 encoded string and limited to less than 129 bytes */
bytes: string;
};
};
/**
* Data size comparison filter for getProgramAccounts
*/
export type DataSizeFilter = {
/** Size of data for program account data length comparison */
dataSize: number;
};
/**
* A filter object for getProgramAccounts
*/
export type GetProgramAccountsFilter = MemcmpFilter | DataSizeFilter;
/**
* Configuration object for getProgramAccounts requests
*/
export type GetProgramAccountsConfig = {
/** Optional commitment level */
commitment?: Commitment;
/** Optional encoding for account data (default base64) */
encoding?: 'base64' | 'jsonParsed';
/** Optional data slice to limit the returned account data */
dataSlice?: DataSlice;
/** Optional array of filters to apply to accounts */
filters?: GetProgramAccountsFilter[];
};
/**
* Configuration object for getParsedProgramAccounts
*/
export type GetParsedProgramAccountsConfig = Exclude<
GetProgramAccountsConfig,
'encoding' | 'dataSlice'
>;
/** /**
* Information describing an account * Information describing an account
*/ */
@ -2080,9 +2137,34 @@ export class Connection {
*/ */
async getProgramAccounts( async getProgramAccounts(
programId: PublicKey, programId: PublicKey,
commitment?: Commitment, configOrCommitment?: GetProgramAccountsConfig | Commitment,
): Promise<Array<{pubkey: PublicKey; account: AccountInfo<Buffer>}>> { ): Promise<Array<{pubkey: PublicKey; account: AccountInfo<Buffer>}>> {
const args = this._buildArgs([programId.toBase58()], commitment, 'base64'); const extra: Pick<GetProgramAccountsConfig, 'dataSlice' | 'filters'> = {};
let commitment;
let encoding;
if (configOrCommitment) {
if (typeof configOrCommitment === 'string') {
commitment = configOrCommitment;
} else {
commitment = configOrCommitment.commitment;
encoding = configOrCommitment.encoding;
if (configOrCommitment.dataSlice) {
extra.dataSlice = configOrCommitment.dataSlice;
}
if (configOrCommitment.filters) {
extra.filters = configOrCommitment.filters;
}
}
}
const args = this._buildArgs(
[programId.toBase58()],
commitment,
encoding || 'base64',
extra,
);
const unsafeRes = await this._rpcRequest('getProgramAccounts', args); const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
const res = create(unsafeRes, jsonRpcResult(array(KeyedAccountInfoResult))); const res = create(unsafeRes, jsonRpcResult(array(KeyedAccountInfoResult)));
if ('error' in res) { if ('error' in res) {
@ -2103,17 +2185,33 @@ export class Connection {
*/ */
async getParsedProgramAccounts( async getParsedProgramAccounts(
programId: PublicKey, programId: PublicKey,
commitment?: Commitment, configOrCommitment?: GetParsedProgramAccountsConfig | Commitment,
): Promise< ): Promise<
Array<{ Array<{
pubkey: PublicKey; pubkey: PublicKey;
account: AccountInfo<Buffer | ParsedAccountData>; account: AccountInfo<Buffer | ParsedAccountData>;
}> }>
> { > {
const extra: Pick<GetParsedProgramAccountsConfig, 'filters'> = {};
let commitment;
if (configOrCommitment) {
if (typeof configOrCommitment === 'string') {
commitment = configOrCommitment;
} else {
commitment = configOrCommitment.commitment;
if (configOrCommitment.filters) {
extra.filters = configOrCommitment.filters;
}
}
}
const args = this._buildArgs( const args = this._buildArgs(
[programId.toBase58()], [programId.toBase58()],
commitment, commitment,
'jsonParsed', 'jsonParsed',
extra,
); );
const unsafeRes = await this._rpcRequest('getProgramAccounts', args); const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
const res = create( const res = create(

View File

@ -163,6 +163,59 @@ describe('Connection', () => {
const feeCalculator = (await helpers.recentBlockhash({connection})) const feeCalculator = (await helpers.recentBlockhash({connection}))
.feeCalculator; .feeCalculator;
{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{commitment: 'confirmed', encoding: 'base64'},
],
value: [
{
account: {
data: ['', 'base64'],
executable: false,
lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account0.publicKey.toBase58(),
},
{
account: {
data: ['', 'base64'],
executable: false,
lamports:
0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account1.publicKey.toBase58(),
},
],
});
const programAccounts = await connection.getProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
},
);
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,
);
} else {
expect(keyedAccount.pubkey).to.eql(account1.publicKey);
expect(keyedAccount.account.lamports).to.eq(
0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
);
}
});
}
{ {
await mockRpcResponse({ await mockRpcResponse({
method: 'getProgramAccounts', method: 'getProgramAccounts',
@ -214,6 +267,95 @@ describe('Connection', () => {
}); });
} }
{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{
commitment: 'confirmed',
encoding: 'base64',
filters: [
{
dataSize: 0,
},
],
},
],
value: [
{
account: {
data: ['', 'base64'],
executable: false,
lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account0.publicKey.toBase58(),
},
{
account: {
data: ['', 'base64'],
executable: false,
lamports:
0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account1.publicKey.toBase58(),
},
],
});
const programAccountsDoMatchFilter = await connection.getProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
encoding: 'base64',
filters: [{dataSize: 0}],
},
);
expect(programAccountsDoMatchFilter).to.have.length(2);
}
{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{
commitment: 'confirmed',
encoding: 'base64',
filters: [
{
memcmp: {
offset: 0,
bytes: 'XzdZ3w',
},
},
],
},
],
value: [],
});
const programAccountsDontMatchFilter = await connection.getProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
filters: [
{
memcmp: {
offset: 0,
bytes: 'XzdZ3w',
},
},
],
},
);
expect(programAccountsDontMatchFilter).to.have.length(0);
}
{ {
await mockRpcResponse({ await mockRpcResponse({
method: 'getProgramAccounts', method: 'getProgramAccounts',
@ -248,7 +390,9 @@ describe('Connection', () => {
const programAccounts = await connection.getParsedProgramAccounts( const programAccounts = await connection.getParsedProgramAccounts(
programId.publicKey, programId.publicKey,
'confirmed', {
commitment: 'confirmed',
},
); );
expect(programAccounts).to.have.length(2); expect(programAccounts).to.have.length(2);
@ -265,6 +409,94 @@ describe('Connection', () => {
} }
}); });
} }
{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{
commitment: 'confirmed',
encoding: 'jsonParsed',
filters: [
{
dataSize: 2,
},
],
},
],
value: [],
});
const programAccountsDontMatchFilter = await connection.getParsedProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
filters: [{dataSize: 2}],
},
);
expect(programAccountsDontMatchFilter).to.have.length(0);
}
{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{
commitment: 'confirmed',
encoding: 'jsonParsed',
filters: [
{
memcmp: {
offset: 0,
bytes: '',
},
},
],
},
],
value: [
{
account: {
data: ['', 'base64'],
executable: false,
lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account0.publicKey.toBase58(),
},
{
account: {
data: ['', 'base64'],
executable: false,
lamports:
0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account1.publicKey.toBase58(),
},
],
});
const programAccountsDoMatchFilter = await connection.getParsedProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
filters: [
{
memcmp: {
offset: 0,
bytes: '',
},
},
],
},
);
expect(programAccountsDoMatchFilter).to.have.length(2);
}
}); });
it('get balance', async () => { it('get balance', async () => {