@xyo-network/xl1-cli-lib 1.19.7 → 1.19.8

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.
@@ -0,0 +1,197 @@
1
+ import {
2
+ asAddress, assertEx, isString, type Logger,
3
+ } from '@xylabs/sdk-js'
4
+ import {
5
+ initApiWallet,
6
+ initBalanceSummaryMap,
7
+ initEvmProvidersIfAvailable, initFinalizationArchivistIfNeeded, initProducerAccount, initServerNode, initStatusReporter,
8
+ initTransferSummaryMap,
9
+ } from '@xyo-network/chain-orchestration'
10
+ import { SimpleBlockRunner } from '@xyo-network/chain-services'
11
+ import { initTelemetry } from '@xyo-network/chain-telemetry'
12
+ import { validateHydratedBlock, validateHydratedBlockState } from '@xyo-network/chain-validation'
13
+ import type {
14
+ Config, CreatableProviderContext, ProviderFactoryLocatorInstance,
15
+ } from '@xyo-network/xl1-sdk'
16
+ import {
17
+ ProviderFactoryLocator, SimpleAccountBalanceViewer, SimpleBlockRewardViewer, SimpleBlockValidationViewer,
18
+ SimpleBlockViewer,
19
+ SimpleFinalizationViewer,
20
+ SimpleMempoolRunner,
21
+ SimpleMempoolViewer,
22
+ SimpleNetworkStakeViewer,
23
+ SimpleStepRewardsByPositionViewer,
24
+ SimpleStepRewardsByStakerViewer,
25
+ SimpleStepRewardsByStepViewer,
26
+ SimpleStepRewardsTotalViewer,
27
+ SimpleStepRewardsViewer,
28
+ SimpleStepViewer,
29
+ SimpleTimeSyncViewer,
30
+ SimpleWindowedBlockViewer,
31
+ SimpleXyoViewer,
32
+ } from '@xyo-network/xl1-sdk'
33
+
34
+ export async function telemetryContextFromConfig(config: Config, serviceName: string, serviceVersion: string) {
35
+ const { otlpEndpoint } = config.telemetry?.otel ?? {}
36
+ const { path: endpoint = '/metrics', port: port = 9466 } = config.telemetry?.metrics?.scrape ?? {}
37
+ return await initTelemetry({
38
+ attributes: {
39
+ serviceName,
40
+ serviceVersion,
41
+ },
42
+ otlpEndpoint,
43
+ metricsConfig: { endpoint, port },
44
+ })
45
+ }
46
+
47
+ export async function contextFromConfigWithoutLocator(
48
+ config: Config,
49
+ logger: Logger,
50
+ serviceName: string,
51
+ serviceVersion: string,
52
+ ): Promise<Omit<CreatableProviderContext, 'locator'>> {
53
+ const singletons = {}
54
+ const caches = {}
55
+ const telemetryConfig = await telemetryContextFromConfig(config, serviceName, serviceVersion)
56
+ const statusReporter = initStatusReporter({ logger })
57
+ return {
58
+ ...telemetryConfig,
59
+ config,
60
+ singletons,
61
+ caches,
62
+ logger,
63
+ statusReporter,
64
+ }
65
+ }
66
+
67
+ export async function rootLocatorFromConfig(
68
+ config: Config,
69
+ context: Omit<CreatableProviderContext, 'locator'>,
70
+ ): Promise<ProviderFactoryLocatorInstance> {
71
+ let locator: ProviderFactoryLocatorInstance = new ProviderFactoryLocator(context)
72
+
73
+ locator.registerMany([
74
+ SimpleNetworkStakeViewer.factory<SimpleNetworkStakeViewer>(SimpleNetworkStakeViewer.dependencies, {}),
75
+ SimpleTimeSyncViewer.factory<SimpleTimeSyncViewer>(SimpleTimeSyncViewer.dependencies, {}),
76
+ SimpleStepViewer.factory<SimpleStepViewer>(SimpleStepViewer.dependencies, {}),
77
+ SimpleStepRewardsViewer.factory<SimpleStepRewardsViewer>(SimpleStepRewardsViewer.dependencies, {}),
78
+ SimpleStepRewardsByPositionViewer.factory<SimpleStepRewardsByPositionViewer>(SimpleStepRewardsByPositionViewer.dependencies, {}),
79
+ SimpleStepRewardsByStakerViewer.factory<SimpleStepRewardsByStakerViewer>(SimpleStepRewardsByStakerViewer.dependencies, {}),
80
+ SimpleStepRewardsByStepViewer.factory<SimpleStepRewardsByStepViewer>(SimpleStepRewardsByStepViewer.dependencies, {}),
81
+ SimpleStepRewardsTotalViewer.factory<SimpleStepRewardsTotalViewer>(SimpleStepRewardsTotalViewer.dependencies, {}),
82
+ SimpleBlockValidationViewer.factory<SimpleBlockValidationViewer>(
83
+ SimpleBlockValidationViewer.dependencies,
84
+ { state: validateHydratedBlockState, protocol: validateHydratedBlock },
85
+ ),
86
+ ])
87
+ locator = await initEvmProvidersIfAvailable(locator)
88
+ locator = await (isString(config.remote.rpc?.url)
89
+ ? remoteLocatorFromConfig(config, { ...context, locator })
90
+ : localLocatorFromConfig(config, { ...context, locator }))
91
+ locator.freeze()
92
+ return locator
93
+ }
94
+
95
+ export async function localLocatorFromConfig(
96
+ config: Config,
97
+ context: CreatableProviderContext,
98
+ ): Promise<ProviderFactoryLocatorInstance> {
99
+ const balancesSummaryMap = assertEx(await initBalanceSummaryMap(context), () => 'Balance Summary Map not initialized')
100
+ const transfersSummaryMap = assertEx(await initTransferSummaryMap(context), () => 'Transfer Summary Map not initialized')
101
+
102
+ // TODO: this should not be an api wallet, but a server wallet
103
+ const wallet = await initApiWallet(context)
104
+
105
+ const {
106
+ writableChainArchivist, readonlyChainArchivist, pendingTransactionsArchivist, pendingBlocksArchivist,
107
+ } = await initServerNode({
108
+ ...context,
109
+ wallet,
110
+ transfersSummaryMap,
111
+ balancesSummaryMap,
112
+ })
113
+
114
+ await initFinalizationArchivistIfNeeded(writableChainArchivist, config, wallet)
115
+
116
+ const locator = new ProviderFactoryLocator(context)
117
+ locator.registerMany([
118
+ SimpleMempoolViewer.factory<SimpleMempoolViewer>(SimpleMempoolViewer.dependencies, { pendingTransactionsArchivist, pendingBlocksArchivist }),
119
+ SimpleMempoolRunner.factory<SimpleMempoolRunner>(SimpleMempoolRunner.dependencies, { pendingTransactionsArchivist, pendingBlocksArchivist }),
120
+ SimpleAccountBalanceViewer.factory<SimpleAccountBalanceViewer>(SimpleAccountBalanceViewer.dependencies, { balancesSummaryMap, transfersSummaryMap }),
121
+ SimpleFinalizationViewer.factory<SimpleFinalizationViewer>(SimpleFinalizationViewer.dependencies, { finalizedArchivist: readonlyChainArchivist }),
122
+ SimpleBlockViewer.factory<SimpleBlockViewer>(SimpleBlockViewer.dependencies, { finalizedArchivist: readonlyChainArchivist }),
123
+ SimpleWindowedBlockViewer.factory<SimpleWindowedBlockViewer>(SimpleWindowedBlockViewer.dependencies, { maxWindowSize: 10_000, syncInterval: 10_000 }),
124
+ SimpleXyoViewer.factory<SimpleXyoViewer>(SimpleXyoViewer.dependencies, {}),
125
+ ])
126
+ locator.freeze()
127
+ return locator
128
+ }
129
+
130
+ export async function remoteLocatorFromConfig(
131
+ config: Config,
132
+ context: CreatableProviderContext,
133
+ ): Promise<ProviderFactoryLocatorInstance> {
134
+ const balancesSummaryMap = assertEx(await initBalanceSummaryMap(context), () => 'Balance Summary Map not initialized')
135
+ const transfersSummaryMap = assertEx(await initTransferSummaryMap(context), () => 'Transfer Summary Map not initialized')
136
+
137
+ // TODO: this should not be an api wallet, but a server wallet
138
+ const wallet = await initApiWallet(context)
139
+
140
+ const {
141
+ readonlyChainArchivist, pendingTransactionsArchivist, pendingBlocksArchivist,
142
+ } = await initServerNode({
143
+ ...context,
144
+ wallet,
145
+ transfersSummaryMap,
146
+ balancesSummaryMap,
147
+ })
148
+
149
+ const locator = new ProviderFactoryLocator(context)
150
+ locator.registerMany([
151
+ SimpleMempoolViewer.factory<SimpleMempoolViewer>(SimpleMempoolViewer.dependencies, { pendingTransactionsArchivist, pendingBlocksArchivist }),
152
+ SimpleMempoolRunner.factory<SimpleMempoolRunner>(SimpleMempoolRunner.dependencies, { pendingTransactionsArchivist, pendingBlocksArchivist }),
153
+ SimpleAccountBalanceViewer.factory<SimpleAccountBalanceViewer>(SimpleAccountBalanceViewer.dependencies, { balancesSummaryMap, transfersSummaryMap }),
154
+ SimpleFinalizationViewer.factory<SimpleFinalizationViewer>(SimpleFinalizationViewer.dependencies, { finalizedArchivist: readonlyChainArchivist }),
155
+ SimpleBlockViewer.factory<SimpleBlockViewer>(SimpleBlockViewer.dependencies, { finalizedArchivist: readonlyChainArchivist }),
156
+ SimpleWindowedBlockViewer.factory<SimpleWindowedBlockViewer>(SimpleWindowedBlockViewer.dependencies, { maxWindowSize: 10_000, syncInterval: 10_000 }),
157
+ SimpleXyoViewer.factory<SimpleXyoViewer>(SimpleXyoViewer.dependencies, {}),
158
+ ])
159
+ locator.freeze()
160
+ return locator
161
+ }
162
+
163
+ export async function producerLocatorFromConfig(
164
+ config: Config,
165
+ context: CreatableProviderContext,
166
+ ): Promise<ProviderFactoryLocatorInstance> {
167
+ const { logger } = context
168
+ const account = await initProducerAccount({ config })
169
+ const locator = new ProviderFactoryLocator(context)
170
+ const rewardAddress = asAddress(config.actors.producer.rewardAddress ?? account.address, true)
171
+ logger?.info(`Using reward address ${rewardAddress}`)
172
+ if (rewardAddress === account.address) {
173
+ logger?.warn(`Using producer account address as reward address: ${account.address}`)
174
+ }
175
+
176
+ locator.registerMany([
177
+ SimpleBlockRewardViewer.factory<SimpleBlockRewardViewer>(SimpleBlockRewardViewer.dependencies, {}),
178
+ SimpleBlockRunner.factory<SimpleBlockRunner>(
179
+ SimpleBlockRunner.dependencies,
180
+ { account, rewardAddress },
181
+ )])
182
+ locator.freeze()
183
+ return locator
184
+ }
185
+
186
+ export async function locatorsFromConfig(
187
+ actors: string[],
188
+ config: Config,
189
+ context: Omit<CreatableProviderContext, 'locator'>,
190
+ ): Promise<Record<string, ProviderFactoryLocatorInstance>> {
191
+ const result: Record<string, ProviderFactoryLocatorInstance> = { _root: await rootLocatorFromConfig(config, context) }
192
+ const producer = actors.includes('producer') ? await producerLocatorFromConfig(config, result._root.context) : undefined
193
+ if (producer) {
194
+ result.producer = producer
195
+ }
196
+ return result
197
+ }
package/src/runCLI.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import type { Logger } from '@xylabs/sdk-js'
2
- import { isDefined, toAddress } from '@xylabs/sdk-js'
2
+ import {
3
+ deepMerge, isDefined, toAddress,
4
+ } from '@xylabs/sdk-js'
3
5
  import { runApi } from '@xyo-network/chain-api'
