dexbot 1.4.24 → 1.4.25
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/CHANGELOG.md +39 -0
- package/README.md +1 -0
- package/analysis/README.md +49 -0
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/grid_correction_check.ts +896 -0
- package/analysis/trend_detection/package.json +1 -1
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/analysis/grid_correction_check.d.ts +90 -0
- package/dist/analysis/grid_correction_check.d.ts.map +1 -0
- package/dist/analysis/grid_correction_check.js +861 -0
- package/dist/analysis/grid_correction_check.js.map +1 -0
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +62 -6
- package/dist/dexbot.js.map +1 -1
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +53 -5
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/account_orders.d.ts +7 -10
- package/dist/modules/account_orders.d.ts.map +1 -1
- package/dist/modules/account_orders.js +26 -2
- package/dist/modules/account_orders.js.map +1 -1
- package/dist/modules/chain_orders.d.ts.map +1 -1
- package/dist/modules/chain_orders.js +21 -2
- package/dist/modules/chain_orders.js.map +1 -1
- package/dist/modules/config.d.ts.map +1 -1
- package/dist/modules/config.js +27 -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 +57 -6
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +24 -0
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +66 -12
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +22 -17
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +646 -128
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +11 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +108 -1
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +17 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +215 -26
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_startup_runtime.d.ts +1 -0
- package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_startup_runtime.js +58 -7
- package/dist/modules/dexbot_startup_runtime.js.map +1 -1
- package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
- package/dist/modules/dexbot_state_recovery.js +55 -3
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/graceful_shutdown.d.ts +12 -1
- package/dist/modules/graceful_shutdown.d.ts.map +1 -1
- package/dist/modules/graceful_shutdown.js +39 -1
- package/dist/modules/graceful_shutdown.js.map +1 -1
- package/dist/modules/key_store.d.ts.map +1 -1
- package/dist/modules/key_store.js +38 -1
- package/dist/modules/key_store.js.map +1 -1
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +20 -3
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/grid.d.ts +1 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +288 -31
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts +4 -2
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +242 -222
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +11 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +132 -6
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +68 -14
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +241 -52
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/strategy.d.ts.map +1 -1
- package/dist/modules/order/strategy.js +25 -8
- package/dist/modules/order/strategy.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +261 -166
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +47 -5
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +175 -11
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +16 -15
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +199 -32
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/slot.d.ts +8 -0
- package/dist/modules/order/utils/slot.d.ts.map +1 -0
- package/dist/modules/order/utils/slot.js +16 -0
- package/dist/modules/order/utils/slot.js.map +1 -0
- 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/order/utils/validate.d.ts +5 -8
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +64 -82
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/order/working_grid.d.ts +5 -0
- package/dist/modules/order/working_grid.d.ts.map +1 -1
- package/dist/modules/order/working_grid.js +21 -0
- package/dist/modules/order/working_grid.js.map +1 -1
- package/dist/modules/paths.d.ts +4 -2
- package/dist/modules/paths.d.ts.map +1 -1
- package/dist/modules/paths.js +79 -24
- package/dist/modules/paths.js.map +1 -1
- package/dist/scripts/analyze-orders.d.ts.map +1 -1
- package/dist/scripts/analyze-orders.js +23 -2
- package/dist/scripts/analyze-orders.js.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.d.ts +2 -5
- package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.js +83 -1
- package/dist/scripts/generate_market_adapter_whitelist.js.map +1 -1
- package/docs/DEXBOT_COMPARISON.md +11 -11
- package/docs/EVOLUTION.md +9 -8
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/GRID_RECALCULATION.md +6 -0
- package/docs/README.md +2 -2
- package/market_adapter/README.md +10 -0
- package/package.json +2 -1
- package/scripts/README.md +4 -0
|
@@ -51,6 +51,9 @@ function calculateSwapInAmount(...args) { return require('./order/utils/math').c
|
|
|
51
51
|
function floatToBlockchainInt(...args) { return require('./order/utils/math').floatToBlockchainInt(...args); }
|
|
52
52
|
function blockchainToFloat(...args) { return require('./order/utils/math').blockchainToFloat(...args); }
|
|
53
53
|
function updateDynamicGridSnapshotSync(...args) { return require('../market_adapter/utils/dynamic_grid_snapshot').updateDynamicGridSnapshotSync(...args); }
|
|
54
|
+
// Lazy require: dexbot_fill_runtime imports this module, so a static import
|
|
55
|
+
// would be circular; at call time both modules are fully loaded.
|
|
56
|
+
function scheduleFillConsumerRestartFn(...args) { return require('./dexbot_fill_runtime').scheduleFillConsumerRestart(...args); }
|
|
54
57
|
function reconcileGridOrders(...args) { return require('./order/grid_reconcile').reconcileGridOrders(...args); }
|
|
55
58
|
function formatUnmatchedChainOrder(...args) { return require('./order/utils/order').formatUnmatchedChainOrder(...args); }
|
|
56
59
|
function getSideBudget(...args) { return require('./order/utils/order').getSideBudget(...args); }
|
|
@@ -94,7 +97,7 @@ const GRID_RESYNC_REASONS = Object.freeze({
|
|
|
94
97
|
...MARKET_ADAPTER_TRIGGER_RESETS,
|
|
95
98
|
manual_grid_resync: MANUAL_TRIGGER_METADATA,
|
|
96
99
|
rms_structural_grid_resync: {
|
|
97
|
-
shouldRefreshCenterPrice:
|
|
100
|
+
shouldRefreshCenterPrice: false,
|
|
98
101
|
centerRefreshContext: 'RMS structural grid resync',
|
|
99
102
|
centerRefreshLabel: 'RMS structural grid resync',
|
|
100
103
|
},
|
|
@@ -782,14 +785,38 @@ function performGridResync(bot, options = {}) {
|
|
|
782
785
|
// of virtualizing live ACTIVE slots; the trigger file is retained
|
|
783
786
|
// so the resync retries on a clean read.
|
|
784
787
|
let resyncReadAmbiguous = false;
|
|
788
|
+
const readOpenOrdersForResync = (label) => readOpenOrdersGuarded(chainOrders, self.accountId, {
|
|
789
|
+
log: (message, level) => self._log(message, level),
|
|
790
|
+
label,
|
|
791
|
+
detail: 'trigger-file resync',
|
|
792
|
+
});
|
|
793
|
+
// Empty-read confirm guard: an EMPTY read during a trigger reset is
|
|
794
|
+
// only accepted after one confirming re-read. A single 0-order
|
|
795
|
+
// snapshot from a lagging/partial node must never wipe a live grid
|
|
796
|
+
// and rebuild over existing orders (phantom reset — duplicated
|
|
797
|
+
// price levels and mis-tracked orders). A contradicted re-read
|
|
798
|
+
// (non-empty) feeds the fresh snapshot to the resync so the rebuild
|
|
799
|
+
// reconciles against the real chain state instead of overwriting it.
|
|
785
800
|
const readFn = async () => {
|
|
786
|
-
const orders = await
|
|
787
|
-
|
|
788
|
-
label: 'GRID-RESYNC',
|
|
789
|
-
detail: 'trigger-file resync',
|
|
790
|
-
});
|
|
791
|
-
if (orders === null)
|
|
801
|
+
const orders = await readOpenOrdersForResync('GRID-RESYNC');
|
|
802
|
+
if (orders === null) {
|
|
792
803
|
resyncReadAmbiguous = true;
|
|
804
|
+
return orders;
|
|
805
|
+
}
|
|
806
|
+
if (orders.length > 0)
|
|
807
|
+
return orders;
|
|
808
|
+
await sleep(TIMING.SYNC_EMPTY_READ_CONFIRM_DELAY_MS);
|
|
809
|
+
const confirmed = await readOpenOrdersForResync('GRID-RESYNC-CONFIRM');
|
|
810
|
+
if (confirmed === null) {
|
|
811
|
+
resyncReadAmbiguous = true;
|
|
812
|
+
return orders;
|
|
813
|
+
}
|
|
814
|
+
if (confirmed.length > 0) {
|
|
815
|
+
self._log(`[GRID-RESYNC] Empty open-order read contradicted by confirm re-read ` +
|
|
816
|
+
`(${confirmed.length} order(s) present) — using fresh non-empty snapshot`, 'warn');
|
|
817
|
+
return confirmed;
|
|
818
|
+
}
|
|
819
|
+
self._log('[GRID-RESYNC] Empty open-order read confirmed by re-read — accepting empty account', 'info');
|
|
793
820
|
return orders;
|
|
794
821
|
};
|
|
795
822
|
await recalculateGrid(self.manager, {
|
|
@@ -861,8 +888,11 @@ async function handlePendingTriggerReset(bot) {
|
|
|
861
888
|
bot._log('Pending trigger file detected. Processing reset before startup...');
|
|
862
889
|
const triggerInfo = readTriggerMetadata(bot.triggerFile);
|
|
863
890
|
let resetSucceeded = false;
|
|
891
|
+
// skipIdle: a pending trigger is an explicit reset command that must run
|
|
892
|
+
// before the startup sequence. The idle gate can block it indefinitely
|
|
893
|
+
// when the fill queue never drains, which would silently drop the reset.
|
|
864
894
|
await bot.manager._fillProcessingLock.acquire(async () => {
|
|
865
|
-
resetSucceeded = await performGridResync(bot, buildGridResyncOptions(triggerInfo));
|
|
895
|
+
resetSucceeded = await performGridResync(bot, { ...buildGridResyncOptions(triggerInfo), skipIdle: true });
|
|
866
896
|
});
|
|
867
897
|
if (!resetSucceeded) {
|
|
868
898
|
bot._warn('Pending trigger reset failed. Continuing with normal startup path.');
|
|
@@ -907,7 +937,13 @@ async function setupTriggerFileDetection(bot) {
|
|
|
907
937
|
bot.manager._fillProcessingLock.acquire(async () => {
|
|
908
938
|
if (bot._shuttingDown)
|
|
909
939
|
return;
|
|
910
|
-
|
|
940
|
+
// skipIdle: a trigger file is an explicit reset command.
|
|
941
|
+
// Deferring it behind the idle cooldown can loop forever
|
|
942
|
+
// when grid activity is continuous (fills arriving during
|
|
943
|
+
// placement batches refresh the settle window); the fill
|
|
944
|
+
// lock below already serializes the reset with in-flight
|
|
945
|
+
// batches.
|
|
946
|
+
const ok = await performGridResync(bot, { ...buildGridResyncOptions(triggerInfo), skipIdle: true });
|
|
911
947
|
if (!ok) {
|
|
912
948
|
bot._warn('Runtime trigger reset failed; retaining existing grid state.');
|
|
913
949
|
}
|
|
@@ -1003,7 +1039,7 @@ function startOpenOrdersSyncLoop(bot) {
|
|
|
1003
1039
|
if (syncResult?.filledOrders && syncResult.filledOrders.length > 0) {
|
|
1004
1040
|
bot._log(`Open-orders sync loop: ${syncResult.filledOrders.length} grid order(s) found filled on-chain. Triggering rebalance.`, 'info');
|
|
1005
1041
|
bot._markGridActivity?.('open-orders sync fill');
|
|
1006
|
-
const batchResult = await bot._processFillsWithBatching(syncResult.filledOrders, new Set(), 'open-orders sync fill rebalance');
|
|
1042
|
+
const batchResult = await bot._processFillsWithBatching(syncResult.filledOrders, new Set(), 'open-orders sync fill rebalance', { isReplay: true });
|
|
1007
1043
|
if (!batchResult?.aborted) {
|
|
1008
1044
|
await bot.manager.persistGrid();
|
|
1009
1045
|
}
|
|
@@ -1134,7 +1170,7 @@ function setupBlockchainFetchInterval(bot) {
|
|
|
1134
1170
|
if (syncResult.filledOrders && syncResult.filledOrders.length > 0) {
|
|
1135
1171
|
bot._log(`Periodic sync: ${syncResult.filledOrders.length} grid order(s) found filled on-chain. Triggering rebalance.`, 'info');
|
|
1136
1172
|
bot._markGridActivity?.('periodic sync fill rebalance');
|
|
1137
|
-
const batchResult = await bot._processFillsWithBatching(syncResult.filledOrders, new Set(), 'periodic sync fill rebalance');
|
|
1173
|
+
const batchResult = await bot._processFillsWithBatching(syncResult.filledOrders, new Set(), 'periodic sync fill rebalance', { isReplay: true });
|
|
1138
1174
|
if (!batchResult?.aborted) {
|
|
1139
1175
|
await bot.manager.persistGrid();
|
|
1140
1176
|
}
|
|
@@ -1180,6 +1216,72 @@ function stopBlockchainFetchInterval(bot) {
|
|
|
1180
1216
|
bot._log('Stopped periodic blockchain fetch interval');
|
|
1181
1217
|
}
|
|
1182
1218
|
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Set up the periodic bots.json fingerprint poll interval.
|
|
1221
|
+
* Decoupled from the heavy blockchain fetch interval (default 240min) so
|
|
1222
|
+
* config changes are visible within BOTS_CONFIG_POLL_INTERVAL_MS (default 5min).
|
|
1223
|
+
* Uses the shared fingerprint via syncMarketAdapterOnPeriodicConfigCheck so
|
|
1224
|
+
* the market adapter start/stop logic stays in one place.
|
|
1225
|
+
* @param {import('./dexbot_class.js').DEXBot} bot
|
|
1226
|
+
*/
|
|
1227
|
+
function setupBotsConfigPollInterval(bot) {
|
|
1228
|
+
// Allow per-bot timing override (e.g. tests or per-bot tuning)
|
|
1229
|
+
let intervalMs = bot.config?.timing?.BOTS_CONFIG_POLL_INTERVAL_MS;
|
|
1230
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
1231
|
+
intervalMs = Number(TIMING.BOTS_CONFIG_POLL_INTERVAL_MS);
|
|
1232
|
+
}
|
|
1233
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
1234
|
+
bot._log(`Bots-config poll interval disabled (value: ${intervalMs}). Fingerprint changes will only be seen on blockchain fetch ticks.`);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
// Ensure the fingerprint exists before the first interval fires.
|
|
1238
|
+
// setupBlockchainFetchInterval already fires syncMarketAdapterOnPeriodicConfigCheck
|
|
1239
|
+
// at startup, so this is a fallback for bots that never run the blockchain interval
|
|
1240
|
+
// (e.g. dryRun or accountId missing).
|
|
1241
|
+
if (!bot._marketAdapterWatchdogFingerprint) {
|
|
1242
|
+
syncMarketAdapterOnPeriodicConfigCheck(bot, 'startup bots-config poll setup').catch((err) => {
|
|
1243
|
+
bot._warn(`Bots-config poll setup failed: ${getErrorMessage(err)}`);
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
if (bot._botsConfigPollInterval !== null && bot._botsConfigPollInterval !== undefined) {
|
|
1247
|
+
stopBotsConfigPollInterval(bot);
|
|
1248
|
+
}
|
|
1249
|
+
bot._botsConfigPollInterval = setInterval(async () => {
|
|
1250
|
+
if (bot._shuttingDown)
|
|
1251
|
+
return;
|
|
1252
|
+
// Coalesce with the blockchain-fetch tick: if its tick is already
|
|
1253
|
+
// driving the watchdog, skip to avoid double PM2 queries.
|
|
1254
|
+
if (bot._marketAdapterWatchdogInFlight)
|
|
1255
|
+
return;
|
|
1256
|
+
if (bot._botsConfigPollInFlight)
|
|
1257
|
+
return;
|
|
1258
|
+
bot._botsConfigPollInFlight = true;
|
|
1259
|
+
try {
|
|
1260
|
+
await syncMarketAdapterOnPeriodicConfigCheck(bot, 'bots-config poll');
|
|
1261
|
+
}
|
|
1262
|
+
catch (err) {
|
|
1263
|
+
bot._warn(`Bots-config poll failed: ${getErrorMessage(err)}`);
|
|
1264
|
+
}
|
|
1265
|
+
finally {
|
|
1266
|
+
bot._botsConfigPollInFlight = false;
|
|
1267
|
+
}
|
|
1268
|
+
}, intervalMs);
|
|
1269
|
+
if (typeof bot._botsConfigPollInterval.unref === 'function') {
|
|
1270
|
+
bot._botsConfigPollInterval.unref();
|
|
1271
|
+
}
|
|
1272
|
+
bot._log(`Started bots-config poll interval: every ${Math.round(intervalMs / 1000)}s (fingerprint check)`);
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Stop the periodic bots.json fingerprint poll interval.
|
|
1276
|
+
* @param {import('./dexbot_class.js').DEXBot} bot
|
|
1277
|
+
*/
|
|
1278
|
+
function stopBotsConfigPollInterval(bot) {
|
|
1279
|
+
if (bot._botsConfigPollInterval !== null && bot._botsConfigPollInterval !== undefined) {
|
|
1280
|
+
clearInterval(bot._botsConfigPollInterval);
|
|
1281
|
+
bot._botsConfigPollInterval = null;
|
|
1282
|
+
bot._log('Stopped bots-config poll interval');
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1183
1285
|
/**
|
|
1184
1286
|
* Release the market adapter runtime for a bot.
|
|
1185
1287
|
* In PM2 mode this is a no-op; in direct mode it calls the shared runtime's releaseBot.
|
|
@@ -1417,7 +1519,13 @@ async function executeMaintenanceLogic(bot, context) {
|
|
|
1417
1519
|
if (pendingCorrections > 0) {
|
|
1418
1520
|
const correctionResult = await correctAllPriceMismatches(bot.manager, bot.account, bot.privateKey, chainOrders);
|
|
1419
1521
|
if (correctionResult.failed > 0) {
|
|
1420
|
-
|
|
1522
|
+
const failedDetails = (correctionResult.results || [])
|
|
1523
|
+
.filter((r) => !(r.result && r.result.success))
|
|
1524
|
+
.slice(0, 3)
|
|
1525
|
+
.map((r) => `${r.chainOrderId ?? '?'}: ${r.result?.error ?? 'unknown'}`)
|
|
1526
|
+
.join(' | ');
|
|
1527
|
+
bot._warn(`[MAINT] ${correctionResult.failed}/${pendingCorrections} price correction(s) failed` +
|
|
1528
|
+
(failedDetails ? ` — ${failedDetails}` : ''));
|
|
1421
1529
|
}
|
|
1422
1530
|
}
|
|
1423
1531
|
// Dust detection runs before the pipeline gate. Cancellation is immediate
|
|
@@ -1652,7 +1760,7 @@ async function cancelDustOrders(bot, { buy: buyDust = [], sell: sellDust = [] }
|
|
|
1652
1760
|
}
|
|
1653
1761
|
if (syntheticFills.length === 0)
|
|
1654
1762
|
return { cancelledCount: 0, batchResult: null };
|
|
1655
|
-
const result = await bot._processFillsWithBatching(syntheticFills, new Set(), `dust cancel [${syntheticFills.map((o) => o.id).join(', ')}]
|
|
1763
|
+
const result = await bot._processFillsWithBatching(syntheticFills, new Set(), `dust cancel [${syntheticFills.map((o) => o.id).join(', ')}]`, { skipAnchorUpdate: true });
|
|
1656
1764
|
if (!result.aborted) {
|
|
1657
1765
|
await bot.manager.persistGrid();
|
|
1658
1766
|
}
|
|
@@ -1896,6 +2004,32 @@ function setupDustHealthCheckInterval(bot) {
|
|
|
1896
2004
|
bot._dustHealthCheckTimer.unref();
|
|
1897
2005
|
}
|
|
1898
2006
|
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Retrigger the fill consumer once the recovery/pipeline window has fully
|
|
2009
|
+
* cleared. The consumer's defer branch (batchInFlight / recoverySyncInFlight /
|
|
2010
|
+
* broadcasting) returns WITHOUT rescheduling, so fills enqueued during a long
|
|
2011
|
+
* window would otherwise sit in the queue forever — which also keeps the
|
|
2012
|
+
* maintenance idle gate shut (queue-length check returns the full settle
|
|
2013
|
+
* delay) and blocks deferred grid resyncs and periodic maintenance.
|
|
2014
|
+
* @param {import('./dexbot_class.js').DEXBot} bot
|
|
2015
|
+
*/
|
|
2016
|
+
function drainFillQueueAfterPipelineClear(bot) {
|
|
2017
|
+
try {
|
|
2018
|
+
if (!bot || bot._shuttingDown)
|
|
2019
|
+
return;
|
|
2020
|
+
if ((bot._recoverySyncInFlight || 0) > 0)
|
|
2021
|
+
return;
|
|
2022
|
+
if ((bot._batchInFlight || 0) > 0)
|
|
2023
|
+
return;
|
|
2024
|
+
if (bot.manager?.isBroadcastingActive?.())
|
|
2025
|
+
return;
|
|
2026
|
+
if (!bot._incomingFillQueue || bot._incomingFillQueue.length === 0)
|
|
2027
|
+
return;
|
|
2028
|
+
bot._log(`[FILL-QUEUE] Pipeline cleared; draining ${bot._incomingFillQueue.length} deferred fill(s).`, 'info');
|
|
2029
|
+
scheduleFillConsumerRestartFn(bot, chainOrders);
|
|
2030
|
+
}
|
|
2031
|
+
catch { }
|
|
2032
|
+
}
|
|
1899
2033
|
/**
|
|
1900
2034
|
* Request a full grid reset from fresh on-chain state.
|
|
1901
2035
|
* @param {import('./dexbot_class.js').DEXBot} bot
|
|
@@ -1930,6 +2064,8 @@ async function requestGridReset(bot, reason = 'structural change', options = {})
|
|
|
1930
2064
|
}
|
|
1931
2065
|
finally {
|
|
1932
2066
|
bot._recoverySyncInFlight = Math.max(0, (bot._recoverySyncInFlight || 0) - 1);
|
|
2067
|
+
if ((bot._recoverySyncInFlight || 0) === 0)
|
|
2068
|
+
drainFillQueueAfterPipelineClear(bot);
|
|
1933
2069
|
}
|
|
1934
2070
|
}
|
|
1935
2071
|
return bot.manager._fillProcessingLock.acquire(async () => performGridResync(bot, resetOptions));
|
|
@@ -1943,14 +2079,21 @@ function wireStructuralGridResyncRequest(bot) {
|
|
|
1943
2079
|
return;
|
|
1944
2080
|
bot.manager.requestStructuralGridResync = async (reason = 'structural recovery', details = {}) => {
|
|
1945
2081
|
if (bot._shuttingDown) {
|
|
2082
|
+
bot._warn(`[RECOVERY] Structural resync skip (shutting down): ${reason}`);
|
|
1946
2083
|
return { skipped: true, reason: 'shutting down' };
|
|
1947
2084
|
}
|
|
1948
2085
|
if (bot._structuralGridResyncRunning || bot._structuralGridResyncTimer) {
|
|
1949
|
-
|
|
2086
|
+
const why = bot._structuralGridResyncRunning ? 'already running' : 'already scheduled';
|
|
2087
|
+
bot._warn(`[RECOVERY] Structural resync skip (${why}): ${reason}`);
|
|
2088
|
+
return { skipped: true, reason: `structural grid resync ${why}` };
|
|
1950
2089
|
}
|
|
1951
2090
|
const unmatchedCount = Array.isArray(details?.unmatchedChainOrders)
|
|
1952
2091
|
? details.unmatchedChainOrders.length
|
|
1953
2092
|
: 0;
|
|
2093
|
+
// P5 max-defer: cap batchInFlight re-arm so a fill storm cannot starve
|
|
2094
|
+
// the resync forever (a fill-storm defer loop once kept the flag armed
|
|
2095
|
+
// indefinitely). After the cap, the resync forces instead of re-arming.
|
|
2096
|
+
const STRUCTURAL_RESYNC_MAX_DEFER_MS = 30000;
|
|
1954
2097
|
const runStructuralResync = async () => {
|
|
1955
2098
|
bot._structuralGridResyncTimer = null;
|
|
1956
2099
|
if (bot._shuttingDown)
|
|
@@ -1962,8 +2105,36 @@ function wireStructuralGridResyncRequest(bot) {
|
|
|
1962
2105
|
// completes; the fill consumer is already gated on _batchInFlight,
|
|
1963
2106
|
// so this only blocks the recovery itself, never new fills.
|
|
1964
2107
|
if (bot._batchInFlight > 0) {
|
|
1965
|
-
|
|
1966
|
-
|
|
2108
|
+
const now = Date.now();
|
|
2109
|
+
if (!bot._structuralGridResyncDeferStartedAt) {
|
|
2110
|
+
bot._structuralGridResyncDeferStartedAt = now;
|
|
2111
|
+
bot._structuralGridResyncDeferCount = 0;
|
|
2112
|
+
}
|
|
2113
|
+
bot._structuralGridResyncDeferCount = (bot._structuralGridResyncDeferCount || 0) + 1;
|
|
2114
|
+
const deferredMs = now - bot._structuralGridResyncDeferStartedAt;
|
|
2115
|
+
if (deferredMs >= STRUCTURAL_RESYNC_MAX_DEFER_MS) {
|
|
2116
|
+
bot._warn(`[RECOVERY] Structural resync max-defer reached (${deferredMs}ms, cap ${STRUCTURAL_RESYNC_MAX_DEFER_MS}ms, deferred ${bot._structuralGridResyncDeferCount}x) — forcing despite _batchInFlight=${bot._batchInFlight}: ${reason}`);
|
|
2117
|
+
bot._structuralGridResyncDeferStartedAt = null;
|
|
2118
|
+
bot._structuralGridResyncDeferCount = 0;
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
// Fix #7 (docs/CONSOLIDATED_ORPHAN_FIX_SUMMARY.md §2): throttle the defer log to the
|
|
2122
|
+
// first occurrence per cap window (a fill storm can emit hundreds of
|
|
2123
|
+
// defer lines in seconds); count the rest and surface them only in
|
|
2124
|
+
// the final forced message.
|
|
2125
|
+
if (bot._structuralGridResyncDeferCount === 1) {
|
|
2126
|
+
bot._warn(`[RECOVERY] Structural resync defer (batchInFlight=${bot._batchInFlight}, deferred ${deferredMs}ms/${STRUCTURAL_RESYNC_MAX_DEFER_MS}ms): ${reason}`);
|
|
2127
|
+
}
|
|
2128
|
+
else {
|
|
2129
|
+
bot.manager?.logger?.log?.(`[RECOVERY] Structural resync defer (suppressed; count=${bot._structuralGridResyncDeferCount}, deferred ${deferredMs}ms/${STRUCTURAL_RESYNC_MAX_DEFER_MS}ms): ${reason}`, 'debug');
|
|
2130
|
+
}
|
|
2131
|
+
bot._structuralGridResyncTimer = setTimeout(runStructuralResync, TIMING.LOCK_REFRESH_MIN_MS);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
else {
|
|
2136
|
+
bot._structuralGridResyncDeferStartedAt = null;
|
|
2137
|
+
bot._structuralGridResyncDeferCount = 0;
|
|
1967
2138
|
}
|
|
1968
2139
|
bot._structuralGridResyncRunning++;
|
|
1969
2140
|
try {
|
|
@@ -1989,14 +2160,30 @@ function wireStructuralGridResyncRequest(bot) {
|
|
|
1989
2160
|
}
|
|
1990
2161
|
const suffix = unmatchedCount > 0 ? ` (${unmatchedCount} unmatched chain order(s))` : '';
|
|
1991
2162
|
bot._warn(`[RECOVERY] Running structural full grid resync for ${reason}${suffix}`);
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2163
|
+
// Same protection as the persisted-grid reload above: the
|
|
2164
|
+
// reset's reconcile Phase-2 placement runs for minutes; a
|
|
2165
|
+
// fill arriving mid-reset must not start a COW batch whose
|
|
2166
|
+
// broadcast overlaps it (commit refusal + duplicate
|
|
2167
|
+
// generation orphans). _recoverFromPersistedGrid already
|
|
2168
|
+
// incremented and released the counter; increment again
|
|
2169
|
+
// for the full-resync branch.
|
|
2170
|
+
bot._recoverySyncInFlight = (bot._recoverySyncInFlight || 0) + 1;
|
|
2171
|
+
let resetResult;
|
|
2172
|
+
try {
|
|
2173
|
+
resetResult = await bot.requestGridReset('rms_structural_grid_resync', {
|
|
2174
|
+
refreshCenterPrice: false,
|
|
2175
|
+
// Structural resync is a chain-read/rebuild; do not let a
|
|
2176
|
+
// fill storm starve it behind the idle cooldown or the
|
|
2177
|
+
// fill-processing lock.
|
|
2178
|
+
skipIdle: true,
|
|
2179
|
+
skipFillLock: true,
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
finally {
|
|
2183
|
+
bot._recoverySyncInFlight = Math.max(0, (bot._recoverySyncInFlight || 0) - 1);
|
|
2184
|
+
if ((bot._recoverySyncInFlight || 0) === 0)
|
|
2185
|
+
drainFillQueueAfterPipelineClear(bot);
|
|
2186
|
+
}
|
|
2000
2187
|
if (resetResult && bot.manager?._recoveryState) {
|
|
2001
2188
|
bot.manager._recoveryState = { ...bot.manager._recoveryState, attemptCount: 0, lastAttemptAt: 0, lastFailureAt: 0 };
|
|
2002
2189
|
}
|
|
@@ -2101,7 +2288,7 @@ async function syncOpenOrdersAndProcessFillsImpl(bot, tag) {
|
|
|
2101
2288
|
if (syncResult?.filledOrders?.length > 0) {
|
|
2102
2289
|
bot._refreshDynamicWeightDistribution(`${tag} sync-fill`);
|
|
2103
2290
|
bot._log(`[SYNC-CHAIN] ${syncResult.filledOrders.length} filled order(s) found during ${tag}`, 'info');
|
|
2104
|
-
const batchResult = await bot._processFillsWithBatching(syncResult.filledOrders, new Set(), `${tag} sync-fill
|
|
2291
|
+
const batchResult = await bot._processFillsWithBatching(syncResult.filledOrders, new Set(), `${tag} sync-fill`, { isReplay: true });
|
|
2105
2292
|
if (!batchResult?.aborted) {
|
|
2106
2293
|
// Reassign the returned snapshot to the post-fill re-read: the
|
|
2107
2294
|
// caller feeds openOrders into reconcileGridOrders, which must
|
|
@@ -2128,7 +2315,7 @@ async function syncOpenOrdersAndProcessFillsImpl(bot, tag) {
|
|
|
2128
2315
|
return { syncResult: null, aborted: true, hasUnmatched: -1, openOrders: null };
|
|
2129
2316
|
}
|
|
2130
2317
|
}
|
|
2131
|
-
export { loadBotsConfigSnapshot, refreshDynamicWeightDistribution, performGridResync, updateBotGridResetMetadata, handlePendingTriggerReset, setupTriggerFileDetection, performPeriodicGridChecks, isOpenOrdersSyncLoopEnabled, startOpenOrdersSyncLoop, stopOpenOrdersSyncLoop, setupBlockchainFetchInterval, stopBlockchainFetchInterval, executeMaintenanceLogic, cancelDustOrders, isOrderDoesNotExistError, runGridMaintenance, stopMarketAdapterPm2, releaseMarketAdapterRuntime, syncMarketAdapterOnPeriodicConfigCheck, findSnapshotBotForRuntimeConfig, runtimeConfigNeedsMarketAdapter, usesAmaGridPrice, checkBtsBalanceAndAcquire, acquireBts, runDustHealthCheck, setupDustHealthCheckInterval, requestGridReset, wireStructuralGridResyncRequest, getPipelineSignals, markGridActivity, getMetrics, syncOpenOrdersAndProcessFills };
|
|
2318
|
+
export { loadBotsConfigSnapshot, refreshDynamicWeightDistribution, performGridResync, updateBotGridResetMetadata, handlePendingTriggerReset, setupTriggerFileDetection, performPeriodicGridChecks, isOpenOrdersSyncLoopEnabled, startOpenOrdersSyncLoop, stopOpenOrdersSyncLoop, setupBlockchainFetchInterval, stopBlockchainFetchInterval, setupBotsConfigPollInterval, stopBotsConfigPollInterval, executeMaintenanceLogic, cancelDustOrders, isOrderDoesNotExistError, runGridMaintenance, stopMarketAdapterPm2, releaseMarketAdapterRuntime, syncMarketAdapterOnPeriodicConfigCheck, findSnapshotBotForRuntimeConfig, runtimeConfigNeedsMarketAdapter, usesAmaGridPrice, checkBtsBalanceAndAcquire, acquireBts, runDustHealthCheck, setupDustHealthCheckInterval, requestGridReset, wireStructuralGridResyncRequest, getPipelineSignals, markGridActivity, getMetrics, syncOpenOrdersAndProcessFills };
|
|
2132
2319
|
export default {
|
|
2133
2320
|
loadBotsConfigSnapshot,
|
|
2134
2321
|
refreshDynamicWeightDistribution,
|
|
@@ -2142,6 +2329,8 @@ export default {
|
|
|
2142
2329
|
stopOpenOrdersSyncLoop,
|
|
2143
2330
|
setupBlockchainFetchInterval,
|
|
2144
2331
|
stopBlockchainFetchInterval,
|
|
2332
|
+
setupBotsConfigPollInterval,
|
|
2333
|
+
stopBotsConfigPollInterval,
|
|
2145
2334
|
executeMaintenanceLogic,
|
|
2146
2335
|
cancelDustOrders,
|
|
2147
2336
|
isOrderDoesNotExistError,
|