@clonegod/ttd-sui-common 2.0.5 → 2.0.6
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/grpc/grpc-connection.js +3 -1
- package/dist/grpc/grpc_provider_registry.d.ts +2 -2
- package/dist/grpc/grpc_provider_registry.js +13 -3
- package/dist/trade/abstract_sui_dex_trade.js +1 -1
- package/dist/trade/check/tx_result_checker.d.ts +1 -0
- package/dist/trade/check/tx_result_checker.js +20 -0
- package/dist/trade/executor/central_executor.d.ts +4 -0
- package/dist/trade/executor/central_executor.js +36 -6
- package/dist/trade/executor/core_channel.js +3 -0
- package/dist/trade/parse/sui_tx_parser.js +1 -1
- package/dist/trade/tx_result_channel.d.ts +3 -0
- package/package.json +1 -1
|
@@ -64,9 +64,11 @@ class GrpcConnection {
|
|
|
64
64
|
'grpc.max_reconnect_backoff_ms': 30000,
|
|
65
65
|
'grpc.enable_retries': 1,
|
|
66
66
|
'grpc.retry_buffer_size': 256 * 1024,
|
|
67
|
+
'grpc-node.flow_control_window': 8 * 1024 * 1024,
|
|
68
|
+
'grpc-node.max_session_memory': 64,
|
|
69
|
+
'grpc.default_compression_algorithm': 2,
|
|
67
70
|
'grpc.http2.max_frame_size': 16384,
|
|
68
71
|
'grpc.http2.max_header_list_size': 16384,
|
|
69
|
-
'grpc.http2.initial_window_size': 65535,
|
|
70
72
|
}; }
|
|
71
73
|
getChannel() {
|
|
72
74
|
if (!this.channel) {
|
|
@@ -9,6 +9,6 @@ interface RedisLike {
|
|
|
9
9
|
hgetall(key: string): Promise<Record<string, string> | null>;
|
|
10
10
|
subscribe(channel: string, listener: (message: string) => void): Promise<void> | void;
|
|
11
11
|
}
|
|
12
|
-
export declare function resolveGrpcProvider(redis: RedisLike, chainId: string): Promise<GrpcProviderConfig>;
|
|
13
|
-
export declare function watchGrpcProviderChange(redis: RedisLike, chainId: string, active: GrpcProviderConfig, onChange: (next: GrpcProviderConfig | null) => void): void;
|
|
12
|
+
export declare function resolveGrpcProvider(redis: RedisLike, chainId: string, preferId?: string): Promise<GrpcProviderConfig>;
|
|
13
|
+
export declare function watchGrpcProviderChange(redis: RedisLike, chainId: string, active: GrpcProviderConfig, onChange: (next: GrpcProviderConfig | null) => void, preferId?: string): void;
|
|
14
14
|
export {};
|
|
@@ -11,7 +11,7 @@ function getRpcProvidersKey(chainId) {
|
|
|
11
11
|
function getRpcConfigChangeChannel(chainId) {
|
|
12
12
|
return `${chainId.toLowerCase()}:rpc:config:change`;
|
|
13
13
|
}
|
|
14
|
-
async function resolveGrpcProvider(redis, chainId) {
|
|
14
|
+
async function resolveGrpcProvider(redis, chainId, preferId) {
|
|
15
15
|
const key = getRpcProvidersKey(chainId);
|
|
16
16
|
const map = (await redis.hgetall(key)) || {};
|
|
17
17
|
const candidates = [];
|
|
@@ -28,6 +28,16 @@ async function resolveGrpcProvider(redis, chainId) {
|
|
|
28
28
|
if (candidates.length === 0) {
|
|
29
29
|
throw new Error(`[grpc-registry] ${key} 无 enabled 的 grpc provider —— 请在 trade-analyze Config/RPC 页新增 type=grpc 的 Provider(grpc_endpoint 填 host:port,auth_token 填 x-token)`);
|
|
30
30
|
}
|
|
31
|
+
if (preferId) {
|
|
32
|
+
const preferred = candidates.find(c => c.id === preferId);
|
|
33
|
+
if (!preferred) {
|
|
34
|
+
throw new Error(`[grpc-registry] 指定的 grpc provider '${preferId}' 不存在或未 enabled(${key} 可用: ${candidates.map(c => c.id).join(',')})`);
|
|
35
|
+
}
|
|
36
|
+
if (!preferred.auth_token)
|
|
37
|
+
(0, dist_1.log_warn)(`[grpc-registry] provider ${preferred.id} auth_token 为空,按无鉴权端点连接`);
|
|
38
|
+
(0, dist_1.log_info)(`[grpc-registry] 使用指定 provider: ${preferred.id}`);
|
|
39
|
+
return { id: preferred.id, endpoint: preferred.grpc_endpoint, token: preferred.auth_token || '' };
|
|
40
|
+
}
|
|
31
41
|
candidates.sort((a, b) => (b.default_for_quote === true ? 1 : 0) - (a.default_for_quote === true ? 1 : 0)
|
|
32
42
|
|| a.id.localeCompare(b.id));
|
|
33
43
|
const picked = candidates[0];
|
|
@@ -39,13 +49,13 @@ async function resolveGrpcProvider(redis, chainId) {
|
|
|
39
49
|
}
|
|
40
50
|
return { id: picked.id, endpoint: picked.grpc_endpoint, token: picked.auth_token || '' };
|
|
41
51
|
}
|
|
42
|
-
function watchGrpcProviderChange(redis, chainId, active, onChange) {
|
|
52
|
+
function watchGrpcProviderChange(redis, chainId, active, onChange, preferId) {
|
|
43
53
|
const channel = getRpcConfigChangeChannel(chainId);
|
|
44
54
|
redis.subscribe(channel, (message) => {
|
|
45
55
|
void (async () => {
|
|
46
56
|
let next;
|
|
47
57
|
try {
|
|
48
|
-
next = await resolveGrpcProvider(redis, chainId);
|
|
58
|
+
next = await resolveGrpcProvider(redis, chainId, preferId);
|
|
49
59
|
}
|
|
50
60
|
catch {
|
|
51
61
|
next = null;
|
|
@@ -46,7 +46,7 @@ class AbstractSuiDexTrade extends dist_1.AbastrcatTrade {
|
|
|
46
46
|
try {
|
|
47
47
|
const msg = JSON.parse(message);
|
|
48
48
|
if (msg.digest)
|
|
49
|
-
this.appConfig.emit(`SUI_TX_RESULT_${msg.digest}`, msg.digest);
|
|
49
|
+
this.appConfig.emit(`SUI_TX_RESULT_${msg.digest}`, msg.receipt ?? msg.digest);
|
|
50
50
|
}
|
|
51
51
|
catch (e) {
|
|
52
52
|
(0, dist_1.log_warn)(`[trade] bad tx result msg`, message);
|
|
@@ -10,5 +10,6 @@ export declare class TransactionResultChecker extends AbstractTransactionResultC
|
|
|
10
10
|
private isValidTransactionResult;
|
|
11
11
|
check_tx_result_interval(): Promise<void>;
|
|
12
12
|
on_subscibe_transaction(): void;
|
|
13
|
+
private ensureCheckpoint;
|
|
13
14
|
private processTransactionResult;
|
|
14
15
|
}
|
|
@@ -59,6 +59,7 @@ class TransactionResultChecker extends trade_1.AbstractTransactionResultCheck {
|
|
|
59
59
|
this.event_emitter.once(`SUI_TX_RESULT_${this.txid}`, async (response) => {
|
|
60
60
|
(0, dist_1.log_info)(`receive tx result notification, txid=${this.txid}`);
|
|
61
61
|
if (response.transaction) {
|
|
62
|
+
await this.ensureCheckpoint(response);
|
|
62
63
|
this.processTransactionResult(response, 'grpc');
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
@@ -86,6 +87,25 @@ class TransactionResultChecker extends trade_1.AbstractTransactionResultCheck {
|
|
|
86
87
|
}
|
|
87
88
|
});
|
|
88
89
|
}
|
|
90
|
+
async ensureCheckpoint(receipt) {
|
|
91
|
+
if (receipt.transaction?.checkpoint)
|
|
92
|
+
return;
|
|
93
|
+
for (let i = 0; i < 10; i++) {
|
|
94
|
+
try {
|
|
95
|
+
const r = await this.leadgerService.getTransaction(this.txid, ['checkpoint', 'timestamp']);
|
|
96
|
+
const t = r?.transaction;
|
|
97
|
+
if (t?.checkpoint) {
|
|
98
|
+
receipt.transaction.checkpoint = t.checkpoint;
|
|
99
|
+
if (t.timestamp)
|
|
100
|
+
receipt.transaction.timestamp = t.timestamp;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
await (0, dist_1.sleep)(200);
|
|
106
|
+
}
|
|
107
|
+
(0, dist_1.log_warn)(`ensureCheckpoint: 补查超窗仍无 checkpoint 号, txid=${this.txid}(block_number 将为 0)`);
|
|
108
|
+
}
|
|
89
109
|
async processTransactionResult(txReceipt, source) {
|
|
90
110
|
if (this.trade_result_already_processed) {
|
|
91
111
|
(0, dist_1.log_warn)(`trade_result_already_processed=${this.trade_result_already_processed}, ignore trade result fetch by ${source} check!`);
|
|
@@ -30,6 +30,9 @@ export interface CentralExecutorOptions {
|
|
|
30
30
|
digest: string;
|
|
31
31
|
success: boolean;
|
|
32
32
|
error?: string;
|
|
33
|
+
receipt?: {
|
|
34
|
+
transaction: any;
|
|
35
|
+
};
|
|
33
36
|
}) => void;
|
|
34
37
|
}
|
|
35
38
|
type TxResponse = SuiClientTypes.Transaction<{
|
|
@@ -64,6 +67,7 @@ export declare class CentralExecutor {
|
|
|
64
67
|
private registerShared;
|
|
65
68
|
submitSwap(req: SwapExecRequest): Promise<SwapSubmitResult>;
|
|
66
69
|
private broadcastAndCommit;
|
|
70
|
+
private toCheckerReceipt;
|
|
67
71
|
private reconcileAfterFailure;
|
|
68
72
|
simulateSwap(req: SwapExecRequest): Promise<TxResponse>;
|
|
69
73
|
private buildSwapTx;
|
|
@@ -208,16 +208,21 @@ class CentralExecutor {
|
|
|
208
208
|
tx.object(transactions_1.Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable }));
|
|
209
209
|
}
|
|
210
210
|
async submitSwap(req) {
|
|
211
|
+
const t0 = Date.now();
|
|
211
212
|
const inType = this.inputCoinType(req);
|
|
212
213
|
const wallet = this.chooseTradeWallet(req, inType, req.amountIn);
|
|
213
214
|
const inputTag = this.nextTag('in');
|
|
214
215
|
const inputRes = this.cache.acquire(wallet, inType, req.amountIn, inputTag);
|
|
216
|
+
const tAcquire = Date.now();
|
|
217
|
+
(0, dist_1.log_info)(`[executor] coin 已占用 wallet=${wallet.slice(0, 10)}… ${inputRes.coins.length} 个 ${inType.split('::').pop()} (选钱包+占币 ${tAcquire - t0}ms)`);
|
|
215
218
|
try {
|
|
216
219
|
const { txBytes, tx } = await this.buildSwapTx(req, wallet, inputRes.coins);
|
|
220
|
+
const tBuild = Date.now();
|
|
217
221
|
const { signature: senderSig } = await this.tradeWallets.get(wallet).signTransaction(txBytes);
|
|
218
222
|
const digest = await tx.getDigest();
|
|
223
|
+
const tSign = Date.now();
|
|
219
224
|
void this.broadcastAndCommit(txBytes, [senderSig], digest, wallet, inType, this.outputCoinType(req), inputRes.coins, inputTag);
|
|
220
|
-
(0, dist_1.log_info)(`[executor] swap 已签提交 digest=${digest} dex=${req.dexId} ${req.a2b ? 'a2b' : 'b2a'} in=${req.amountIn}`);
|
|
225
|
+
(0, dist_1.log_info)(`[executor] swap 已签提交 digest=${digest} dex=${req.dexId} ${req.a2b ? 'a2b' : 'b2a'} in=${req.amountIn} cost={build:${tBuild - tAcquire}ms, sign+digest:${tSign - tBuild}ms, total:${tSign - t0}ms}`);
|
|
221
226
|
return { digest, submitted: true };
|
|
222
227
|
}
|
|
223
228
|
catch (e) {
|
|
@@ -227,6 +232,7 @@ class CentralExecutor {
|
|
|
227
232
|
}
|
|
228
233
|
}
|
|
229
234
|
async broadcastAndCommit(txBytes, signatures, digest, wallet, inType, outType, inputCoins, inputTag) {
|
|
235
|
+
const tBroadcast = Date.now();
|
|
230
236
|
try {
|
|
231
237
|
const resp = await this.core.executeTransaction({ transaction: txBytes, signatures, include: { effects: true, objectTypes: true, balanceChanges: true } });
|
|
232
238
|
const tr = resp.Transaction;
|
|
@@ -241,20 +247,44 @@ class CentralExecutor {
|
|
|
241
247
|
if (!success) {
|
|
242
248
|
this.cache.abort(inputTag, true);
|
|
243
249
|
this.reconcileAfterFailure(wallet, inType, outType);
|
|
244
|
-
(0, dist_1.log_warn)(`[executor] swap 链上失败 digest=${digest} err=${error}`);
|
|
245
|
-
this.onBroadcastResult?.({ digest, success: false, error });
|
|
250
|
+
(0, dist_1.log_warn)(`[executor] swap 链上失败 digest=${digest} err=${error} broadcast=${Date.now() - tBroadcast}ms`);
|
|
251
|
+
this.onBroadcastResult?.({ digest, success: false, error, receipt: this.toCheckerReceipt(tr, digest) });
|
|
246
252
|
return;
|
|
247
253
|
}
|
|
248
254
|
await this.onSuccess(tr, wallet, inType, outType, inputCoins, inputTag);
|
|
249
|
-
(0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest}`);
|
|
250
|
-
this.onBroadcastResult?.({ digest, success: true });
|
|
255
|
+
(0, dist_1.log_info)(`[executor] swap quorum-executed 确认 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`);
|
|
256
|
+
this.onBroadcastResult?.({ digest, success: true, receipt: this.toCheckerReceipt(tr, digest) });
|
|
251
257
|
}
|
|
252
258
|
catch (e) {
|
|
253
259
|
this.cache.abort(inputTag, true);
|
|
254
260
|
this.reconcileAfterFailure(wallet, inType, outType);
|
|
255
|
-
(0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest}`, e);
|
|
261
|
+
(0, dist_1.log_error)(`[executor] broadcast 失败 digest=${digest} broadcast=${Date.now() - tBroadcast}ms`, e);
|
|
256
262
|
}
|
|
257
263
|
}
|
|
264
|
+
toCheckerReceipt(tr, digest) {
|
|
265
|
+
const eff = tr.effects;
|
|
266
|
+
const status = eff?.status?.success
|
|
267
|
+
? { success: true }
|
|
268
|
+
: { success: false, error: { kind: eff?.status?.error?.message ?? JSON.stringify(eff?.status?.error ?? 'unknown') } };
|
|
269
|
+
const g = eff?.gasUsed ?? {};
|
|
270
|
+
return {
|
|
271
|
+
transaction: {
|
|
272
|
+
digest,
|
|
273
|
+
effects: {
|
|
274
|
+
status,
|
|
275
|
+
gas_used: {
|
|
276
|
+
computation_cost: g.computationCost,
|
|
277
|
+
storage_cost: g.storageCost,
|
|
278
|
+
storage_rebate: g.storageRebate,
|
|
279
|
+
non_refundable_storage_fee: g.nonRefundableStorageFee,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
balance_changes: (tr.balanceChanges ?? []).map((b) => ({
|
|
283
|
+
address: b.address, coin_type: b.coinType, amount: b.amount,
|
|
284
|
+
})),
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
258
288
|
reconcileAfterFailure(wallet, inType, outType) {
|
|
259
289
|
for (const t of new Set([inType, outType, constants_1.SUI_TOKEN_ADDRESS.LONG]))
|
|
260
290
|
void this.reconcileCoins(wallet, t);
|
|
@@ -21,6 +21,9 @@ const GRPC_CLIENT_OPTIONS = {
|
|
|
21
21
|
'grpc.max_send_message_length': 8 * 1024 * 1024,
|
|
22
22
|
'grpc.initial_reconnect_backoff_ms': 1000,
|
|
23
23
|
'grpc.max_reconnect_backoff_ms': 30000,
|
|
24
|
+
'grpc-node.flow_control_window': 8 * 1024 * 1024,
|
|
25
|
+
'grpc-node.max_session_memory': 64,
|
|
26
|
+
'grpc.default_compression_algorithm': 2,
|
|
24
27
|
};
|
|
25
28
|
function buildGrpcCore(opts = {}) {
|
|
26
29
|
const endpoint = opts.endpoint ?? (0, dist_1.getCoreEnv)().grpc_endpoint;
|
|
@@ -177,7 +177,7 @@ class SuiTransactionParser {
|
|
|
177
177
|
return Date.now();
|
|
178
178
|
}
|
|
179
179
|
extractCheckpoint(receipt) {
|
|
180
|
-
return parseInt(receipt.checkpoint || '0');
|
|
180
|
+
return parseInt(receipt.transaction?.checkpoint || receipt.checkpoint || '0');
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
exports.SuiTransactionParser = SuiTransactionParser;
|