4
6
  import { Orchestrator } from '@xyo-network/chain-orchestration'
5
7
  import { runProducer } from '@xyo-network/chain-producer'
6
- import type { Config } from '@xyo-network/xl1-sdk'
8
+ import type { ActorName, Config } from '@xyo-network/xl1-sdk'
7
9
  import { ConfigZod, isZodError } from '@xyo-network/xl1-sdk'
8
- import { merge as deepMerge } from 'ts-deepmerge'
9
10
  import type { Argv } from 'yargs'
10
11
  import yargs from 'yargs'
11
12
  import { hideBin } from 'yargs/helpers'
@@ -15,18 +16,19 @@ import {
15
16
  } from './commands/index.ts'
16
17
  import { XL1LogoColorizedAscii } from './images.ts'
17
18
  import { initLogger } from './initLogger.ts'
19
+ import { contextFromConfigWithoutLocator, locatorsFromConfig } from './locatorFromConfig.ts'
18
20
  import { optionsFromGlobalZodRegistry } from './optionsFromGlobalZodRegistry.ts'
19
21
  import { tryParseConfig } from './tryParseConfig.ts'
20
22
  import { waitForHostPort } from './waitForHostPort.ts'
21
23
 
22
- /** Version string injected by Rollup at build time. */
23
- declare const __VERSION__: string
24
-
25
24
  interface RunCliContext {
26
25
  logger: Logger
27
26
  orchestrator: Orchestrator
28
27
  }
