dexbot 1.2.0 → 1.2.1
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/credential-daemon.d.ts.map +1 -1
- package/dist/credential-daemon.js +6 -3
- package/dist/credential-daemon.js.map +1 -1
- package/dist/modules/credential_policy.d.ts.map +1 -1
- package/dist/modules/credential_policy.js +7 -5
- package/dist/modules/credential_policy.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +52 -20
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +4 -0
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +8 -2
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +25 -15
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/grid.js +1 -1
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +13 -2
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +82 -16
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +126 -235
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts +2 -3
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +13 -3
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +42 -6
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/types.d.ts +7 -12
- package/dist/modules/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* SECTION 1: EXTERNAL DEPENDENCIES
|
|
13
13
|
* SECTION 2: COW REBALANCE ENGINE
|
|
14
|
-
* SECTION 3:
|
|
15
|
-
* SECTION 4: ORDER MANAGER CLASS
|
|
14
|
+
* SECTION 3: ORDER MANAGER CLASS
|
|
16
15
|
* ===============================================================================
|
|
17
16
|
*/
|
|
18
17
|
// ===============================================================================
|
|
@@ -57,7 +56,7 @@ const { toFiniteNumber, isValidNumber } = Format;
|
|
|
57
56
|
// 1. Master grid NEVER modified during planning (immutable during rebalance)
|
|
58
57
|
// 2. Fills that arrive during rebalance are QUEUED, not lost
|
|
59
58
|
// 3. Working grid is DISPOSABLE - if rebalance fails, discard it
|
|
60
|
-
// 4. Only ONE rebalance plan active at a time (
|
|
59
|
+
// 4. Only ONE rebalance plan active at a time (_rebalanceState)
|
|
61
60
|
// 5. Staleness detection aborts if master changes (fills, manual commands)
|
|
62
61
|
//
|
|
63
62
|
// FILL HANDLING DURING REBALANCE:
|
|
@@ -138,189 +137,7 @@ class COWRebalanceEngine {
|
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
139
|
// ===============================================================================
|
|
141
|
-
// SECTION 3:
|
|
142
|
-
// ===============================================================================
|
|
143
|
-
class StateManager {
|
|
144
|
-
logger;
|
|
145
|
-
rebalance;
|
|
146
|
-
recovery;
|
|
147
|
-
gridRegen;
|
|
148
|
-
bootstrap;
|
|
149
|
-
broadcast;
|
|
150
|
-
signals;
|
|
151
|
-
pipeline;
|
|
152
|
-
constructor(options = {}) {
|
|
153
|
-
this.logger = options.logger || null;
|
|
154
|
-
this.reset();
|
|
155
|
-
}
|
|
156
|
-
reset() {
|
|
157
|
-
this.rebalance = {
|
|
158
|
-
state: REBALANCE_STATES.NORMAL,
|
|
159
|
-
currentWorkingGrid: null
|
|
160
|
-
};
|
|
161
|
-
this.recovery = {
|
|
162
|
-
phase: 'idle',
|
|
163
|
-
attemptCount: 0,
|
|
164
|
-
lastAttemptAt: 0,
|
|
165
|
-
inFlight: false,
|
|
166
|
-
lastFailureAt: 0
|
|
167
|
-
};
|
|
168
|
-
this.gridRegen = {
|
|
169
|
-
buy: { armed: true, lastTriggeredAt: 0 },
|
|
170
|
-
sell: { armed: true, lastTriggeredAt: 0 }
|
|
171
|
-
};
|
|
172
|
-
this.bootstrap = {
|
|
173
|
-
isBootstrapping: false
|
|
174
|
-
};
|
|
175
|
-
this.broadcast = {
|
|
176
|
-
isBroadcasting: false,
|
|
177
|
-
startedAt: 0,
|
|
178
|
-
};
|
|
179
|
-
this.signals = {
|
|
180
|
-
lastIllegalState: null,
|
|
181
|
-
lastAccountingFailure: null
|
|
182
|
-
};
|
|
183
|
-
this.pipeline = {
|
|
184
|
-
blockedSince: null,
|
|
185
|
-
recoveryAttempted: false
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
getRebalanceState() {
|
|
189
|
-
return this.rebalance.state;
|
|
190
|
-
}
|
|
191
|
-
setRebalanceState(state) {
|
|
192
|
-
this.rebalance.state = state;
|
|
193
|
-
this.logger?.log(`[COW] Rebalance state: ${state}`, 'debug');
|
|
194
|
-
}
|
|
195
|
-
isRebalancing() {
|
|
196
|
-
return this.rebalance.state === REBALANCE_STATES.REBALANCING;
|
|
197
|
-
}
|
|
198
|
-
isBroadcasting() {
|
|
199
|
-
return this.rebalance.state === REBALANCE_STATES.BROADCASTING;
|
|
200
|
-
}
|
|
201
|
-
setWorkingGrid(workingGrid) {
|
|
202
|
-
this.rebalance.currentWorkingGrid = workingGrid;
|
|
203
|
-
}
|
|
204
|
-
getWorkingGrid() {
|
|
205
|
-
return this.rebalance.currentWorkingGrid;
|
|
206
|
-
}
|
|
207
|
-
clearWorkingGrid() {
|
|
208
|
-
this.rebalance.currentWorkingGrid = null;
|
|
209
|
-
}
|
|
210
|
-
recordRecoveryAttempt() {
|
|
211
|
-
this.recovery.phase = 'scanning';
|
|
212
|
-
this.recovery.attemptCount++;
|
|
213
|
-
this.recovery.lastAttemptAt = Date.now();
|
|
214
|
-
this.recovery.inFlight = true;
|
|
215
|
-
}
|
|
216
|
-
completeRecovery(success) {
|
|
217
|
-
this.recovery.phase = success ? 'idle' : 'executing';
|
|
218
|
-
this.recovery.inFlight = false;
|
|
219
|
-
if (!success) {
|
|
220
|
-
this.recovery.lastFailureAt = Date.now();
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
isRecoveryInFlight() {
|
|
224
|
-
return this.recovery.inFlight;
|
|
225
|
-
}
|
|
226
|
-
getRecoveryStats() {
|
|
227
|
-
return { ...this.recovery };
|
|
228
|
-
}
|
|
229
|
-
isSideArmed(side) {
|
|
230
|
-
return this.gridRegen[side]?.armed ?? false;
|
|
231
|
-
}
|
|
232
|
-
disarmSide(side) {
|
|
233
|
-
if (this.gridRegen[side]) {
|
|
234
|
-
this.gridRegen[side].armed = false;
|
|
235
|
-
this.gridRegen[side].lastTriggeredAt = Date.now();
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
armSide(side) {
|
|
239
|
-
if (this.gridRegen[side]) {
|
|
240
|
-
this.gridRegen[side].armed = true;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
startBootstrap() {
|
|
244
|
-
this.bootstrap.isBootstrapping = true;
|
|
245
|
-
this.logger?.log('[BOOTSTRAP] Started', 'debug');
|
|
246
|
-
}
|
|
247
|
-
finishBootstrap() {
|
|
248
|
-
this.bootstrap.isBootstrapping = false;
|
|
249
|
-
this.logger?.log('[BOOTSTRAP] Finished', 'debug');
|
|
250
|
-
}
|
|
251
|
-
isBootstrapping() {
|
|
252
|
-
return this.bootstrap.isBootstrapping;
|
|
253
|
-
}
|
|
254
|
-
startBroadcasting() {
|
|
255
|
-
this.broadcast.isBroadcasting = true;
|
|
256
|
-
this.broadcast.startedAt = Date.now();
|
|
257
|
-
this.logger?.log?.('[BROADCAST] Flag set — fill processing will be deferred until stopBroadcasting()', 'debug');
|
|
258
|
-
}
|
|
259
|
-
stopBroadcasting() {
|
|
260
|
-
this.broadcast.isBroadcasting = false;
|
|
261
|
-
this.broadcast.startedAt = 0;
|
|
262
|
-
}
|
|
263
|
-
isBroadcastingActive() {
|
|
264
|
-
if (this.broadcast.isBroadcasting && this.broadcast.startedAt > 0) {
|
|
265
|
-
const elapsed = Date.now() - this.broadcast.startedAt;
|
|
266
|
-
if (elapsed > 120000) {
|
|
267
|
-
this.logger?.log?.('[BROADCAST] Auto-clearing stale broadcast flag after 120s', 'warn');
|
|
268
|
-
this.broadcast.isBroadcasting = false;
|
|
269
|
-
this.broadcast.startedAt = 0;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return this.broadcast.isBroadcasting;
|
|
273
|
-
}
|
|
274
|
-
setIllegalStateSignal(signal) {
|
|
275
|
-
this.signals.lastIllegalState = {
|
|
276
|
-
...signal,
|
|
277
|
-
at: Date.now()
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
consumeIllegalStateSignal() {
|
|
281
|
-
const signal = this.signals.lastIllegalState;
|
|
282
|
-
this.signals.lastIllegalState = null;
|
|
283
|
-
return signal;
|
|
284
|
-
}
|
|
285
|
-
setAccountingFailureSignal(signal) {
|
|
286
|
-
this.signals.lastAccountingFailure = {
|
|
287
|
-
...signal,
|
|
288
|
-
at: Date.now()
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
consumeAccountingFailureSignal() {
|
|
292
|
-
const signal = this.signals.lastAccountingFailure;
|
|
293
|
-
this.signals.lastAccountingFailure = null;
|
|
294
|
-
return signal;
|
|
295
|
-
}
|
|
296
|
-
markPipelineBlocked() {
|
|
297
|
-
if (!this.pipeline.blockedSince) {
|
|
298
|
-
this.pipeline.blockedSince = Date.now();
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
markPipelineClear() {
|
|
302
|
-
this.pipeline.blockedSince = null;
|
|
303
|
-
this.pipeline.recoveryAttempted = false;
|
|
304
|
-
}
|
|
305
|
-
getPipelineBlockedDuration() {
|
|
306
|
-
return this.pipeline.blockedSince ? Date.now() - this.pipeline.blockedSince : 0;
|
|
307
|
-
}
|
|
308
|
-
isPipelineBlocked() {
|
|
309
|
-
return this.pipeline.blockedSince !== null;
|
|
310
|
-
}
|
|
311
|
-
getState() {
|
|
312
|
-
return {
|
|
313
|
-
rebalance: { ...this.rebalance, currentWorkingGrid: null },
|
|
314
|
-
recovery: { ...this.recovery },
|
|
315
|
-
gridRegen: { ...this.gridRegen },
|
|
316
|
-
bootstrap: { ...this.bootstrap },
|
|
317
|
-
broadcast: { ...this.broadcast },
|
|
318
|
-
pipeline: { ...this.pipeline }
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
// ===============================================================================
|
|
323
|
-
// SECTION 4: ORDER MANAGER CLASS
|
|
140
|
+
// SECTION 3: ORDER MANAGER CLASS
|
|
324
141
|
// ===============================================================================
|
|
325
142
|
class OrderManager {
|
|
326
143
|
config;
|
|
@@ -332,7 +149,14 @@ class OrderManager {
|
|
|
332
149
|
accountant;
|
|
333
150
|
strategy;
|
|
334
151
|
sync;
|
|
335
|
-
|
|
152
|
+
_rebalanceState;
|
|
153
|
+
_bootstrapping;
|
|
154
|
+
_broadcastingFlag;
|
|
155
|
+
_broadcastingStartedAt;
|
|
156
|
+
_illegalStateSignal;
|
|
157
|
+
_accountingFailureSignal;
|
|
158
|
+
_recoveryStateValue;
|
|
159
|
+
_gridRegenStateValue;
|
|
336
160
|
_ordersByState;
|
|
337
161
|
_ordersByType;
|
|
338
162
|
targetSpreadCount;
|
|
@@ -395,7 +219,23 @@ class OrderManager {
|
|
|
395
219
|
this.accountant = new Accountant(this);
|
|
396
220
|
this.strategy = new StrategyEngine(this);
|
|
397
221
|
this.sync = new SyncEngine(this);
|
|
398
|
-
this.
|
|
222
|
+
this._rebalanceState = REBALANCE_STATES.NORMAL;
|
|
223
|
+
this._bootstrapping = false;
|
|
224
|
+
this._broadcastingFlag = false;
|
|
225
|
+
this._broadcastingStartedAt = 0;
|
|
226
|
+
this._illegalStateSignal = null;
|
|
227
|
+
this._accountingFailureSignal = null;
|
|
228
|
+
this._recoveryStateValue = {
|
|
229
|
+
phase: 'idle',
|
|
230
|
+
attemptCount: 0,
|
|
231
|
+
lastAttemptAt: 0,
|
|
232
|
+
inFlight: false,
|
|
233
|
+
lastFailureAt: 0
|
|
234
|
+
};
|
|
235
|
+
this._gridRegenStateValue = {
|
|
236
|
+
buy: { armed: true, lastTriggeredAt: 0 },
|
|
237
|
+
sell: { armed: true, lastTriggeredAt: 0 }
|
|
238
|
+
};
|
|
399
239
|
// Index Sets use mutable mutation patterns controlled via _applyOrderUpdate
|
|
400
240
|
// These are private implementation details and must NOT be mutated directly
|
|
401
241
|
// All external code must go through the COW pipeline
|
|
@@ -430,7 +270,7 @@ class OrderManager {
|
|
|
430
270
|
// Level 2: _gridLock (grid mutations)
|
|
431
271
|
// Level 3: _syncLock (sync operations)
|
|
432
272
|
// Level 4: _fundLock (fund operations — innermost)
|
|
433
|
-
//
|
|
273
|
+
// Note: AsyncLock IS re-entrant (acquire detects nested calls via _holding).
|
|
434
274
|
this._divergenceLock = new AsyncLock({ level: 0 });
|
|
435
275
|
this._fillProcessingLock = new AsyncLock({ level: 1 });
|
|
436
276
|
this._gridLock = new AsyncLock({ level: 2 });
|
|
@@ -461,7 +301,8 @@ class OrderManager {
|
|
|
461
301
|
lastSyncDurationMs: 0,
|
|
462
302
|
metricsStartTime: Date.now()
|
|
463
303
|
};
|
|
464
|
-
this.
|
|
304
|
+
this._bootstrapping = true;
|
|
305
|
+
this.logger?.log('[BOOTSTRAP] Started', 'debug');
|
|
465
306
|
this._currentWorkingGrid = null;
|
|
466
307
|
this._cowEngine = null;
|
|
467
308
|
this._cleanExpiredLocks();
|
|
@@ -479,48 +320,77 @@ class OrderManager {
|
|
|
479
320
|
}
|
|
480
321
|
_clearWorkingGridRef() {
|
|
481
322
|
this._currentWorkingGrid = null;
|
|
482
|
-
this.
|
|
483
|
-
this._state.setRebalanceState(REBALANCE_STATES.NORMAL);
|
|
323
|
+
this._rebalanceState = REBALANCE_STATES.NORMAL;
|
|
484
324
|
}
|
|
485
325
|
_setRebalanceState(state) {
|
|
486
|
-
this.
|
|
326
|
+
this._rebalanceState = state;
|
|
327
|
+
this.logger?.log(`[COW] Rebalance state: ${state}`, 'debug');
|
|
487
328
|
}
|
|
488
329
|
/**
|
|
489
330
|
* @returns {boolean}
|
|
490
331
|
*/
|
|
491
332
|
isRebalancing() {
|
|
492
|
-
return this.
|
|
333
|
+
return this._rebalanceState === REBALANCE_STATES.REBALANCING;
|
|
493
334
|
}
|
|
494
335
|
/**
|
|
495
336
|
* @returns {boolean}
|
|
496
337
|
*/
|
|
497
338
|
isBroadcasting() {
|
|
498
|
-
return this.
|
|
339
|
+
return this._rebalanceState === REBALANCE_STATES.BROADCASTING;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* @returns {boolean}
|
|
343
|
+
*/
|
|
344
|
+
isBootstrapping() {
|
|
345
|
+
return this._bootstrapping;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Whether a broadcast is currently active (no side-effects).
|
|
349
|
+
* @returns {boolean}
|
|
350
|
+
*/
|
|
351
|
+
isBroadcastingActive() {
|
|
352
|
+
return this._broadcastingFlag;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Auto-clears a stale broadcast flag after 120s so a hung
|
|
356
|
+
* broadcast cannot permanently block rebalancing.
|
|
357
|
+
* Separated from isBroadcastingActive() so the side-effect only
|
|
358
|
+
* runs from one well-defined location (maintenance logic) instead
|
|
359
|
+
* of from every read call-site.
|
|
360
|
+
* @returns {void}
|
|
361
|
+
*/
|
|
362
|
+
_clearStaleBroadcastFlag() {
|
|
363
|
+
if (this._broadcastingFlag && this._broadcastingStartedAt > 0) {
|
|
364
|
+
const elapsed = Date.now() - this._broadcastingStartedAt;
|
|
365
|
+
if (elapsed > 120000) {
|
|
366
|
+
this.logger?.log?.('[BROADCAST] Auto-clearing stale broadcast flag after 120s', 'warn');
|
|
367
|
+
this._broadcastingFlag = false;
|
|
368
|
+
this._broadcastingStartedAt = 0;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
499
371
|
}
|
|
500
372
|
/**
|
|
501
373
|
* Whether the manager is in the middle of a rebalance plan or broadcast.
|
|
502
|
-
* NOT a pure predicate: delegates to _state.isBroadcastingActive()
|
|
503
|
-
* (see StateManager.isBroadcastingActive, manager.ts:362) which
|
|
504
|
-
* auto-clears a stale broadcast flag after 120s (producing a warn log)
|
|
505
|
-
* so that a hung broadcast cannot permanently block rebalancing.
|
|
506
374
|
* @returns {boolean}
|
|
507
375
|
*/
|
|
508
376
|
isPlanningActive() {
|
|
509
|
-
return this.isRebalancing() || this.
|
|
377
|
+
return this.isRebalancing() || this.isBroadcastingActive();
|
|
510
378
|
}
|
|
511
379
|
/**
|
|
512
380
|
* @returns {void}
|
|
513
381
|
*/
|
|
514
382
|
startBootstrap() {
|
|
515
|
-
this.
|
|
383
|
+
this._bootstrapping = true;
|
|
384
|
+
this.logger?.log('[BOOTSTRAP] Started', 'debug');
|
|
516
385
|
}
|
|
517
386
|
/**
|
|
518
387
|
* @returns {import('./types').BootstrapResult}
|
|
519
388
|
*/
|
|
520
389
|
finishBootstrap() {
|
|
521
390
|
const result = { hadDrift: false, driftInfo: null };
|
|
522
|
-
if (this.
|
|
523
|
-
this.
|
|
391
|
+
if (this._bootstrapping) {
|
|
392
|
+
this._bootstrapping = false;
|
|
393
|
+
this.logger?.log('[BOOTSTRAP] Finished', 'debug');
|
|
524
394
|
// Validate fund state at bootstrap completion - if drift exists here,
|
|
525
395
|
// it's not transient (grid is now stable) and indicates a potential bug
|
|
526
396
|
const driftCheck = this.checkFundDriftAfterFills();
|
|
@@ -538,13 +408,16 @@ class OrderManager {
|
|
|
538
408
|
* @returns {void}
|
|
539
409
|
*/
|
|
540
410
|
startBroadcasting() {
|
|
541
|
-
this.
|
|
411
|
+
this._broadcastingFlag = true;
|
|
412
|
+
this._broadcastingStartedAt = Date.now();
|
|
413
|
+
this.logger?.log?.('[BROADCAST] Flag set — fill processing will be deferred until stopBroadcasting()', 'debug');
|
|
542
414
|
}
|
|
543
415
|
/**
|
|
544
416
|
* @returns {void}
|
|
545
417
|
*/
|
|
546
418
|
stopBroadcasting() {
|
|
547
|
-
this.
|
|
419
|
+
this._broadcastingFlag = false;
|
|
420
|
+
this._broadcastingStartedAt = 0;
|
|
548
421
|
}
|
|
549
422
|
/**
|
|
550
423
|
* @returns {void}
|
|
@@ -765,6 +638,9 @@ class OrderManager {
|
|
|
765
638
|
*/
|
|
766
639
|
pauseRecalcLogging() {
|
|
767
640
|
this._pauseRecalcLogging = true;
|
|
641
|
+
if (this._pauseRecalcLoggingWatchdog) {
|
|
642
|
+
clearTimeout(this._pauseRecalcLoggingWatchdog);
|
|
643
|
+
}
|
|
768
644
|
this._pauseRecalcLoggingWatchdog = setTimeout(() => {
|
|
769
645
|
this.logger?.log?.(`[MANAGER] pauseRecalcLogging safety watchdog: forcing resume after ${TIMING.SAFETY_PAUSE_TIMEOUT_MS}ms`, 'warn');
|
|
770
646
|
this._pauseRecalcLogging = false;
|
|
@@ -895,11 +771,12 @@ class OrderManager {
|
|
|
895
771
|
const fatalError = validation.errors.find(e => e.isFatal || e.code === 'ILLEGAL_SPREAD_STATE');
|
|
896
772
|
if (fatalError) {
|
|
897
773
|
this.logger.log(fatalError.message, 'error');
|
|
898
|
-
this.
|
|
774
|
+
this._illegalStateSignal = {
|
|
899
775
|
id: order.id,
|
|
900
776
|
context,
|
|
901
|
-
message: fatalError.message
|
|
902
|
-
|
|
777
|
+
message: fatalError.message,
|
|
778
|
+
at: Date.now()
|
|
779
|
+
};
|
|
903
780
|
if (this._throwOnIllegalState) {
|
|
904
781
|
const err = new Error(fatalError.message);
|
|
905
782
|
err.code = fatalError.code;
|
|
@@ -1217,13 +1094,17 @@ class OrderManager {
|
|
|
1217
1094
|
* @returns {Object|null} The consumed signal or null
|
|
1218
1095
|
*/
|
|
1219
1096
|
consumeIllegalStateSignal() {
|
|
1220
|
-
|
|
1097
|
+
const signal = this._illegalStateSignal;
|
|
1098
|
+
this._illegalStateSignal = null;
|
|
1099
|
+
return signal;
|
|
1221
1100
|
}
|
|
1222
1101
|
/**
|
|
1223
1102
|
* @returns {Object|null} The consumed signal or null
|
|
1224
1103
|
*/
|
|
1225
1104
|
consumeAccountingFailureSignal() {
|
|
1226
|
-
|
|
1105
|
+
const signal = this._accountingFailureSignal;
|
|
1106
|
+
this._accountingFailureSignal = null;
|
|
1107
|
+
return signal;
|
|
1227
1108
|
}
|
|
1228
1109
|
/**
|
|
1229
1110
|
* @param {Object} BitShares - BitShares API instance
|
|
@@ -1289,16 +1170,14 @@ class OrderManager {
|
|
|
1289
1170
|
if (recoveryInFlight) {
|
|
1290
1171
|
reasons.push('recovery sync in-flight');
|
|
1291
1172
|
}
|
|
1292
|
-
if (broadcasting || this.
|
|
1173
|
+
if (broadcasting || this.isBroadcastingActive()) {
|
|
1293
1174
|
reasons.push('broadcasting active orders');
|
|
1294
1175
|
}
|
|
1295
1176
|
if (reasons.length > 0 && !this._pipelineBlockedSince) {
|
|
1296
1177
|
this._pipelineBlockedSince = Date.now();
|
|
1297
|
-
this._state.markPipelineBlocked();
|
|
1298
1178
|
}
|
|
1299
1179
|
else if (reasons.length === 0) {
|
|
1300
1180
|
this._pipelineBlockedSince = null;
|
|
1301
|
-
this._state.markPipelineClear();
|
|
1302
1181
|
}
|
|
1303
1182
|
return {
|
|
1304
1183
|
isEmpty: reasons.length === 0,
|
|
@@ -1316,7 +1195,6 @@ class OrderManager {
|
|
|
1316
1195
|
return false;
|
|
1317
1196
|
this.ordersNeedingPriceCorrection = [];
|
|
1318
1197
|
this._pipelineBlockedSince = null;
|
|
1319
|
-
this._state.markPipelineClear();
|
|
1320
1198
|
return true;
|
|
1321
1199
|
}
|
|
1322
1200
|
/**
|
|
@@ -1510,6 +1388,13 @@ class OrderManager {
|
|
|
1510
1388
|
}
|
|
1511
1389
|
try {
|
|
1512
1390
|
if (!skipRecalc) {
|
|
1391
|
+
// If the last accounting failure was due to stale accountTotals,
|
|
1392
|
+
// the commit proceeded without locking funds. Refresh accountTotals
|
|
1393
|
+
// before recalculateFunds() to avoid a false-positive drift recovery.
|
|
1394
|
+
if (this._lastAccountingFailure?.reason === 'stale') {
|
|
1395
|
+
this.logger.log('[COW] Stale accountTotals during commit; refreshing before recalculateFunds to avoid false recovery', 'warn');
|
|
1396
|
+
await this.fetchAccountTotals(this.accountId);
|
|
1397
|
+
}
|
|
1513
1398
|
await this.recalculateFunds();
|
|
1514
1399
|
}
|
|
1515
1400
|
const duration = Date.now() - startTime;
|
|
@@ -1520,8 +1405,10 @@ class OrderManager {
|
|
|
1520
1405
|
}
|
|
1521
1406
|
catch (recalcErr) {
|
|
1522
1407
|
this.logger.log(`[COW] Fund recalculation failed post-commit: ${recalcErr.message}`, 'error');
|
|
1523
|
-
|
|
1524
|
-
|
|
1408
|
+
if (!this._recoveryStateValue) {
|
|
1409
|
+
this._recoveryStateValue = { phase: 'idle', attemptCount: 0, lastAttemptAt: 0, inFlight: false, lastFailureAt: 0 };
|
|
1410
|
+
}
|
|
1411
|
+
this._recoveryStateValue.lastFailureAt = Date.now();
|
|
1525
1412
|
}
|
|
1526
1413
|
finally {
|
|
1527
1414
|
this._clearWorkingGridRef();
|
|
@@ -1536,7 +1423,7 @@ class OrderManager {
|
|
|
1536
1423
|
validateGridStateForPersistence(options = {}) {
|
|
1537
1424
|
const result = validateGridForPersistence(this.orders, this.accountTotals);
|
|
1538
1425
|
const allowBootstrapTransient = options.allowBootstrapTransient !== false;
|
|
1539
|
-
if (!result.isValid && allowBootstrapTransient && this.
|
|
1426
|
+
if (!result.isValid && allowBootstrapTransient && this._bootstrapping) {
|
|
1540
1427
|
this.logger.log(`[BOOTSTRAP] Transient state (expected): ${result.reason}`, 'debug');
|
|
1541
1428
|
return { isValid: true, reason: null };
|
|
1542
1429
|
}
|
|
@@ -1596,7 +1483,14 @@ class OrderManager {
|
|
|
1596
1483
|
getMetrics() {
|
|
1597
1484
|
return {
|
|
1598
1485
|
...this._metrics,
|
|
1599
|
-
state:
|
|
1486
|
+
state: {
|
|
1487
|
+
rebalance: { state: this._rebalanceState, currentWorkingGrid: null },
|
|
1488
|
+
recovery: { ...this._recoveryStateValue },
|
|
1489
|
+
gridRegen: { ...this._gridRegenStateValue },
|
|
1490
|
+
bootstrap: { isBootstrapping: this._bootstrapping },
|
|
1491
|
+
broadcast: { isBroadcasting: this._broadcastingFlag, startedAt: this._broadcastingStartedAt },
|
|
1492
|
+
pipeline: { blockedSince: this._pipelineBlockedSince, recoveryAttempted: this._recoveryAttempted }
|
|
1493
|
+
},
|
|
1600
1494
|
currentTime: Date.now()
|
|
1601
1495
|
};
|
|
1602
1496
|
}
|
|
@@ -1625,33 +1519,30 @@ class OrderManager {
|
|
|
1625
1519
|
comparePrecisions
|
|
1626
1520
|
});
|
|
1627
1521
|
}
|
|
1628
|
-
// Delegates to _state.rebalance (StateManager's internal field).
|
|
1629
|
-
// Single source of truth — the getter/setter exists only for backward
|
|
1630
|
-
// compat with code that reads _rebalanceState directly.
|
|
1631
|
-
get _rebalanceState() {
|
|
1632
|
-
return this._state.getRebalanceState();
|
|
1633
|
-
}
|
|
1634
|
-
set _rebalanceState(value) {
|
|
1635
|
-
this._state.setRebalanceState(value);
|
|
1636
|
-
}
|
|
1637
1522
|
get _lastIllegalState() {
|
|
1638
|
-
return this.
|
|
1523
|
+
return this._illegalStateSignal || null;
|
|
1639
1524
|
}
|
|
1640
1525
|
set _lastIllegalState(value) {
|
|
1641
1526
|
if (value) {
|
|
1642
|
-
this.
|
|
1527
|
+
this._illegalStateSignal = {
|
|
1528
|
+
...value,
|
|
1529
|
+
at: Date.now()
|
|
1530
|
+
};
|
|
1643
1531
|
}
|
|
1644
1532
|
}
|
|
1645
1533
|
get _lastAccountingFailure() {
|
|
1646
|
-
return this.
|
|
1534
|
+
return this._accountingFailureSignal || null;
|
|
1647
1535
|
}
|
|
1648
1536
|
set _lastAccountingFailure(value) {
|
|
1649
1537
|
if (value) {
|
|
1650
|
-
this.
|
|
1538
|
+
this._accountingFailureSignal = {
|
|
1539
|
+
...value,
|
|
1540
|
+
at: Date.now()
|
|
1541
|
+
};
|
|
1651
1542
|
}
|
|
1652
1543
|
}
|
|
1653
1544
|
get _recoveryState() {
|
|
1654
|
-
return this.
|
|
1545
|
+
return this._recoveryStateValue;
|
|
1655
1546
|
}
|
|
1656
1547
|
set _recoveryState(value) {
|
|
1657
1548
|
const fallback = {
|
|
@@ -1662,19 +1553,19 @@ class OrderManager {
|
|
|
1662
1553
|
lastFailureAt: 0,
|
|
1663
1554
|
structuralResyncRequested: false
|
|
1664
1555
|
};
|
|
1665
|
-
this.
|
|
1556
|
+
this._recoveryStateValue = {
|
|
1666
1557
|
...fallback,
|
|
1667
1558
|
...(value && typeof value === 'object' ? value : {})
|
|
1668
1559
|
};
|
|
1669
1560
|
}
|
|
1670
1561
|
get _gridRegenState() {
|
|
1671
|
-
return this.
|
|
1562
|
+
return this._gridRegenStateValue;
|
|
1672
1563
|
}
|
|
1673
1564
|
set _gridRegenState(value) {
|
|
1674
1565
|
if (!value || typeof value !== 'object')
|
|
1675
1566
|
return;
|
|
1676
1567
|
const defaultSide = { armed: true, lastTriggeredAt: 0 };
|
|
1677
|
-
this.
|
|
1568
|
+
this._gridRegenStateValue = {
|
|
1678
1569
|
buy: { ...defaultSide, ...(value.buy || {}) },
|
|
1679
1570
|
sell: { ...defaultSide, ...(value.sell || {}) }
|
|
1680
1571
|
};
|