dexbot 1.1.14 → 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/README.md +1 -0
- 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/market_adapter/market_adapter.js +1 -1
- package/dist/market_adapter/market_adapter.js.map +1 -1
- package/dist/modules/bitshares-native/lru_cache.d.ts +20 -0
- package/dist/modules/bitshares-native/lru_cache.d.ts.map +1 -0
- package/dist/modules/bitshares-native/lru_cache.js +45 -0
- package/dist/modules/bitshares-native/lru_cache.js.map +1 -0
- package/dist/modules/bitshares-native/resolvers.d.ts +2 -18
- package/dist/modules/bitshares-native/resolvers.d.ts.map +1 -1
- package/dist/modules/bitshares-native/resolvers.js +2 -41
- package/dist/modules/bitshares-native/resolvers.js.map +1 -1
- package/dist/modules/bitshares-native/serial/chain_constants.d.ts +1 -0
- package/dist/modules/bitshares-native/serial/chain_constants.d.ts.map +1 -1
- package/dist/modules/bitshares-native/serial/chain_constants.js +3 -0
- package/dist/modules/bitshares-native/serial/chain_constants.js.map +1 -1
- package/dist/modules/bitshares-native/signing_client.d.ts.map +1 -1
- package/dist/modules/bitshares-native/signing_client.js +12 -1
- package/dist/modules/bitshares-native/signing_client.js.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.js +42 -3
- package/dist/modules/bitshares-native/subscriptions.js.map +1 -1
- package/dist/modules/bitshares-native/transport.d.ts.map +1 -1
- package/dist/modules/bitshares-native/transport.js +77 -27
- package/dist/modules/bitshares-native/transport.js.map +1 -1
- package/dist/modules/bitshares-native/tx/builder.d.ts.map +1 -1
- package/dist/modules/bitshares-native/tx/builder.js +26 -16
- package/dist/modules/bitshares-native/tx/builder.js.map +1 -1
- package/dist/modules/bitshares-native/tx/tx_cache.d.ts +17 -0
- package/dist/modules/bitshares-native/tx/tx_cache.d.ts.map +1 -0
- package/dist/modules/bitshares-native/tx/tx_cache.js +62 -0
- package/dist/modules/bitshares-native/tx/tx_cache.js.map +1 -0
- package/dist/modules/bot_settings.d.ts.map +1 -1
- package/dist/modules/bot_settings.js +2 -1
- package/dist/modules/bot_settings.js.map +1 -1
- package/dist/modules/chain_orders.d.ts +4 -1
- package/dist/modules/chain_orders.d.ts.map +1 -1
- package/dist/modules/chain_orders.js +30 -1
- package/dist/modules/chain_orders.js.map +1 -1
- package/dist/modules/config.d.ts +6 -0
- package/dist/modules/config.d.ts.map +1 -1
- package/dist/modules/config.js +11 -0
- package/dist/modules/config.js.map +1 -1
- package/dist/modules/constants.d.ts +14 -1
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +64 -6
- 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/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +40 -29
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +6 -12
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +155 -81
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +1 -5
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +23 -48
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/launcher/child_env.d.ts.map +1 -1
- package/dist/modules/launcher/child_env.js +1 -0
- package/dist/modules/launcher/child_env.js.map +1 -1
- package/dist/modules/launcher/launch_modes.d.ts +2 -0
- package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
- package/dist/modules/launcher/launch_modes.js +7 -6
- package/dist/modules/launcher/launch_modes.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 +64 -26
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/async_lock.d.ts +12 -4
- package/dist/modules/order/async_lock.d.ts.map +1 -1
- package/dist/modules/order/async_lock.js +27 -5
- package/dist/modules/order/async_lock.js.map +1 -1
- package/dist/modules/order/grid.d.ts +434 -475
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +1612 -1592
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts +3 -73
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +11 -843
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +8 -0
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -0
- package/dist/modules/order/grid_reconcile_internal.js +782 -0
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -0
- package/dist/modules/order/logger.d.ts +1 -1
- package/dist/modules/order/logger.d.ts.map +1 -1
- package/dist/modules/order/logger.js +26 -2
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts +118 -15
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +206 -236
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/runner.js +2 -2
- package/dist/modules/order/runner.js.map +1 -1
- package/dist/modules/order/strategy.js +3 -3
- package/dist/modules/order/strategy.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts +3 -4
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +85 -32
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +17 -0
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +81 -24
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.js +4 -4
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +6 -8
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/types.d.ts +7 -12
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/scripts/print_grid.js +3 -3
- package/dist/scripts/print_grid.js.map +1 -1
- package/dist/scripts/test-credit-renewal.js +1 -1
- package/dist/scripts/test-credit-renewal.js.map +1 -1
- package/dist/unlock.d.ts.map +1 -1
- package/dist/unlock.js +26 -12
- package/dist/unlock.js.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
|
// ===============================================================================
|
|
@@ -29,7 +28,7 @@ const AsyncLock = require('./async_lock');
|
|
|
29
28
|
const Accountant = require('./accounting');
|
|
30
29
|
const StrategyEngine = require('./strategy');
|
|
31
30
|
const SyncEngine = require('./sync_engine');
|
|
32
|
-
const
|
|
31
|
+
const { calculateCurrentSpread, checkSpreadCondition, checkGridHealth } = require('./grid');
|
|
33
32
|
const Format = require('./format');
|
|
34
33
|
const { toFiniteNumber, isValidNumber } = Format;
|
|
35
34
|
// ===============================================================================
|
|
@@ -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,186 +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
|
-
attemptCount: 0,
|
|
163
|
-
lastAttemptAt: 0,
|
|
164
|
-
inFlight: false,
|
|
165
|
-
lastFailureAt: 0
|
|
166
|
-
};
|
|
167
|
-
this.gridRegen = {
|
|
168
|
-
buy: { armed: true, lastTriggeredAt: 0 },
|
|
169
|
-
sell: { armed: true, lastTriggeredAt: 0 }
|
|
170
|
-
};
|
|
171
|
-
this.bootstrap = {
|
|
172
|
-
isBootstrapping: false
|
|
173
|
-
};
|
|
174
|
-
this.broadcast = {
|
|
175
|
-
isBroadcasting: false,
|
|
176
|
-
startedAt: 0,
|
|
177
|
-
};
|
|
178
|
-
this.signals = {
|
|
179
|
-
lastIllegalState: null,
|
|
180
|
-
lastAccountingFailure: null
|
|
181
|
-
};
|
|
182
|
-
this.pipeline = {
|
|
183
|
-
blockedSince: null,
|
|
184
|
-
recoveryAttempted: false
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
getRebalanceState() {
|
|
188
|
-
return this.rebalance.state;
|
|
189
|
-
}
|
|
190
|
-
setRebalanceState(state) {
|
|
191
|
-
this.rebalance.state = state;
|
|
192
|
-
this.logger?.log(`[COW] Rebalance state: ${state}`, 'debug');
|
|
193
|
-
}
|
|
194
|
-
isRebalancing() {
|
|
195
|
-
return this.rebalance.state === REBALANCE_STATES.REBALANCING;
|
|
196
|
-
}
|
|
197
|
-
isBroadcasting() {
|
|
198
|
-
return this.rebalance.state === REBALANCE_STATES.BROADCASTING;
|
|
199
|
-
}
|
|
200
|
-
setWorkingGrid(workingGrid) {
|
|
201
|
-
this.rebalance.currentWorkingGrid = workingGrid;
|
|
202
|
-
}
|
|
203
|
-
getWorkingGrid() {
|
|
204
|
-
return this.rebalance.currentWorkingGrid;
|
|
205
|
-
}
|
|
206
|
-
clearWorkingGrid() {
|
|
207
|
-
this.rebalance.currentWorkingGrid = null;
|
|
208
|
-
}
|
|
209
|
-
recordRecoveryAttempt() {
|
|
210
|
-
this.recovery.attemptCount++;
|
|
211
|
-
this.recovery.lastAttemptAt = Date.now();
|
|
212
|
-
this.recovery.inFlight = true;
|
|
213
|
-
}
|
|
214
|
-
completeRecovery(success) {
|
|
215
|
-
this.recovery.inFlight = false;
|
|
216
|
-
if (!success) {
|
|
217
|
-
this.recovery.lastFailureAt = Date.now();
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
isRecoveryInFlight() {
|
|
221
|
-
return this.recovery.inFlight;
|
|
222
|
-
}
|
|
223
|
-
getRecoveryStats() {
|
|
224
|
-
return { ...this.recovery };
|
|
225
|
-
}
|
|
226
|
-
isSideArmed(side) {
|
|
227
|
-
return this.gridRegen[side]?.armed ?? false;
|
|
228
|
-
}
|
|
229
|
-
disarmSide(side) {
|
|
230
|
-
if (this.gridRegen[side]) {
|
|
231
|
-
this.gridRegen[side].armed = false;
|
|
232
|
-
this.gridRegen[side].lastTriggeredAt = Date.now();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
armSide(side) {
|
|
236
|
-
if (this.gridRegen[side]) {
|
|
237
|
-
this.gridRegen[side].armed = true;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
startBootstrap() {
|
|
241
|
-
this.bootstrap.isBootstrapping = true;
|
|
242
|
-
this.logger?.log('[BOOTSTRAP] Started', 'debug');
|
|
243
|
-
}
|
|
244
|
-
finishBootstrap() {
|
|
245
|
-
this.bootstrap.isBootstrapping = false;
|
|
246
|
-
this.logger?.log('[BOOTSTRAP] Finished', 'debug');
|
|
247
|
-
}
|
|
248
|
-
isBootstrapping() {
|
|
249
|
-
return this.bootstrap.isBootstrapping;
|
|
250
|
-
}
|
|
251
|
-
startBroadcasting() {
|
|
252
|
-
this.broadcast.isBroadcasting = true;
|
|
253
|
-
this.broadcast.startedAt = Date.now();
|
|
254
|
-
this.logger?.log?.('[BROADCAST] Flag set — fill processing will be deferred until stopBroadcasting()', 'debug');
|
|
255
|
-
}
|
|
256
|
-
stopBroadcasting() {
|
|
257
|
-
this.broadcast.isBroadcasting = false;
|
|
258
|
-
this.broadcast.startedAt = 0;
|
|
259
|
-
}
|
|
260
|
-
isBroadcastingActive() {
|
|
261
|
-
if (this.broadcast.isBroadcasting && this.broadcast.startedAt > 0) {
|
|
262
|
-
const elapsed = Date.now() - this.broadcast.startedAt;
|
|
263
|
-
if (elapsed > 120000) {
|
|
264
|
-
this.logger?.log?.('[BROADCAST] Auto-clearing stale broadcast flag after 120s', 'warn');
|
|
265
|
-
this.broadcast.isBroadcasting = false;
|
|
266
|
-
this.broadcast.startedAt = 0;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return this.broadcast.isBroadcasting;
|
|
270
|
-
}
|
|
271
|
-
setIllegalStateSignal(signal) {
|
|
272
|
-
this.signals.lastIllegalState = {
|
|
273
|
-
...signal,
|
|
274
|
-
at: Date.now()
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
consumeIllegalStateSignal() {
|
|
278
|
-
const signal = this.signals.lastIllegalState;
|
|
279
|
-
this.signals.lastIllegalState = null;
|
|
280
|
-
return signal;
|
|
281
|
-
}
|
|
282
|
-
setAccountingFailureSignal(signal) {
|
|
283
|
-
this.signals.lastAccountingFailure = {
|
|
284
|
-
...signal,
|
|
285
|
-
at: Date.now()
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
consumeAccountingFailureSignal() {
|
|
289
|
-
const signal = this.signals.lastAccountingFailure;
|
|
290
|
-
this.signals.lastAccountingFailure = null;
|
|
291
|
-
return signal;
|
|
292
|
-
}
|
|
293
|
-
markPipelineBlocked() {
|
|
294
|
-
if (!this.pipeline.blockedSince) {
|
|
295
|
-
this.pipeline.blockedSince = Date.now();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
markPipelineClear() {
|
|
299
|
-
this.pipeline.blockedSince = null;
|
|
300
|
-
this.pipeline.recoveryAttempted = false;
|
|
301
|
-
}
|
|
302
|
-
getPipelineBlockedDuration() {
|
|
303
|
-
return this.pipeline.blockedSince ? Date.now() - this.pipeline.blockedSince : 0;
|
|
304
|
-
}
|
|
305
|
-
isPipelineBlocked() {
|
|
306
|
-
return this.pipeline.blockedSince !== null;
|
|
307
|
-
}
|
|
308
|
-
getState() {
|
|
309
|
-
return {
|
|
310
|
-
rebalance: { ...this.rebalance, currentWorkingGrid: null },
|
|
311
|
-
recovery: { ...this.recovery },
|
|
312
|
-
gridRegen: { ...this.gridRegen },
|
|
313
|
-
bootstrap: { ...this.bootstrap },
|
|
314
|
-
broadcast: { ...this.broadcast },
|
|
315
|
-
pipeline: { ...this.pipeline }
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
// ===============================================================================
|
|
320
|
-
// SECTION 4: ORDER MANAGER CLASS
|
|
140
|
+
// SECTION 3: ORDER MANAGER CLASS
|
|
321
141
|
// ===============================================================================
|
|
322
142
|
class OrderManager {
|
|
323
143
|
config;
|
|
@@ -329,7 +149,14 @@ class OrderManager {
|
|
|
329
149
|
accountant;
|
|
330
150
|
strategy;
|
|
331
151
|
sync;
|
|
332
|
-
|
|
152
|
+
_rebalanceState;
|
|
153
|
+
_bootstrapping;
|
|
154
|
+
_broadcastingFlag;
|
|
155
|
+
_broadcastingStartedAt;
|
|
156
|
+
_illegalStateSignal;
|
|
157
|
+
_accountingFailureSignal;
|
|
158
|
+
_recoveryStateValue;
|
|
159
|
+
_gridRegenStateValue;
|
|
333
160
|
_ordersByState;
|
|
334
161
|
_ordersByType;
|
|
335
162
|
targetSpreadCount;
|
|
@@ -354,15 +181,19 @@ class OrderManager {
|
|
|
354
181
|
_recentlyRotatedOrderIds;
|
|
355
182
|
_gridSidesUpdated;
|
|
356
183
|
_pauseFundRecalc;
|
|
184
|
+
_pauseFundRecalcWatchdog;
|
|
357
185
|
_pauseRecalcLogging;
|
|
186
|
+
_pauseRecalcLoggingWatchdog;
|
|
358
187
|
_throwOnIllegalState;
|
|
359
188
|
_pipelineBlockedSince;
|
|
360
189
|
_recoveryAttempted;
|
|
190
|
+
_syncGeneration;
|
|
361
191
|
_gridVersion;
|
|
362
192
|
_gridPersistenceSuspendedReason;
|
|
363
193
|
_pendingBroadcasts;
|
|
364
194
|
_committedOrderIds;
|
|
365
195
|
_gridDirtyAt;
|
|
196
|
+
_pendingRecovery;
|
|
366
197
|
_metrics;
|
|
367
198
|
_currentWorkingGrid;
|
|
368
199
|
_cowEngine;
|
|
@@ -388,7 +219,23 @@ class OrderManager {
|
|
|
388
219
|
this.accountant = new Accountant(this);
|
|
389
220
|
this.strategy = new StrategyEngine(this);
|
|
390
221
|
this.sync = new SyncEngine(this);
|
|
391
|
-
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
|
+
};
|
|
392
239
|
// Index Sets use mutable mutation patterns controlled via _applyOrderUpdate
|
|
393
240
|
// These are private implementation details and must NOT be mutated directly
|
|
394
241
|
// All external code must go through the COW pipeline
|
|
@@ -423,7 +270,7 @@ class OrderManager {
|
|
|
423
270
|
// Level 2: _gridLock (grid mutations)
|
|
424
271
|
// Level 3: _syncLock (sync operations)
|
|
425
272
|
// Level 4: _fundLock (fund operations — innermost)
|
|
426
|
-
//
|
|
273
|
+
// Note: AsyncLock IS re-entrant (acquire detects nested calls via _holding).
|
|
427
274
|
this._divergenceLock = new AsyncLock({ level: 0 });
|
|
428
275
|
this._fillProcessingLock = new AsyncLock({ level: 1 });
|
|
429
276
|
this._gridLock = new AsyncLock({ level: 2 });
|
|
@@ -432,15 +279,19 @@ class OrderManager {
|
|
|
432
279
|
this._recentlyRotatedOrderIds = new Set();
|
|
433
280
|
this._gridSidesUpdated = new Set();
|
|
434
281
|
this._pauseFundRecalc = 0;
|
|
282
|
+
this._pauseFundRecalcWatchdog = null;
|
|
435
283
|
this._pauseRecalcLogging = false;
|
|
284
|
+
this._pauseRecalcLoggingWatchdog = null;
|
|
436
285
|
this._throwOnIllegalState = false;
|
|
437
286
|
this._pipelineBlockedSince = null;
|
|
438
287
|
this._recoveryAttempted = false;
|
|
288
|
+
this._syncGeneration = 0;
|
|
439
289
|
this._gridVersion = 0;
|
|
440
290
|
this._gridPersistenceSuspendedReason = null;
|
|
441
291
|
this._pendingBroadcasts = new Map();
|
|
442
292
|
this._committedOrderIds = new Set();
|
|
443
293
|
this._gridDirtyAt = null;
|
|
294
|
+
this._pendingRecovery = null;
|
|
444
295
|
this._metrics = {
|
|
445
296
|
fundRecalcCount: 0,
|
|
446
297
|
invariantViolations: { buy: 0, sell: 0 },
|
|
@@ -450,7 +301,8 @@ class OrderManager {
|
|
|
450
301
|
lastSyncDurationMs: 0,
|
|
451
302
|
metricsStartTime: Date.now()
|
|
452
303
|
};
|
|
453
|
-
this.
|
|
304
|
+
this._bootstrapping = true;
|
|
305
|
+
this.logger?.log('[BOOTSTRAP] Started', 'debug');
|
|
454
306
|
this._currentWorkingGrid = null;
|
|
455
307
|
this._cowEngine = null;
|
|
456
308
|
this._cleanExpiredLocks();
|
|
@@ -468,43 +320,77 @@ class OrderManager {
|
|
|
468
320
|
}
|
|
469
321
|
_clearWorkingGridRef() {
|
|
470
322
|
this._currentWorkingGrid = null;
|
|
471
|
-
this.
|
|
472
|
-
this._state.setRebalanceState(REBALANCE_STATES.NORMAL);
|
|
323
|
+
this._rebalanceState = REBALANCE_STATES.NORMAL;
|
|
473
324
|
}
|
|
474
325
|
_setRebalanceState(state) {
|
|
475
|
-
this.
|
|
326
|
+
this._rebalanceState = state;
|
|
327
|
+
this.logger?.log(`[COW] Rebalance state: ${state}`, 'debug');
|
|
476
328
|
}
|
|
477
329
|
/**
|
|
478
330
|
* @returns {boolean}
|
|
479
331
|
*/
|
|
480
332
|
isRebalancing() {
|
|
481
|
-
return this.
|
|
333
|
+
return this._rebalanceState === REBALANCE_STATES.REBALANCING;
|
|
482
334
|
}
|
|
483
335
|
/**
|
|
484
336
|
* @returns {boolean}
|
|
485
337
|
*/
|
|
486
338
|
isBroadcasting() {
|
|
487
|
-
return this.
|
|
339
|
+
return this._rebalanceState === REBALANCE_STATES.BROADCASTING;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* @returns {boolean}
|
|
343
|
+
*/
|
|
344
|
+
isBootstrapping() {
|
|
345
|
+
return this._bootstrapping;
|
|
488
346
|
}
|
|
489
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
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Whether the manager is in the middle of a rebalance plan or broadcast.
|
|
490
374
|
* @returns {boolean}
|
|
491
375
|
*/
|
|
492
376
|
isPlanningActive() {
|
|
493
|
-
return this.isRebalancing() || this.
|
|
377
|
+
return this.isRebalancing() || this.isBroadcastingActive();
|
|
494
378
|
}
|
|
495
379
|
/**
|
|
496
380
|
* @returns {void}
|
|
497
381
|
*/
|
|
498
382
|
startBootstrap() {
|
|
499
|
-
this.
|
|
383
|
+
this._bootstrapping = true;
|
|
384
|
+
this.logger?.log('[BOOTSTRAP] Started', 'debug');
|
|
500
385
|
}
|
|
501
386
|
/**
|
|
502
387
|
* @returns {import('./types').BootstrapResult}
|
|
503
388
|
*/
|
|
504
389
|
finishBootstrap() {
|
|
505
390
|
const result = { hadDrift: false, driftInfo: null };
|
|
506
|
-
if (this.
|
|
507
|
-
this.
|
|
391
|
+
if (this._bootstrapping) {
|
|
392
|
+
this._bootstrapping = false;
|
|
393
|
+
this.logger?.log('[BOOTSTRAP] Finished', 'debug');
|
|
508
394
|
// Validate fund state at bootstrap completion - if drift exists here,
|
|
509
395
|
// it's not transient (grid is now stable) and indicates a potential bug
|
|
510
396
|
const driftCheck = this.checkFundDriftAfterFills();
|
|
@@ -522,13 +408,16 @@ class OrderManager {
|
|
|
522
408
|
* @returns {void}
|
|
523
409
|
*/
|
|
524
410
|
startBroadcasting() {
|
|
525
|
-
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');
|
|
526
414
|
}
|
|
527
415
|
/**
|
|
528
416
|
* @returns {void}
|
|
529
417
|
*/
|
|
530
418
|
stopBroadcasting() {
|
|
531
|
-
this.
|
|
419
|
+
this._broadcastingFlag = false;
|
|
420
|
+
this._broadcastingStartedAt = 0;
|
|
532
421
|
}
|
|
533
422
|
/**
|
|
534
423
|
* @returns {void}
|
|
@@ -613,7 +502,7 @@ class OrderManager {
|
|
|
613
502
|
});
|
|
614
503
|
}
|
|
615
504
|
async _setAccountTotals(totals) {
|
|
616
|
-
this.accountTotals = { ...(this.accountTotals || {}), ...totals };
|
|
505
|
+
this.accountTotals = { ...(this.accountTotals || {}), ...totals, _lastFetchedAt: Date.now() };
|
|
617
506
|
if (!this.funds)
|
|
618
507
|
this.resetFunds();
|
|
619
508
|
await this._recalculateFunds();
|
|
@@ -678,31 +567,96 @@ class OrderManager {
|
|
|
678
567
|
await this.accountant.recalculateFunds();
|
|
679
568
|
}
|
|
680
569
|
/**
|
|
570
|
+
* Suppress fund recalculation during batch mutations.
|
|
571
|
+
*
|
|
572
|
+
* Use when multiple _applyOrderUpdate calls would each trigger redundant
|
|
573
|
+
* recalculateFunds (e.g. grid load, grid init, fill batch). Must be paired
|
|
574
|
+
* with resumeFundRecalc() in a try/finally block.
|
|
575
|
+
*
|
|
576
|
+
* Supports nesting via depth counter — only the outermost resume triggers
|
|
577
|
+
* the single consolidated recalculation.
|
|
578
|
+
*
|
|
579
|
+
* For bulk mutation sites that also need to suppress debug RECALC logging
|
|
580
|
+
* during the loop, pair this with pauseRecalcLogging()/resumeRecalcLogging().
|
|
581
|
+
*
|
|
582
|
+
* Use independently of pauseRecalcLogging when you need recalc to still run
|
|
583
|
+
* per-operation but want to batch the trigger (e.g. _updateOrdersForSide
|
|
584
|
+
* needs per-call chain-free tracking but suppresses log spam).
|
|
585
|
+
*
|
|
681
586
|
* @returns {void}
|
|
682
587
|
*/
|
|
683
588
|
pauseFundRecalc() {
|
|
684
589
|
this._pauseFundRecalc++;
|
|
590
|
+
// Safety watchdog: if pauseFundRecalc is not resumed within the timeout,
|
|
591
|
+
// force-reset the counter to prevent permanent suppression from a missed
|
|
592
|
+
// finally block. Only set when depth goes from 0 → 1 so only the outermost
|
|
593
|
+
// pause has a watchdog.
|
|
594
|
+
if (this._pauseFundRecalc === 1) {
|
|
595
|
+
if (this._pauseFundRecalcWatchdog) {
|
|
596
|
+
clearTimeout(this._pauseFundRecalcWatchdog);
|
|
597
|
+
}
|
|
598
|
+
this._pauseFundRecalcWatchdog = setTimeout(() => {
|
|
599
|
+
if (this._pauseFundRecalc > 0) {
|
|
600
|
+
this.logger?.log?.(`[MANAGER] pauseFundRecalc safety watchdog: resetting depth from ${this._pauseFundRecalc} to 0`, 'warn');
|
|
601
|
+
this._pauseFundRecalc = 0;
|
|
602
|
+
this._pauseFundRecalcWatchdog = null;
|
|
603
|
+
this._recalculateFunds().catch((err) => {
|
|
604
|
+
this.logger?.log?.(`[MANAGER] Watchdog recalc failed: ${err.message}`, 'error');
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}, TIMING.SAFETY_PAUSE_TIMEOUT_MS);
|
|
608
|
+
}
|
|
685
609
|
}
|
|
686
610
|
/**
|
|
611
|
+
* Resume fund recalculation and trigger one consolidated recalculateFunds()
|
|
612
|
+
* when the outermost resume completes.
|
|
687
613
|
* @returns {Promise<void>}
|
|
688
614
|
*/
|
|
689
615
|
async resumeFundRecalc() {
|
|
690
616
|
this._pauseFundRecalc = Math.max(0, this._pauseFundRecalc - 1);
|
|
691
617
|
if (this._pauseFundRecalc === 0) {
|
|
618
|
+
if (this._pauseFundRecalcWatchdog) {
|
|
619
|
+
clearTimeout(this._pauseFundRecalcWatchdog);
|
|
620
|
+
this._pauseFundRecalcWatchdog = null;
|
|
621
|
+
}
|
|
692
622
|
await this.recalculateFunds();
|
|
693
623
|
}
|
|
694
624
|
}
|
|
695
625
|
/**
|
|
626
|
+
* Suppress only the debug-level [RECALC] log lines inside recalculateFunds().
|
|
627
|
+
*
|
|
628
|
+
* Lighter than pauseFundRecalc — recalculation still runs, only log output
|
|
629
|
+
* is suppressed. Use when individual _updateOrder calls must update chain-free
|
|
630
|
+
* balances but the iterative debug logging would flood output
|
|
631
|
+
* (e.g. _updateOrdersForSide grid resize loop).
|
|
632
|
+
*
|
|
633
|
+
* When paired with pauseFundRecalc/resumeFundRecalc (grid load/init),
|
|
634
|
+
* both calcs and logging are suppressed during the batch. When used alone
|
|
635
|
+
* (_updateOrdersForSide), recalc runs per-call but logs are suppressed.
|
|
636
|
+
*
|
|
696
637
|
* @returns {void}
|
|
697
638
|
*/
|
|
698
639
|
pauseRecalcLogging() {
|
|
699
640
|
this._pauseRecalcLogging = true;
|
|
641
|
+
if (this._pauseRecalcLoggingWatchdog) {
|
|
642
|
+
clearTimeout(this._pauseRecalcLoggingWatchdog);
|
|
643
|
+
}
|
|
644
|
+
this._pauseRecalcLoggingWatchdog = setTimeout(() => {
|
|
645
|
+
this.logger?.log?.(`[MANAGER] pauseRecalcLogging safety watchdog: forcing resume after ${TIMING.SAFETY_PAUSE_TIMEOUT_MS}ms`, 'warn');
|
|
646
|
+
this._pauseRecalcLogging = false;
|
|
647
|
+
this._pauseRecalcLoggingWatchdog = null;
|
|
648
|
+
}, TIMING.SAFETY_PAUSE_TIMEOUT_MS);
|
|
700
649
|
}
|
|
701
650
|
/**
|
|
651
|
+
* Resume recalc debug logging.
|
|
702
652
|
* @returns {void}
|
|
703
653
|
*/
|
|
704
654
|
resumeRecalcLogging() {
|
|
705
655
|
this._pauseRecalcLogging = false;
|
|
656
|
+
if (this._pauseRecalcLoggingWatchdog) {
|
|
657
|
+
clearTimeout(this._pauseRecalcLoggingWatchdog);
|
|
658
|
+
this._pauseRecalcLoggingWatchdog = null;
|
|
659
|
+
}
|
|
706
660
|
}
|
|
707
661
|
/**
|
|
708
662
|
* @param {Array<import('./types').Order>} orders
|
|
@@ -817,11 +771,12 @@ class OrderManager {
|
|
|
817
771
|
const fatalError = validation.errors.find(e => e.isFatal || e.code === 'ILLEGAL_SPREAD_STATE');
|
|
818
772
|
if (fatalError) {
|
|
819
773
|
this.logger.log(fatalError.message, 'error');
|
|
820
|
-
this.
|
|
774
|
+
this._illegalStateSignal = {
|
|
821
775
|
id: order.id,
|
|
822
776
|
context,
|
|
823
|
-
message: fatalError.message
|
|
824
|
-
|
|
777
|
+
message: fatalError.message,
|
|
778
|
+
at: Date.now()
|
|
779
|
+
};
|
|
825
780
|
if (this._throwOnIllegalState) {
|
|
826
781
|
const err = new Error(fatalError.message);
|
|
827
782
|
err.code = fatalError.code;
|
|
@@ -860,7 +815,7 @@ class OrderManager {
|
|
|
860
815
|
if (this._pauseFundRecalc === 0) {
|
|
861
816
|
await this.recalculateFunds();
|
|
862
817
|
}
|
|
863
|
-
this._markGridDirty(
|
|
818
|
+
this._markGridDirty();
|
|
864
819
|
return true;
|
|
865
820
|
}
|
|
866
821
|
_syncWorkingGridFromMasterMutation(orderId, context) {
|
|
@@ -868,7 +823,7 @@ class OrderManager {
|
|
|
868
823
|
return;
|
|
869
824
|
}
|
|
870
825
|
try {
|
|
871
|
-
this._currentWorkingGrid.markStale(`master mutation during ${this._rebalanceState.toLowerCase()} (${context})`);
|
|
826
|
+
this._currentWorkingGrid.markStale(`master mutation during ${(this._rebalanceState || '').toLowerCase()} (${context})`);
|
|
872
827
|
this._currentWorkingGrid.syncFromMaster(this.orders, orderId, this._gridVersion);
|
|
873
828
|
}
|
|
874
829
|
catch (syncErr) {
|
|
@@ -898,11 +853,9 @@ class OrderManager {
|
|
|
898
853
|
* other paths (e.g. raw `this.orders.set(...)` from a refactor) should
|
|
899
854
|
* invoke this explicitly so the dirty-flag invariant still holds.
|
|
900
855
|
*
|
|
901
|
-
* @param {string} [reason='unknown'] - Context label for diagnostics
|
|
902
|
-
* @param {string} [orderId=null] - Order that triggered the mutation
|
|
903
856
|
* @returns {void}
|
|
904
857
|
*/
|
|
905
|
-
_markGridDirty(
|
|
858
|
+
_markGridDirty() {
|
|
906
859
|
if (this._gridDirtyAt == null) {
|
|
907
860
|
this._gridDirtyAt = Date.now();
|
|
908
861
|
}
|
|
@@ -974,7 +927,6 @@ class OrderManager {
|
|
|
974
927
|
const rebalanceResult = await this.performSafeRebalance(orders, excl);
|
|
975
928
|
return rebalanceResult;
|
|
976
929
|
}
|
|
977
|
-
const { WorkingGrid } = require('./working_grid');
|
|
978
930
|
const workingGrid = new WorkingGrid(this.orders, { baseVersion: this._gridVersion });
|
|
979
931
|
return {
|
|
980
932
|
actions: [],
|
|
@@ -1142,13 +1094,17 @@ class OrderManager {
|
|
|
1142
1094
|
* @returns {Object|null} The consumed signal or null
|
|
1143
1095
|
*/
|
|
1144
1096
|
consumeIllegalStateSignal() {
|
|
1145
|
-
|
|
1097
|
+
const signal = this._illegalStateSignal;
|
|
1098
|
+
this._illegalStateSignal = null;
|
|
1099
|
+
return signal;
|
|
1146
1100
|
}
|
|
1147
1101
|
/**
|
|
1148
1102
|
* @returns {Object|null} The consumed signal or null
|
|
1149
1103
|
*/
|
|
1150
1104
|
consumeAccountingFailureSignal() {
|
|
1151
|
-
|
|
1105
|
+
const signal = this._accountingFailureSignal;
|
|
1106
|
+
this._accountingFailureSignal = null;
|
|
1107
|
+
return signal;
|
|
1152
1108
|
}
|
|
1153
1109
|
/**
|
|
1154
1110
|
* @param {Object} BitShares - BitShares API instance
|
|
@@ -1156,20 +1112,20 @@ class OrderManager {
|
|
|
1156
1112
|
* @returns {Promise<Object>}
|
|
1157
1113
|
*/
|
|
1158
1114
|
async checkSpreadCondition(BitShares, batchCb) {
|
|
1159
|
-
return await
|
|
1115
|
+
return await checkSpreadCondition(this, BitShares, batchCb);
|
|
1160
1116
|
}
|
|
1161
1117
|
/**
|
|
1162
1118
|
* @param {Function} batchCb - Batch callback
|
|
1163
1119
|
* @returns {Promise<Object>}
|
|
1164
1120
|
*/
|
|
1165
1121
|
async checkGridHealth(batchCb) {
|
|
1166
|
-
return await
|
|
1122
|
+
return await checkGridHealth(this, batchCb);
|
|
1167
1123
|
}
|
|
1168
1124
|
/**
|
|
1169
1125
|
* @returns {Object} Current spread calculation
|
|
1170
1126
|
*/
|
|
1171
1127
|
calculateCurrentSpread() {
|
|
1172
|
-
return
|
|
1128
|
+
return calculateCurrentSpread(this);
|
|
1173
1129
|
}
|
|
1174
1130
|
/**
|
|
1175
1131
|
* @returns {import('./types').DriftCheckResult}
|
|
@@ -1214,16 +1170,14 @@ class OrderManager {
|
|
|
1214
1170
|
if (recoveryInFlight) {
|
|
1215
1171
|
reasons.push('recovery sync in-flight');
|
|
1216
1172
|
}
|
|
1217
|
-
if (broadcasting || this.
|
|
1173
|
+
if (broadcasting || this.isBroadcastingActive()) {
|
|
1218
1174
|
reasons.push('broadcasting active orders');
|
|
1219
1175
|
}
|
|
1220
1176
|
if (reasons.length > 0 && !this._pipelineBlockedSince) {
|
|
1221
1177
|
this._pipelineBlockedSince = Date.now();
|
|
1222
|
-
this._state.markPipelineBlocked();
|
|
1223
1178
|
}
|
|
1224
1179
|
else if (reasons.length === 0) {
|
|
1225
1180
|
this._pipelineBlockedSince = null;
|
|
1226
|
-
this._state.markPipelineClear();
|
|
1227
1181
|
}
|
|
1228
1182
|
return {
|
|
1229
1183
|
isEmpty: reasons.length === 0,
|
|
@@ -1241,7 +1195,6 @@ class OrderManager {
|
|
|
1241
1195
|
return false;
|
|
1242
1196
|
this.ordersNeedingPriceCorrection = [];
|
|
1243
1197
|
this._pipelineBlockedSince = null;
|
|
1244
|
-
this._state.markPipelineClear();
|
|
1245
1198
|
return true;
|
|
1246
1199
|
}
|
|
1247
1200
|
/**
|
|
@@ -1435,6 +1388,13 @@ class OrderManager {
|
|
|
1435
1388
|
}
|
|
1436
1389
|
try {
|
|
1437
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
|
+
}
|
|
1438
1398
|
await this.recalculateFunds();
|
|
1439
1399
|
}
|
|
1440
1400
|
const duration = Date.now() - startTime;
|
|
@@ -1445,8 +1405,10 @@ class OrderManager {
|
|
|
1445
1405
|
}
|
|
1446
1406
|
catch (recalcErr) {
|
|
1447
1407
|
this.logger.log(`[COW] Fund recalculation failed post-commit: ${recalcErr.message}`, 'error');
|
|
1448
|
-
|
|
1449
|
-
|
|
1408
|
+
if (!this._recoveryStateValue) {
|
|
1409
|
+
this._recoveryStateValue = { phase: 'idle', attemptCount: 0, lastAttemptAt: 0, inFlight: false, lastFailureAt: 0 };
|
|
1410
|
+
}
|
|
1411
|
+
this._recoveryStateValue.lastFailureAt = Date.now();
|
|
1450
1412
|
}
|
|
1451
1413
|
finally {
|
|
1452
1414
|
this._clearWorkingGridRef();
|
|
@@ -1461,7 +1423,7 @@ class OrderManager {
|
|
|
1461
1423
|
validateGridStateForPersistence(options = {}) {
|
|
1462
1424
|
const result = validateGridForPersistence(this.orders, this.accountTotals);
|
|
1463
1425
|
const allowBootstrapTransient = options.allowBootstrapTransient !== false;
|
|
1464
|
-
if (!result.isValid && allowBootstrapTransient && this.
|
|
1426
|
+
if (!result.isValid && allowBootstrapTransient && this._bootstrapping) {
|
|
1465
1427
|
this.logger.log(`[BOOTSTRAP] Transient state (expected): ${result.reason}`, 'debug');
|
|
1466
1428
|
return { isValid: true, reason: null };
|
|
1467
1429
|
}
|
|
@@ -1521,7 +1483,14 @@ class OrderManager {
|
|
|
1521
1483
|
getMetrics() {
|
|
1522
1484
|
return {
|
|
1523
1485
|
...this._metrics,
|
|
1524
|
-
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
|
+
},
|
|
1525
1494
|
currentTime: Date.now()
|
|
1526
1495
|
};
|
|
1527
1496
|
}
|
|
@@ -1550,52 +1519,53 @@ class OrderManager {
|
|
|
1550
1519
|
comparePrecisions
|
|
1551
1520
|
});
|
|
1552
1521
|
}
|
|
1553
|
-
get _rebalanceState() {
|
|
1554
|
-
return this._state.getRebalanceState();
|
|
1555
|
-
}
|
|
1556
|
-
set _rebalanceState(value) {
|
|
1557
|
-
this._state.setRebalanceState(value);
|
|
1558
|
-
}
|
|
1559
1522
|
get _lastIllegalState() {
|
|
1560
|
-
return this.
|
|
1523
|
+
return this._illegalStateSignal || null;
|
|
1561
1524
|
}
|
|
1562
1525
|
set _lastIllegalState(value) {
|
|
1563
1526
|
if (value) {
|
|
1564
|
-
this.
|
|
1527
|
+
this._illegalStateSignal = {
|
|
1528
|
+
...value,
|
|
1529
|
+
at: Date.now()
|
|
1530
|
+
};
|
|
1565
1531
|
}
|
|
1566
1532
|
}
|
|
1567
1533
|
get _lastAccountingFailure() {
|
|
1568
|
-
return this.
|
|
1534
|
+
return this._accountingFailureSignal || null;
|
|
1569
1535
|
}
|
|
1570
1536
|
set _lastAccountingFailure(value) {
|
|
1571
1537
|
if (value) {
|
|
1572
|
-
this.
|
|
1538
|
+
this._accountingFailureSignal = {
|
|
1539
|
+
...value,
|
|
1540
|
+
at: Date.now()
|
|
1541
|
+
};
|
|
1573
1542
|
}
|
|
1574
1543
|
}
|
|
1575
1544
|
get _recoveryState() {
|
|
1576
|
-
return this.
|
|
1545
|
+
return this._recoveryStateValue;
|
|
1577
1546
|
}
|
|
1578
1547
|
set _recoveryState(value) {
|
|
1579
1548
|
const fallback = {
|
|
1549
|
+
phase: 'idle',
|
|
1580
1550
|
attemptCount: 0,
|
|
1581
1551
|
lastAttemptAt: 0,
|
|
1582
1552
|
inFlight: false,
|
|
1583
1553
|
lastFailureAt: 0,
|
|
1584
1554
|
structuralResyncRequested: false
|
|
1585
1555
|
};
|
|
1586
|
-
this.
|
|
1556
|
+
this._recoveryStateValue = {
|
|
1587
1557
|
...fallback,
|
|
1588
1558
|
...(value && typeof value === 'object' ? value : {})
|
|
1589
1559
|
};
|
|
1590
1560
|
}
|
|
1591
1561
|
get _gridRegenState() {
|
|
1592
|
-
return this.
|
|
1562
|
+
return this._gridRegenStateValue;
|
|
1593
1563
|
}
|
|
1594
1564
|
set _gridRegenState(value) {
|
|
1595
1565
|
if (!value || typeof value !== 'object')
|
|
1596
1566
|
return;
|
|
1597
1567
|
const defaultSide = { armed: true, lastTriggeredAt: 0 };
|
|
1598
|
-
this.
|
|
1568
|
+
this._gridRegenStateValue = {
|
|
1599
1569
|
buy: { ...defaultSide, ...(value.buy || {}) },
|
|
1600
1570
|
sell: { ...defaultSide, ...(value.sell || {}) }
|
|
1601
1571
|
};
|