dexbot 1.2.5 → 1.2.7
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 +30 -27
- package/dist/dexbot.d.ts +22 -22
- package/dist/dexbot.js +29 -29
- package/dist/dexbot.js.map +1 -1
- package/dist/modules/account_orders.d.ts +8 -1
- package/dist/modules/account_orders.d.ts.map +1 -1
- package/dist/modules/account_orders.js +24 -1
- package/dist/modules/account_orders.js.map +1 -1
- package/dist/modules/chain_orders.d.ts +16 -0
- package/dist/modules/chain_orders.d.ts.map +1 -1
- package/dist/modules/chain_orders.js +41 -0
- package/dist/modules/chain_orders.js.map +1 -1
- package/dist/modules/constants.d.ts +0 -3
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +0 -14
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +10 -2
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +176 -57
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +2 -2
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +3 -3
- 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 +8 -9
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/node_manager.d.ts.map +1 -1
- package/dist/modules/node_manager.js +7 -0
- package/dist/modules/node_manager.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +2 -18
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +22 -49
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/async_lock.d.ts +10 -0
- package/dist/modules/order/async_lock.d.ts.map +1 -1
- package/dist/modules/order/async_lock.js +60 -5
- package/dist/modules/order/async_lock.js.map +1 -1
- package/dist/modules/order/grid.js +2 -2
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/manager.d.ts +8 -1
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +28 -14
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/strategy.d.ts +2 -49
- package/dist/modules/order/strategy.d.ts.map +1 -1
- package/dist/modules/order/strategy.js +4 -132
- package/dist/modules/order/strategy.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts +53 -6
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +384 -204
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +8 -3
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.js +1 -1
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +1 -1
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +3 -2
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/runtime_settings.d.ts +3 -0
- package/dist/modules/runtime_settings.d.ts.map +1 -1
- package/dist/modules/runtime_settings.js +34 -1
- package/dist/modules/runtime_settings.js.map +1 -1
- package/dist/modules/types.d.ts +29 -4
- package/dist/modules/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/README.md +9 -9
- package/scripts/reset-settings.sh +1 -1
|
@@ -111,7 +111,6 @@ class DEXBot {
|
|
|
111
111
|
_staleCleanupRetentionMs;
|
|
112
112
|
_metrics;
|
|
113
113
|
_shuttingDown;
|
|
114
|
-
_shutdownStarted;
|
|
115
114
|
_shutdownPromise;
|
|
116
115
|
_blockchainFetchInterval;
|
|
117
116
|
_blockchainFetchInFlight;
|
|
@@ -166,6 +165,8 @@ class DEXBot {
|
|
|
166
165
|
this.config.feeParams = rs.feeParams;
|
|
167
166
|
this.config.incrementBounds = rs.incrementBounds;
|
|
168
167
|
this.config.timing = rs.timing;
|
|
168
|
+
this.config.fillProcessing = rs.fillProcessing;
|
|
169
|
+
this.config.pipelineTiming = rs.pipelineTiming;
|
|
169
170
|
this.config.logging = rs.logging;
|
|
170
171
|
this._fillDedupeWindowMs = this.config.timing.FILL_DEDUPE_WINDOW_MS;
|
|
171
172
|
this._fillRecordRetentionMs = this.config.timing.FILL_RECORD_RETENTION_MS;
|
|
@@ -200,7 +201,6 @@ class DEXBot {
|
|
|
200
201
|
};
|
|
201
202
|
// Shutdown state
|
|
202
203
|
this._shuttingDown = false;
|
|
203
|
-
this._shutdownStarted = false;
|
|
204
204
|
this._shutdownPromise = null;
|
|
205
205
|
// Runtime handles for graceful lifecycle management
|
|
206
206
|
this._blockchainFetchInterval = null;
|
|
@@ -322,6 +322,7 @@ class DEXBot {
|
|
|
322
322
|
* @private
|
|
323
323
|
*/
|
|
324
324
|
async _persistAndRecoverIfNeeded() {
|
|
325
|
+
this.manager._recentFillKeysSnapshot = this._getRecentFillKeysSnapshot();
|
|
325
326
|
const validation = await this.manager.persistGrid();
|
|
326
327
|
if (!validation.isValid) {
|
|
327
328
|
this._warn(`Startup validation failed: ${validation.reason}. Triggering immediate recovery...`);
|
|
@@ -329,6 +330,7 @@ class DEXBot {
|
|
|
329
330
|
const recoveryValidation = await this.manager.accountant._performStateRecovery(this.manager);
|
|
330
331
|
if (recoveryValidation.isValid) {
|
|
331
332
|
this._log(`✓ Startup recovery successful. Persistent state restored.`);
|
|
333
|
+
this.manager._recentFillKeysSnapshot = this._getRecentFillKeysSnapshot();
|
|
332
334
|
await this.manager.persistGrid();
|
|
333
335
|
}
|
|
334
336
|
else {
|
|
@@ -336,6 +338,37 @@ class DEXBot {
|
|
|
336
338
|
}
|
|
337
339
|
}
|
|
338
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Snapshot the recently queued fill keys as a plain object for crash-durable persistence.
|
|
343
|
+
* Merges with any existing snapshot to avoid losing keys that were evicted from the
|
|
344
|
+
* in-memory map between persist cycles but remain within the dedup window.
|
|
345
|
+
* Only includes entries within the dedup window to avoid writing stale keys.
|
|
346
|
+
* @returns {Record<string, number>}
|
|
347
|
+
*/
|
|
348
|
+
_getRecentFillKeysSnapshot() {
|
|
349
|
+
const snapshot = {};
|
|
350
|
+
const now = Date.now();
|
|
351
|
+
// 1. Collect live keys from the in-memory map
|
|
352
|
+
for (const [key, timestamp] of this._recentlyQueuedFills) {
|
|
353
|
+
if (now - Number(timestamp) < this._fillDedupeWindowMs) {
|
|
354
|
+
snapshot[key] = Number(timestamp);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// 2. Merge with the previous snapshot — carry forward any keys that
|
|
358
|
+
// were present in a prior persist cycle, are still within the
|
|
359
|
+
// dedup window, but have been evicted from _recentlyQueuedFills
|
|
360
|
+
// (e.g. by TTL-based pruning between two persistGrid calls).
|
|
361
|
+
// This prevents a crash + restart from losing keys that were
|
|
362
|
+
// processed but not yet persisted.
|
|
363
|
+
if (this.manager?._recentFillKeysSnapshot) {
|
|
364
|
+
for (const [key, timestamp] of Object.entries(this.manager._recentFillKeysSnapshot)) {
|
|
365
|
+
if (!(key in snapshot) && now - Number(timestamp) < this._fillDedupeWindowMs) {
|
|
366
|
+
snapshot[key] = Number(timestamp);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return snapshot;
|
|
371
|
+
}
|
|
339
372
|
/**
|
|
340
373
|
* Get current pipeline signal state for congestion checks.
|
|
341
374
|
* @returns {{incomingFillQueueLength: number, shadowLocks: number, batchInFlight: boolean, recoveryInFlight: boolean, broadcasting: boolean}}
|
|
@@ -676,8 +709,6 @@ class DEXBot {
|
|
|
676
709
|
* @returns {Promise<boolean>} True if all affected orders were repaired
|
|
677
710
|
*/
|
|
678
711
|
async _targetedOrderRepair(orderIds) {
|
|
679
|
-
const { BitShares } = require('./bitshares_client');
|
|
680
|
-
const { virtualizeOrder } = require('./order/utils/order');
|
|
681
712
|
try {
|
|
682
713
|
const objects = await BitShares.db.get_objects(orderIds);
|
|
683
714
|
if (!Array.isArray(objects) || objects.length !== orderIds.length)
|
|
@@ -801,11 +832,13 @@ class DEXBot {
|
|
|
801
832
|
const persistedBtsFeesOwed = this.accountOrders.loadBtsFeesOwed();
|
|
802
833
|
const persistedBoundaryIdx = this.accountOrders.loadBoundaryIdx();
|
|
803
834
|
const persistedBtsBalance = this.accountOrders.loadBtsBalance();
|
|
835
|
+
const persistedRecentFillKeys = this.accountOrders.loadRecentFillKeys();
|
|
804
836
|
return {
|
|
805
837
|
persistedGrid: repairedGrid,
|
|
806
838
|
persistedBtsFeesOwed,
|
|
807
839
|
persistedBoundaryIdx,
|
|
808
840
|
persistedBtsBalance,
|
|
841
|
+
persistedRecentFillKeys,
|
|
809
842
|
};
|
|
810
843
|
}
|
|
811
844
|
finally {
|
|
@@ -930,10 +963,10 @@ class DEXBot {
|
|
|
930
963
|
* @param {string} [options.context]
|
|
931
964
|
* @param {Object} [options.logger]
|
|
932
965
|
* @param {string} [options.replayMessage]
|
|
933
|
-
* @param {string} [options.persistenceMode='
|
|
966
|
+
* @param {string} [options.persistenceMode='batched']
|
|
934
967
|
* @returns {Promise<import('./types').ReplaySafeFillResult>}
|
|
935
968
|
*/
|
|
936
|
-
async _applyReplaySafeOrphanFillAccounting(fill, fillOp, { context, logger = this.manager?.logger, replayMessage, persistenceMode = PROCESSED_FILL_PERSISTENCE_MODES.
|
|
969
|
+
async _applyReplaySafeOrphanFillAccounting(fill, fillOp, { context, logger = this.manager?.logger, replayMessage, persistenceMode = PROCESSED_FILL_PERSISTENCE_MODES.BATCHED } = {}) {
|
|
937
970
|
return DexbotFillRuntime.applyReplaySafeOrphanFillAccounting(this, fill, fillOp, {
|
|
938
971
|
context,
|
|
939
972
|
logger,
|
|
@@ -956,7 +989,7 @@ class DEXBot {
|
|
|
956
989
|
* @private
|
|
957
990
|
*/
|
|
958
991
|
async _finishStartupSequence(startupState) {
|
|
959
|
-
let { persistedGrid, persistedBtsFeesOwed, persistedBoundaryIdx, persistedBtsBalance, } = startupState;
|
|
992
|
+
let { persistedGrid, persistedBtsFeesOwed, persistedBoundaryIdx, persistedBtsBalance, persistedRecentFillKeys, } = startupState;
|
|
960
993
|
try {
|
|
961
994
|
// CRITICAL: Activate fill listener EARLY - before ANY operations that place orders
|
|
962
995
|
// This ensures fills during trigger reset and grid initialization are captured
|
|
@@ -993,11 +1026,13 @@ class DEXBot {
|
|
|
993
1026
|
if (this._shuttingDown)
|
|
994
1027
|
return;
|
|
995
1028
|
if (syncResult?.filledOrders?.length > 0) {
|
|
1029
|
+
this._refreshDynamicWeightDistribution('post-reconnect sync fill');
|
|
996
1030
|
this._log(`Post-reconnect sync: ${syncResult.filledOrders.length} grid order(s) found filled.`, 'info');
|
|
997
1031
|
await this._processFillsWithBatching(syncResult.filledOrders, new Set(), 'post-reconnect sync fill');
|
|
998
1032
|
if (this._shuttingDown)
|
|
999
1033
|
return;
|
|
1000
1034
|
}
|
|
1035
|
+
this.manager._recentFillKeysSnapshot = this._getRecentFillKeysSnapshot();
|
|
1001
1036
|
await this.manager.persistGrid();
|
|
1002
1037
|
// Cancel any dust created by reconnect fills immediately.
|
|
1003
1038
|
if (!this._shuttingDown) {
|
|
@@ -1139,6 +1174,7 @@ class DEXBot {
|
|
|
1139
1174
|
}
|
|
1140
1175
|
}
|
|
1141
1176
|
await this._flushProcessedFillPersistence('post-reset-batch');
|
|
1177
|
+
this.manager._recentFillKeysSnapshot = this._getRecentFillKeysSnapshot();
|
|
1142
1178
|
await this.manager.persistGrid();
|
|
1143
1179
|
}
|
|
1144
1180
|
// STEP 2: Refresh chain truth before spread correction. Trigger
|
|
@@ -1205,6 +1241,15 @@ class DEXBot {
|
|
|
1205
1241
|
};
|
|
1206
1242
|
}
|
|
1207
1243
|
}
|
|
1244
|
+
// Restore recently queued fill keys for crash-durable dedup window.
|
|
1245
|
+
// All persisted keys are restored; the in-memory TTL (_fillDedupeWindowMs)
|
|
1246
|
+
// will naturally evict any that are already expired on the next fill cycle.
|
|
1247
|
+
if (persistedRecentFillKeys && typeof persistedRecentFillKeys === 'object') {
|
|
1248
|
+
for (const [fillKey, timestamp] of Object.entries(persistedRecentFillKeys)) {
|
|
1249
|
+
this._recentlyQueuedFills.set(fillKey, Number(timestamp));
|
|
1250
|
+
}
|
|
1251
|
+
this._log(`Restored ${Object.keys(persistedRecentFillKeys).length} recently queued fill key(s) from persisted snapshot`, 'debug');
|
|
1252
|
+
}
|
|
1208
1253
|
if (!this.config.dryRun && !this.accountId) {
|
|
1209
1254
|
throw new Error('Cannot start bot without a resolved account ID');
|
|
1210
1255
|
}
|
|
@@ -1410,6 +1455,7 @@ class DEXBot {
|
|
|
1410
1455
|
const syncResult = await this.manager.synchronizeWithChain(openOrders, 'readOpenOrders');
|
|
1411
1456
|
let aborted = false;
|
|
1412
1457
|
if (syncResult?.filledOrders?.length > 0) {
|
|
1458
|
+
this._refreshDynamicWeightDistribution(`${tag} sync-fill`);
|
|
1413
1459
|
this._log(`[SYNC-CHAIN] ${syncResult.filledOrders.length} filled order(s) found during ${tag}`, 'info');
|
|
1414
1460
|
const batchResult = await this._processFillsWithBatching(syncResult.filledOrders, new Set(), `${tag} sync-fill`);
|
|
1415
1461
|
if (!batchResult?.aborted) {
|
|
@@ -1429,7 +1475,7 @@ class DEXBot {
|
|
|
1429
1475
|
}
|
|
1430
1476
|
}
|
|
1431
1477
|
_maxConsecutiveFillConsumerFailures() {
|
|
1432
|
-
return FILL_PROCESSING.MAX_CONSECUTIVE_CONSUMER_FAILURES;
|
|
1478
|
+
return this.config.fillProcessing?.MAX_CONSECUTIVE_CONSUMER_FAILURES ?? FILL_PROCESSING.MAX_CONSECUTIVE_CONSUMER_FAILURES;
|
|
1433
1479
|
}
|
|
1434
1480
|
/**
|
|
1435
1481
|
* Compute the backoff delay for fill-consumer retries after the failure
|
|
@@ -1441,15 +1487,16 @@ class DEXBot {
|
|
|
1441
1487
|
* @private
|
|
1442
1488
|
*/
|
|
1443
1489
|
_computeFillConsumerBackoffMs(failures) {
|
|
1444
|
-
const
|
|
1445
|
-
const
|
|
1446
|
-
const
|
|
1490
|
+
const fp = this.config.fillProcessing || FILL_PROCESSING;
|
|
1491
|
+
const initial = fp.CONSUMER_BACKOFF_INITIAL_MS;
|
|
1492
|
+
const max = fp.CONSUMER_BACKOFF_MAX_MS;
|
|
1493
|
+
const stepAfterMax = Math.max(0, failures - this._maxConsecutiveFillConsumerFailures());
|
|
1447
1494
|
// 0 -> initial, 1 -> 2*initial, 2 -> 4*initial, ... capped at max.
|
|
1448
1495
|
return Math.min(max, initial * Math.pow(2, stepAfterMax));
|
|
1449
1496
|
}
|
|
1450
1497
|
_scheduleFillConsumerRestart(chainOrders) {
|
|
1451
1498
|
const failures = this._consecutiveConsumeFailures;
|
|
1452
|
-
if (failures >=
|
|
1499
|
+
if (failures >= this._maxConsecutiveFillConsumerFailures()) {
|
|
1453
1500
|
// Past the failure budget: switch from tight setImmediate loop to
|
|
1454
1501
|
// exponential backoff. The consumer continues to retry — a transient
|
|
1455
1502
|
// outage (e.g., credential daemon recovery) will resume normal
|
|
@@ -1731,24 +1778,51 @@ class DEXBot {
|
|
|
1731
1778
|
let resolvedOrders = [];
|
|
1732
1779
|
if (fillMode === 'history') {
|
|
1733
1780
|
this.manager.logger.log(`Syncing ${fillsToSync.length} fill(s) (history mode)`, 'info');
|
|
1734
|
-
for
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
const fillKey = buildFillKey({
|
|
1739
|
-
orderId: fill?.op?.[1]?.order_id,
|
|
1740
|
-
blockNum: fill?.block_num,
|
|
1741
|
-
historyId: fill?.id
|
|
1781
|
+
// Batch mode for 2+ fills: acquires _gridLock once, batches drift refetch
|
|
1782
|
+
if (fillsToSync.length >= 2) {
|
|
1783
|
+
const batchResult = await this.manager.syncFromFillHistoryBatch(fillsToSync, {
|
|
1784
|
+
persistenceMode: PROCESSED_FILL_PERSISTENCE_MODES.BATCHED
|
|
1742
1785
|
});
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1786
|
+
for (const fill of fillsToSync) {
|
|
1787
|
+
const fillKey = buildFillKey({
|
|
1788
|
+
orderId: fill?.op?.[1]?.order_id,
|
|
1789
|
+
blockNum: fill?.block_num,
|
|
1790
|
+
historyId: fill?.id
|
|
1791
|
+
});
|
|
1792
|
+
if (fillKey)
|
|
1793
|
+
pendingFillKeysForCurrentCycle.add(fillKey);
|
|
1794
|
+
}
|
|
1795
|
+
if (batchResult.filledOrders)
|
|
1796
|
+
resolvedOrders.push(...batchResult.filledOrders);
|
|
1797
|
+
if (batchResult.requiresOpenOrdersSync)
|
|
1749
1798
|
requiresOpenOrdersSync = true;
|
|
1750
|
-
if (
|
|
1751
|
-
|
|
1799
|
+
if (batchResult.ghostOrderIds?.length > 0) {
|
|
1800
|
+
for (const id of batchResult.ghostOrderIds) {
|
|
1801
|
+
pendingGhostOrders.add(id);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
else {
|
|
1806
|
+
// Single fill: use individual per-fill path
|
|
1807
|
+
for (const fill of fillsToSync) {
|
|
1808
|
+
const resultHistory = await this.manager.syncFromFillHistory(fill, {
|
|
1809
|
+
persistenceMode: PROCESSED_FILL_PERSISTENCE_MODES.BATCHED
|
|
1810
|
+
});
|
|
1811
|
+
const fillKey = buildFillKey({
|
|
1812
|
+
orderId: fill?.op?.[1]?.order_id,
|
|
1813
|
+
blockNum: fill?.block_num,
|
|
1814
|
+
historyId: fill?.id
|
|
1815
|
+
});
|
|
1816
|
+
if (fillKey)
|
|
1817
|
+
pendingFillKeysForCurrentCycle.add(fillKey);
|
|
1818
|
+
// Dust is handled by post-fill detection below.
|
|
1819
|
+
if (resultHistory.filledOrders)
|
|
1820
|
+
resolvedOrders.push(...resultHistory.filledOrders);
|
|
1821
|
+
if (resultHistory.requiresOpenOrdersSync)
|
|
1822
|
+
requiresOpenOrdersSync = true;
|
|
1823
|
+
if (resultHistory.ghostOrderId)
|
|
1824
|
+
pendingGhostOrders.add(resultHistory.ghostOrderId);
|
|
1825
|
+
}
|
|
1752
1826
|
}
|
|
1753
1827
|
}
|
|
1754
1828
|
if (fillMode !== 'history' || requiresOpenOrdersSync) {
|
|
@@ -1845,26 +1919,77 @@ class DEXBot {
|
|
|
1845
1919
|
// cancel tx to clean up the zombie order on chain.
|
|
1846
1920
|
// Runs in the finally so it still fires even if processValidFills
|
|
1847
1921
|
// throws after collecting ghost IDs into pendingGhostOrders.
|
|
1848
|
-
//
|
|
1849
|
-
//
|
|
1922
|
+
//
|
|
1923
|
+
// OPTIMIZATION: Batch all new ghost cancels into one executeBatch
|
|
1924
|
+
// call with multiple cancelOrder ops, reducing per-order tx fees
|
|
1925
|
+
// and broadcast overhead. Falls back to individual cancels on
|
|
1926
|
+
// batch failure.
|
|
1850
1927
|
if (pendingGhostOrders.size > 0) {
|
|
1851
1928
|
if (!this._ghostOrderCancelAttempted)
|
|
1852
1929
|
this._ghostOrderCancelAttempted = new Set();
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1930
|
+
const newGhostIds = [...pendingGhostOrders].filter(id => !this._ghostOrderCancelAttempted.has(id));
|
|
1931
|
+
if (newGhostIds.length > 0) {
|
|
1932
|
+
const MAX_OPS_PER_TX = 200;
|
|
1933
|
+
let batchFailed = false;
|
|
1934
|
+
// Build cancel ops with per-ID error tolerance.
|
|
1935
|
+
// Index correlates 1:1 with newGhostIds (allSettled preserves order).
|
|
1936
|
+
const buildResults = await Promise.allSettled(newGhostIds.map(id => chainOrders.buildCancelOrderOp(this.account, id)));
|
|
1937
|
+
const cancelOps = [];
|
|
1938
|
+
for (let i = 0; i < buildResults.length; i++) {
|
|
1939
|
+
const result = buildResults[i];
|
|
1940
|
+
const id = newGhostIds[i];
|
|
1941
|
+
if (result.status === 'fulfilled') {
|
|
1942
|
+
cancelOps.push(result.value);
|
|
1943
|
+
}
|
|
1944
|
+
else {
|
|
1945
|
+
this.manager.logger.log(`[SYNC] Failed to build cancel op for ghost order ${id}: ${result.reason?.message || result.reason}`, 'warn');
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
// Chunk into batches respecting MAX_OPS_PER_TX
|
|
1949
|
+
for (let i = 0; i < cancelOps.length; i += MAX_OPS_PER_TX) {
|
|
1950
|
+
const chunk = cancelOps.slice(i, i + MAX_OPS_PER_TX);
|
|
1951
|
+
const batchIds = newGhostIds.slice(i, i + MAX_OPS_PER_TX);
|
|
1952
|
+
try {
|
|
1953
|
+
this.manager.logger.log(`[SYNC] Batch-cancelling ${chunk.length} orphaned chain order(s) ` +
|
|
1954
|
+
`(batch ${Math.floor(i / MAX_OPS_PER_TX) + 1}/${Math.ceil(cancelOps.length / MAX_OPS_PER_TX)})`, 'info');
|
|
1955
|
+
await chainOrders.executeBatch(this.account, this.privateKey, chunk);
|
|
1956
|
+
// Mark successfully cancelled IDs immediately
|
|
1957
|
+
for (const id of batchIds) {
|
|
1958
|
+
this._ghostOrderCancelAttempted.add(id);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
catch (batchErr) {
|
|
1962
|
+
batchFailed = true;
|
|
1963
|
+
this.manager.logger.log(`[SYNC] Batch ghost cancel failed for chunk ${Math.floor(i / MAX_OPS_PER_TX) + 1} ` +
|
|
1964
|
+
`(${chunk.length} orders): ${batchErr?.message || batchErr}`, 'warn');
|
|
1965
|
+
}
|
|
1860
1966
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1967
|
+
// Log overall success if all chunks and all builds succeeded
|
|
1968
|
+
if (!batchFailed && cancelOps.length === newGhostIds.length) {
|
|
1969
|
+
this.manager.logger.log(`[SYNC] Successfully batch-cancelled ${newGhostIds.length} orphaned chain order(s)`, 'info');
|
|
1970
|
+
}
|
|
1971
|
+
// Fallback: individual cancels for IDs that were not marked successful
|
|
1972
|
+
for (const ghostOrderId of newGhostIds) {
|
|
1973
|
+
if (this._ghostOrderCancelAttempted.has(ghostOrderId))
|
|
1974
|
+
continue;
|
|
1975
|
+
try {
|
|
1976
|
+
this.manager.logger.log(`[SYNC] Cancelling orphaned chain order ${ghostOrderId} (other-side full-fill residual)`, 'info');
|
|
1977
|
+
await chainOrders.cancelOrder(this.account, this.privateKey, ghostOrderId);
|
|
1978
|
+
this._ghostOrderCancelAttempted.add(ghostOrderId);
|
|
1979
|
+
}
|
|
1980
|
+
catch (err) {
|
|
1981
|
+
this.manager.logger.log(`[SYNC] Failed to cancel orphaned order ${ghostOrderId}: ${err?.message || err}`, 'warn');
|
|
1982
|
+
}
|
|
1863
1983
|
}
|
|
1864
1984
|
}
|
|
1865
1985
|
}
|
|
1866
1986
|
await this.manager.resumeFundRecalc();
|
|
1867
1987
|
}
|
|
1988
|
+
// Refresh dynamic weight distribution before processing fills
|
|
1989
|
+
// so the rebalance uses the latest market adapter weights, not
|
|
1990
|
+
// stale values from the last periodic refresh cycle.
|
|
1991
|
+
// Lightweight: reads local JSON snapshot file.
|
|
1992
|
+
this._refreshDynamicWeightDistribution('fill queue');
|
|
1868
1993
|
// 5. Fixed-Cap Fill Rebalance
|
|
1869
1994
|
// - 1..MAX_FILL_BATCH_SIZE fills: unified full-set planning
|
|
1870
1995
|
// - larger bursts: fixed-size chunking at MAX_FILL_BATCH_SIZE
|
|
@@ -1929,6 +2054,7 @@ class DEXBot {
|
|
|
1929
2054
|
else if (pendingFillKeysForCurrentCycle.size > 0) {
|
|
1930
2055
|
await this._flushProcessedFillPersistenceForKeys(pendingFillKeysForCurrentCycle, 'fill-batch-no-rotations');
|
|
1931
2056
|
}
|
|
2057
|
+
this.manager._recentFillKeysSnapshot = this._getRecentFillKeysSnapshot();
|
|
1932
2058
|
await retryPersistenceIfNeeded(this.manager);
|
|
1933
2059
|
// Periodically clean up old fill records after processing N fills.
|
|
1934
2060
|
// Counter is protected by _fillProcessingLock during fill consumption.
|
|
@@ -2255,7 +2381,7 @@ class DEXBot {
|
|
|
2255
2381
|
* @returns {number} Positive maximum number of fill-driven rotations per broadcast cycle
|
|
2256
2382
|
*/
|
|
2257
2383
|
_getMaxFillBatchSize() {
|
|
2258
|
-
return Math.max(1, FILL_PROCESSING.MAX_FILL_BATCH_SIZE);
|
|
2384
|
+
return Math.max(1, this.config.fillProcessing?.MAX_FILL_BATCH_SIZE ?? FILL_PROCESSING.MAX_FILL_BATCH_SIZE);
|
|
2259
2385
|
}
|
|
2260
2386
|
/**
|
|
2261
2387
|
* Extract operation results from a batch transaction result.
|
|
@@ -2739,7 +2865,7 @@ class DEXBot {
|
|
|
2739
2865
|
// BROADCAST_DEADLINE fires while the transaction may still be valid
|
|
2740
2866
|
// and land in the next block. Wait for up to 3 blocks (~1 BitShares
|
|
2741
2867
|
// block interval each) and re-check before permanently discarding.
|
|
2742
|
-
const UNCERTAIN_RECHECK_MAX_ATTEMPTS = PIPELINE_TIMING.RETRY_MAX_ATTEMPTS; // 3
|
|
2868
|
+
const UNCERTAIN_RECHECK_MAX_ATTEMPTS = this.config.pipelineTiming?.RETRY_MAX_ATTEMPTS ?? PIPELINE_TIMING.RETRY_MAX_ATTEMPTS; // 3
|
|
2743
2869
|
const UNCERTAIN_RECHECK_INTERVAL_MS = TIMING.MILLISECONDS_PER_SECOND * 3; // 3s
|
|
2744
2870
|
let recheckRound = 0;
|
|
2745
2871
|
while (discarded.length > 0 && recheckRound < UNCERTAIN_RECHECK_MAX_ATTEMPTS) {
|
|
@@ -3485,8 +3611,7 @@ class DEXBot {
|
|
|
3485
3611
|
this._runCredentialRecoveryAfterDaemonRestored().catch(err => {
|
|
3486
3612
|
this.manager?.logger?.log?.(`[CREDENTIAL] Deferred recovery failed: ${err.message}`, 'error');
|
|
3487
3613
|
if (this.manager) {
|
|
3488
|
-
this.manager._recoveryState = this.manager._recoveryState
|
|
3489
|
-
this.manager._recoveryState.lastFailureAt = Date.now();
|
|
3614
|
+
this.manager._recoveryState = { ...this.manager._recoveryState, lastFailureAt: Date.now() };
|
|
3490
3615
|
}
|
|
3491
3616
|
});
|
|
3492
3617
|
}, 1000);
|
|
@@ -3873,7 +3998,7 @@ class DEXBot {
|
|
|
3873
3998
|
`broadcast. Running recovery before placing replacement orders.`, 'error');
|
|
3874
3999
|
if (typeof this.manager.requestStructuralGridResync === 'function') {
|
|
3875
4000
|
if (this.manager._recoveryState)
|
|
3876
|
-
this.manager._recoveryState.structuralResyncRequested
|
|
4001
|
+
this.manager._recoveryState = { ...this.manager._recoveryState, structuralResyncRequested: true };
|
|
3877
4002
|
await this.manager.requestStructuralGridResync('pending broadcasts before COW create', { pendingBroadcasts: pendingBroadcasts.map(p => p.slotId) });
|
|
3878
4003
|
}
|
|
3879
4004
|
try {
|
|
@@ -3963,7 +4088,7 @@ class DEXBot {
|
|
|
3963
4088
|
// Request structural resync to rebuild the grid on the next cycle.
|
|
3964
4089
|
if (typeof this.manager.requestStructuralGridResync === 'function') {
|
|
3965
4090
|
if (this.manager._recoveryState)
|
|
3966
|
-
this.manager._recoveryState.structuralResyncRequested
|
|
4091
|
+
this.manager._recoveryState = { ...this.manager._recoveryState, structuralResyncRequested: true };
|
|
3967
4092
|
await this.manager.requestStructuralGridResync('unmatched chain orders before COW create', { unmatchedChainOrders: unmatchedChainOrders });
|
|
3968
4093
|
}
|
|
3969
4094
|
this.manager.logger.log(`[COW] Rejecting CREATE batch after sync: working grid invalidated by master mutation`, 'info');
|
|
@@ -4266,8 +4391,7 @@ class DEXBot {
|
|
|
4266
4391
|
`(${retryResult.reason || 'no reason'}). Master grid in memory ` +
|
|
4267
4392
|
`is ahead of disk snapshot; structural resync requested.`, 'error');
|
|
4268
4393
|
if (typeof this.manager.requestStructuralGridResync === 'function') {
|
|
4269
|
-
this.manager._recoveryState = this.manager._recoveryState
|
|
4270
|
-
this.manager._recoveryState.structuralResyncRequested = true;
|
|
4394
|
+
this.manager._recoveryState = { ...this.manager._recoveryState, structuralResyncRequested: true };
|
|
4271
4395
|
await this.manager.requestStructuralGridResync('persistence guard triggered after COW batch', { persistReason: retryResult.reason || 'unknown' });
|
|
4272
4396
|
}
|
|
4273
4397
|
}
|
|
@@ -4837,9 +4961,7 @@ class DEXBot {
|
|
|
4837
4961
|
const persistedResult = await this._recoverFromPersistedGrid();
|
|
4838
4962
|
if (persistedResult.success) {
|
|
4839
4963
|
if (this.manager?._recoveryState) {
|
|
4840
|
-
this.manager._recoveryState.attemptCount
|
|
4841
|
-
this.manager._recoveryState.lastAttemptAt = 0;
|
|
4842
|
-
this.manager._recoveryState.lastFailureAt = 0;
|
|
4964
|
+
this.manager._recoveryState = { ...this.manager._recoveryState, attemptCount: 0, lastAttemptAt: 0, lastFailureAt: 0 };
|
|
4843
4965
|
}
|
|
4844
4966
|
return;
|
|
4845
4967
|
}
|
|
@@ -4849,9 +4971,7 @@ class DEXBot {
|
|
|
4849
4971
|
refreshCenterPrice: true,
|
|
4850
4972
|
});
|
|
4851
4973
|
if (resetResult && this.manager?._recoveryState) {
|
|
4852
|
-
this.manager._recoveryState.attemptCount
|
|
4853
|
-
this.manager._recoveryState.lastAttemptAt = 0;
|
|
4854
|
-
this.manager._recoveryState.lastFailureAt = 0;
|
|
4974
|
+
this.manager._recoveryState = { ...this.manager._recoveryState, attemptCount: 0, lastAttemptAt: 0, lastFailureAt: 0 };
|
|
4855
4975
|
}
|
|
4856
4976
|
}
|
|
4857
4977
|
catch (err) {
|
|
@@ -4860,7 +4980,7 @@ class DEXBot {
|
|
|
4860
4980
|
finally {
|
|
4861
4981
|
this._structuralGridResyncRunning = false;
|
|
4862
4982
|
if (this.manager?._recoveryState) {
|
|
4863
|
-
this.manager._recoveryState.structuralResyncRequested
|
|
4983
|
+
this.manager._recoveryState = { ...this.manager._recoveryState, structuralResyncRequested: false };
|
|
4864
4984
|
}
|
|
4865
4985
|
}
|
|
4866
4986
|
}, 0);
|
|
@@ -4998,11 +5118,11 @@ class DEXBot {
|
|
|
4998
5118
|
* @returns {Promise<void>}
|
|
4999
5119
|
*/
|
|
5000
5120
|
async shutdown() {
|
|
5001
|
-
if (this.
|
|
5121
|
+
if (this._shuttingDown) {
|
|
5002
5122
|
this._log('Shutdown already in progress; ignoring re-entrant call');
|
|
5003
5123
|
return this._shutdownPromise || Promise.resolve();
|
|
5004
5124
|
}
|
|
5005
|
-
this.
|
|
5125
|
+
this._shuttingDown = true;
|
|
5006
5126
|
const shutdownImpl = typeof this._shutdownImpl === 'function'
|
|
5007
5127
|
? this._shutdownImpl
|
|
5008
5128
|
: DEXBot.prototype._shutdownImpl;
|
|
@@ -5011,7 +5131,6 @@ class DEXBot {
|
|
|
5011
5131
|
}
|
|
5012
5132
|
async _shutdownImpl() {
|
|
5013
5133
|
this._log('Initiating graceful shutdown...');
|
|
5014
|
-
this._shuttingDown = true;
|
|
5015
5134
|
this._processedFillStore.setShuttingDown(true);
|
|
5016
5135
|
// Stop accepting new work
|
|
5017
5136
|
this._stopBlockchainFetchInterval();
|