@sentio/runtime 4.0.0-rc.1 → 4.0.0-rc.3

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.
@@ -1,4 +1,5 @@
1
- import { ExecutionConfig } from './gen/processor/protos/processor.js'
1
+ import { ExecutionConfigSchema } from '@sentio/protos'
2
+ import { type MessageInitShape } from '@bufbuild/protobuf'
2
3
 
3
4
  /**
4
5
  * Configuration for the in-memory cache feature.
@@ -33,7 +34,7 @@ export interface GlobalConfig {
33
34
  * Execution configuration controlling how handlers are processed.
34
35
  * Includes settings for sequential vs parallel execution, block time handling, etc.
35
36
  */
36
- execution: Partial<ExecutionConfig>
37
+ execution: MessageInitShape<typeof ExecutionConfigSchema>
37
38
 
38
39
  /**
39
40
  * Optional cache configuration for enabling in-memory key-value storage.
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ export * from './state.js'
3
3
  export * from './utils.js'
4
4
  export * from './endpoints.js'
5
5
  export * from './chain-config.js'
6
- export * from './service.js'
6
+ export * from './service-v3.js'
7
7
  export { GLOBAL_CONFIG, type GlobalConfig } from './global-config.js'
8
8
  export * from './db-context.js'
9
9
  export * from './provider.js'
package/src/plugin.ts CHANGED
@@ -1,15 +1,20 @@
1
1
  import {
2
- DataBinding,
2
+ type DataBinding,
3
3
  HandlerType,
4
- PreparedData,
5
- PreprocessResult,
6
- ProcessConfigResponse,
7
- ProcessResult,
8
- ProcessStreamResponse_Partitions,
4
+ type PreparedData,
5
+ type PreprocessResult,
6
+ PreprocessResultSchema,
7
+ type ProcessConfigResponse,
8
+ type ProcessResult,
9
+ ProcessResultSchema,
10
+ type StartRequest,
11
+ StartRequestSchema,
12
+ type ProcessStreamResponse_Partitions,
13
+ ProcessStreamResponse_PartitionsSchema,
9
14
  ProcessStreamResponse_Partitions_Partition_SysValue,
10
- StartRequest,
11
- UpdateTemplatesRequest
15
+ type UpdateTemplatesRequest
12
16
  } from '@sentio/protos'
17
+ import { create } from '@bufbuild/protobuf'
13
18
  import { IDataBindingContext, IStoreContext } from './db-context.js'
14
19
  import { AsyncLocalStorage } from 'node:async_hooks'
15
20
 
@@ -29,25 +34,28 @@ export abstract class Plugin {
29
34
  }
30
35
 
31
36
  async processBinding(request: DataBinding, preparedData: PreparedData | undefined): Promise<ProcessResult> {
32
- return ProcessResult.create()
37
+ return create(ProcessResultSchema)
33
38
  }
34
39
 
35
40
  async preprocessBinding(request: DataBinding, preprocessStore: { [k: string]: any }): Promise<PreprocessResult> {
36
- return PreprocessResult.create()
41
+ return create(PreprocessResultSchema)
37
42
  }
38
43
 
39
44
  async partition(request: DataBinding): Promise<ProcessStreamResponse_Partitions> {
40
- return {
45
+ return create(ProcessStreamResponse_PartitionsSchema, {
41
46
  partitions: request.handlerIds.reduce(
42
47
  (acc, id) => ({
43
48
  ...acc,
44
49
  [id]: {
45
- sysValue: ProcessStreamResponse_Partitions_Partition_SysValue.UNRECOGNIZED
50
+ value: {
51
+ case: 'sysValue',
52
+ value: ProcessStreamResponse_Partitions_Partition_SysValue.BLOCK_NUMBER
53
+ }
46
54
  }
47
55
  }),
48
56
  {}
49
57
  )
50
- }
58
+ })
51
59
  }
52
60
 
53
61
  /**
@@ -147,9 +155,11 @@ export class PluginManager {
147
155
 
148
156
  async updateTemplates(request: UpdateTemplatesRequest) {
149
157
  for (const plugin of this.plugins) {
150
- await plugin.start({
151
- templateInstances: request.templateInstances
152
- })
158
+ await plugin.start(
159
+ create(StartRequestSchema, {
160
+ templateInstances: request.templateInstances
161
+ })
162
+ )
153
163
  }
154
164
  }
155
165
  }
@@ -1,26 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'fs-extra'
4
- // import { compressionAlgorithms } from '@grpc/grpc-js'
5
- import { createServer } from 'nice-grpc'
6
- import { errorDetailsServerMiddleware } from 'nice-grpc-error-details'
7
- // import { registry as niceGrpcRegistry } from 'nice-grpc-prometheus'
8
- // import { openTelemetryServerMiddleware } from 'nice-grpc-opentelemetry'
4
+ import { type ConnectRouter } from '@connectrpc/connect'
5
+ import { connectNodeAdapter } from '@connectrpc/connect-node'
9
6
  import http from 'http'
7
+ import http2 from 'node:http2'
10
8
  // @ts-ignore inspector promises is not included in @type/node
11
9
  import { Session } from 'node:inspector/promises'
12
10
  import { fork, ChildProcess } from 'child_process'
13
11
  import { fileURLToPath } from 'url'
14
12
 
15
- import { ProcessorDefinition } from './gen/processor/protos/processor.js'
16
- import { ProcessorServiceImpl } from './service.js'
17
13
  import { configureEndpoints } from './endpoints.js'
18
- import { FullProcessorServiceImpl, FullProcessorServiceV3Impl } from './full-service.js'
14
+ import { FullProcessorServiceV3Impl } from './full-service.js'
19
15
  import { setupLogger } from './logger.js'
20
16
 
21
17
  import { setupOTLP } from './otlp.js'
22
18
  import { ActionServer } from './action-server.js'
23
- import { ProcessorV3Definition } from '@sentio/protos'
19
+ import { ProcessorV3 } from '@sentio/protos'
24
20
  import { ProcessorServiceImplV3 } from './service-v3.js'
25
21
  import { dirname, join } from 'path'
26
22
  import { program, ProcessorRuntimeOptions } from './processor-runner-program.js'
@@ -69,7 +65,7 @@ if (!isChildProcess) {
69
65
  }
70
66
 
71
67
  let server: any
72
- let baseService: ProcessorServiceImpl
68
+ let processorHttp2Server: http2.Http2Server | undefined
73
69
  let httpServer: http.Server | undefined
74
70
 
75
71
  const loader = async () => {
@@ -82,28 +78,24 @@ if (options.startActionServer) {
82
78
  server = new ActionServer(loader)
83
79
  server.listen(options.port)
84
80
  } else {
85
- server = createServer({
86
- 'grpc.max_send_message_length': 768 * 1024 * 1024,
87
- 'grpc.max_receive_message_length': 768 * 1024 * 1024
88
- // 'grpc.default_compression_algorithm': compressionAlgorithms.gzip
89
- })
90
- // .use(prometheusServerMiddleware())
91
- // .use(openTelemetryServerMiddleware())
92
- .use(errorDetailsServerMiddleware)
81
+ const shutdown = () => processorHttp2Server?.close()
93
82
 
94
- // for V2
95
- baseService = new ProcessorServiceImpl(loader, options, server.shutdown)
96
- const service = new FullProcessorServiceImpl(baseService)
83
+ const serviceV3 = new FullProcessorServiceV3Impl(new ProcessorServiceImplV3(loader, options, shutdown))
97
84
 
98
- server.add(ProcessorDefinition, service)
85
+ const routes = (router: ConnectRouter) => {
86
+ router.service(ProcessorV3, serviceV3)
87
+ }
99
88
 
100
- // for V3
101
- server.add(
102
- ProcessorV3Definition,
103
- new FullProcessorServiceV3Impl(new ProcessorServiceImplV3(loader, options, server.shutdown))
104
- )
89
+ const adapter = connectNodeAdapter({
90
+ routes,
91
+ readMaxBytes: 768 * 1024 * 1024,
92
+ writeMaxBytes: 768 * 1024 * 1024
93
+ })
105
94
 
106
- server.listen('0.0.0.0:' + options.port)
95
+ // h2c (cleartext HTTP/2) gRPC server — connect-node serves grpc + grpc-web + connect
96
+ // on the same port, so the Go launcher's gRPC client is unaffected.
97
+ processorHttp2Server = http2.createServer(adapter)
98
+ processorHttp2Server.listen(Number(options.port))
107
99
  console.log('Processor Server Started at:', options.port)
108
100
  }
109
101
 
@@ -208,9 +200,6 @@ process
208
200
  })
209
201
  .on('uncaughtException', (err) => {
210
202
  console.error('Uncaught Exception, please checking if await is properly used', err)
211
- if (baseService) {
212
- baseService.unhandled = err
213
- }
214
203
  // shutdownServers(1)
215
204
  })
216
205
  .on('unhandledRejection', (reason, _p) => {
@@ -219,9 +208,6 @@ process
219
208
  return
220
209
  }
221
210
  console.error('Unhandled Rejection, please checking if await is properly', reason)
222
- if (baseService) {
223
- baseService.unhandled = reason as Error
224
- }
225
211
  // shutdownServers(1)
226
212
  })
227
213
 
@@ -245,7 +231,11 @@ async function dumpHeap(file: string): Promise<void> {
245
231
  }
246
232
 
247
233
  function shutdownServers(exitCode: number): void {
248
- server.forceShutdown()
234
+ if (processorHttp2Server) {
235
+ processorHttp2Server.close()
236
+ } else if (server?.forceShutdown) {
237
+ server.forceShutdown()
238
+ }
249
239
  console.log('RPC server shut down')
250
240
 
251
241
  if (httpServer) {
package/src/service-v3.ts CHANGED
@@ -1,38 +1,37 @@
1
1
  import {
2
- DataBinding,
3
- DeepPartial,
4
- Empty,
2
+ type DataBinding,
3
+ EmptySchema,
5
4
  HandlerType,
6
- ProcessConfigRequest,
7
- ProcessConfigResponse,
8
- ProcessorV3ServiceImplementation,
9
- ProcessResult,
10
- ProcessStreamRequest,
11
- ProcessStreamResponse,
12
- ProcessStreamResponseV3,
13
- StartRequest,
14
- UpdateTemplatesRequest
5
+ type ProcessConfigRequest,
6
+ ProcessConfigResponseSchema,
7
+ ProcessorV3,
8
+ ProcessResultSchema,
9
+ type ProcessStreamRequest,
10
+ ProcessStreamResponseV3Schema,
11
+ type StartRequest,
12
+ type UpdateTemplatesRequest
15
13
  } from '@sentio/protos'
16
- import { CallContext, ServerError, Status } from 'nice-grpc'
17
- import { AsyncIterable } from 'ix'
14
+ import { clone, create, type MessageInitShape } from '@bufbuild/protobuf'
15
+ import { ConnectError, Code, type HandlerContext, type ServiceImpl } from '@connectrpc/connect'
18
16
  import { PluginManager } from './plugin.js'
19
17
  import { Subject } from 'rxjs'
20
18
  import { from } from 'ix/asynciterable'
21
19
  import { withAbort } from 'ix/asynciterable/operators'
22
- import { errorString } from './utils.js'
20
+ import { errorString, recordRuntimeInfo } from './utils.js'
23
21
 
24
22
  import { processMetrics } from './metrics.js'
25
- import { recordRuntimeInfo } from './service.js'
26
23
  import { DataBindingContext } from './db-context.js'
27
24
  import { freezeGlobalConfig } from './global-config.js'
28
25
  import { ProcessorRuntimeOptions } from './processor-runner-program.js'
29
26
 
27
+ type ProcessStreamResponseV3Init = MessageInitShape<typeof ProcessStreamResponseV3Schema>
28
+
30
29
  const { process_binding_count, process_binding_time, process_binding_error } = processMetrics
31
30
 
32
31
  const WRITE_V2_EVENT_LOGS = process.env.WRITE_V2_EVENT_LOGS !== 'false'
33
32
  const TIME_SERIES_RESULT_BATCH_SIZE = 1000
34
33
 
35
- export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation {
34
+ export class ProcessorServiceImplV3 implements ServiceImpl<typeof ProcessorV3> {
36
35
  readonly enablePartition: boolean
37
36
  private readonly loader: () => Promise<any>
38
37
  private readonly shutdownHandler?: () => void
@@ -45,9 +44,9 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
45
44
  this.enablePartition = options?.enablePartition == true
46
45
  }
47
46
 
48
- async start(request: StartRequest, context: CallContext): Promise<Empty> {
47
+ async start(request: StartRequest, context: HandlerContext) {
49
48
  if (this.started) {
50
- return {}
49
+ return create(EmptySchema)
51
50
  }
52
51
 
53
52
  freezeGlobalConfig()
@@ -55,27 +54,27 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
55
54
  try {
56
55
  await this.loader()
57
56
  } catch (e) {
58
- throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
57
+ throw new ConnectError('Failed to load processor: ' + errorString(e), Code.InvalidArgument)
59
58
  }
60
59
 
61
60
  await PluginManager.INSTANCE.start(request)
62
61
 
63
62
  this.started = true
64
- return {}
63
+ return create(EmptySchema)
65
64
  }
66
65
 
67
- async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
66
+ async getConfig(request: ProcessConfigRequest, context: HandlerContext) {
68
67
  if (!this.started) {
69
- throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
68
+ throw new ConnectError('Service Not started.', Code.Unavailable)
70
69
  }
71
70
 
72
- const newConfig = ProcessConfigResponse.fromPartial({})
71
+ const newConfig = create(ProcessConfigResponseSchema, {})
73
72
  await PluginManager.INSTANCE.configure(newConfig)
74
73
  return newConfig
75
74
  }
76
75
 
77
- async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: CallContext) {
78
- const subject = new Subject<DeepPartial<ProcessStreamResponseV3>>()
76
+ async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: HandlerContext) {
77
+ const subject = new Subject<ProcessStreamResponseV3Init>()
79
78
  this.handleRequests(requests, subject)
80
79
  .then(() => {
81
80
  subject.complete()
@@ -89,14 +88,14 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
89
88
 
90
89
  protected async handleRequests(
91
90
  requests: AsyncIterable<ProcessStreamRequest>,
92
- subject: Subject<DeepPartial<ProcessStreamResponse>>
91
+ subject: Subject<ProcessStreamResponseV3Init>
93
92
  ) {
94
93
  let lastBinding: DataBinding | undefined = undefined
95
94
  for await (const request of requests) {
96
95
  try {
97
96
  // console.log('received request:', request, 'lastBinding:', lastBinding)
98
- if (request.binding) {
99
- lastBinding = request.binding
97
+ if (request.value.case === 'binding') {
98
+ lastBinding = request.value.value
100
99
  }
101
100
  this.handleRequest(request, lastBinding, subject)
102
101
  } catch (e) {
@@ -111,26 +110,27 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
111
110
  async handleRequest(
112
111
  request: ProcessStreamRequest,
113
112
  lastBinding: DataBinding | undefined,
114
- subject: Subject<DeepPartial<ProcessStreamResponseV3>>
113
+ subject: Subject<ProcessStreamResponseV3Init>
115
114
  ) {
116
- if (request.binding) {
115
+ const binding = request.value.case === 'binding' ? request.value.value : undefined
116
+ if (binding) {
117
117
  process_binding_count.add(1)
118
118
 
119
- if (request.binding.handlerType === HandlerType.UNKNOWN) {
119
+ if (binding.handlerType === HandlerType.UNKNOWN) {
120
120
  subject.next({
121
121
  processId: request.processId,
122
- result: ProcessResult.create()
122
+ value: { case: 'result', value: create(ProcessResultSchema) }
123
123
  })
124
124
  return
125
125
  }
126
126
 
127
127
  if (this.enablePartition) {
128
128
  try {
129
- console.debug('sending partition request', request.binding)
130
- const partitions = await PluginManager.INSTANCE.partition(request.binding)
129
+ console.debug('sending partition request', binding)
130
+ const partitions = await PluginManager.INSTANCE.partition(binding)
131
131
  subject.next({
132
132
  processId: request.processId,
133
- partitions
133
+ value: { case: 'partitions', value: partitions }
134
134
  })
135
135
  } catch (e) {
136
136
  console.error('Partition error:', e)
@@ -138,11 +138,11 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
138
138
  return
139
139
  }
140
140
  } else {
141
- this.startProcess(request.processId, request.binding, subject)
141
+ this.startProcess(request.processId, binding, subject)
142
142
  }
143
143
  }
144
144
 
145
- if (request.start) {
145
+ if (request.value.case === 'start') {
146
146
  if (!lastBinding) {
147
147
  console.error('start request received without binding')
148
148
  subject.error(new Error('start request received without binding'))
@@ -151,46 +151,44 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
151
151
  this.startProcess(request.processId, lastBinding, subject)
152
152
  }
153
153
 
154
- if (request.dbResult) {
154
+ if (request.value.case === 'dbResult') {
155
155
  const context = this.contexts.get(request.processId)
156
156
  try {
157
- context?.result(request.dbResult)
157
+ context?.result(request.value.value)
158
158
  } catch (e) {
159
159
  subject.error(new Error('db result error, process should stop'))
160
160
  }
161
161
  }
162
162
  }
163
163
 
164
- private startProcess(
165
- processId: number,
166
- binding: DataBinding,
167
- subject: Subject<DeepPartial<ProcessStreamResponseV3>>
168
- ) {
164
+ private startProcess(processId: number, binding: DataBinding, subject: Subject<ProcessStreamResponseV3Init>) {
169
165
  const context = this.contexts.new(processId, subject)
170
166
  const start = Date.now()
171
167
  PluginManager.INSTANCE.processBinding(binding, undefined, context)
172
168
  .then(async (result) => {
173
169
  await context.awaitPendings()
174
- const { timeseriesResult, ...otherResults } = result
170
+ recordRuntimeInfo(result, binding.handlerType)
171
+
172
+ const timeseriesResult = result.timeseriesResult
175
173
  for (let i = 0; i < timeseriesResult.length; i += TIME_SERIES_RESULT_BATCH_SIZE) {
176
174
  const batch = timeseriesResult.slice(i, i + TIME_SERIES_RESULT_BATCH_SIZE)
177
175
  subject.next({
178
176
  processId,
179
- tsRequest: {
180
- data: batch
181
- }
177
+ value: { case: 'tsRequest', value: { data: batch } }
182
178
  })
183
179
  }
184
180
 
181
+ // Send everything except the (already-batched) timeseries result back.
182
+ const otherResults = clone(ProcessResultSchema, result)
183
+ otherResults.timeseriesResult = []
184
+
185
185
  subject.next({
186
- result: WRITE_V2_EVENT_LOGS
187
- ? otherResults
188
- : {
189
- states: otherResults.states
190
- },
191
- processId: processId
186
+ processId,
187
+ value: {
188
+ case: 'result',
189
+ value: WRITE_V2_EVENT_LOGS ? otherResults : create(ProcessResultSchema, { states: result.states })
190
+ }
192
191
  })
193
- recordRuntimeInfo(result, binding.handlerType)
194
192
  })
195
193
  .catch((e) => {
196
194
  console.error(e, e.stack)
@@ -204,9 +202,9 @@ export class ProcessorServiceImplV3 implements ProcessorV3ServiceImplementation
204
202
  })
205
203
  }
206
204
 
207
- async updateTemplates(request: UpdateTemplatesRequest, context: CallContext): Promise<DeepPartial<Empty>> {
205
+ async updateTemplates(request: UpdateTemplatesRequest, context: HandlerContext) {
208
206
  await PluginManager.INSTANCE.updateTemplates(request)
209
- return {}
207
+ return create(EmptySchema)
210
208
  }
211
209
  }
212
210
 
@@ -217,7 +215,7 @@ class Contexts {
217
215
  return this.contexts.get(processId)
218
216
  }
219
217
 
220
- new(processId: number, subject: Subject<DeepPartial<ProcessStreamResponseV3>>) {
218
+ new(processId: number, subject: Subject<ProcessStreamResponseV3Init>) {
221
219
  const context = new DataBindingContext(processId, subject)
222
220
  this.contexts.set(processId, context)
223
221
  return context
package/src/utils.ts CHANGED
@@ -1,18 +1,25 @@
1
- import { EthCallParam, ProcessResult } from '@sentio/protos'
1
+ import {
2
+ type EthCallParam,
3
+ HandlerType,
4
+ type ProcessResult,
5
+ ProcessResultSchema,
6
+ RuntimeInfoSchema,
7
+ StateResultSchema
8
+ } from '@sentio/protos'
9
+ import { create } from '@bufbuild/protobuf'
2
10
  import { createRequire } from 'module'
3
11
 
4
- // TODO better handling this, because old proto doesn't have this
5
- import { StateResult, ProcessResult as ProcessResultFull } from './gen/processor/protos/processor.js'
6
-
7
12
  import { Required } from 'utility-types'
8
13
  import path from 'path'
9
14
  import fs from 'fs'
15
+ ;(BigInt.prototype as any).toJSON = function () {
16
+ return this.toString()
17
+ }
10
18
 
11
19
  export function mergeProcessResults(results: ProcessResult[]): Required<ProcessResult, 'states'> {
12
- const res = {
13
- ...ProcessResultFull.create(),
14
- states: StateResult.create()
15
- }
20
+ const res = create(ProcessResultSchema, {
21
+ states: create(StateResultSchema)
22
+ })
16
23
  return mergeProcessResultsInPlace(res, results)
17
24
  }
18
25
 
@@ -20,7 +27,7 @@ export function mergeProcessResultsInPlace(
20
27
  res: ProcessResult,
21
28
  results: ProcessResult[]
22
29
  ): Required<ProcessResult, 'states'> {
23
- res.states = res.states || StateResult.create()
30
+ res.states = res.states || create(StateResultSchema)
24
31
  for (const r of results) {
25
32
  // not using spread operator since it puts all element on the stack
26
33
  // cause maximum call stack size exceeded error if it's a large array
@@ -30,11 +37,21 @@ export function mergeProcessResultsInPlace(
30
37
  res.events = mergeArrayInPlace(res.events, r.events)
31
38
  res.exports = mergeArrayInPlace(res.exports, r.exports)
32
39
  res.timeseriesResult = mergeArrayInPlace(res.timeseriesResult, r.timeseriesResult)
33
- res.states = {
40
+ res.states = create(StateResultSchema, {
34
41
  configUpdated: res.states?.configUpdated || r.states?.configUpdated || false
35
- }
42
+ })
43
+ }
44
+ return res as Required<ProcessResult, 'states'>
45
+ }
46
+
47
+ export function recordRuntimeInfo(results: ProcessResult, handlerType: HandlerType) {
48
+ for (const list of [results.gauges, results.counters, results.events, results.exports]) {
49
+ list.forEach((e) => {
50
+ e.runtimeInfo = create(RuntimeInfoSchema, {
51
+ from: handlerType
52
+ })
53
+ })
36
54
  }
37
- return res
38
55
  }
39
56
 
40
57
  function mergeArrayInPlace<T>(dst: T[], src: T[]): T[] {