@sentio/runtime 1.37.0-rc.6 → 1.37.0-rc.7
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/lib/chain-config.d.ts +6 -0
- package/lib/chain-config.js +3 -0
- package/lib/chain-config.js.map +1 -0
- package/lib/endpoints.d.ts +8 -0
- package/lib/endpoints.js +15 -0
- package/lib/endpoints.js.map +1 -0
- package/lib/full-service.d.ts +276 -0
- package/lib/full-service.js +47 -0
- package/lib/full-service.js.map +1 -0
- package/lib/gen/{src/google → google}/protobuf/timestamp.d.ts +1 -1
- package/lib/gen/{src/google → google}/protobuf/timestamp.js +4 -4
- package/lib/gen/google/protobuf/timestamp.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +24 -0
- package/lib/index.js.map +1 -0
- package/lib/loader.d.ts +5 -0
- package/lib/loader.js +30 -0
- package/lib/loader.js.map +1 -0
- package/lib/plugin.d.ts +19 -0
- package/lib/plugin.js +50 -0
- package/lib/plugin.js.map +1 -0
- package/lib/processor-runner.d.ts +2 -0
- package/lib/processor-runner.js +104 -0
- package/lib/processor-runner.js.map +1 -0
- package/lib/provider.d.ts +6 -0
- package/lib/provider.js +67 -0
- package/lib/provider.js.map +1 -0
- package/lib/service.d.ts +20 -0
- package/lib/service.js +120 -0
- package/lib/service.js.map +1 -0
- package/lib/service.test.d.ts +2 -0
- package/lib/service.test.js.map +1 -0
- package/lib/state-storage.test.d.ts +1 -0
- package/lib/state-storage.test.js.map +1 -0
- package/lib/state.d.ts +22 -0
- package/lib/state.js +68 -0
- package/lib/state.js.map +1 -0
- package/lib/utils.d.ts +4 -0
- package/lib/utils.js +22 -0
- package/lib/utils.js.map +1 -0
- package/package.json +4 -5
- package/src/chain-config.ts +6 -0
- package/src/endpoints.ts +14 -0
- package/src/full-service.ts +61 -0
- package/src/gen/{src/google → google}/protobuf/timestamp.ts +45 -40
- package/src/index.ts +7 -0
- package/src/loader.ts +24 -0
- package/src/plugin.ts +53 -0
- package/src/processor-runner.ts +97 -0
- package/src/provider.ts +75 -0
- package/src/service.ts +145 -0
- package/src/state.ts +75 -0
- package/src/utils.ts +20 -0
- package/lib/gen/src/google/protobuf/timestamp.js.map +0 -1
@@ -0,0 +1,61 @@
|
|
1
|
+
import { CallContext } from 'nice-grpc'
|
2
|
+
|
3
|
+
// Different than the simple one which
|
4
|
+
import {
|
5
|
+
DataBinding,
|
6
|
+
HandlerType,
|
7
|
+
ProcessBindingsRequest,
|
8
|
+
ProcessConfigRequest,
|
9
|
+
ProcessorServiceImplementation,
|
10
|
+
StartRequest,
|
11
|
+
} from './gen/processor/protos/processor'
|
12
|
+
|
13
|
+
import { Empty } from '@sentio/protos/lib/google/protobuf/empty'
|
14
|
+
|
15
|
+
export class FullProcessorServiceImpl implements ProcessorServiceImplementation {
|
16
|
+
constructor(instance: ProcessorServiceImplementation) {
|
17
|
+
this.instance = instance
|
18
|
+
}
|
19
|
+
|
20
|
+
instance: ProcessorServiceImplementation
|
21
|
+
|
22
|
+
async getConfig(request: ProcessConfigRequest, context: CallContext) {
|
23
|
+
return this.instance.getConfig(request, context)
|
24
|
+
}
|
25
|
+
|
26
|
+
async start(request: StartRequest, context: CallContext) {
|
27
|
+
return this.instance.start(request, context)
|
28
|
+
}
|
29
|
+
|
30
|
+
async stop(request: Empty, context: CallContext) {
|
31
|
+
return this.instance.stop(request, context)
|
32
|
+
}
|
33
|
+
|
34
|
+
async processBindings(request: ProcessBindingsRequest, options: CallContext) {
|
35
|
+
for (const binding of request.bindings) {
|
36
|
+
this.adjustDataBinding(binding)
|
37
|
+
}
|
38
|
+
return this.instance.processBindings(request, options)
|
39
|
+
}
|
40
|
+
|
41
|
+
async *processBindingsStream(requests: AsyncIterable<DataBinding>, context: CallContext) {
|
42
|
+
throw new Error('Not Implemented for streaming')
|
43
|
+
// y this.instance.processBindingsStream(requests, context)
|
44
|
+
}
|
45
|
+
|
46
|
+
protected adjustDataBinding(dataBinding: DataBinding): void {
|
47
|
+
switch (dataBinding.handlerType) {
|
48
|
+
case HandlerType.UNKNOWN:
|
49
|
+
if (dataBinding.data?.ethBlock) {
|
50
|
+
if (dataBinding.data.raw.length === 0) {
|
51
|
+
// This is actually not needed in current system, just as initla test propose, move to test only
|
52
|
+
// when this is stable
|
53
|
+
dataBinding.data.raw = new TextEncoder().encode(JSON.stringify(dataBinding.data.ethBlock.block))
|
54
|
+
}
|
55
|
+
}
|
56
|
+
break
|
57
|
+
default:
|
58
|
+
break
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
@@ -1,86 +1,91 @@
|
|
1
1
|
/* eslint-disable */
|
2
|
-
import Long from
|
3
|
-
import _m0 from
|
2
|
+
import Long from 'long'
|
3
|
+
import _m0 from 'protobufjs/minimal'
|
4
4
|
|
5
5
|
export interface Timestamp {
|
6
|
-
seconds: bigint
|
7
|
-
nanos: number
|
6
|
+
seconds: bigint
|
7
|
+
nanos: number
|
8
8
|
}
|
9
9
|
|
10
10
|
function createBaseTimestamp(): Timestamp {
|
11
|
-
return { seconds: BigInt(
|
11
|
+
return { seconds: BigInt('0'), nanos: 0 }
|
12
12
|
}
|
13
13
|
|
14
14
|
export const Timestamp = {
|
15
15
|
encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
16
|
-
if (message.seconds !== BigInt(
|
17
|
-
writer.uint32(8).int64(message.seconds.toString())
|
16
|
+
if (message.seconds !== BigInt('0')) {
|
17
|
+
writer.uint32(8).int64(message.seconds.toString())
|
18
18
|
}
|
19
19
|
if (message.nanos !== 0) {
|
20
|
-
writer.uint32(16).int32(message.nanos)
|
20
|
+
writer.uint32(16).int32(message.nanos)
|
21
21
|
}
|
22
|
-
return writer
|
22
|
+
return writer
|
23
23
|
},
|
24
24
|
|
25
25
|
decode(input: _m0.Reader | Uint8Array, length?: number): Timestamp {
|
26
|
-
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input)
|
27
|
-
let end = length === undefined ? reader.len : reader.pos + length
|
28
|
-
const message = createBaseTimestamp()
|
26
|
+
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input)
|
27
|
+
let end = length === undefined ? reader.len : reader.pos + length
|
28
|
+
const message = createBaseTimestamp()
|
29
29
|
while (reader.pos < end) {
|
30
|
-
const tag = reader.uint32()
|
30
|
+
const tag = reader.uint32()
|
31
31
|
switch (tag >>> 3) {
|
32
32
|
case 1:
|
33
|
-
message.seconds = longToBigint(reader.int64() as Long)
|
34
|
-
break
|
33
|
+
message.seconds = longToBigint(reader.int64() as Long)
|
34
|
+
break
|
35
35
|
case 2:
|
36
|
-
message.nanos = reader.int32()
|
37
|
-
break
|
36
|
+
message.nanos = reader.int32()
|
37
|
+
break
|
38
38
|
default:
|
39
|
-
reader.skipType(tag & 7)
|
40
|
-
break
|
39
|
+
reader.skipType(tag & 7)
|
40
|
+
break
|
41
41
|
}
|
42
42
|
}
|
43
|
-
return message
|
43
|
+
return message
|
44
44
|
},
|
45
45
|
|
46
46
|
fromJSON(object: any): Timestamp {
|
47
47
|
return {
|
48
|
-
seconds: isSet(object.seconds) ? BigInt(object.seconds) : BigInt(
|
48
|
+
seconds: isSet(object.seconds) ? BigInt(object.seconds) : BigInt('0'),
|
49
49
|
nanos: isSet(object.nanos) ? Number(object.nanos) : 0,
|
50
|
-
}
|
50
|
+
}
|
51
51
|
},
|
52
52
|
|
53
53
|
toJSON(message: Timestamp): unknown {
|
54
|
-
const obj: any = {}
|
55
|
-
message.seconds !== undefined && (obj.seconds = message.seconds.toString())
|
56
|
-
message.nanos !== undefined && (obj.nanos = Math.round(message.nanos))
|
57
|
-
return obj
|
54
|
+
const obj: any = {}
|
55
|
+
message.seconds !== undefined && (obj.seconds = message.seconds.toString())
|
56
|
+
message.nanos !== undefined && (obj.nanos = Math.round(message.nanos))
|
57
|
+
return obj
|
58
58
|
},
|
59
59
|
|
60
60
|
fromPartial(object: DeepPartial<Timestamp>): Timestamp {
|
61
|
-
const message = createBaseTimestamp()
|
62
|
-
message.seconds = object.seconds ?? BigInt(
|
63
|
-
message.nanos = object.nanos ?? 0
|
64
|
-
return message
|
61
|
+
const message = createBaseTimestamp()
|
62
|
+
message.seconds = object.seconds ?? BigInt('0')
|
63
|
+
message.nanos = object.nanos ?? 0
|
64
|
+
return message
|
65
65
|
},
|
66
|
-
}
|
66
|
+
}
|
67
67
|
|
68
|
-
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined
|
68
|
+
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined
|
69
69
|
|
70
|
-
type DeepPartial<T> = T extends Builtin
|
71
|
-
|
72
|
-
: T extends
|
73
|
-
|
70
|
+
type DeepPartial<T> = T extends Builtin
|
71
|
+
? T
|
72
|
+
: T extends Array<infer U>
|
73
|
+
? Array<DeepPartial<U>>
|
74
|
+
: T extends ReadonlyArray<infer U>
|
75
|
+
? ReadonlyArray<DeepPartial<U>>
|
76
|
+
: T extends {}
|
77
|
+
? { [K in keyof T]?: DeepPartial<T[K]> }
|
78
|
+
: Partial<T>
|
74
79
|
|
75
80
|
function longToBigint(long: Long) {
|
76
|
-
return BigInt(long.toString())
|
81
|
+
return BigInt(long.toString())
|
77
82
|
}
|
78
83
|
|
79
84
|
if (_m0.util.Long !== Long) {
|
80
|
-
_m0.util.Long = Long as any
|
81
|
-
_m0.configure()
|
85
|
+
_m0.util.Long = Long as any
|
86
|
+
_m0.configure()
|
82
87
|
}
|
83
88
|
|
84
89
|
function isSet(value: any): boolean {
|
85
|
-
return value !== null && value !== undefined
|
90
|
+
return value !== null && value !== undefined
|
86
91
|
}
|
package/src/index.ts
ADDED
package/src/loader.ts
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
export function load(name: string): { module: any; name: string; path: string } | undefined {
|
2
|
+
const req = eval('require')
|
3
|
+
|
4
|
+
try {
|
5
|
+
let path: string
|
6
|
+
try {
|
7
|
+
path = req.resolve(name, { paths: [process.cwd()] })
|
8
|
+
} catch {
|
9
|
+
path = req.resolve(name)
|
10
|
+
}
|
11
|
+
|
12
|
+
const module = { module: req(path), name, path }
|
13
|
+
console.log('Processor Load successfully')
|
14
|
+
return module
|
15
|
+
} catch (err) {
|
16
|
+
if (err instanceof Error && err.message.startsWith(`Cannot find module '${name}'`)) {
|
17
|
+
// this error is expected
|
18
|
+
console.log("Couldn't load (expected): ", name)
|
19
|
+
return undefined
|
20
|
+
} else {
|
21
|
+
throw err
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
package/src/plugin.ts
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
import { DataBinding, HandlerType, ProcessConfigResponse, ProcessResult, StartRequest } from '@sentio/protos'
|
2
|
+
|
3
|
+
export abstract class Plugin {
|
4
|
+
name: string
|
5
|
+
supportedHandlers: HandlerType[] = []
|
6
|
+
|
7
|
+
configure(config: ProcessConfigResponse) {}
|
8
|
+
start(start: StartRequest) {}
|
9
|
+
stateDiff(config: ProcessConfigResponse): boolean {
|
10
|
+
return false
|
11
|
+
}
|
12
|
+
async processBinding(request: DataBinding): Promise<ProcessResult> {
|
13
|
+
return ProcessResult.fromPartial({})
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
export class PluginManager {
|
18
|
+
static INSTANCE = new PluginManager()
|
19
|
+
|
20
|
+
plugins: Plugin[] = []
|
21
|
+
typesToPlugin = new Map<HandlerType, Plugin>()
|
22
|
+
|
23
|
+
register(plugin: Plugin) {
|
24
|
+
this.plugins.push(plugin)
|
25
|
+
for (const handlerType of plugin.supportedHandlers) {
|
26
|
+
const exsited = this.typesToPlugin.get(handlerType)
|
27
|
+
if (exsited) {
|
28
|
+
throw new Error(`Duplicate plugin for ${handlerType}: ${exsited.name} and ${plugin.name}`)
|
29
|
+
}
|
30
|
+
this.typesToPlugin.set(handlerType, plugin)
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
configure(config: ProcessConfigResponse) {
|
35
|
+
this.plugins.forEach((plugin) => plugin.configure(config))
|
36
|
+
}
|
37
|
+
|
38
|
+
start(start: StartRequest) {
|
39
|
+
this.plugins.forEach((plugin) => plugin.start(start))
|
40
|
+
}
|
41
|
+
|
42
|
+
stateDiff(config: ProcessConfigResponse): boolean {
|
43
|
+
return this.plugins.some((plugin) => plugin.stateDiff(config))
|
44
|
+
}
|
45
|
+
|
46
|
+
processBinding(request: DataBinding): Promise<ProcessResult> {
|
47
|
+
const plugin = this.typesToPlugin.get(request.handlerType)
|
48
|
+
if (!plugin) {
|
49
|
+
throw new Error(`No plugin for ${request.handlerType}`)
|
50
|
+
}
|
51
|
+
return plugin.processBinding(request)
|
52
|
+
}
|
53
|
+
}
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
import path from 'path'
|
4
|
+
import fs from 'fs-extra'
|
5
|
+
import * as util from 'util'
|
6
|
+
|
7
|
+
import commandLineArgs from 'command-line-args'
|
8
|
+
import { createServer } from 'nice-grpc'
|
9
|
+
import { createLogger, transports, format } from 'winston'
|
10
|
+
import { CompressionAlgorithms } from '@grpc/grpc-js/build/src/compression-algorithms'
|
11
|
+
|
12
|
+
import { ProcessorDefinition } from '@sentio/protos'
|
13
|
+
import { ProcessorServiceImpl } from './service'
|
14
|
+
import { setProvider } from './provider'
|
15
|
+
import { State } from './state'
|
16
|
+
import { Endpoints } from './endpoints'
|
17
|
+
|
18
|
+
import { load } from './loader'
|
19
|
+
import { FullProcessorServiceImpl } from './full-service'
|
20
|
+
|
21
|
+
State.reset()
|
22
|
+
Endpoints.reset()
|
23
|
+
|
24
|
+
const optionDefinitions = [
|
25
|
+
{ name: 'target', type: String, defaultOption: true },
|
26
|
+
{ name: 'port', alias: 'p', type: String, defaultValue: '4000' },
|
27
|
+
{ name: 'concurrency', type: Number, defaultValue: 4 },
|
28
|
+
{ name: 'use-chainserver', type: Boolean, defaultValue: false },
|
29
|
+
{
|
30
|
+
name: 'chains-config',
|
31
|
+
alias: 'c',
|
32
|
+
type: String,
|
33
|
+
defaultValue: 'chains-config.json',
|
34
|
+
},
|
35
|
+
{ name: 'chainquery-server', type: String, defaultValue: '' },
|
36
|
+
{ name: 'pricefeed-server', type: String, defaultValue: '' },
|
37
|
+
{ name: 'log-format', type: String, defaultValue: 'console' },
|
38
|
+
{ name: 'debug', type: Boolean, defaultValue: false },
|
39
|
+
]
|
40
|
+
|
41
|
+
const options = commandLineArgs(optionDefinitions, { partial: true })
|
42
|
+
|
43
|
+
if (options['log-format'] === 'json') {
|
44
|
+
const utilFormatter = {
|
45
|
+
transform: (info: any) => {
|
46
|
+
const args = info[Symbol.for('splat')]
|
47
|
+
if (args) {
|
48
|
+
info.message = util.format(info.message, ...args)
|
49
|
+
}
|
50
|
+
return info
|
51
|
+
},
|
52
|
+
}
|
53
|
+
const logger = createLogger({
|
54
|
+
format: format.combine(
|
55
|
+
format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ssZ' }),
|
56
|
+
utilFormatter,
|
57
|
+
format.errors({ stack: true }),
|
58
|
+
format.json()
|
59
|
+
),
|
60
|
+
transports: [new transports.Console()],
|
61
|
+
})
|
62
|
+
|
63
|
+
console.log = (...args) => logger.info.call(logger, ...args)
|
64
|
+
console.info = (...args) => logger.info.call(logger, ...args)
|
65
|
+
console.warn = (...args) => logger.warn.call(logger, ...args)
|
66
|
+
console.error = (...args) => logger.error.call(logger, ...args)
|
67
|
+
console.debug = (...args) => logger.debug.call(logger, ...args)
|
68
|
+
}
|
69
|
+
if (options.debug) {
|
70
|
+
console.log('Starting with', options.target)
|
71
|
+
}
|
72
|
+
|
73
|
+
const fullPath = path.resolve(options['chains-config'])
|
74
|
+
const chainsConfig = fs.readJsonSync(fullPath)
|
75
|
+
|
76
|
+
setProvider(chainsConfig, options.concurrency, options['use-chainserver'])
|
77
|
+
Endpoints.INSTANCE.chainQueryAPI = options['chainquery-server']
|
78
|
+
Endpoints.INSTANCE.priceFeedAPI = options['pricefeed-server']
|
79
|
+
|
80
|
+
if (options.debug) {
|
81
|
+
console.log('Starting Server', options)
|
82
|
+
}
|
83
|
+
|
84
|
+
const server = createServer({
|
85
|
+
'grpc.max_send_message_length': 128 * 1024 * 1024,
|
86
|
+
'grpc.max_receive_message_length': 128 * 1024 * 1024,
|
87
|
+
'grpc.default_compression_algorithm': CompressionAlgorithms.gzip,
|
88
|
+
})
|
89
|
+
|
90
|
+
const baseService = new ProcessorServiceImpl(() => load(options.target), server.shutdown)
|
91
|
+
const service = new FullProcessorServiceImpl(baseService)
|
92
|
+
|
93
|
+
server.add(ProcessorDefinition, service)
|
94
|
+
|
95
|
+
server.listen('0.0.0.0:' + options.port)
|
96
|
+
|
97
|
+
console.log('Processor Server Started')
|
package/src/provider.ts
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
import { getNetwork, Provider, StaticJsonRpcProvider } from '@ethersproject/providers'
|
2
|
+
import { Networkish } from '@ethersproject/networks'
|
3
|
+
import PQueue from 'p-queue'
|
4
|
+
import { ConnectionInfo } from '@ethersproject/web'
|
5
|
+
import { ChainConfig } from './chain-config'
|
6
|
+
import { Endpoints } from './endpoints'
|
7
|
+
|
8
|
+
export const DummyProvider = new StaticJsonRpcProvider(undefined, 1)
|
9
|
+
|
10
|
+
export function getProvider(networkish?: Networkish): Provider {
|
11
|
+
if (!networkish) {
|
12
|
+
networkish = 1
|
13
|
+
}
|
14
|
+
const network = getNetwork(networkish)
|
15
|
+
|
16
|
+
if (!Endpoints.INSTANCE.providers) {
|
17
|
+
throw Error('Provider not set')
|
18
|
+
}
|
19
|
+
const value = Endpoints.INSTANCE.providers.get(network.chainId)
|
20
|
+
if (value === undefined) {
|
21
|
+
throw Error(
|
22
|
+
'Provider not found for chain ' +
|
23
|
+
network.chainId +
|
24
|
+
', configured chains: ' +
|
25
|
+
[...Endpoints.INSTANCE.providers.keys()].join(' ')
|
26
|
+
)
|
27
|
+
}
|
28
|
+
return value
|
29
|
+
}
|
30
|
+
|
31
|
+
export function setProvider(config: Record<string, ChainConfig>, concurrency = 4, useChainServer = false) {
|
32
|
+
Endpoints.INSTANCE.providers = new Map<number, Provider>()
|
33
|
+
|
34
|
+
for (const chainIdStr in config) {
|
35
|
+
if (isNaN(Number.parseInt(chainIdStr))) {
|
36
|
+
continue
|
37
|
+
}
|
38
|
+
|
39
|
+
const chainConfig = config[chainIdStr]
|
40
|
+
const chainId = Number(chainIdStr)
|
41
|
+
|
42
|
+
// let providers: StaticJsonRpcProvider[] = []
|
43
|
+
// for (const http of chainConfig.Https) {
|
44
|
+
// providers.push(new StaticJsonRpcProvider(http, chainId))
|
45
|
+
// }
|
46
|
+
// random shuffle
|
47
|
+
// providers = providers.sort(() => Math.random() - 0.5)
|
48
|
+
|
49
|
+
// const provider = new FallbackProvider(providers)
|
50
|
+
|
51
|
+
let rpcAddress = ''
|
52
|
+
if (useChainServer && chainConfig.ChainServer) {
|
53
|
+
rpcAddress = chainConfig.ChainServer
|
54
|
+
} else {
|
55
|
+
const idx = Math.floor(Math.random() * chainConfig.Https.length)
|
56
|
+
rpcAddress = chainConfig.Https[idx]
|
57
|
+
}
|
58
|
+
|
59
|
+
const provider = new QueuedStaticJsonRpcProvider(rpcAddress, chainId, concurrency)
|
60
|
+
Endpoints.INSTANCE.providers.set(chainId, provider)
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
class QueuedStaticJsonRpcProvider extends StaticJsonRpcProvider {
|
65
|
+
executor: PQueue
|
66
|
+
|
67
|
+
constructor(url: ConnectionInfo | string, network: Networkish, concurrency: number) {
|
68
|
+
super(url, network)
|
69
|
+
this.executor = new PQueue({ concurrency: concurrency })
|
70
|
+
}
|
71
|
+
|
72
|
+
send(method: string, params: Array<any>): Promise<any> {
|
73
|
+
return this.executor.add(() => super.send(method, params))
|
74
|
+
}
|
75
|
+
}
|
package/src/service.ts
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
import { CallContext, ServerError, Status } from 'nice-grpc'
|
2
|
+
|
3
|
+
import {
|
4
|
+
DataBinding,
|
5
|
+
HandlerType,
|
6
|
+
ProcessBindingResponse,
|
7
|
+
ProcessBindingsRequest,
|
8
|
+
ProcessConfigRequest,
|
9
|
+
ProcessConfigResponse,
|
10
|
+
ProcessorServiceImplementation,
|
11
|
+
ProcessResult,
|
12
|
+
StartRequest,
|
13
|
+
} from '@sentio/protos'
|
14
|
+
|
15
|
+
import { Empty } from '@sentio/protos/lib/google/protobuf/empty'
|
16
|
+
|
17
|
+
import { PluginManager } from './plugin'
|
18
|
+
import { errorString, mergeProcessResults } from './utils'
|
19
|
+
;(BigInt.prototype as any).toJSON = function () {
|
20
|
+
return this.toString()
|
21
|
+
}
|
22
|
+
|
23
|
+
export class ProcessorServiceImpl implements ProcessorServiceImplementation {
|
24
|
+
private started = false
|
25
|
+
private processorConfig: ProcessConfigResponse
|
26
|
+
|
27
|
+
private readonly loader: () => void
|
28
|
+
|
29
|
+
private readonly shutdownHandler?: () => void
|
30
|
+
|
31
|
+
constructor(loader: () => void, shutdownHandler?: () => void) {
|
32
|
+
this.loader = loader
|
33
|
+
this.shutdownHandler = shutdownHandler
|
34
|
+
}
|
35
|
+
|
36
|
+
async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
|
37
|
+
if (!this.started) {
|
38
|
+
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
39
|
+
}
|
40
|
+
if (!this.processorConfig) {
|
41
|
+
throw new ServerError(Status.INTERNAL, 'Process config empty.')
|
42
|
+
}
|
43
|
+
return this.processorConfig
|
44
|
+
}
|
45
|
+
|
46
|
+
async configure() {
|
47
|
+
this.processorConfig = ProcessConfigResponse.fromPartial({})
|
48
|
+
PluginManager.INSTANCE.configure(this.processorConfig)
|
49
|
+
}
|
50
|
+
|
51
|
+
async start(request: StartRequest, context: CallContext): Promise<Empty> {
|
52
|
+
if (this.started) {
|
53
|
+
return {}
|
54
|
+
}
|
55
|
+
|
56
|
+
try {
|
57
|
+
for (const plugin of [
|
58
|
+
'@sentio/sdk/lib/core/core-plugin',
|
59
|
+
'@sentio/sdk/lib/core/eth-plugin',
|
60
|
+
'@sentio/sdk/lib/core/sui-plugin',
|
61
|
+
'@sentio/sdk-aptos/lib/aptos-plugin',
|
62
|
+
'@sentio/sdk-solana/lib/solana-plugin',
|
63
|
+
]) {
|
64
|
+
try {
|
65
|
+
require(plugin)
|
66
|
+
} catch (e) {
|
67
|
+
console.error('Failed to load plugin: ', plugin)
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
this.loader()
|
72
|
+
} catch (e) {
|
73
|
+
throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
|
74
|
+
}
|
75
|
+
|
76
|
+
PluginManager.INSTANCE.start(request)
|
77
|
+
|
78
|
+
try {
|
79
|
+
await this.configure()
|
80
|
+
} catch (e) {
|
81
|
+
throw new ServerError(Status.INTERNAL, 'Failed to start processor : ' + errorString(e))
|
82
|
+
}
|
83
|
+
this.started = true
|
84
|
+
return {}
|
85
|
+
}
|
86
|
+
|
87
|
+
async stop(request: Empty, context: CallContext): Promise<Empty> {
|
88
|
+
console.log('Server Shutting down in 5 seconds')
|
89
|
+
if (this.shutdownHandler) {
|
90
|
+
setTimeout(this.shutdownHandler, 5000)
|
91
|
+
}
|
92
|
+
return {}
|
93
|
+
}
|
94
|
+
|
95
|
+
async processBindings(request: ProcessBindingsRequest, options?: CallContext): Promise<ProcessBindingResponse> {
|
96
|
+
if (!this.started) {
|
97
|
+
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
98
|
+
}
|
99
|
+
|
100
|
+
const promises = request.bindings.map((binding) => this.processBinding(binding))
|
101
|
+
const result = mergeProcessResults(await Promise.all(promises))
|
102
|
+
|
103
|
+
let updated = false
|
104
|
+
if (PluginManager.INSTANCE.stateDiff(this.processorConfig)) {
|
105
|
+
await this.configure()
|
106
|
+
updated = true
|
107
|
+
}
|
108
|
+
|
109
|
+
return {
|
110
|
+
result,
|
111
|
+
configUpdated: updated,
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
async processBinding(request: DataBinding, options?: CallContext): Promise<ProcessResult> {
|
116
|
+
const result = await PluginManager.INSTANCE.processBinding(request)
|
117
|
+
recordRuntimeInfo(result, request.handlerType)
|
118
|
+
return result
|
119
|
+
}
|
120
|
+
|
121
|
+
async *processBindingsStream(requests: AsyncIterable<DataBinding>, context: CallContext) {
|
122
|
+
for await (const request of requests) {
|
123
|
+
const result = await this.processBinding(request)
|
124
|
+
let updated = false
|
125
|
+
if (PluginManager.INSTANCE.stateDiff(this.processorConfig)) {
|
126
|
+
await this.configure()
|
127
|
+
updated = true
|
128
|
+
}
|
129
|
+
yield {
|
130
|
+
result,
|
131
|
+
configUpdated: updated,
|
132
|
+
}
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
function recordRuntimeInfo(results: ProcessResult, handlerType: HandlerType) {
|
138
|
+
for (const list of [results.gauges, results.counters, results.logs, results.events, results.exports]) {
|
139
|
+
list.forEach((e) => {
|
140
|
+
e.runtimeInfo = {
|
141
|
+
from: handlerType,
|
142
|
+
}
|
143
|
+
})
|
144
|
+
}
|
145
|
+
}
|
package/src/state.ts
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
export class State {
|
2
|
+
stateMap = new Map<string, any>()
|
3
|
+
|
4
|
+
static INSTANCE = new State()
|
5
|
+
|
6
|
+
static reset() {
|
7
|
+
State.INSTANCE = new State()
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
export abstract class StateStorage<T> {
|
12
|
+
// TODO learn how to define single instance for all subclasses
|
13
|
+
|
14
|
+
protected constructor() {
|
15
|
+
//
|
16
|
+
}
|
17
|
+
|
18
|
+
abstract initValue(): T
|
19
|
+
|
20
|
+
key(): string {
|
21
|
+
return this.constructor.name
|
22
|
+
}
|
23
|
+
|
24
|
+
getOrRegister(): T {
|
25
|
+
let metricState: T = State.INSTANCE.stateMap.get(this.key())
|
26
|
+
if (!metricState) {
|
27
|
+
metricState = this.initValue()
|
28
|
+
State.INSTANCE.stateMap.set(this.key(), metricState)
|
29
|
+
}
|
30
|
+
return metricState
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
export abstract class MapStateStorage<T> extends StateStorage<Map<string, T>> {
|
35
|
+
initValue() {
|
36
|
+
return new Map<string, T>()
|
37
|
+
}
|
38
|
+
|
39
|
+
getValue(key: string): T | undefined {
|
40
|
+
const m = this.getOrRegister()
|
41
|
+
return m.get(key)
|
42
|
+
}
|
43
|
+
|
44
|
+
getValues(): T[] {
|
45
|
+
const m = this.getOrRegister()
|
46
|
+
return Array.from(m.values())
|
47
|
+
}
|
48
|
+
|
49
|
+
getOrSetValue(key: string, value: T): T {
|
50
|
+
const m = this.getOrRegister()
|
51
|
+
const oldValue = m.get(key)
|
52
|
+
if (oldValue) {
|
53
|
+
console.warn(key, 'has been registered twice, use the previous one')
|
54
|
+
return oldValue
|
55
|
+
}
|
56
|
+
m.set(key, value)
|
57
|
+
return value
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
export abstract class ListStateStorage<T> extends StateStorage<T[]> {
|
62
|
+
initValue() {
|
63
|
+
return []
|
64
|
+
}
|
65
|
+
|
66
|
+
getValues(): T[] {
|
67
|
+
return this.getOrRegister()
|
68
|
+
}
|
69
|
+
|
70
|
+
addValue(value: T): T {
|
71
|
+
const m = this.getOrRegister()
|
72
|
+
m.push(value)
|
73
|
+
return value
|
74
|
+
}
|
75
|
+
}
|
package/src/utils.ts
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
import { ProcessResult } from '@sentio/protos'
|
2
|
+
|
3
|
+
export function mergeProcessResults(results: ProcessResult[]): ProcessResult {
|
4
|
+
const res = ProcessResult.fromPartial({})
|
5
|
+
|
6
|
+
for (const r of results) {
|
7
|
+
res.counters = res.counters.concat(r.counters)
|
8
|
+
res.gauges = res.gauges.concat(r.gauges)
|
9
|
+
res.logs = res.logs.concat(r.logs)
|
10
|
+
res.events = res.events.concat(r.events)
|
11
|
+
res.exports = res.exports.concat(r.exports)
|
12
|
+
}
|
13
|
+
return res
|
14
|
+
}
|
15
|
+
|
16
|
+
export function errorString(e: Error): string {
|
17
|
+
return e.stack || e.message
|
18
|
+
}
|
19
|
+
|
20
|
+
export const USER_PROCESSOR = 'user_processor'
|