dexbot 1.2.0 → 1.2.2
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/bitshares-native/lru_cache.d.ts +9 -0
- package/dist/modules/bitshares-native/lru_cache.d.ts.map +1 -1
- package/dist/modules/bitshares-native/lru_cache.js +12 -0
- package/dist/modules/bitshares-native/lru_cache.js.map +1 -1
- package/dist/modules/bitshares-native/tx/builder.d.ts +4 -0
- package/dist/modules/bitshares-native/tx/builder.d.ts.map +1 -1
- package/dist/modules/bitshares-native/tx/builder.js +19 -0
- package/dist/modules/bitshares-native/tx/builder.js.map +1 -1
- package/dist/modules/bitshares-native/tx/tx_cache.d.ts +7 -0
- package/dist/modules/bitshares-native/tx/tx_cache.d.ts.map +1 -1
- package/dist/modules/bitshares-native/tx/tx_cache.js +11 -0
- package/dist/modules/bitshares-native/tx/tx_cache.js.map +1 -1
- package/dist/modules/bitshares_client.d.ts.map +1 -1
- package/dist/modules/bitshares_client.js +5 -0
- package/dist/modules/bitshares_client.js.map +1 -1
- package/dist/modules/constants.d.ts +1 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +1 -0
- package/dist/modules/constants.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 +0 -1
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +119 -57
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +3 -0
- package/dist/modules/dexbot_fill_runtime.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/node_manager.d.ts +1 -0
- package/dist/modules/node_manager.d.ts.map +1 -1
- package/dist/modules/node_manager.js +11 -1
- package/dist/modules/node_manager.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 +29 -16
- 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/index.d.ts +3 -8
- package/dist/modules/order/index.d.ts.map +1 -1
- package/dist/modules/order/index.js +3 -10
- package/dist/modules/order/index.js.map +1 -1
- package/dist/modules/order/manager.d.ts +84 -16
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +131 -235
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/strategy.js +1 -1
- package/dist/modules/order/strategy.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 +53 -20
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +28 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +40 -2
- package/dist/modules/order/utils/math.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/dist/modules/utils/math_utils.d.ts +3 -6
- package/dist/modules/utils/math_utils.d.ts.map +1 -1
- package/dist/modules/utils/math_utils.js +1 -14
- package/dist/modules/utils/math_utils.js.map +1 -1
- package/dist/{modules/order → scripts}/runner.d.ts +15 -9
- package/dist/scripts/runner.d.ts.map +1 -0
- package/dist/{modules/order → scripts}/runner.js +24 -45
- package/dist/scripts/runner.js.map +1 -0
- package/package.json +1 -2
- package/scripts/README.md +13 -0
- package/dist/modules/cli_whitelist_args.d.ts +0 -6
- package/dist/modules/cli_whitelist_args.d.ts.map +0 -1
- package/dist/modules/cli_whitelist_args.js +0 -13
- package/dist/modules/cli_whitelist_args.js.map +0 -1
- package/dist/modules/order/runner.d.ts.map +0 -1
- package/dist/modules/order/runner.js.map +0 -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;
|
|
@@ -368,7 +192,9 @@ class OrderManager {
|
|
|
368
192
|
_gridPersistenceSuspendedReason;
|
|
369
193
|
_pendingBroadcasts;
|
|
370
194
|
_committedOrderIds;
|
|
195
|
+
_committedOrderIdsBuiltAt;
|
|
371
196
|
_gridDirtyAt;
|
|
197
|
+
_orphanFillsCreditedAt;
|
|
372
198
|
_pendingRecovery;
|
|
373
199
|
_metrics;
|
|
374
200
|
_currentWorkingGrid;
|
|
@@ -395,7 +221,23 @@ class OrderManager {
|
|
|
395
221
|
this.accountant = new Accountant(this);
|
|
396
222
|
this.strategy = new StrategyEngine(this);
|
|
397
223
|
this.sync = new SyncEngine(this);
|
|
398
|
-
this.
|
|
224
|
+
this._rebalanceState = REBALANCE_STATES.NORMAL;
|
|
225
|
+
this._bootstrapping = false;
|
|
226
|
+
this._broadcastingFlag = false;
|
|
227
|
+
this._broadcastingStartedAt = 0;
|
|
228
|
+
this._illegalStateSignal = null;
|
|
229
|
+
this._accountingFailureSignal = null;
|
|
230
|
+
this._recoveryStateValue = {
|
|
231
|
+
phase: 'idle',
|
|
232
|
+
attemptCount: 0,
|
|
233
|
+
lastAttemptAt: 0,
|
|
234
|
+
inFlight: false,
|
|
235
|
+
lastFailureAt: 0
|
|
236
|
+
};
|
|
237
|
+
this._gridRegenStateValue = {
|
|
238
|
+
buy: { armed: true, lastTriggeredAt: 0 },
|
|
239
|
+
sell: { armed: true, lastTriggeredAt: 0 }
|
|
240
|
+
};
|
|
399
241
|
// Index Sets use mutable mutation patterns controlled via _applyOrderUpdate
|
|
400
242
|
// These are private implementation details and must NOT be mutated directly
|
|
401
243
|
// All external code must go through the COW pipeline
|
|
@@ -430,7 +272,7 @@ class OrderManager {
|
|
|
430
272
|
// Level 2: _gridLock (grid mutations)
|
|
431
273
|
// Level 3: _syncLock (sync operations)
|
|
432
274
|
// Level 4: _fundLock (fund operations — innermost)
|
|
433
|
-
//
|
|
275
|
+
// Note: AsyncLock IS re-entrant (acquire detects nested calls via _holding).
|
|
434
276
|
this._divergenceLock = new AsyncLock({ level: 0 });
|
|
435
277
|
this._fillProcessingLock = new AsyncLock({ level: 1 });
|
|
436
278
|
this._gridLock = new AsyncLock({ level: 2 });
|
|
@@ -450,7 +292,9 @@ class OrderManager {
|
|
|
450
292
|
this._gridPersistenceSuspendedReason = null;
|
|
451
293
|
this._pendingBroadcasts = new Map();
|
|
452
294
|
this._committedOrderIds = new Set();
|
|
295
|
+
this._committedOrderIdsBuiltAt = 0;
|
|
453
296
|
this._gridDirtyAt = null;
|
|
297
|
+
this._orphanFillsCreditedAt = null;
|
|
454
298
|
this._pendingRecovery = null;
|
|
455
299
|
this._metrics = {
|
|
456
300
|
fundRecalcCount: 0,
|
|
@@ -461,7 +305,8 @@ class OrderManager {
|
|
|
461
305
|
lastSyncDurationMs: 0,
|
|
462
306
|
metricsStartTime: Date.now()
|
|
463
307
|
};
|
|
464
|
-
this.
|
|
308
|
+
this._bootstrapping = true;
|
|
309
|
+
this.logger?.log('[BOOTSTRAP] Started', 'debug');
|
|
465
310
|
this._currentWorkingGrid = null;
|
|
466
311
|
this._cowEngine = null;
|
|
467
312
|
this._cleanExpiredLocks();
|
|
@@ -479,48 +324,77 @@ class OrderManager {
|
|
|
479
324
|
}
|
|
480
325
|
_clearWorkingGridRef() {
|
|
481
326
|
this._currentWorkingGrid = null;
|
|
482
|
-
this.
|
|
483
|
-
this._state.setRebalanceState(REBALANCE_STATES.NORMAL);
|
|
327
|
+
this._rebalanceState = REBALANCE_STATES.NORMAL;
|
|
484
328
|
}
|
|
485
329
|
_setRebalanceState(state) {
|
|
486
|
-
this.
|
|
330
|
+
this._rebalanceState = state;
|
|
331
|
+
this.logger?.log(`[COW] Rebalance state: ${state}`, 'debug');
|
|
487
332
|
}
|
|
488
333
|
/**
|
|
489
334
|
* @returns {boolean}
|
|
490
335
|
*/
|
|
491
336
|
isRebalancing() {
|
|
492
|
-
return this.
|
|
337
|
+
return this._rebalanceState === REBALANCE_STATES.REBALANCING;
|
|
493
338
|
}
|
|
494
339
|
/**
|
|
495
340
|
* @returns {boolean}
|
|
496
341
|
*/
|
|
497
342
|
isBroadcasting() {
|
|
498
|
-
return this.
|
|
343
|
+
return this._rebalanceState === REBALANCE_STATES.BROADCASTING;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* @returns {boolean}
|
|
347
|
+
*/
|
|
348
|
+
isBootstrapping() {
|
|
349
|
+
return this._bootstrapping;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Whether a broadcast is currently active (no side-effects).
|
|
353
|
+
* @returns {boolean}
|
|
354
|
+
*/
|
|
355
|
+
isBroadcastingActive() {
|
|
356
|
+
return this._broadcastingFlag;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Auto-clears a stale broadcast flag after 120s so a hung
|
|
360
|
+
* broadcast cannot permanently block rebalancing.
|
|
361
|
+
* Separated from isBroadcastingActive() so the side-effect only
|
|
362
|
+
* runs from one well-defined location (maintenance logic) instead
|
|
363
|
+
* of from every read call-site.
|
|
364
|
+
* @returns {void}
|
|
365
|
+
*/
|
|
366
|
+
_clearStaleBroadcastFlag() {
|
|
367
|
+
if (this._broadcastingFlag && this._broadcastingStartedAt > 0) {
|
|
368
|
+
const elapsed = Date.now() - this._broadcastingStartedAt;
|
|
369
|
+
if (elapsed > 120000) {
|
|
370
|
+
this.logger?.log?.('[BROADCAST] Auto-clearing stale broadcast flag after 120s', 'warn');
|
|
371
|
+
this._broadcastingFlag = false;
|
|
372
|
+
this._broadcastingStartedAt = 0;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
499
375
|
}
|
|
500
376
|
/**
|
|
501
377
|
* 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
378
|
* @returns {boolean}
|
|
507
379
|
*/
|
|
508
380
|
isPlanningActive() {
|
|
509
|
-
return this.isRebalancing() || this.
|
|
381
|
+
return this.isRebalancing() || this.isBroadcastingActive();
|
|
510
382
|
}
|
|
511
383
|
/**
|
|
512
384
|
* @returns {void}
|
|
513
385
|
*/
|
|
514
386
|
startBootstrap() {
|
|
515
|
-
this.
|
|
387
|
+
this._bootstrapping = true;
|
|
388
|
+
this.logger?.log('[BOOTSTRAP] Started', 'debug');
|
|
516
389
|
}
|
|
517
390
|
/**
|
|
518
391
|
* @returns {import('./types').BootstrapResult}
|
|
519
392
|
*/
|
|
520
393
|
finishBootstrap() {
|
|
521
394
|
const result = { hadDrift: false, driftInfo: null };
|
|
522
|
-
if (this.
|
|
523
|
-
this.
|
|
395
|
+
if (this._bootstrapping) {
|
|
396
|
+
this._bootstrapping = false;
|
|
397
|
+
this.logger?.log('[BOOTSTRAP] Finished', 'debug');
|
|
524
398
|
// Validate fund state at bootstrap completion - if drift exists here,
|
|
525
399
|
// it's not transient (grid is now stable) and indicates a potential bug
|
|
526
400
|
const driftCheck = this.checkFundDriftAfterFills();
|
|
@@ -538,13 +412,16 @@ class OrderManager {
|
|
|
538
412
|
* @returns {void}
|
|
539
413
|
*/
|
|
540
414
|
startBroadcasting() {
|
|
541
|
-
this.
|
|
415
|
+
this._broadcastingFlag = true;
|
|
416
|
+
this._broadcastingStartedAt = Date.now();
|
|
417
|
+
this.logger?.log?.('[BROADCAST] Flag set — fill processing will be deferred until stopBroadcasting()', 'debug');
|
|
542
418
|
}
|
|
543
419
|
/**
|
|
544
420
|
* @returns {void}
|
|
545
421
|
*/
|
|
546
422
|
stopBroadcasting() {
|
|
547
|
-
this.
|
|
423
|
+
this._broadcastingFlag = false;
|
|
424
|
+
this._broadcastingStartedAt = 0;
|
|
548
425
|
}
|
|
549
426
|
/**
|
|
550
427
|
* @returns {void}
|
|
@@ -765,6 +642,9 @@ class OrderManager {
|
|
|
765
642
|
*/
|
|
766
643
|
pauseRecalcLogging() {
|
|
767
644
|
this._pauseRecalcLogging = true;
|
|
645
|
+
if (this._pauseRecalcLoggingWatchdog) {
|
|
646
|
+
clearTimeout(this._pauseRecalcLoggingWatchdog);
|
|
647
|
+
}
|
|
768
648
|
this._pauseRecalcLoggingWatchdog = setTimeout(() => {
|
|
769
649
|
this.logger?.log?.(`[MANAGER] pauseRecalcLogging safety watchdog: forcing resume after ${TIMING.SAFETY_PAUSE_TIMEOUT_MS}ms`, 'warn');
|
|
770
650
|
this._pauseRecalcLogging = false;
|
|
@@ -895,11 +775,12 @@ class OrderManager {
|
|
|
895
775
|
const fatalError = validation.errors.find(e => e.isFatal || e.code === 'ILLEGAL_SPREAD_STATE');
|
|
896
776
|
if (fatalError) {
|
|
897
777
|
this.logger.log(fatalError.message, 'error');
|
|
898
|
-
this.
|
|
778
|
+
this._illegalStateSignal = {
|
|
899
779
|
id: order.id,
|
|
900
780
|
context,
|
|
901
|
-
message: fatalError.message
|
|
902
|
-
|
|
781
|
+
message: fatalError.message,
|
|
782
|
+
at: Date.now()
|
|
783
|
+
};
|
|
903
784
|
if (this._throwOnIllegalState) {
|
|
904
785
|
const err = new Error(fatalError.message);
|
|
905
786
|
err.code = fatalError.code;
|
|
@@ -1217,13 +1098,17 @@ class OrderManager {
|
|
|
1217
1098
|
* @returns {Object|null} The consumed signal or null
|
|
1218
1099
|
*/
|
|
1219
1100
|
consumeIllegalStateSignal() {
|
|
1220
|
-
|
|
1101
|
+
const signal = this._illegalStateSignal;
|
|
1102
|
+
this._illegalStateSignal = null;
|
|
1103
|
+
return signal;
|
|
1221
1104
|
}
|
|
1222
1105
|
/**
|
|
1223
1106
|
* @returns {Object|null} The consumed signal or null
|
|
1224
1107
|
*/
|
|
1225
1108
|
consumeAccountingFailureSignal() {
|
|
1226
|
-
|
|
1109
|
+
const signal = this._accountingFailureSignal;
|
|
1110
|
+
this._accountingFailureSignal = null;
|
|
1111
|
+
return signal;
|
|
1227
1112
|
}
|
|
1228
1113
|
/**
|
|
1229
1114
|
* @param {Object} BitShares - BitShares API instance
|
|
@@ -1289,16 +1174,14 @@ class OrderManager {
|
|
|
1289
1174
|
if (recoveryInFlight) {
|
|
1290
1175
|
reasons.push('recovery sync in-flight');
|
|
1291
1176
|
}
|
|
1292
|
-
if (broadcasting || this.
|
|
1177
|
+
if (broadcasting || this.isBroadcastingActive()) {
|
|
1293
1178
|
reasons.push('broadcasting active orders');
|
|
1294
1179
|
}
|
|
1295
1180
|
if (reasons.length > 0 && !this._pipelineBlockedSince) {
|
|
1296
1181
|
this._pipelineBlockedSince = Date.now();
|
|
1297
|
-
this._state.markPipelineBlocked();
|
|
1298
1182
|
}
|
|
1299
1183
|
else if (reasons.length === 0) {
|
|
1300
1184
|
this._pipelineBlockedSince = null;
|
|
1301
|
-
this._state.markPipelineClear();
|
|
1302
1185
|
}
|
|
1303
1186
|
return {
|
|
1304
1187
|
isEmpty: reasons.length === 0,
|
|
@@ -1316,7 +1199,6 @@ class OrderManager {
|
|
|
1316
1199
|
return false;
|
|
1317
1200
|
this.ordersNeedingPriceCorrection = [];
|
|
1318
1201
|
this._pipelineBlockedSince = null;
|
|
1319
|
-
this._state.markPipelineClear();
|
|
1320
1202
|
return true;
|
|
1321
1203
|
}
|
|
1322
1204
|
/**
|
|
@@ -1492,6 +1374,7 @@ class OrderManager {
|
|
|
1492
1374
|
newCommittedIds.add(order.orderId);
|
|
1493
1375
|
}
|
|
1494
1376
|
this._committedOrderIds = newCommittedIds;
|
|
1377
|
+
this._committedOrderIdsBuiltAt = Date.now();
|
|
1495
1378
|
const freshIndexes = workingGrid.getIndexes();
|
|
1496
1379
|
this._ordersByState = {
|
|
1497
1380
|
[ORDER_STATES.VIRTUAL]: freshIndexes[ORDER_STATES.VIRTUAL] || new Set(),
|
|
@@ -1510,6 +1393,13 @@ class OrderManager {
|
|
|
1510
1393
|
}
|
|
1511
1394
|
try {
|
|
1512
1395
|
if (!skipRecalc) {
|
|
1396
|
+
// If the last accounting failure was due to stale accountTotals,
|
|
1397
|
+
// the commit proceeded without locking funds. Refresh accountTotals
|
|
1398
|
+
// before recalculateFunds() to avoid a false-positive drift recovery.
|
|
1399
|
+
if (this._lastAccountingFailure?.reason === 'stale') {
|
|
1400
|
+
this.logger.log('[COW] Stale accountTotals during commit; refreshing before recalculateFunds to avoid false recovery', 'warn');
|
|
1401
|
+
await this.fetchAccountTotals(this.accountId);
|
|
1402
|
+
}
|
|
1513
1403
|
await this.recalculateFunds();
|
|
1514
1404
|
}
|
|
1515
1405
|
const duration = Date.now() - startTime;
|
|
@@ -1520,8 +1410,10 @@ class OrderManager {
|
|
|
1520
1410
|
}
|
|
1521
1411
|
catch (recalcErr) {
|
|
1522
1412
|
this.logger.log(`[COW] Fund recalculation failed post-commit: ${recalcErr.message}`, 'error');
|
|
1523
|
-
|
|
1524
|
-
|
|
1413
|
+
if (!this._recoveryStateValue) {
|
|
1414
|
+
this._recoveryStateValue = { phase: 'idle', attemptCount: 0, lastAttemptAt: 0, inFlight: false, lastFailureAt: 0 };
|
|
1415
|
+
}
|
|
1416
|
+
this._recoveryStateValue.lastFailureAt = Date.now();
|
|
1525
1417
|
}
|
|
1526
1418
|
finally {
|
|
1527
1419
|
this._clearWorkingGridRef();
|
|
@@ -1536,7 +1428,7 @@ class OrderManager {
|
|
|
1536
1428
|
validateGridStateForPersistence(options = {}) {
|
|
1537
1429
|
const result = validateGridForPersistence(this.orders, this.accountTotals);
|
|
1538
1430
|
const allowBootstrapTransient = options.allowBootstrapTransient !== false;
|
|
1539
|
-
if (!result.isValid && allowBootstrapTransient && this.
|
|
1431
|
+
if (!result.isValid && allowBootstrapTransient && this._bootstrapping) {
|
|
1540
1432
|
this.logger.log(`[BOOTSTRAP] Transient state (expected): ${result.reason}`, 'debug');
|
|
1541
1433
|
return { isValid: true, reason: null };
|
|
1542
1434
|
}
|
|
@@ -1596,7 +1488,14 @@ class OrderManager {
|
|
|
1596
1488
|
getMetrics() {
|
|
1597
1489
|
return {
|
|
1598
1490
|
...this._metrics,
|
|
1599
|
-
state:
|
|
1491
|
+
state: {
|
|
1492
|
+
rebalance: { state: this._rebalanceState, currentWorkingGrid: null },
|
|
1493
|
+
recovery: { ...this._recoveryStateValue },
|
|
1494
|
+
gridRegen: { ...this._gridRegenStateValue },
|
|
1495
|
+
bootstrap: { isBootstrapping: this._bootstrapping },
|
|
1496
|
+
broadcast: { isBroadcasting: this._broadcastingFlag, startedAt: this._broadcastingStartedAt },
|
|
1497
|
+
pipeline: { blockedSince: this._pipelineBlockedSince, recoveryAttempted: this._recoveryAttempted }
|
|
1498
|
+
},
|
|
1600
1499
|
currentTime: Date.now()
|
|
1601
1500
|
};
|
|
1602
1501
|
}
|
|
@@ -1625,33 +1524,30 @@ class OrderManager {
|
|
|
1625
1524
|
comparePrecisions
|
|
1626
1525
|
});
|
|
1627
1526
|
}
|
|
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
1527
|
get _lastIllegalState() {
|
|
1638
|
-
return this.
|
|
1528
|
+
return this._illegalStateSignal || null;
|
|
1639
1529
|
}
|
|
1640
1530
|
set _lastIllegalState(value) {
|
|
1641
1531
|
if (value) {
|
|
1642
|
-
this.
|
|
1532
|
+
this._illegalStateSignal = {
|
|
1533
|
+
...value,
|
|
1534
|
+
at: Date.now()
|
|
1535
|
+
};
|
|
1643
1536
|
}
|
|
1644
1537
|
}
|
|
1645
1538
|
get _lastAccountingFailure() {
|
|
1646
|
-
return this.
|
|
1539
|
+
return this._accountingFailureSignal || null;
|
|
1647
1540
|
}
|
|
1648
1541
|
set _lastAccountingFailure(value) {
|
|
1649
1542
|
if (value) {
|
|
1650
|
-
this.
|
|
1543
|
+
this._accountingFailureSignal = {
|
|
1544
|
+
...value,
|
|
1545
|
+
at: Date.now()
|
|
1546
|
+
};
|
|
1651
1547
|
}
|
|
1652
1548
|
}
|
|
1653
1549
|
get _recoveryState() {
|
|
1654
|
-
return this.
|
|
1550
|
+
return this._recoveryStateValue;
|
|
1655
1551
|
}
|
|
1656
1552
|
set _recoveryState(value) {
|
|
1657
1553
|
const fallback = {
|
|
@@ -1662,19 +1558,19 @@ class OrderManager {
|
|
|
1662
1558
|
lastFailureAt: 0,
|
|
1663
1559
|
structuralResyncRequested: false
|
|
1664
1560
|
};
|
|
1665
|
-
this.
|
|
1561
|
+
this._recoveryStateValue = {
|
|
1666
1562
|
...fallback,
|
|
1667
1563
|
...(value && typeof value === 'object' ? value : {})
|
|
1668
1564
|
};
|
|
1669
1565
|
}
|
|
1670
1566
|
get _gridRegenState() {
|
|
1671
|
-
return this.
|
|
1567
|
+
return this._gridRegenStateValue;
|
|
1672
1568
|
}
|
|
1673
1569
|
set _gridRegenState(value) {
|
|
1674
1570
|
if (!value || typeof value !== 'object')
|
|
1675
1571
|
return;
|
|
1676
1572
|
const defaultSide = { armed: true, lastTriggeredAt: 0 };
|
|
1677
|
-
this.
|
|
1573
|
+
this._gridRegenStateValue = {
|
|
1678
1574
|
buy: { ...defaultSide, ...(value.buy || {}) },
|
|
1679
1575
|
sell: { ...defaultSide, ...(value.sell || {}) }
|
|
1680
1576
|
};
|