@sentio/runtime 2.40.0-rc.1 → 2.40.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/provider.ts CHANGED
@@ -37,6 +37,8 @@ export function getProvider(chainId?: EthChainId): Provider {
37
37
 
38
38
  const address = Endpoints.INSTANCE.chainServer.get(chainId)
39
39
  const key = network.chainId.toString() + '-' + address
40
+
41
+ console.debug(`init provider for ${chainId}, address: ${address}`)
40
42
  let provider = providers.get(key)
41
43
 
42
44
  if (provider) {
package/src/service.ts CHANGED
@@ -11,6 +11,8 @@ import {
11
11
  HandlerType,
12
12
  PreparedData,
13
13
  PreprocessResult,
14
+ PreprocessStreamRequest,
15
+ PreprocessStreamResponse,
14
16
  ProcessBindingResponse,
15
17
  ProcessBindingsRequest,
16
18
  ProcessConfigRequest,
@@ -23,7 +25,7 @@ import {
23
25
  } from '@sentio/protos'
24
26
 
25
27
  import { PluginManager } from './plugin.js'
26
- import { errorString, mergeProcessResults } from './utils.js'
28
+ import { errorString, makeEthCallKey, mergeProcessResults } from './utils.js'
27
29
  import { freezeGlobalConfig, GLOBAL_CONFIG } from './global-config.js'
28
30
 
29
31
  import { StoreContext } from './db-context.js'
@@ -31,7 +33,7 @@ import { Subject } from 'rxjs'
31
33
  import { metrics } from '@opentelemetry/api'
32
34
  import { getProvider } from './provider.js'
33
35
  import { EthChainId } from '@sentio/chain'
34
- import { Provider, Interface } from 'ethers'
36
+ import { Provider } from 'ethers'
35
37
 
36
38
  const meter = metrics.getMeter('processor_service')
37
39
  const process_binding_count = meter.createCounter('process_binding_count')
@@ -52,6 +54,8 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
52
54
 
53
55
  private readonly shutdownHandler?: () => void
54
56
 
57
+ private readonly preprocessedEthCalls: { [calldata: string]: any[] }
58
+
55
59
  constructor(loader: () => Promise<any>, shutdownHandler?: () => void) {
56
60
  this.loader = loader
57
61
  this.shutdownHandler = shutdownHandler
@@ -124,7 +128,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
124
128
  }
125
129
 
126
130
  async processBindings(request: ProcessBindingsRequest, options?: CallContext): Promise<ProcessBindingResponse> {
127
- const ethCallResults = await this.preprocessBindings(request, options)
131
+ const ethCallResults = await this.preprocessBindings(request.bindings, undefined, options)
128
132
 
129
133
  const promises = []
130
134
  for (const binding of request.bindings) {
@@ -154,12 +158,14 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
154
158
  }
155
159
 
156
160
  async preprocessBindings(
157
- request: ProcessBindingsRequest,
161
+ bindings: DataBinding[],
162
+ dbContext?: StoreContext,
158
163
  options?: CallContext
159
- ): Promise<{ [calldata: string]: any[] }> {
164
+ ): Promise<{ [ethCallKey: string]: string }> {
165
+ console.debug(`preprocessBindings start, bindings: ${bindings.length}`)
160
166
  const promises = []
161
- for (const binding of request.bindings) {
162
- promises.push(this.preprocessBinding(binding, options))
167
+ for (const binding of bindings) {
168
+ promises.push(this.preprocessBinding(binding, dbContext, options))
163
169
  }
164
170
  let preprocessResults: PreprocessResult[]
165
171
  try {
@@ -167,14 +173,19 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
167
173
  } catch (e) {
168
174
  throw e
169
175
  }
176
+ console.debug(
177
+ 'ethCallParams: ',
178
+ preprocessResults.map((r) => r.ethCallParams)
179
+ )
170
180
  const groupedRequests = new Map<string, EthCallParam[]>()
171
181
  const providers = new Map<string, Provider>()
172
182
  for (const result of preprocessResults) {
173
183
  for (const param of result.ethCallParams) {
174
- if (!providers.has(param.chainId)) {
175
- providers.set(param.chainId, getProvider(param.chainId as EthChainId))
184
+ const { chainId, address, blockTag } = param.context!
185
+ if (!providers.has(chainId)) {
186
+ providers.set(chainId, getProvider(chainId as EthChainId))
176
187
  }
177
- const key = param.chainId + '|' + param.address
188
+ const key = [chainId, address, blockTag].join('|')
178
189
  if (!groupedRequests.has(key)) {
179
190
  groupedRequests.set(key, [])
180
191
  }
@@ -185,27 +196,37 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
185
196
  const start = Date.now()
186
197
  const callPromises = []
187
198
  for (const params of groupedRequests.values()) {
188
- console.log(`chain: ${params[0].chainId}, address: ${params[0].address}, totalCalls: ${params.length}`)
199
+ const { chainId, address, blockTag } = params[0].context!
200
+ console.log(`chain: ${chainId}, address: ${address}, blockTag: ${blockTag}, totalCalls: ${params.length}`)
201
+ // TODO multicall
189
202
  for (const param of params) {
190
- const frag = new Interface(param.signature)
191
- const calldata = frag.encodeFunctionData(param.function, param.args)
192
203
  callPromises.push(
193
204
  providers
194
- .get(param.chainId)!
205
+ .get(chainId)!
195
206
  .call({
196
- to: param.address,
197
- data: calldata
207
+ to: address,
208
+ data: param.calldata,
209
+ blockTag
198
210
  })
199
- .then((ret) => [calldata, frag.decodeFunctionResult(param.function, ret).toArray()] as [string, any[]])
211
+ .then((result) => [makeEthCallKey(param), result])
200
212
  )
201
213
  }
202
214
  }
203
- const results = Object.fromEntries(await Promise.all(callPromises))
215
+ let results = {}
216
+ try {
217
+ results = Object.fromEntries(await Promise.all(callPromises))
218
+ } catch (e) {
219
+ console.error(`eth call error: ${e}`)
220
+ }
204
221
  console.log(`${callPromises.length} calls finished, elapsed: ${Date.now() - start}ms`)
205
222
  return results
206
223
  }
207
224
 
208
- async preprocessBinding(request: DataBinding, options?: CallContext): Promise<PreprocessResult> {
225
+ async preprocessBinding(
226
+ request: DataBinding,
227
+ dbContext?: StoreContext,
228
+ options?: CallContext
229
+ ): Promise<PreprocessResult> {
209
230
  if (!this.started) {
210
231
  throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
211
232
  }
@@ -221,7 +242,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
221
242
  ]
222
243
  )
223
244
  }
224
- return await PluginManager.INSTANCE.preprocessBinding(request)
245
+ return await PluginManager.INSTANCE.preprocessBinding(request, dbContext)
225
246
  }
226
247
 
227
248
  async processBinding(
@@ -266,6 +287,63 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
266
287
  yield* from(subject).pipe(withAbort(context.signal))
267
288
  }
268
289
 
290
+ async handlePreprocessRequests(
291
+ requests: AsyncIterable<PreprocessStreamRequest>,
292
+ subject: Subject<DeepPartial<PreprocessStreamResponse>>
293
+ ) {
294
+ const contexts = new Contexts()
295
+
296
+ for await (const request of requests) {
297
+ try {
298
+ console.debug('received request:', request)
299
+ if (request.bindings) {
300
+ const bindings = request.bindings.bindings
301
+ const dbContext = contexts.new(request.processId, subject)
302
+ const start = Date.now()
303
+ this.preprocessBindings(bindings, dbContext)
304
+ .then(() => {
305
+ subject.next({
306
+ processId: request.processId
307
+ })
308
+ })
309
+ .catch((e) => {
310
+ console.debug(e)
311
+ dbContext.error(request.processId, e)
312
+ })
313
+ .finally(() => {
314
+ const cost = Date.now() - start
315
+ console.debug('preprocessBinding', request.processId, ' took', cost, 'ms')
316
+ contexts.delete(request.processId)
317
+ })
318
+ }
319
+ if (request.dbResult) {
320
+ const dbContext = contexts.get(request.processId)
321
+ dbContext?.result(request.dbResult)
322
+ }
323
+ } catch (e) {
324
+ // should not happen
325
+ console.error('unexpect error during handle loop', e)
326
+ }
327
+ }
328
+ }
329
+
330
+ async *preprocessBindingsStream(requests: AsyncIterable<PreprocessStreamRequest>, context: CallContext) {
331
+ if (!this.started) {
332
+ throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
333
+ }
334
+
335
+ const subject = new Subject<DeepPartial<PreprocessStreamResponse>>()
336
+ this.handlePreprocessRequests(requests, subject)
337
+ .then(() => {
338
+ subject.complete()
339
+ })
340
+ .catch((e) => {
341
+ console.error(e)
342
+ subject.error(e)
343
+ })
344
+ yield* from(subject).pipe(withAbort(context.signal))
345
+ }
346
+
269
347
  private async handleRequests(
270
348
  requests: AsyncIterable<ProcessStreamRequest>,
271
349
  subject: Subject<DeepPartial<ProcessStreamResponse>>
package/src/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ProcessResult } from '@sentio/protos'
1
+ import { EthCallParam, ProcessResult } from '@sentio/protos'
2
2
 
3
3
  // TODO better handling this, because old proto doesn't have this
4
4
  import { StateResult, ProcessResult as ProcessResultFull } from './gen/processor/protos/processor.js'
@@ -8,7 +8,7 @@ import { Required } from 'utility-types'
8
8
  export function mergeProcessResults(results: ProcessResult[]): Required<ProcessResult, 'states'> {
9
9
  const res = {
10
10
  ...ProcessResultFull.create(),
11
- states: StateResult.create(),
11
+ states: StateResult.create()
12
12
  }
13
13
 
14
14
  for (const r of results) {
@@ -17,7 +17,7 @@ export function mergeProcessResults(results: ProcessResult[]): Required<ProcessR
17
17
  res.events = res.events.concat(r.events)
18
18
  res.exports = res.exports.concat(r.exports)
19
19
  res.states = {
20
- configUpdated: res.states?.configUpdated || r.states?.configUpdated || false,
20
+ configUpdated: res.states?.configUpdated || r.states?.configUpdated || false
21
21
  }
22
22
  }
23
23
  return res
@@ -28,3 +28,11 @@ export function errorString(e: Error): string {
28
28
  }
29
29
 
30
30
  export const USER_PROCESSOR = 'user_processor'
31
+
32
+ export function makeEthCallKey(param: EthCallParam) {
33
+ if (!param.context) {
34
+ throw new Error('null context for eth call')
35
+ }
36
+ const { chainId, address, blockTag } = param.context
37
+ return `${chainId}|${address}|${blockTag}|${param.calldata}`
38
+ }