29
28
 
29
+ /** Version string injected by Rollup at build time. */
30
+ declare const __VERSION__: string
31
+
30
32
  /**
31
33
  * The configuration that will be used throughout the CLI.
32
34
  * This is materialized after parsing the command-line arguments,
@@ -67,6 +69,28 @@ const getContextFromConfig = async (configuration: Config): Promise<RunCliContex
67
69
  return { logger, orchestrator }
68
70
  }
69
71
 
72
+ const getLocatorsFromConfig = async (actors: string[], configuration: Config) => {
73
+ const logger = initLogger(configuration)
74
+ const orchestrator = await Orchestrator.create({ logger })
75
+ const context = await contextFromConfigWithoutLocator(configuration, logger, 'xl1-cli', version)
76
+ const locators = await locatorsFromConfig(actors, configuration, context)
77
+ // Handle cancellation (Ctrl+C)
78
+ process.on('SIGINT', () => {
79
+ void (async () => {
80
+ try {
81
+ logger.log('\nSIGINT received. Attempting graceful shutdown...')
82
+ await orchestrator?.stop()
83
+ logger.log('Orchestrator stopped, exiting now.')
84
+ process.exit(0)
85
+ } catch (err) {
86
+ logger.error('Error stopping orchestrator:', err)
87
+ process.exit(1)
88
+ }
89
+ })()
90
+ })
91
+ return { locators, orchestrator }
92
+ }
93
+
70
94
  // Main entry point
71
95
  export async function runCLI() {
72
96
  // Parse command-line arguments using Yargs
@@ -114,6 +138,24 @@ $0 <command> [options]`)
114
138
  throw validatedConfigResult.error
115
139
  }
116
140
  configuration = validatedConfigResult.data
141
+
142
+ const { actors, ...rootConfig } = configuration
143
+ const actorNames = Object.keys(actors) as ActorName[]
144
+
145
+ for (const actorName of actorNames) {
146
+ configuration.actors[actorName] = deepMerge(
147
+ rootConfig ?? {},
148
+ configuration.actors[actorName] ?? {},
149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
+ ) as any
151
+ }
152
+
153
+ // Check if user wants to dump config and exit
154
+ if (argv['dump-config']) {
155
+ console.log(JSON.stringify(configuration, null, 2))
156
+ // eslint-disable-next-line unicorn/no-process-exit
157
+ process.exit(0)
158
+ }
117
159
  } catch (err) {
118
160
  if (isZodError(err)) {
119
161
  console.error(`Zod error: ${err.message}`)
@@ -153,10 +195,8 @@ $0 <command> [options]`)
153
195
  .command('producer', 'Run a XL1 Producer Node', (yargs) => {
154
196
  return yargs
155
197
  .command('$0', 'Run a XL1 Producer Node', () => {}, async () => {
156
- const context = await getContextFromConfig(configuration)
157
- await runProducer({
158
- ...context, config: configuration, singletons: {},
159
- })
198
+ const { locators, orchestrator } = await getLocatorsFromConfig(['producer'], configuration)
199
+ await runProducer(configuration, orchestrator, locators['producer'])
160
200
  })
161
201
  })
162
202
  .command('reward-redemption-api', 'Run a XL1 Rewards Redemption API Node', (yargs) => {
@@ -167,23 +207,33 @@ $0 <command> [options]`)
167
207
  })
168
208
  })
169
209
  .command('$0', 'Run a full XL1 Node', () => {}, async () => {
210
+ const actors = ['producer', 'api']
211
+ if (configuration.actors.mempool.enabled) {
212
+ actors.push('mempool')
213
+ }
170
214
  const context = await getContextFromConfig(configuration)
171
- if (configuration.mempool.enabled) {
215
+ const { locators, orchestrator } = await getLocatorsFromConfig(['producer'], configuration)
216
+ if (configuration.actors.mempool.enabled) {
172
217
  // Start Mempool but do not block
173
218
  runMempool({ ...context, config: configuration })
174
219
  // Wait for Mempool to be ready
175
- await waitForHostPort(configuration.mempool.host, configuration.mempool.port)
220
+ await waitForHostPort(configuration.actors.mempool.host, configuration.actors.mempool.port)
176
221
  }
177
222
  // Start API but do not block
178
223
  await runApi({
179
224
  ...context, config: configuration, singletons: {},
180
225
  })
181
226
  // Wait for API to be ready
182
- await waitForHostPort(configuration.api.host, configuration.api.port)
227
+ await waitForHostPort(configuration.actors.api.host, configuration.actors.api.port)
183
228
  // Start Producer and block on it
184
- await runProducer({
185
- ...context, config: configuration, singletons: {},
186
- })
229
+ await runProducer(configuration, orchestrator, locators['producer'])
230
+ })
231
+ .options({
232
+ 'dump-config': {
233
+ type: 'boolean',
234
+ description: 'Just process the configuration and print the resolved config to stdout, then exit.',
235
+ default: false,
236
+ },
187
237
  })
188
238
  .help()
189
239
  .alias('help', 'h')