Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Commit

Permalink
refactor(experimental): graphql: expand on batch loader for accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Feb 19, 2024
1 parent 6b596b0 commit 5633432
Show file tree
Hide file tree
Showing 12 changed files with 1,834 additions and 68 deletions.
1 change: 1 addition & 0 deletions packages/rpc-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
],
"dependencies": {
"@graphql-tools/schema": "^10.0.0",
"@solana/codecs-strings": "workspace:*",
"dataloader": "^2.2.2",
"graphql": "^16.8.0",
"json-stable-stringify": "^1.1.0"
Expand Down
164 changes: 162 additions & 2 deletions packages/rpc-graphql/src/__tests__/account-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
createSolanaRpcApi,
GetAccountInfoApi,
GetBlockApi,
GetMultipleAccountsApi,
GetProgramAccountsApi,
GetTransactionApi,
} from '@solana/rpc-core';
Expand All @@ -12,12 +13,14 @@ import fetchMock from 'jest-fetch-mock-fork';
import { createRpcGraphQL, RpcGraphQL } from '../index';

describe('account', () => {
let rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetProgramAccountsApi & GetTransactionApi>;
let rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi>;
let rpcGraphQL: RpcGraphQL;
beforeEach(() => {
fetchMock.resetMocks();
fetchMock.dontMock();
rpc = createJsonRpc<GetAccountInfoApi & GetBlockApi & GetProgramAccountsApi & GetTransactionApi>({
rpc = createJsonRpc<
GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi
>({
api: createSolanaRpcApi(),
transport: createHttpTransport({ url: 'http://127.0.0.1:8899' }),
});
Expand Down Expand Up @@ -252,6 +255,31 @@ describe('account', () => {
},
});
});
it('can get account data as base58 with data slice', async () => {
expect.assertions(1);
// See scripts/fixtures/gpa1.json
const variableValues = {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
encoding: 'BASE_58',
};
const source = /* GraphQL */ `
query testQuery($address: Address!, $encoding: AccountEncoding!) {
account(address: $address) {
address
data(encoding: $encoding, dataSlice: { offset: 0, length: 5 })
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
expect(result).toMatchObject({
data: {
account: {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
data: 'E8f4pET', // As tested on local RPC
},
},
});
});
it('can get account data as base64', async () => {
expect.assertions(1);
const source = /* GraphQL */ `
Expand All @@ -272,6 +300,58 @@ describe('account', () => {
},
});
});
it('can get account data as base64 with data slice', async () => {
expect.assertions(1);
// See scripts/fixtures/gpa1.json
const variableValues = {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
encoding: 'BASE_64',
};
const source = /* GraphQL */ `
query testQuery($address: Address!, $encoding: AccountEncoding!) {
account(address: $address) {
address
data(encoding: $encoding, dataSlice: { offset: 0, length: 5 })
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
expect(result).toMatchObject({
data: {
account: {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
data: 'dGVzdCA=',
},
},
});
});
it('can get account data as base64 with two data slices', async () => {
expect.assertions(1);
// See scripts/fixtures/gpa1.json
const variableValues = {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
encoding: 'BASE_64',
};
const source = /* GraphQL */ `
query testQuery($address: Address!, $encoding: AccountEncoding!) {
account(address: $address) {
address
dataA: data(encoding: $encoding, dataSlice: { offset: 2, length: 5 })
dataB: data(encoding: $encoding, dataSlice: { offset: 4, length: 5 })
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
expect(result).toMatchObject({
data: {
account: {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
dataA: 'c3QgZGE=', // As tested on local RPC
dataB: 'IGRhdGE=', // As tested on local RPC
},
},
});
});
it('can get account data as base64+zstd', async () => {
expect.assertions(1);
const source = /* GraphQL */ `
Expand All @@ -292,6 +372,58 @@ describe('account', () => {
},
});
});
it('can get account data as base64+zstd with data slice', async () => {
expect.assertions(1);
// See scripts/fixtures/gpa1.json
const variableValues = {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
encoding: 'BASE_64_ZSTD',
};
const source = /* GraphQL */ `
query testQuery($address: Address!, $encoding: AccountEncoding!) {
account(address: $address) {
address
data(encoding: $encoding, dataSlice: { offset: 0, length: 15 })
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
expect(result).toMatchObject({
data: {
account: {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
data: 'KLUv/QBYSQAAdGVzdCBkYXRh', // As tested on local RPC
},
},
});
});
it('can get account data as base64+zstd with multiple data slices', async () => {
expect.assertions(1);
// See scripts/fixtures/gpa1.json
const variableValues = {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
encoding: 'BASE_64_ZSTD',
};
const source = /* GraphQL */ `
query testQuery($address: Address!, $encoding: AccountEncoding!) {
account(address: $address) {
address
dataA: data(encoding: $encoding, dataSlice: { offset: 0, length: 5 })
dataB: data(encoding: $encoding, dataSlice: { offset: 0, length: 15 })
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
expect(result).toMatchObject({
data: {
account: {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
dataA: 'KLUv/QBYKQAAdGVzdCA=', // As tested on local RPC
dataB: 'KLUv/QBYSQAAdGVzdCBkYXRh', // As tested on local RPC
},
},
});
});
it('can get account data with multiple encodings', async () => {
expect.assertions(1);
const source = /* GraphQL */ `
Expand All @@ -316,6 +448,34 @@ describe('account', () => {
},
});
});
it('can get account data with multiple encodings and data slices', async () => {
expect.assertions(1);
// See scripts/fixtures/gpa1.json
const variableValues = {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
};
const source = /* GraphQL */ `
query testQuery($address: Address!) {
account(address: $address) {
address
dataBase58: data(encoding: BASE_58, dataSlice: { offset: 0, length: 5 })
dataBase64: data(encoding: BASE_64, dataSlice: { offset: 0, length: 5 })
dataBase64Zstd: data(encoding: BASE_64_ZSTD, dataSlice: { offset: 0, length: 5 })
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
expect(result).toMatchObject({
data: {
account: {
address: 'CcYNb7WqpjaMrNr7B1mapaNfWctZRH7LyAjWRLBGt1Fk',
dataBase58: 'E8f4pET', // As tested on local RPC
dataBase64: 'dGVzdCA=', // As tested on local RPC
dataBase64Zstd: 'KLUv/QBYKQAAdGVzdCA=', // As tested on local RPC
},
},
});
});
it('can get account data as jsonParsed', async () => {
expect.assertions(1);
const source = /* GraphQL */ `
Expand Down
7 changes: 5 additions & 2 deletions packages/rpc-graphql/src/__tests__/block-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
createSolanaRpcApi,
type GetAccountInfoApi,
GetBlockApi,
GetMultipleAccountsApi,
GetProgramAccountsApi,
GetTransactionApi,
} from '@solana/rpc-core';
Expand All @@ -21,7 +22,7 @@ import {
} from './__setup__';

describe('block', () => {
let rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetProgramAccountsApi & GetTransactionApi>;
let rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi>;
let rpcGraphQL: RpcGraphQL;

// Random slot for testing.
Expand All @@ -31,7 +32,9 @@ describe('block', () => {
beforeEach(() => {
fetchMock.resetMocks();
fetchMock.dontMock();
rpc = createJsonRpc<GetAccountInfoApi & GetBlockApi & GetProgramAccountsApi & GetTransactionApi>({
rpc = createJsonRpc<
GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi
>({
api: createSolanaRpcApi(),
transport: createHttpTransport({ url: 'http://127.0.0.1:8899' }),
});
Expand Down
11 changes: 7 additions & 4 deletions packages/rpc-graphql/src/__tests__/program-accounts-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
createSolanaRpcApi,
GetAccountInfoApi,
GetBlockApi,
GetMultipleAccountsApi,
GetProgramAccountsApi,
GetTransactionApi,
} from '@solana/rpc-core';
Expand All @@ -12,12 +13,14 @@ import fetchMock from 'jest-fetch-mock-fork';
import { createRpcGraphQL, RpcGraphQL } from '../index';

describe('programAccounts', () => {
let rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetProgramAccountsApi & GetTransactionApi>;
let rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi>;
let rpcGraphQL: RpcGraphQL;
beforeEach(() => {
fetchMock.resetMocks();
fetchMock.dontMock();
rpc = createJsonRpc<GetAccountInfoApi & GetBlockApi & GetProgramAccountsApi & GetTransactionApi>({
rpc = createJsonRpc<
GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi
>({
api: createSolanaRpcApi(),
transport: createHttpTransport({ url: 'http://127.0.0.1:8899' }),
});
Expand Down Expand Up @@ -599,7 +602,7 @@ describe('programAccounts', () => {
data: {
programAccounts: expect.arrayContaining([
{
data: 'E8f4pET',
data: 'E8f4pET', // As tested on local RPC
},
]),
},
Expand Down Expand Up @@ -642,7 +645,7 @@ describe('programAccounts', () => {
data: {
programAccounts: expect.arrayContaining([
{
data: 'dGVzdCA=',
data: 'dGVzdCA=', // As tested on local RPC
},
]),
},
Expand Down
7 changes: 5 additions & 2 deletions packages/rpc-graphql/src/__tests__/transaction-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
createSolanaRpcApi,
GetAccountInfoApi,
GetBlockApi,
GetMultipleAccountsApi,
GetProgramAccountsApi,
GetTransactionApi,
} from '@solana/rpc-core';
Expand All @@ -24,7 +25,7 @@ import {
} from './__setup__';

describe('transaction', () => {
let rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetProgramAccountsApi & GetTransactionApi>;
let rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi>;
let rpcGraphQL: RpcGraphQL;

// Random signature for testing.
Expand All @@ -35,7 +36,9 @@ describe('transaction', () => {
beforeEach(() => {
fetchMock.resetMocks();
fetchMock.dontMock();
rpc = createJsonRpc<GetAccountInfoApi & GetBlockApi & GetProgramAccountsApi & GetTransactionApi>({
rpc = createJsonRpc<
GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi
>({
api: createSolanaRpcApi(),
transport: createHttpTransport({ url: 'http://127.0.0.1:8899' }),
});
Expand Down
30 changes: 26 additions & 4 deletions packages/rpc-graphql/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import type { createRpcGraphQL } from './index';
import type {
GetAccountInfoApi,
GetBlockApi,
GetMultipleAccountsApi,
GetProgramAccountsApi,
GetTransactionApi,
} from '@solana/rpc-core';
import type { Rpc } from '@solana/rpc-types';

import {
createAccountLoader,
createBlockLoader,
Expand All @@ -7,16 +15,30 @@ import {
RpcGraphQLLoaders,
} from './loaders';

export type Rpc = Parameters<typeof createRpcGraphQL>[0];
type Config = {
/**
* Maximum number of acceptable bytes to waste before splitting two
* `dataSlice` requests into two requests.
*/
maxDataSliceByteRange: number;
/**
* Maximum number of accounts to fetch in a single batch.
* See https://docs.solana.com/api/http#getmultipleaccounts.
*/
maxMultipleAccountsBatchSize: number;
}

export interface RpcGraphQLContext {
loaders: RpcGraphQLLoaders;
}

export function createSolanaGraphQLContext(rpc: Rpc): RpcGraphQLContext {
export function createSolanaGraphQLContext(
rpc: Rpc<GetAccountInfoApi & GetBlockApi & GetMultipleAccountsApi & GetProgramAccountsApi & GetTransactionApi>,
config: Config,
): RpcGraphQLContext {
return {
loaders: {
account: createAccountLoader(rpc),
account: createAccountLoader(rpc, config),
block: createBlockLoader(rpc),
programAccounts: createProgramAccountsLoader(rpc),
transaction: createTransactionLoader(rpc),
Expand Down
Loading

0 comments on commit 5633432

Please sign in to comment.