@clonegod/ttd-core 3.1.25 → 3.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alert/codes.js +1 -0
- package/dist/alert/log_rules.js +1 -0
- package/dist/cache/arb_cache.d.ts +28 -1
- package/dist/cache/arb_cache.js +143 -30
- package/package.json +1 -1
- package/types/index.d.ts +7 -1
package/dist/alert/codes.js
CHANGED
|
@@ -68,6 +68,7 @@ exports.ALERT_CODES = {
|
|
|
68
68
|
},
|
|
69
69
|
INFRA_PROCESS_CRASH_LOOP: { code: 'INFRA_PROCESS_CRASH_LOOP', severity: S.CRITICAL, category: 'infra', desc: '进程 5 分钟内反复重启', suggested_action: '停用该进程' },
|
|
70
70
|
INFRA_ENV_MISSING: { code: 'INFRA_ENV_MISSING', severity: S.ERROR, category: 'infra', desc: '关键 env 未配置' },
|
|
71
|
+
CONFIG_TOKEN_DUPLICATE: { code: 'CONFIG_TOKEN_DUPLICATE', severity: S.WARN, category: 'infra', desc: '同一 address 对应多个 symbol 或 pool 引用缺失 token(配置垃圾数据)', suggested_action: '人工清理 token-list.json / pool-list.json / fixed_symbol_address.json' },
|
|
71
72
|
ANALYZE_UPSTREAM_MISSING: { code: 'ANALYZE_UPSTREAM_MISSING', severity: S.WARN, category: 'analyze', desc: '上游消息缺环节(PreTrade/OnTrade/PostTrade)' },
|
|
72
73
|
ANALYZE_SQLITE_FULL: { code: 'ANALYZE_SQLITE_FULL', severity: S.WARN, category: 'analyze', desc: 'SQLite 接近容量上限' },
|
|
73
74
|
ANALYZE_SQLITE_WRITE_FAIL: { code: 'ANALYZE_SQLITE_WRITE_FAIL', severity: S.ERROR, category: 'analyze', desc: 'SQLite 写失败' },
|
package/dist/alert/log_rules.js
CHANGED
|
@@ -46,6 +46,7 @@ registerLogRules([
|
|
|
46
46
|
{ pattern: /pricefeed.*(parse failed|not an array|empty)|empty response at page/i,
|
|
47
47
|
level: 'warn', code: 'THIRDPARTY_DATA_INVALID' },
|
|
48
48
|
{ pattern: /price cache miss/i, level: 'warn', code: 'QUOTE_PRICE_CACHE_MISS' },
|
|
49
|
+
{ pattern: /\[token-duplicate\]|\[pool-skip\]/, level: 'warn', code: 'CONFIG_TOKEN_DUPLICATE' },
|
|
49
50
|
{ pattern: /sqlite.*(disk|full|i\/o error|cleanup error|write.*fail)/i,
|
|
50
51
|
level: 'error', code: 'ANALYZE_SQLITE_WRITE_FAIL' },
|
|
51
52
|
{ pattern: /db size.*\d+mb.*limit/i, level: 'warn', code: 'ANALYZE_SQLITE_FULL' },
|
|
@@ -33,7 +33,34 @@ export declare class ArbCache {
|
|
|
33
33
|
get_one_token_info_by_symbol(symbol: string): Promise<StandardTokenInfoType>;
|
|
34
34
|
publish_token_change_event(): Promise<void>;
|
|
35
35
|
cache_pool_list(): Promise<void>;
|
|
36
|
-
|
|
36
|
+
private scan_duplicate_address;
|
|
37
|
+
resolve_duplicate_address(authoritative_mappings: Array<{
|
|
38
|
+
address: string;
|
|
39
|
+
symbol: string;
|
|
40
|
+
}>): Promise<{
|
|
41
|
+
cleaned_tokens: Array<{
|
|
42
|
+
address: string;
|
|
43
|
+
dropped_symbols: string[];
|
|
44
|
+
kept_symbol: string;
|
|
45
|
+
}>;
|
|
46
|
+
cleaned_pools: Array<{
|
|
47
|
+
pool_address: string;
|
|
48
|
+
pool_name: string;
|
|
49
|
+
dex_id: string;
|
|
50
|
+
}>;
|
|
51
|
+
skipped_trading_pools: Array<{
|
|
52
|
+
pool_address: string;
|
|
53
|
+
pool_name: string;
|
|
54
|
+
dex_id: string;
|
|
55
|
+
reason: string;
|
|
56
|
+
}>;
|
|
57
|
+
not_found: Array<{
|
|
58
|
+
address: string;
|
|
59
|
+
symbol: string;
|
|
60
|
+
reason: string;
|
|
61
|
+
}>;
|
|
62
|
+
}>;
|
|
63
|
+
set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): boolean;
|
|
37
64
|
set_pool_extra(pool: StandardPoolInfoType): void;
|
|
38
65
|
update_pool_list(input_dex_pool_list: StandardDexPoolConfigType[]): Promise<void>;
|
|
39
66
|
get_pool_list_by_pair(pair: string): Promise<StandardPoolInfoType[]>;
|
package/dist/cache/arb_cache.js
CHANGED
|
@@ -17,6 +17,7 @@ const core_env_1 = require("../app_config/core_env");
|
|
|
17
17
|
const fs_1 = __importDefault(require("fs"));
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
19
|
const index_1 = require("../index");
|
|
20
|
+
const fixed_symbol_address_1 = require("../token/fixed_symbol_address");
|
|
20
21
|
class ArbCache {
|
|
21
22
|
constructor(env_args) {
|
|
22
23
|
this.chain_id = env_args.chain_id;
|
|
@@ -81,33 +82,23 @@ class ArbCache {
|
|
|
81
82
|
return path_1.default.join('config', chain_id, file_name);
|
|
82
83
|
}
|
|
83
84
|
refresh_local_cache_token_list(token_list) {
|
|
84
|
-
const
|
|
85
|
+
const prev_by_symbol = new Map(this.token_symbol_map);
|
|
86
|
+
this.token_symbol_map.clear();
|
|
87
|
+
this.token_address_map.clear();
|
|
85
88
|
const merged = [];
|
|
86
89
|
for (const fresh of token_list) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
for (const [addr, existing] of this.token_address_map) {
|
|
91
|
-
const fresh = new_by_addr.get(addr);
|
|
92
|
-
if (fresh) {
|
|
90
|
+
const existing = prev_by_symbol.get(fresh.symbol);
|
|
91
|
+
let token_ref;
|
|
92
|
+
if (existing) {
|
|
93
93
|
Object.assign(existing, fresh);
|
|
94
|
-
|
|
95
|
-
new_by_addr.delete(addr);
|
|
94
|
+
token_ref = existing;
|
|
96
95
|
}
|
|
97
96
|
else {
|
|
98
|
-
|
|
97
|
+
token_ref = fresh;
|
|
99
98
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
for (const fresh of new_by_addr.values()) {
|
|
105
|
-
this.token_address_map.set(fresh.address, fresh);
|
|
106
|
-
merged.push(fresh);
|
|
107
|
-
}
|
|
108
|
-
this.token_symbol_map.clear();
|
|
109
|
-
for (const token of merged) {
|
|
110
|
-
this.token_symbol_map.set(token.symbol, token);
|
|
99
|
+
this.token_symbol_map.set(token_ref.symbol, token_ref);
|
|
100
|
+
this.token_address_map.set(token_ref.address, token_ref);
|
|
101
|
+
merged.push(token_ref);
|
|
111
102
|
}
|
|
112
103
|
this.token_list = merged;
|
|
113
104
|
this.token_list_cache_last_update_time = (0, index_1.getCurDateTime)();
|
|
@@ -156,6 +147,7 @@ class ArbCache {
|
|
|
156
147
|
}
|
|
157
148
|
let config = (0, index_1.readFile)(filepath);
|
|
158
149
|
let token_list = config.token_list.filter(e => e.enable);
|
|
150
|
+
this.scan_duplicate_address(token_list);
|
|
159
151
|
for (let token of token_list) {
|
|
160
152
|
let field = token.symbol;
|
|
161
153
|
let value = JSON.stringify(token);
|
|
@@ -254,12 +246,22 @@ class ArbCache {
|
|
|
254
246
|
let all_dex_pool = [];
|
|
255
247
|
let config = (0, index_1.readFile)(filepath);
|
|
256
248
|
let token_list = yield this.get_token_list();
|
|
249
|
+
const skipped = [];
|
|
257
250
|
for (let { dex_id, pool_list } of config.dex_list) {
|
|
258
251
|
pool_list = pool_list.filter(e => e.enable);
|
|
259
252
|
(0, index_1.log_trace)(`cache_pool_list`, { dex_id, pool_list_len: pool_list.length });
|
|
253
|
+
const valid_pools = [];
|
|
260
254
|
for (let pool of pool_list) {
|
|
261
255
|
pool.dex_id = dex_id;
|
|
262
|
-
this.set_pool_token_info(pool, token_list)
|
|
256
|
+
if (!this.set_pool_token_info(pool, token_list)) {
|
|
257
|
+
const [t0, t1] = pool.pool_name.split('/');
|
|
258
|
+
const missing = [
|
|
259
|
+
!pool.tokenA ? t0 : null,
|
|
260
|
+
!pool.tokenB ? t1 : null,
|
|
261
|
+
].filter(Boolean).join(',');
|
|
262
|
+
skipped.push({ pool_address: pool.pool_address, pool_name: pool.pool_name, dex_id, missing });
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
263
265
|
this.set_pool_extra(pool);
|
|
264
266
|
let field = pool.pool_address;
|
|
265
267
|
let value = JSON.stringify(pool);
|
|
@@ -267,14 +269,131 @@ class ArbCache {
|
|
|
267
269
|
if (index_1.LOG.debug) {
|
|
268
270
|
(0, index_1.log_trace)(`cache_pool_list, pool_address=${pool.pool_address}, result: ` + JSON.stringify(result));
|
|
269
271
|
}
|
|
272
|
+
valid_pools.push(pool);
|
|
270
273
|
}
|
|
271
|
-
all_dex_pool.push(...
|
|
274
|
+
all_dex_pool.push(...valid_pools);
|
|
275
|
+
}
|
|
276
|
+
if (skipped.length > 0) {
|
|
277
|
+
const lines = skipped.map((s, i) => ` ${i + 1}. [${s.dex_id}] ${s.pool_name} (pool=${s.pool_address}) — missing symbol(s): ${s.missing}`);
|
|
278
|
+
(0, index_1.log_warn)(`[pool-skip] 跳过 ${skipped.length} 个池子:token 在 token-list 中找不到(多 symbol 冲突 / symbol 已删等),请人工清理 pool-list.json 或相关 token 映射:\n${lines.join('\n')}`);
|
|
272
279
|
}
|
|
273
280
|
this.refresh_local_cache_pool_list(all_dex_pool);
|
|
274
281
|
let result = yield this.loading_cache.hlen(this.chain_id, index_1.CACHE_KEY_TYPE.CONFIG_POOL_LIST);
|
|
275
282
|
(0, index_1.log_trace)(`cache_pool_list, end`, result);
|
|
276
283
|
});
|
|
277
284
|
}
|
|
285
|
+
scan_duplicate_address(token_list) {
|
|
286
|
+
var _a;
|
|
287
|
+
const by_addr = new Map();
|
|
288
|
+
for (const t of token_list) {
|
|
289
|
+
const addr = (t.address || '').toLowerCase();
|
|
290
|
+
if (!addr)
|
|
291
|
+
continue;
|
|
292
|
+
if (!by_addr.has(addr))
|
|
293
|
+
by_addr.set(addr, []);
|
|
294
|
+
by_addr.get(addr).push(t);
|
|
295
|
+
}
|
|
296
|
+
const fixed_by_addr = new Map();
|
|
297
|
+
for (const f of (0, fixed_symbol_address_1.get_fixed_symbol_address)()) {
|
|
298
|
+
fixed_by_addr.set((f.address || '').toLowerCase(), f.symbol);
|
|
299
|
+
}
|
|
300
|
+
const conflicts = [];
|
|
301
|
+
for (const [addr, tokens] of by_addr) {
|
|
302
|
+
if (tokens.length <= 1)
|
|
303
|
+
continue;
|
|
304
|
+
conflicts.push({
|
|
305
|
+
address: addr,
|
|
306
|
+
symbols: tokens.map(t => t.symbol),
|
|
307
|
+
fixed_symbol: (_a = fixed_by_addr.get(addr)) !== null && _a !== void 0 ? _a : null,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (conflicts.length === 0)
|
|
311
|
+
return;
|
|
312
|
+
const lines = conflicts.map((c, i) => {
|
|
313
|
+
const auth = c.fixed_symbol ? ` 权威 symbol=${c.fixed_symbol}` : ' 未在 fixed_symbol_address.json 登记(需先登记权威 symbol)';
|
|
314
|
+
return ` ${i + 1}. address=${c.address} symbols=[${c.symbols.join(', ')}]${auth}`;
|
|
315
|
+
});
|
|
316
|
+
(0, index_1.log_warn)(`[token-duplicate] 发现 ${conflicts.length} 个 address 被多 symbol 引用(垃圾数据,需人工清理 token-list.json + pool-list.json + Redis):\n${lines.join('\n')}`);
|
|
317
|
+
}
|
|
318
|
+
resolve_duplicate_address(authoritative_mappings) {
|
|
319
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
320
|
+
const token_list = yield this.get_token_list();
|
|
321
|
+
const by_addr = new Map();
|
|
322
|
+
for (const t of token_list) {
|
|
323
|
+
const addr = (t.address || '').toLowerCase();
|
|
324
|
+
if (!addr)
|
|
325
|
+
continue;
|
|
326
|
+
if (!by_addr.has(addr))
|
|
327
|
+
by_addr.set(addr, []);
|
|
328
|
+
by_addr.get(addr).push(t);
|
|
329
|
+
}
|
|
330
|
+
const tokens_to_disable = [];
|
|
331
|
+
const cleaned_tokens = [];
|
|
332
|
+
const not_found = [];
|
|
333
|
+
for (const { address, symbol } of authoritative_mappings) {
|
|
334
|
+
const addr = (address || '').toLowerCase();
|
|
335
|
+
const tokens = by_addr.get(addr) || [];
|
|
336
|
+
if (tokens.length === 0) {
|
|
337
|
+
not_found.push({ address: addr, symbol, reason: `address 在 token-list 中未找到` });
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const canonical = tokens.find(t => t.symbol === symbol);
|
|
341
|
+
if (!canonical) {
|
|
342
|
+
not_found.push({ address: addr, symbol, reason: `symbol=${symbol} 在 address ${addr} 的条目中未找到(token-list 里 symbols=[${tokens.map(t => t.symbol).join(',')}])` });
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const dropped = tokens.filter(t => t.symbol !== symbol);
|
|
346
|
+
if (dropped.length === 0)
|
|
347
|
+
continue;
|
|
348
|
+
for (const t of dropped) {
|
|
349
|
+
tokens_to_disable.push(Object.assign(Object.assign({}, t), { enable: false }));
|
|
350
|
+
}
|
|
351
|
+
cleaned_tokens.push({
|
|
352
|
+
address: addr,
|
|
353
|
+
dropped_symbols: dropped.map(t => t.symbol),
|
|
354
|
+
kept_symbol: symbol,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
const dropped_symbols = new Set(tokens_to_disable.map(t => t.symbol));
|
|
358
|
+
const cleaned_pools = [];
|
|
359
|
+
const skipped_trading_pools = [];
|
|
360
|
+
if (dropped_symbols.size > 0) {
|
|
361
|
+
const pool_list = yield this.get_pool_list();
|
|
362
|
+
const trade_pool_set = new Set((yield this.get_all_trade_pools()).map(p => p.pool_address));
|
|
363
|
+
const pools_to_disable_by_dex = new Map();
|
|
364
|
+
for (const p of pool_list) {
|
|
365
|
+
const [t0, t1] = (p.pool_name || '').split('/');
|
|
366
|
+
if (!dropped_symbols.has(t0) && !dropped_symbols.has(t1))
|
|
367
|
+
continue;
|
|
368
|
+
if (trade_pool_set.has(p.pool_address)) {
|
|
369
|
+
skipped_trading_pools.push({
|
|
370
|
+
pool_address: p.pool_address,
|
|
371
|
+
pool_name: p.pool_name,
|
|
372
|
+
dex_id: p.dex_id,
|
|
373
|
+
reason: 'pool in trading config (protected)',
|
|
374
|
+
});
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (!pools_to_disable_by_dex.has(p.dex_id))
|
|
378
|
+
pools_to_disable_by_dex.set(p.dex_id, []);
|
|
379
|
+
pools_to_disable_by_dex.get(p.dex_id).push(Object.assign(Object.assign({}, p), { enable: false }));
|
|
380
|
+
cleaned_pools.push({ pool_address: p.pool_address, pool_name: p.pool_name, dex_id: p.dex_id });
|
|
381
|
+
}
|
|
382
|
+
if (pools_to_disable_by_dex.size > 0) {
|
|
383
|
+
const dex_pool_list = [];
|
|
384
|
+
for (const [dex_id, pools] of pools_to_disable_by_dex) {
|
|
385
|
+
dex_pool_list.push({ dex_id: dex_id, pool_list: pools });
|
|
386
|
+
}
|
|
387
|
+
yield this.update_pool_list(dex_pool_list);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (tokens_to_disable.length > 0) {
|
|
391
|
+
yield this.update_token_list(tokens_to_disable);
|
|
392
|
+
}
|
|
393
|
+
(0, index_1.log_warn)(`[resolve-duplicate] 完成: disabled ${tokens_to_disable.length} 个 symbol, 删 ${cleaned_pools.length} 个 pool, 跳过交易中 pool ${skipped_trading_pools.length} 个, 映射 not_found ${not_found.length} 个`);
|
|
394
|
+
return { cleaned_tokens, cleaned_pools, skipped_trading_pools, not_found };
|
|
395
|
+
});
|
|
396
|
+
}
|
|
278
397
|
set_pool_token_info(pool, token_info_list) {
|
|
279
398
|
if (index_1.LOG.debug) {
|
|
280
399
|
(0, index_1.log_trace)(`set_pool_token_info`);
|
|
@@ -282,13 +401,7 @@ class ArbCache {
|
|
|
282
401
|
let [token0, token1] = pool.pool_name.split('/');
|
|
283
402
|
pool.tokenA = token_info_list.find(e => e.symbol === token0);
|
|
284
403
|
pool.tokenB = token_info_list.find(e => e.symbol === token1);
|
|
285
|
-
|
|
286
|
-
(0, index_1.log_warn)('set_pool_token_info failed! tokenA or tokenB not exists!', {
|
|
287
|
-
pool_info: pool,
|
|
288
|
-
token_symbol_list: token_info_list.map(e => e.symbol)
|
|
289
|
-
});
|
|
290
|
-
throw new Error(`set_pool_token_info failed! tokenA or tokenB not exists!`);
|
|
291
|
-
}
|
|
404
|
+
return !!(pool.tokenA && pool.tokenB);
|
|
292
405
|
}
|
|
293
406
|
set_pool_extra(pool) {
|
|
294
407
|
if (index_1.LOG.debug) {
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -599,7 +599,13 @@ export declare class ArbCache {
|
|
|
599
599
|
get_one_token_info_by_symbol(symbol: string): Promise<StandardTokenInfoType>;
|
|
600
600
|
publish_token_change_event(): Promise<void>;
|
|
601
601
|
cache_pool_list(): Promise<void>;
|
|
602
|
-
set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]):
|
|
602
|
+
set_pool_token_info(pool: StandardPoolInfoType, token_info_list: StandardTokenInfoType[]): boolean;
|
|
603
|
+
resolve_duplicate_address(authoritative_mappings: Array<{ address: string; symbol: string }>): Promise<{
|
|
604
|
+
cleaned_tokens: Array<{ address: string; dropped_symbols: string[]; kept_symbol: string }>;
|
|
605
|
+
cleaned_pools: Array<{ pool_address: string; pool_name: string; dex_id: string }>;
|
|
606
|
+
skipped_trading_pools: Array<{ pool_address: string; pool_name: string; dex_id: string; reason: string }>;
|
|
607
|
+
not_found: Array<{ address: string; symbol: string; reason: string }>;
|
|
608
|
+
}>;
|
|
603
609
|
set_pool_extra(pool: StandardPoolInfoType): void;
|
|
604
610
|
update_pool_list(input_dex_pool_list: StandardDexPoolConfigType[]): Promise<void>;
|
|
605
611
|
get_pool_list_by_pair(pair: string): Promise<StandardPoolInfoType[]>;
|