@xmbl/simulator 0.1.0

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,846 @@
1
+ import { EventEmitter } from 'events';
2
+ import { faker } from '@faker-js/faker';
3
+ import Chance from 'chance';
4
+ import { StructuredLogger } from './logger.js';
5
+
6
+ // Initialize faker with seed for deterministic mode if needed
7
+ if (process.env.FAKER_SEED) {
8
+ faker.seed(parseInt(process.env.FAKER_SEED));
9
+ }
10
+
11
+ const chance = new Chance();
12
+
13
+ /**
14
+ * Comprehensive XMBL System Simulator
15
+ * Demonstrates all core functionality: identities, consensus, ledger, state machine, storage, compute
16
+ */
17
+ export class SystemSimulator extends EventEmitter {
18
+ constructor(options = {}) {
19
+ super();
20
+
21
+ this.options = {
22
+ initialIdentities: options.initialIdentities || 10,
23
+ transactionRate: options.transactionRate || 2, // transactions per second
24
+ stateDiffRate: options.stateDiffRate || 1, // state diffs per second
25
+ storageOpRate: options.storageOpRate || 0.5, // storage ops per second
26
+ computeOpRate: options.computeOpRate || 0.5, // compute ops per second
27
+ useRealModules: options.useRealModules !== false, // Try to use real modules if available
28
+ ...options
29
+ };
30
+
31
+ this.logger = new StructuredLogger({ logToConsole: true });
32
+ this.identities = [];
33
+ this.transactions = [];
34
+ this.stateDiffs = [];
35
+ this.blocks = [];
36
+ this.cubes = [];
37
+ this.superCubes = [];
38
+
39
+ // Module instances (will be initialized if available)
40
+ this.modules = {
41
+ xid: null,
42
+ xn: null,
43
+ xclt: null,
44
+ xvsm: null,
45
+ xpc: null,
46
+ xsc: null
47
+ };
48
+
49
+ this.metrics = {
50
+ identitiesCreated: 0,
51
+ transactionsCreated: 0,
52
+ transactionsValidated: 0,
53
+ blocksAdded: 0,
54
+ facesCompleted: 0,
55
+ cubesCompleted: 0,
56
+ superCubesCompleted: 0,
57
+ stateDiffsCreated: 0,
58
+ stateAssemblies: 0,
59
+ storageOperations: 0,
60
+ computeOperations: 0,
61
+ startTime: null,
62
+ uptime: 0
63
+ };
64
+
65
+ this.running = false;
66
+ this.intervals = [];
67
+ this.leaders = []; // Consensus leaders
68
+ }
69
+
70
+ /**
71
+ * Initialize and connect to real modules if available
72
+ */
73
+ async initializeModules() {
74
+ this.logger.system('initializing', { useRealModules: this.options.useRealModules });
75
+
76
+ if (!this.options.useRealModules) {
77
+ this.logger.system('modules_disabled', { reason: 'useRealModules is false' });
78
+ return;
79
+ }
80
+
81
+ // Try to import and initialize XID
82
+ try {
83
+ const { Identity } = await import('../../identity/index.js');
84
+ this.modules.xid = Identity;
85
+ this.logger.system('module_connected', { module: '@xmbl/identity', status: 'connected' });
86
+ } catch (error) {
87
+ this.logger.system('module_unavailable', { module: '@xmbl/identity', reason: error.message });
88
+ }
89
+
90
+ // Try to import and initialize XN
91
+ try {
92
+ const { XNNode, ConnectionManager } = await import('../../networking/index.js');
93
+ this.modules.xn = { XNNode, ConnectionManager };
94
+ this.logger.system('module_connected', { module: '@xmbl/networking', status: 'connected' });
95
+ } catch (error) {
96
+ this.logger.system('module_unavailable', { module: '@xmbl/networking', reason: error.message });
97
+ }
98
+
99
+ // Try to import and initialize XCLT (Ledger)
100
+ try {
101
+ const { Ledger } = await import('../../cubic-ledger/index.js');
102
+ this.modules.xclt = new Ledger({
103
+ dbPath: './data/xsim/ledger',
104
+ xid: this.modules.xid,
105
+ xn: this.modules.xn,
106
+ // Pass identity lookup function so ledger can verify signatures
107
+ getPublicKeyByAddress: (address) => {
108
+ const identity = this.identities.find(id => id.address === address);
109
+ return identity ? identity.publicKey : null;
110
+ }
111
+ });
112
+
113
+ // Listen to ledger events
114
+ this.modules.xclt.on('block:added', (block) => {
115
+ this.metrics.blocksAdded++;
116
+ this.blocks.push(block);
117
+ this.logger.ledger('block_added', {
118
+ blockId: block.id,
119
+ txId: block.txId,
120
+ timestamp: block.timestamp
121
+ });
122
+ });
123
+
124
+ this.modules.xclt.on('face:complete', (data) => {
125
+ this.metrics.facesCompleted++;
126
+ const face = data.face || data;
127
+ this.logger.ledger('face_complete', {
128
+ faceIndex: face.index || face.faceIndex || data.faceIndex,
129
+ blockCount: face.blocks?.size || face.blocks?.length || 0,
130
+ timestamp: face.timestamp || data.timestamp
131
+ });
132
+ });
133
+
134
+ this.modules.xclt.on('cube:complete', (data) => {
135
+ this.metrics.cubesCompleted++;
136
+ const cube = data.cube || data;
137
+ this.cubes.push(cube);
138
+ this.logger.ledger('cube_complete', {
139
+ cubeId: cube.id || data.cubeId,
140
+ level: cube.level || data.level || 1,
141
+ faceCount: cube.faces?.size || cube.faces?.length || 0,
142
+ timestamp: cube.timestamp || data.timestamp
143
+ });
144
+ });
145
+
146
+ this.modules.xclt.on('supercube:complete', (data) => {
147
+ this.metrics.superCubesCompleted++;
148
+ const superCube = data.superCube || data;
149
+ this.superCubes.push(superCube);
150
+ this.logger.ledger('supercube_complete', {
151
+ superCubeId: superCube.id || data.superCubeId,
152
+ level: superCube.level || data.level,
153
+ cubeCount: superCube.cubes?.size || superCube.cubes?.length || 0,
154
+ timestamp: superCube.timestamp || data.timestamp
155
+ });
156
+ });
157
+
158
+ this.logger.system('module_connected', { module: '@xmbl/cubic-ledger', status: 'connected' });
159
+ } catch (error) {
160
+ this.logger.system('module_unavailable', { module: '@xmbl/cubic-ledger', reason: error.message });
161
+ }
162
+
163
+ // Try to import and initialize XVSM (State Machine)
164
+ try {
165
+ const { StateMachine } = await import('../../state-machine/index.js');
166
+ this.modules.xvsm = new StateMachine({
167
+ dbPath: './data/xsim/xvsm',
168
+ xclt: this.modules.xclt
169
+ });
170
+
171
+ // StateMachine doesn't extend EventEmitter, so we'll track state through ledger events
172
+ // State diffs are processed automatically when blocks with state_diff type are added
173
+ this.logger.system('module_connected', { module: '@xmbl/state-machine', status: 'connected' });
174
+
175
+ // Periodically check state machine statistics
176
+ setInterval(() => {
177
+ if (this.modules.xvsm) {
178
+ try {
179
+ const stats = this.modules.xvsm.getStatistics();
180
+ this.logger.stateMachine('statistics', {
181
+ totalTransactions: stats.totalTransactions,
182
+ totalDiffs: stats.totalDiffs,
183
+ stateRoot: stats.stateRoot,
184
+ shardCount: stats.shards.length
185
+ });
186
+ } catch (error) {
187
+ // Ignore errors
188
+ }
189
+ }
190
+ }, 10000); // Every 10 seconds
191
+ } catch (error) {
192
+ this.logger.system('module_unavailable', { module: '@xmbl/state-machine', reason: error.message });
193
+ }
194
+
195
+ // Try to import and initialize XPC (Consensus)
196
+ try {
197
+ const { ConsensusWorkflow } = await import('../../consensus/index.js');
198
+ this.modules.xpc = new ConsensusWorkflow({
199
+ dbPath: './data/xsim/xpc',
200
+ xid: this.modules.xid,
201
+ xclt: this.modules.xclt,
202
+ xn: this.modules.xn,
203
+ // Pass identity lookup function so xpc can verify signatures
204
+ getPublicKeyByAddress: (address) => {
205
+ const identity = this.identities.find(id => id.address === address);
206
+ return identity ? identity.publicKey : null;
207
+ }
208
+ });
209
+
210
+ // Listen to consensus events
211
+ this.modules.xpc.on('raw_tx:added', (data) => {
212
+ this.logger.consensus('raw_tx_added', {
213
+ leaderId: data.leaderId,
214
+ rawTxId: data.rawTxId
215
+ });
216
+ });
217
+
218
+ this.modules.xpc.on('validation_tasks:created', (data) => {
219
+ this.logger.consensus('validation_tasks_created', {
220
+ rawTxId: data.rawTxId,
221
+ taskCount: data.tasks.length
222
+ });
223
+ });
224
+
225
+ this.modules.xpc.on('validation:complete', (data) => {
226
+ this.logger.consensus('validation_complete', {
227
+ rawTxId: data.rawTxId,
228
+ taskId: data.taskId,
229
+ validatorId: data.validatorId
230
+ });
231
+ });
232
+
233
+ this.modules.xpc.on('tx:finalized', (data) => {
234
+ this.metrics.transactionsValidated++;
235
+ this.logger.consensus('tx_finalized', {
236
+ rawTxId: data.rawTxId,
237
+ txId: data.txData.id
238
+ });
239
+ });
240
+
241
+ // Simulate validation completion for transactions
242
+ this.modules.xpc.on('validation_tasks:created', async (data) => {
243
+ console.log('[XSIM] ===== VALIDATION TASKS CREATED - AUTO-COMPLETING =====');
244
+ console.log('[XSIM] rawTxId:', data.rawTxId);
245
+ console.log('[XSIM] tasks count:', data.tasks ? data.tasks.length : 0);
246
+
247
+ if (data.tasks && Array.isArray(data.tasks) && data.tasks.length > 0) {
248
+ for (const task of data.tasks) {
249
+ setTimeout(async () => {
250
+ if (this.modules.xpc) {
251
+ try {
252
+ // Find the validator identity by leaderId (which is an identity address)
253
+ const validatorIdentity = this.identities.find(id => id.address === task.leaderId);
254
+ if (!validatorIdentity) {
255
+ console.error(`[XSIM] Validator identity not found for leaderId: ${task.leaderId}`);
256
+ return;
257
+ }
258
+
259
+ console.log(`[XSIM] Completing validation for rawTxId: ${data.rawTxId}, taskId: ${task.task}, validator: ${task.leaderId}`);
260
+
261
+ // Sign validation if we have the validator identity
262
+ let validationSignature = null;
263
+ if (validatorIdentity && validatorIdentity.privateKey) {
264
+ try {
265
+ // Create Identity instance if needed
266
+ let identity = validatorIdentity;
267
+ if (!validatorIdentity.signTransaction || typeof validatorIdentity.signTransaction !== 'function') {
268
+ const { Identity } = await import('../../identity/index.js');
269
+ identity = new Identity(validatorIdentity.publicKey, validatorIdentity.privateKey);
270
+ }
271
+
272
+ // Sign validation message (rawTxId + taskId)
273
+ const validationMessage = { rawTxId: data.rawTxId, taskId: task.task };
274
+ const signedValidation = await identity.signTransaction(validationMessage);
275
+ validationSignature = signedValidation.sig;
276
+ } catch (error) {
277
+ console.error('[XSIM] Failed to sign validation:', error);
278
+ }
279
+ }
280
+
281
+ await this.modules.xpc.completeValidation(
282
+ data.rawTxId,
283
+ task.task,
284
+ process.hrtime.bigint(), // timestamp
285
+ validationSignature, // signature from validator identity
286
+ task.leaderId // validatorId (identity address)
287
+ );
288
+ console.log(`[XSIM] Validation completed successfully for task: ${task.task}`);
289
+ } catch (error) {
290
+ console.error('[XSIM] Failed to complete validation:', error);
291
+ console.error('[XSIM] Error stack:', error.stack);
292
+ }
293
+ }
294
+ }, 50 + Math.random() * 100); // Random delay 50-150ms per task
295
+ }
296
+ }
297
+ });
298
+
299
+ // Auto-finalize transactions after they move to processing
300
+ this.modules.xpc.on('tx:processing', async (data) => {
301
+ setTimeout(async () => {
302
+ if (this.modules.xpc) {
303
+ try {
304
+ // Use validatedHash (txId) to finalize
305
+ const result = await this.modules.xpc.finalizeTransaction(data.txId);
306
+ if (result) {
307
+ console.log(`[XSIM] Auto-finalized transaction: ${data.txId}`);
308
+ } else {
309
+ console.warn(`[XSIM] Failed to finalize transaction: ${data.txId}`);
310
+ }
311
+ } catch (error) {
312
+ console.error('[XSIM] Finalization error:', error);
313
+ this.logger.error('consensus', 'finalization_failed', error);
314
+ }
315
+ }
316
+ }, 100); // Small delay before finalization
317
+ });
318
+
319
+ this.logger.system('module_connected', { module: '@xmbl/consensus', status: 'connected' });
320
+ } catch (error) {
321
+ this.logger.system('module_unavailable', { module: '@xmbl/consensus', reason: error.message });
322
+ }
323
+
324
+ // Try to import and initialize XSC (Storage and Compute)
325
+ try {
326
+ const { StorageNode, ComputeRuntime } = await import('../../storage-compute/index.js');
327
+ this.modules.xsc = {
328
+ StorageNode: StorageNode,
329
+ ComputeRuntime: ComputeRuntime
330
+ };
331
+ this.logger.system('module_connected', { module: '@xmbl/storage-compute', status: 'connected' });
332
+ } catch (error) {
333
+ this.logger.system('module_unavailable', { module: '@xmbl/storage-compute', reason: error.message });
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Create an identity using real module if available, otherwise simulate
339
+ */
340
+ async createIdentity() {
341
+ let identity;
342
+
343
+ if (this.modules.xid) {
344
+ try {
345
+ identity = await this.modules.xid.create();
346
+ this.logger.identity('identity_created', {
347
+ address: identity.address,
348
+ publicKey: identity.publicKey?.substring(0, 20) + '...',
349
+ source: 'real_module'
350
+ });
351
+ } catch (error) {
352
+ this.logger.error('identity', 'creation_failed', error);
353
+ // Fall back to simulated identity
354
+ identity = this._createSimulatedIdentity();
355
+ }
356
+ } else {
357
+ identity = this._createSimulatedIdentity();
358
+ }
359
+
360
+ this.identities.push(identity);
361
+ this.metrics.identitiesCreated++;
362
+
363
+ // If we have network module, add as node
364
+ if (this.modules.xn && this.modules.xn.XNNode) {
365
+ try {
366
+ // In real implementation, would create XNNode here
367
+ this.logger.network('node_added', {
368
+ identityAddress: identity.address,
369
+ source: 'identity_creation'
370
+ });
371
+ } catch (error) {
372
+ // Ignore network errors
373
+ }
374
+ }
375
+
376
+ this.emit('identity:created', identity);
377
+ return identity;
378
+ }
379
+
380
+ _createSimulatedIdentity() {
381
+ const identity = {
382
+ address: faker.string.alphanumeric({ length: 40 }),
383
+ publicKey: faker.string.alphanumeric({ length: 64 }),
384
+ privateKey: faker.string.alphanumeric({ length: 64 }),
385
+ createdAt: Date.now()
386
+ };
387
+
388
+ this.logger.identity('identity_created', {
389
+ address: identity.address,
390
+ publicKey: identity.publicKey.substring(0, 20) + '...',
391
+ source: 'simulated'
392
+ });
393
+
394
+ return identity;
395
+ }
396
+
397
+ /**
398
+ * Create and submit a transaction
399
+ */
400
+ async createTransaction() {
401
+ if (this.identities.length < 2) {
402
+ return;
403
+ }
404
+
405
+ const from = chance.pickone(this.identities);
406
+ const to = chance.pickone(this.identities.filter(i => i.address !== from.address));
407
+
408
+ const txType = chance.pickone(['utxo', 'token_creation', 'contract', 'state_diff']);
409
+ const tx = {
410
+ id: `tx_${Date.now()}_${chance.string({ length: 8 })}`,
411
+ type: txType,
412
+ from: from.address,
413
+ to: to.address,
414
+ amount: parseFloat(chance.floating({ min: 0.1, max: 1000, fixed: 2 })),
415
+ fee: parseFloat(chance.floating({ min: 0.01, max: 10, fixed: 2 })),
416
+ stake: parseFloat(chance.floating({ min: 0.1, max: 100, fixed: 2 })),
417
+ timestamp: Date.now(),
418
+ data: faker.lorem.sentence()
419
+ };
420
+
421
+ // Add required fields based on transaction type
422
+ if (txType === 'token_creation') {
423
+ tx.creator = from.address;
424
+ tx.tokenId = faker.string.alphanumeric({ length: 32 });
425
+ } else if (txType === 'contract') {
426
+ tx.contractHash = faker.string.alphanumeric({ length: 64 });
427
+ tx.abi = JSON.stringify([{ type: 'function', name: 'test' }]);
428
+ } else if (txType === 'state_diff') {
429
+ tx.function = faker.hacker.verb() + '_' + faker.hacker.noun();
430
+ tx.args = { key: faker.string.alphanumeric({ length: 20 }) };
431
+ }
432
+
433
+ // Sign transaction if we have identity module
434
+ if (this.modules.xid && from.privateKey) {
435
+ try {
436
+ // If from is already an Identity instance, use it directly
437
+ let identity = from;
438
+ if (!from.signTransaction || typeof from.signTransaction !== 'function') {
439
+ // Create Identity instance from privateKey/publicKey
440
+ const { Identity } = await import('../../identity/index.js');
441
+ identity = new Identity(from.publicKey, from.privateKey);
442
+ }
443
+
444
+ // Ensure from address is set (derived from public key)
445
+ tx.from = identity.address;
446
+
447
+ // Sign transaction (this adds sig field)
448
+ const signedTx = await identity.signTransaction(tx);
449
+ tx.sig = signedTx.sig;
450
+ // DO NOT include publicKey - only address in from field
451
+ } catch (error) {
452
+ console.error('[XSIM] Transaction signing failed:', error);
453
+ // If signing fails, continue without signature
454
+ }
455
+ }
456
+
457
+ this.transactions.push(tx);
458
+ this.metrics.transactionsCreated++;
459
+
460
+ this.logger.transaction('transaction_created', {
461
+ txId: tx.id,
462
+ type: tx.type,
463
+ from: tx.from.substring(0, 10) + '...',
464
+ to: tx.to.substring(0, 10) + '...',
465
+ amount: tx.amount
466
+ });
467
+
468
+ // Submit to consensus if available
469
+ if (this.modules.xpc && this.leaders.length > 0) {
470
+ try {
471
+ const leaderId = chance.pickone(this.leaders);
472
+ await this.modules.xpc.submitTransaction(leaderId, tx);
473
+ this.logger.consensus('transaction_submitted', {
474
+ txId: tx.id,
475
+ leaderId
476
+ });
477
+ } catch (error) {
478
+ this.logger.error('consensus', 'transaction_submission_failed', error);
479
+ }
480
+ } else if (this.modules.xclt) {
481
+ // If no consensus, add directly to ledger
482
+ try {
483
+ await this.modules.xclt.addTransaction(tx);
484
+ } catch (error) {
485
+ this.logger.error('ledger', 'transaction_addition_failed', error);
486
+ }
487
+ }
488
+
489
+ this.emit('transaction:created', tx);
490
+ return tx;
491
+ }
492
+
493
+ /**
494
+ * Create a state diff transaction
495
+ */
496
+ async createStateDiff() {
497
+ if (this.identities.length === 0) return;
498
+
499
+ const from = chance.pickone(this.identities);
500
+ const changes = {
501
+ [faker.string.alphanumeric({ length: 20 })]: faker.string.alphanumeric({ length: 50 }),
502
+ [faker.string.alphanumeric({ length: 20 })]: faker.string.alphanumeric({ length: 50 })
503
+ };
504
+
505
+ // Create a state_diff type transaction
506
+ const tx = {
507
+ id: `tx_state_${Date.now()}_${chance.string({ length: 8 })}`,
508
+ type: 'state_diff',
509
+ from: from.address,
510
+ function: faker.hacker.verb() + '_' + faker.hacker.noun(),
511
+ args: changes, // State diff changes go in args (required field)
512
+ timestamp: Date.now()
513
+ };
514
+
515
+ // Sign transaction if we have identity module
516
+ if (this.modules.xid && from.privateKey) {
517
+ try {
518
+ // If from is already an Identity instance, use it directly
519
+ let identity = from;
520
+ if (!from.signTransaction || typeof from.signTransaction !== 'function') {
521
+ // Create Identity instance from privateKey/publicKey
522
+ const { Identity } = await import('../../identity/index.js');
523
+ identity = new Identity(from.publicKey, from.privateKey);
524
+ }
525
+
526
+ // Ensure from address is set (derived from public key)
527
+ tx.from = identity.address;
528
+
529
+ // Sign transaction (this adds sig field)
530
+ const signedTx = await identity.signTransaction(tx);
531
+ tx.sig = signedTx.sig;
532
+ // DO NOT include publicKey - only address in from field
533
+ } catch (error) {
534
+ console.error('[XSIM] State diff signing failed:', error);
535
+ // If signing fails, continue without signature
536
+ }
537
+ }
538
+
539
+ this.metrics.stateDiffsCreated++;
540
+ this.logger.stateMachine('state_diff_created', {
541
+ txId: tx.id,
542
+ changeCount: Object.keys(changes).length,
543
+ source: 'transaction'
544
+ });
545
+
546
+ // Submit to consensus or ledger
547
+ if (this.modules.xpc && this.leaders.length > 0) {
548
+ try {
549
+ const leaderId = chance.pickone(this.leaders);
550
+ await this.modules.xpc.submitTransaction(leaderId, tx);
551
+ this.logger.consensus('state_diff_submitted', {
552
+ txId: tx.id,
553
+ leaderId
554
+ });
555
+ } catch (error) {
556
+ this.logger.error('consensus', 'state_diff_submission_failed', error);
557
+ }
558
+ } else if (this.modules.xclt) {
559
+ // If no consensus, add directly to ledger
560
+ try {
561
+ await this.modules.xclt.addTransaction(tx);
562
+ } catch (error) {
563
+ this.logger.error('ledger', 'state_diff_addition_failed', error);
564
+ }
565
+ }
566
+
567
+ this.emit('state:diff:created', { txId: tx.id, changes });
568
+ return tx;
569
+ }
570
+
571
+ /**
572
+ * Simulate storage operation
573
+ */
574
+ async simulateStorageOperation() {
575
+ const operation = {
576
+ type: chance.pickone(['store', 'retrieve', 'delete']),
577
+ key: faker.string.alphanumeric({ length: 32 }),
578
+ size: chance.integer({ min: 100, max: 10000 }),
579
+ timestamp: Date.now()
580
+ };
581
+
582
+ this.metrics.storageOperations++;
583
+ this.logger.storage('operation', {
584
+ type: operation.type,
585
+ key: operation.key.substring(0, 10) + '...',
586
+ size: operation.size
587
+ });
588
+
589
+ this.emit('storage:operation', operation);
590
+ return operation;
591
+ }
592
+
593
+ /**
594
+ * Simulate compute operation
595
+ */
596
+ async simulateComputeOperation() {
597
+ const operation = {
598
+ functionName: `${faker.hacker.verb()}_${faker.hacker.noun()}`,
599
+ duration: chance.normal({ mean: 100, dev: 20 }),
600
+ memory: chance.integer({ min: 10, max: 100 }),
601
+ timestamp: Date.now()
602
+ };
603
+
604
+ this.metrics.computeOperations++;
605
+ this.logger.compute('operation', {
606
+ functionName: operation.functionName,
607
+ duration: Math.max(0, operation.duration),
608
+ memory: operation.memory
609
+ });
610
+
611
+ this.emit('compute:operation', operation);
612
+
613
+ // Compute operations can trigger state changes. This used to call a fake
614
+ // xvsm.executeTransaction with a comment string as "WASM"; that path is gone (real
615
+ // contract execution is @xmbl/contracts' ContractHost over @xmbl/storage-compute). The
616
+ // simulator's honest job here is just to drive a state change, so it uses the real
617
+ // state-diff path directly.
618
+ if (this.modules.xvsm && chance.bool({ likelihood: 70 })) {
619
+ setTimeout(() => {
620
+ this.createStateDiff().catch(e => {
621
+ this.logger.error('stateMachine', 'compute_triggered_state_error', e);
622
+ });
623
+ }, Math.max(50, operation.duration));
624
+ }
625
+
626
+ return operation;
627
+ }
628
+
629
+ /**
630
+ * Start the simulator
631
+ */
632
+ async start() {
633
+ if (this.running) {
634
+ return;
635
+ }
636
+
637
+ this.logger.system('starting', { options: this.options });
638
+ this.running = true;
639
+ this.metrics.startTime = Date.now();
640
+
641
+ // Initialize modules
642
+ await this.initializeModules();
643
+
644
+ // Create initial identities
645
+ this.logger.system('creating_initial_identities', { count: this.options.initialIdentities });
646
+ for (let i = 0; i < this.options.initialIdentities; i++) {
647
+ await this.createIdentity();
648
+ // Small delay between identity creation
649
+ await new Promise(resolve => setTimeout(resolve, 100));
650
+ }
651
+
652
+ // Set up leaders for consensus (use first few identities)
653
+ this.leaders = this.identities.slice(0, Math.min(5, this.identities.length)).map(id => id.address);
654
+ this.logger.consensus('leaders_established', {
655
+ leaderCount: this.leaders.length,
656
+ leaders: this.leaders.map(l => l.substring(0, 10) + '...')
657
+ });
658
+
659
+ // Update xpc with actual validation leaders (identity addresses)
660
+ if (this.modules.xpc && this.leaders.length >= 3) {
661
+ this.modules.xpc.validationLeaders = this.leaders.slice(0, 3);
662
+ console.log('[XSIM] Set validation leaders in xpc:', this.modules.xpc.validationLeaders);
663
+ }
664
+
665
+ // Start transaction generation
666
+ const txInterval = setInterval(() => {
667
+ this.createTransaction().catch(err => {
668
+ this.logger.error('transaction', 'creation_error', err);
669
+ });
670
+ }, 1000 / this.options.transactionRate);
671
+ this.intervals.push(txInterval);
672
+
673
+ // Start state diff generation
674
+ const stateDiffInterval = setInterval(() => {
675
+ this.createStateDiff().catch(err => {
676
+ this.logger.error('stateMachine', 'state_diff_error', err);
677
+ });
678
+ }, 1000 / this.options.stateDiffRate);
679
+ this.intervals.push(stateDiffInterval);
680
+
681
+ // Start storage operations (varied rate with randomness)
682
+ const storageInterval = setInterval(() => {
683
+ const delay = (1000 / this.options.storageOpRate) * (0.7 + Math.random() * 0.6); // ±30% variation
684
+ setTimeout(() => {
685
+ this.simulateStorageOperation().catch(err => {
686
+ this.logger.error('storage', 'operation_error', err);
687
+ });
688
+ }, delay);
689
+ }, 1000 / this.options.storageOpRate);
690
+ this.intervals.push(storageInterval);
691
+
692
+ // Start compute operations (varied rate with randomness, slightly different base rate)
693
+ const computeBaseRate = this.options.computeOpRate * (0.8 + Math.random() * 0.4); // Vary base rate
694
+ const computeInterval = setInterval(() => {
695
+ const delay = (1000 / computeBaseRate) * (0.7 + Math.random() * 0.6); // ±30% variation
696
+ setTimeout(() => {
697
+ this.simulateComputeOperation().catch(err => {
698
+ this.logger.error('compute', 'operation_error', err);
699
+ });
700
+ }, delay);
701
+ }, 1000 / computeBaseRate);
702
+ this.intervals.push(computeInterval);
703
+
704
+ // Periodic state assembly (simulate app-centric state assembly)
705
+ const stateAssemblyInterval = setInterval(() => {
706
+ if (this.modules.xvsm && this.stateDiffs.length > 0) {
707
+ try {
708
+ // Assemble state from diffs
709
+ const state = this.modules.xvsm.getState();
710
+ this.metrics.stateAssemblies++;
711
+ this.logger.stateMachine('state_assembled', {
712
+ keyCount: Object.keys(state).length,
713
+ diffCount: this.modules.xvsm.diffs.length,
714
+ stateRoot: this.modules.xvsm.getStateRoot()
715
+ });
716
+ } catch (error) {
717
+ // Ignore errors
718
+ }
719
+ }
720
+ }, 15000); // Every 15 seconds
721
+ this.intervals.push(stateAssemblyInterval);
722
+
723
+ // Periodic metrics update
724
+ const metricsInterval = setInterval(() => {
725
+ this.metrics.uptime = Date.now() - this.metrics.startTime;
726
+ this.logger.system('metrics_update', { ...this.metrics });
727
+ this.emit('metrics:update', { ...this.metrics });
728
+ }, 5000);
729
+ this.intervals.push(metricsInterval);
730
+
731
+ this.logger.system('started', {
732
+ identities: this.identities.length,
733
+ transactionRate: this.options.transactionRate,
734
+ stateDiffRate: this.options.stateDiffRate
735
+ });
736
+
737
+ this.emit('started');
738
+ }
739
+
740
+ /**
741
+ * Pause the simulator (keeps state, can be resumed)
742
+ */
743
+ pause() {
744
+ if (!this.running) {
745
+ return;
746
+ }
747
+
748
+ this.logger.system('pausing', {});
749
+ this.running = false;
750
+
751
+ // Clear intervals but keep all state
752
+ this.intervals.forEach(interval => clearInterval(interval));
753
+ this.intervals = [];
754
+
755
+ this.logger.system('paused', {
756
+ metrics: { ...this.metrics }
757
+ });
758
+
759
+ this.emit('paused');
760
+ }
761
+
762
+ /**
763
+ * Resume the simulator from paused state
764
+ */
765
+ resume() {
766
+ if (this.running) {
767
+ return;
768
+ }
769
+
770
+ this.logger.system('resuming', {});
771
+ this.running = true;
772
+
773
+ // Restart transaction generation
774
+ const txInterval = setInterval(() => {
775
+ this.createTransaction().catch(err => {
776
+ this.logger.error('transaction', 'creation_error', err);
777
+ });
778
+ }, 1000 / this.options.transactionRate);
779
+ this.intervals.push(txInterval);
780
+
781
+ // Restart state diff generation
782
+ const stateDiffInterval = setInterval(() => {
783
+ this.createStateDiff().catch(err => {
784
+ this.logger.error('stateMachine', 'state_diff_error', err);
785
+ });
786
+ }, 1000 / this.options.stateDiffRate);
787
+ this.intervals.push(stateDiffInterval);
788
+
789
+ // Restart storage operations
790
+ const storageInterval = setInterval(() => {
791
+ this.simulateStorageOperation().catch(err => {
792
+ this.logger.error('storage', 'operation_error', err);
793
+ });
794
+ }, 1000 / this.options.storageOpRate);
795
+ this.intervals.push(storageInterval);
796
+
797
+ // Restart compute operations
798
+ const computeInterval = setInterval(() => {
799
+ this.simulateComputeOperation().catch(err => {
800
+ this.logger.error('compute', 'operation_error', err);
801
+ });
802
+ }, 1000 / this.options.computeOpRate);
803
+ this.intervals.push(computeInterval);
804
+
805
+ this.logger.system('resumed', {});
806
+ this.emit('resumed');
807
+ }
808
+
809
+ /**
810
+ * Stop the simulator (completely stops, different from pause)
811
+ */
812
+ stop() {
813
+ if (!this.running) {
814
+ return;
815
+ }
816
+
817
+ this.logger.system('stopping', {});
818
+ this.running = false;
819
+
820
+ this.intervals.forEach(interval => clearInterval(interval));
821
+ this.intervals = [];
822
+
823
+ this.logger.system('stopped', {
824
+ finalMetrics: { ...this.metrics }
825
+ });
826
+
827
+ this.emit('stopped');
828
+ }
829
+
830
+ /**
831
+ * Get current metrics
832
+ */
833
+ getMetrics() {
834
+ return {
835
+ ...this.metrics,
836
+ uptime: this.running ? Date.now() - this.metrics.startTime : this.metrics.uptime
837
+ };
838
+ }
839
+
840
+ /**
841
+ * Check if simulator is running
842
+ */
843
+ isRunning() {
844
+ return this.running;
845
+ }
846
+ }