-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add context.rpc #801
base: main
Are you sure you want to change the base?
Add context.rpc #801
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -13,14 +13,22 @@ import type IndexerConfig from '../indexer-config'; | |||||
import { type PostgresConnectionParams } from '../pg-client'; | ||||||
import IndexerMeta, { IndexerStatus } from '../indexer-meta'; | ||||||
import { wrapSpan } from '../utility'; | ||||||
import { type IRpcClient } from '../rpc-client/rpc-client'; | ||||||
import { type CodeResult } from '@near-js/types/lib/provider/response'; | ||||||
|
||||||
interface Dependencies { | ||||||
fetch: typeof fetch | ||||||
provisioner: Provisioner | ||||||
dmlHandler?: DmlHandler | ||||||
indexerMeta?: IndexerMeta | ||||||
parser: Parser | ||||||
}; | ||||||
rpcClient?: IRpcClient | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I'm not sure there is any reason not to include this? This would also tidy up the |
||||||
} | ||||||
|
||||||
interface IRpcContext { | ||||||
viewCallRaw: (contractId: string, methodName: string, args: Record<string, string | number | object>) => Promise<CodeResult> | ||||||
viewCallJSON: (contractId: string, methodName: string, args: Record<string, string | number | object>) => Promise<any> | ||||||
} | ||||||
|
||||||
interface Context { | ||||||
graphql: (operation: string, variables?: Record<string, any>) => Promise<any> | ||||||
|
@@ -31,6 +39,7 @@ interface Context { | |||||
error: (message: string) => void | ||||||
fetchFromSocialApi: (path: string, options?: any) => Promise<any> | ||||||
db: Record<string, Record<string, (...args: any[]) => any>> | ||||||
rpc: IRpcContext | ||||||
} | ||||||
|
||||||
export interface TableDefinitionNames { | ||||||
|
@@ -200,7 +209,8 @@ export default class Indexer { | |||||
fetchFromSocialApi: async (path, options) => { | ||||||
return await this.deps.fetch(`https://api.near.social${path}`, options); | ||||||
}, | ||||||
db: this.buildDatabaseContext(blockHeight, logEntries) | ||||||
db: this.buildDatabaseContext(blockHeight, logEntries), | ||||||
rpc: this.buildRPCContext(blockHeight) | ||||||
}; | ||||||
} | ||||||
|
||||||
|
@@ -277,6 +287,25 @@ export default class Indexer { | |||||
return pascalCaseTableName; | ||||||
} | ||||||
|
||||||
buildRPCContext ( | ||||||
currentBlockHeight: number, | ||||||
): IRpcContext { | ||||||
const rpcClient = (): IRpcClient => { | ||||||
if (!this.deps.rpcClient) { | ||||||
throw new Error('RPC client is not configured'); | ||||||
} | ||||||
return this.deps.rpcClient; | ||||||
}; | ||||||
return { | ||||||
viewCallRaw: async (contractId: string, methodName: string, args: Record<string, string | number | object> = {}, blockHeight = currentBlockHeight): Promise<CodeResult> => { | ||||||
return await rpcClient().viewCallRaw(blockHeight, contractId, methodName, args); | ||||||
}, | ||||||
viewCallJSON: async (contractId: string, methodName: string, args: Record<string, string | number | object> = {}, blockHeight = currentBlockHeight): Promise<any> => { | ||||||
return await rpcClient().viewCallJSON(blockHeight, contractId, methodName, args); | ||||||
} | ||||||
}; | ||||||
} | ||||||
|
||||||
buildDatabaseContext ( | ||||||
blockHeight: number, | ||||||
logEntries: LogEntry[], | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import RpcClient from './rpc-client'; | ||
|
||
describe('RPCClient unit tests', () => { | ||
const rpcClient = RpcClient.fromConfig({ | ||
networkId: 'mainnet', | ||
nodeUrl: 'https://beta.rpc.mainnet.near.org', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add tests which don't rely on real endpoints? I don't want unnecessary CI failures due to network failures. |
||
}); | ||
const testBlockHeight = 121_031_955; | ||
|
||
it('Should make a get_total_staked_balance view call to pool.near', async () => { | ||
const response = await rpcClient.viewCallJSON(testBlockHeight, 'epic.poolv1.near', 'get_total_staked_balance', {}); | ||
console.log(response); | ||
expect(response).toBeDefined(); | ||
}); | ||
|
||
it('Should return non-empty dataplatform.near.list_by_account', async () => { | ||
const response = await rpcClient.viewCallJSON(testBlockHeight, 'queryapi.dataplatform.near', 'list_by_account', { account_id: 'dataplatform.near' }); | ||
expect(Object.keys(response).length).toBeGreaterThanOrEqual(0); | ||
}, 30_000); | ||
|
||
it('Should get_contracts_metadata from sputnik-dao.near', async () => { | ||
const response = await rpcClient.viewCallJSON(testBlockHeight, 'sputnik-dao.near', 'get_contracts_metadata', {}); | ||
expect(response.length).toBeGreaterThanOrEqual(3); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,53 @@ | ||||||
import { type NearConfig } from '@near-js/wallet-account/lib/near'; | ||||||
import { connect, type Near } from 'near-api-js'; | ||||||
import { type CodeResult } from '@near-js/types/lib/provider/response'; | ||||||
|
||||||
type RpcViewCallArgs = Record<string, string | number | object>; | ||||||
|
||||||
export interface IRpcClient { | ||||||
viewCallRaw: (blockHeight: number, contractId: string, methodName: string, args: RpcViewCallArgs) => Promise<CodeResult> | ||||||
viewCallJSON: (blockHeight: number, contractId: string, methodName: string, args: RpcViewCallArgs) => Promise<any> | ||||||
} | ||||||
|
||||||
export default class RpcClient implements IRpcClient { | ||||||
#near: Near | undefined; | ||||||
|
||||||
private constructor (private readonly config: NearConfig) {} | ||||||
|
||||||
async nearConnection (): Promise<Near> { | ||||||
if (!this.#near) { | ||||||
this.#near = await connect(this.config); | ||||||
} | ||||||
return this.#near; | ||||||
} | ||||||
|
||||||
async viewCallRaw (blockHeight: number, contractId: string, methodName: string, args: RpcViewCallArgs = {}): Promise<CodeResult> { | ||||||
const near = await this.nearConnection(); | ||||||
return await near.connection.provider.query({ | ||||||
request_type: 'call_function', | ||||||
blockId: blockHeight, | ||||||
account_id: contractId, | ||||||
method_name: methodName, | ||||||
args_base64: Buffer.from(JSON.stringify(args)).toString('base64'), | ||||||
}); | ||||||
} | ||||||
|
||||||
async viewCallJSON (blockHeight: number, contractId: string, methodName: string, args: RpcViewCallArgs = {}): Promise<any> { | ||||||
const response: CodeResult = await this.viewCallRaw(blockHeight, contractId, methodName, args); | ||||||
return JSON.parse(Buffer.from(response.result).toString('ascii')); | ||||||
} | ||||||
|
||||||
static fromConfig (config: NearConfig): IRpcClient { | ||||||
return new RpcClient(config); | ||||||
} | ||||||
|
||||||
static fromEnv (): IRpcClient { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, I like it :) |
||||||
if (!process.env.RPC_URL) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I've deployed under this environment variable, I didn't want it to be confused with the other RPC endpoints we use internally. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That actually makes sense. I was thinking to actually name it 'ARCHIVAL_RPC_ENDPOINT' – do you want me to make this change in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I've deployed |
||||||
throw new Error('Missing RPC_URL env var for RpcClient'); | ||||||
} | ||||||
return RpcClient.fromConfig({ | ||||||
networkId: 'mainnet', | ||||||
nodeUrl: process.env.RPC_URL, | ||||||
}); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ import { IndexerStatus } from '../indexer-meta/indexer-meta'; | |
import IndexerConfig from '../indexer-config'; | ||
import parentLogger from '../logger'; | ||
import { wrapSpan } from '../utility'; | ||
import RpcClient from '../rpc-client/rpc-client'; | ||
|
||
if (isMainThread) { | ||
throw new Error('Worker should not be run on main thread'); | ||
|
@@ -96,7 +97,9 @@ async function blockQueueProducer (workerContext: WorkerContext): Promise<void> | |
async function blockQueueConsumer (workerContext: WorkerContext): Promise<void> { | ||
let previousError: string = ''; | ||
const indexerConfig: IndexerConfig = workerContext.indexerConfig; | ||
const indexer = new Indexer(indexerConfig); | ||
const indexer = new Indexer(indexerConfig, { | ||
rpcClient: RpcClient.fromEnv() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We typically add defaults for injected dependencies to add convenience, could you do that please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was actually proposing to not have defaults to make configuration of this class explicit with all dependencies – the number of places it is being configured in is very low. This will also be needed for the testing framework. Happy to discuss or change my opinion if defaults indeed make things more convenient. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What benefit would the explicitness provide? For me, defaults allow us to abstract the implementation away, so therefore don't need to think about it from the user side. I hadn't really considered otherwise, interested to see your side. |
||
}); | ||
let streamMessageId = ''; | ||
let currBlockHeight = 0; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ import block_115185108 from './blocks/00115185108/streamer_message.json'; | |
import block_115185109 from './blocks/00115185109/streamer_message.json'; | ||
import { LogLevel } from '../src/indexer-meta/log-entry'; | ||
import IndexerConfig from '../src/indexer-config'; | ||
import RpcClient, { type IRpcClient } from '../src/rpc-client/rpc-client'; | ||
|
||
describe('Indexer integration', () => { | ||
jest.setTimeout(300_000); | ||
|
@@ -25,6 +26,7 @@ describe('Indexer integration', () => { | |
let postgresContainer: StartedPostgreSqlContainer; | ||
let hasuraContainer: StartedHasuraGraphQLContainer; | ||
let graphqlClient: GraphQLClient; | ||
let rpcClient: IRpcClient; | ||
|
||
beforeEach(async () => { | ||
hasuraClient = new HasuraClient({}, { | ||
|
@@ -56,6 +58,11 @@ describe('Indexer integration', () => { | |
pgBouncerPort: Number(postgresContainer.getPort()), | ||
} | ||
); | ||
|
||
rpcClient = RpcClient.fromConfig({ | ||
networkId: 'mainnet', | ||
nodeUrl: 'https://beta.rpc.mainnet.near.org', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar comment around adding tests which don't depend on real infrastructure. This isn't being used in integration tests so I would just remove it. |
||
}); | ||
}); | ||
|
||
beforeAll(async () => { | ||
|
@@ -117,7 +124,8 @@ describe('Indexer integration', () => { | |
const indexer = new Indexer( | ||
indexerConfig, | ||
{ | ||
provisioner | ||
provisioner, | ||
rpcClient | ||
}, | ||
undefined, | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be using
@near-js
packages instead, so for this I think that would mean@near-js/types
and@near-js/providers
.near-api-js
includes much more than we need, and@near-js
is more likely to be maintained going forward.