@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.
- package/dist/node/index.mjs +246 -20
- package/dist/node/index.mjs.map +1 -1
- package/dist/node/locatorFromConfig.d.ts +10 -0
- package/dist/node/locatorFromConfig.d.ts.map +1 -0
- package/dist/node/runCLI.d.ts.map +1 -1
- package/dist/node/tryParseConfig.d.ts +373 -68
- package/dist/node/tryParseConfig.d.ts.map +1 -1
- package/dist/node/xl1.mjs +246 -20
- package/dist/node/xl1.mjs.map +1 -1
- package/package.json +17 -15
- package/src/initLogger.ts +3 -3
- package/src/locatorFromConfig.ts +197 -0
- package/src/runCLI.ts +66 -16
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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')
|