@usherlabs/cex-broker 0.2.26 → 0.2.28
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 +2 -2
- package/dist/commands/cli.js +686 -540
- package/dist/helpers/broker-execution-archive/capture.d.ts +1 -1
- package/dist/helpers/broker-execution-archive/index.d.ts +1 -1
- package/dist/helpers/broker-execution-archive/rows.d.ts +1 -0
- package/dist/helpers/broker-execution-archive/writer.d.ts +11 -2
- package/dist/helpers/market-data-archive/capture.d.ts +1 -1
- package/dist/helpers/market-data-archive/index.d.ts +3 -3
- package/dist/helpers/market-data-archive/ohlcv-history.d.ts +1 -1
- package/dist/helpers/market-data-archive/rows.d.ts +1 -1
- package/dist/helpers/market-data-archive/types.d.ts +1 -1
- package/dist/helpers/shared/errors.d.ts +7 -5
- package/dist/index.js +717 -569
- package/dist/index.js.map +19 -20
- package/package.json +1 -1
package/dist/commands/cli.js
CHANGED
|
@@ -314993,6 +314993,65 @@ function validateDeposit(policy, exchange, network, ticker) {
|
|
|
314993
314993
|
return { valid: true };
|
|
314994
314994
|
}
|
|
314995
314995
|
|
|
314996
|
+
// src/helpers/shared/errors.ts
|
|
314997
|
+
var MAX_ERROR_DETAIL_LENGTH = 512;
|
|
314998
|
+
var REDACTED_ERROR_MESSAGE = "redacted_error";
|
|
314999
|
+
function getErrorMessage(error) {
|
|
315000
|
+
return error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
315001
|
+
}
|
|
315002
|
+
function errorClassName(error) {
|
|
315003
|
+
if (!(error instanceof Error)) {
|
|
315004
|
+
return;
|
|
315005
|
+
}
|
|
315006
|
+
const name = error.constructor?.name || error.name;
|
|
315007
|
+
return name && name !== "Error" ? name : undefined;
|
|
315008
|
+
}
|
|
315009
|
+
function errorCode(error) {
|
|
315010
|
+
if (error === null || typeof error !== "object") {
|
|
315011
|
+
return;
|
|
315012
|
+
}
|
|
315013
|
+
const code = error.code;
|
|
315014
|
+
if (typeof code === "string") {
|
|
315015
|
+
return code.trim() || undefined;
|
|
315016
|
+
}
|
|
315017
|
+
if (typeof code === "number" && Number.isFinite(code)) {
|
|
315018
|
+
return String(code);
|
|
315019
|
+
}
|
|
315020
|
+
if (typeof code === "bigint") {
|
|
315021
|
+
return String(code);
|
|
315022
|
+
}
|
|
315023
|
+
return;
|
|
315024
|
+
}
|
|
315025
|
+
function sanitizeErrorDetail(error, options = {}) {
|
|
315026
|
+
const className = errorClassName(error);
|
|
315027
|
+
const message = getErrorMessage(error);
|
|
315028
|
+
const code = options.includeCode ? errorCode(error) : undefined;
|
|
315029
|
+
const prefix = [className, code ? `[code=${code}]` : undefined].filter((part) => Boolean(part)).join(" ");
|
|
315030
|
+
const detail = prefix ? `${prefix}: ${message}` : message;
|
|
315031
|
+
return detail.replace(/\s+/g, " ").trim().slice(0, MAX_ERROR_DETAIL_LENGTH);
|
|
315032
|
+
}
|
|
315033
|
+
function safeLogError(context2, error) {
|
|
315034
|
+
try {
|
|
315035
|
+
log.error(context2, { error });
|
|
315036
|
+
} catch {
|
|
315037
|
+
console.error(context2, error);
|
|
315038
|
+
}
|
|
315039
|
+
}
|
|
315040
|
+
function safeLogRedactedError(context2, error) {
|
|
315041
|
+
const errorType = errorClassName(error) ?? (error instanceof Error ? error.name : typeof error);
|
|
315042
|
+
try {
|
|
315043
|
+
log.error(context2, {
|
|
315044
|
+
error_type: errorType,
|
|
315045
|
+
error_message: REDACTED_ERROR_MESSAGE
|
|
315046
|
+
});
|
|
315047
|
+
} catch {
|
|
315048
|
+
console.error(context2, {
|
|
315049
|
+
error_type: errorType,
|
|
315050
|
+
error_message: REDACTED_ERROR_MESSAGE
|
|
315051
|
+
});
|
|
315052
|
+
}
|
|
315053
|
+
}
|
|
315054
|
+
|
|
314996
315055
|
// src/helpers/shared/guards.ts
|
|
314997
315056
|
function isRecord(value) {
|
|
314998
315057
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -315013,7 +315072,6 @@ var NUMERIC_METRICS = [
|
|
|
315013
315072
|
["feeAmount", "cex_market_action_fee_amount"],
|
|
315014
315073
|
["feeRate", "cex_market_action_fee_rate"]
|
|
315015
315074
|
];
|
|
315016
|
-
var REDACTED_ERROR_MESSAGE = "redacted_error";
|
|
315017
315075
|
async function emitOrderExecutionTelemetry(otelMetrics, context2, order, error) {
|
|
315018
315076
|
try {
|
|
315019
315077
|
const telemetry = buildOrderExecutionTelemetry(context2, order, error);
|
|
@@ -315505,7 +315563,7 @@ function buildOrderEventArchiveRow(input) {
|
|
|
315505
315563
|
fee_rate: telemetry.feeRate,
|
|
315506
315564
|
exchange_timestamp: telemetry.exchangeTimestamp,
|
|
315507
315565
|
error_type: telemetry.errorType,
|
|
315508
|
-
error_message: telemetry.errorMessage,
|
|
315566
|
+
error_message: input.errorDetail ?? telemetry.errorMessage,
|
|
315509
315567
|
payload_json: JSON.stringify(telemetry)
|
|
315510
315568
|
})
|
|
315511
315569
|
};
|
|
@@ -315637,418 +315695,163 @@ function buildMarketMetadataSnapshotRow(input) {
|
|
|
315637
315695
|
};
|
|
315638
315696
|
}
|
|
315639
315697
|
|
|
315640
|
-
// src/helpers/broker-execution-archive/
|
|
315641
|
-
|
|
315642
|
-
|
|
315643
|
-
|
|
315698
|
+
// src/helpers/broker-execution-archive/writer.ts
|
|
315699
|
+
var import_api_logs2 = __toESM(require_src7(), 1);
|
|
315700
|
+
import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
|
|
315701
|
+
import { request as httpRequest2 } from "node:http";
|
|
315702
|
+
import { request as httpsRequest2 } from "node:https";
|
|
315703
|
+
var BROKER_EXECUTION_ARCHIVE_TABLES = new Set([
|
|
315704
|
+
"broker_execution.order_events",
|
|
315705
|
+
"broker_execution.market_metadata_snapshots",
|
|
315706
|
+
"broker_execution.transfer_events",
|
|
315707
|
+
"broker_execution.fill_events"
|
|
315708
|
+
]);
|
|
315709
|
+
function isBrokerExecutionArchiveTable(table) {
|
|
315710
|
+
return BROKER_EXECUTION_ARCHIVE_TABLES.has(table);
|
|
315711
|
+
}
|
|
315712
|
+
|
|
315713
|
+
class BrokerExecutionArchiveDurabilityError extends Error {
|
|
315714
|
+
constructor(message, options) {
|
|
315715
|
+
super(message, options);
|
|
315716
|
+
this.name = "BrokerExecutionArchiveDurabilityError";
|
|
315644
315717
|
}
|
|
315645
|
-
queueMicrotask(() => {
|
|
315646
|
-
try {
|
|
315647
|
-
const telemetry = buildOrderExecutionTelemetry(context2, order, error);
|
|
315648
|
-
const tags = buildCommonArchiveTags({
|
|
315649
|
-
deploymentId: archiver.getDeploymentId(),
|
|
315650
|
-
accountSelector: context2.accountLabel,
|
|
315651
|
-
exchange: context2.cex,
|
|
315652
|
-
symbol: telemetry.symbol,
|
|
315653
|
-
brokerObservedTimestamp: telemetry.brokerObservedTimestamp
|
|
315654
|
-
});
|
|
315655
|
-
archiver.enqueue(buildOrderEventArchiveRow({
|
|
315656
|
-
tags,
|
|
315657
|
-
action: context2.action,
|
|
315658
|
-
telemetry,
|
|
315659
|
-
marketMetadataHash: options?.marketMetadataHash
|
|
315660
|
-
}));
|
|
315661
|
-
} catch (archiveError) {
|
|
315662
|
-
log.warn("Failed to archive order execution", { error: archiveError });
|
|
315663
|
-
}
|
|
315664
|
-
});
|
|
315665
315718
|
}
|
|
315666
|
-
function
|
|
315667
|
-
if (
|
|
315668
|
-
|
|
315719
|
+
function rethrowArchiveDurabilityError(error) {
|
|
315720
|
+
if (error instanceof BrokerExecutionArchiveDurabilityError) {
|
|
315721
|
+
throw error;
|
|
315669
315722
|
}
|
|
315670
|
-
|
|
315723
|
+
}
|
|
315724
|
+
var DEFAULT_MAX_QUEUE_SIZE = 1e4;
|
|
315725
|
+
var DEFAULT_BATCH_SIZE = 10;
|
|
315726
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
315727
|
+
var DEFAULT_FORWARDER_TIMEOUT_MS = 3000;
|
|
315728
|
+
var SHED_WARN_INTERVAL_MS = 60000;
|
|
315729
|
+
function isArchiveOtelLogsEnabled() {
|
|
315730
|
+
return process.env.CEX_BROKER_ARCHIVE_OTEL_LOGS_ENABLED === "true";
|
|
315731
|
+
}
|
|
315732
|
+
function resolveArchiveForwarderUrlFromEnv() {
|
|
315733
|
+
return process.env.CEX_BROKER_ARCHIVE_FORWARDER_URL?.trim() || undefined;
|
|
315734
|
+
}
|
|
315735
|
+
|
|
315736
|
+
class BrokerExecutionArchiver {
|
|
315737
|
+
deploymentId;
|
|
315738
|
+
otelLogs;
|
|
315739
|
+
otelMetrics;
|
|
315740
|
+
forwarderUrl;
|
|
315741
|
+
deadLetterPath;
|
|
315742
|
+
deadLetterFd;
|
|
315743
|
+
maxQueueSize;
|
|
315744
|
+
batchSize;
|
|
315745
|
+
flushIntervalMs;
|
|
315746
|
+
forwarderTimeoutMs;
|
|
315747
|
+
queue = [];
|
|
315748
|
+
stats = {
|
|
315749
|
+
enqueued: 0,
|
|
315750
|
+
shed: 0,
|
|
315751
|
+
flushed: 0,
|
|
315752
|
+
forwarderFailures: 0
|
|
315753
|
+
};
|
|
315754
|
+
flushTimer = null;
|
|
315755
|
+
flushInFlight = null;
|
|
315756
|
+
lastShedWarnAtMs = 0;
|
|
315757
|
+
closed = false;
|
|
315758
|
+
enabled;
|
|
315759
|
+
forwarderAuthToken;
|
|
315760
|
+
constructor(options) {
|
|
315761
|
+
this.deploymentId = options.deploymentId?.trim() || process.env.CEX_BROKER_DEPLOYMENT_ID?.trim() || "unknown";
|
|
315762
|
+
this.otelLogs = options.otelLogs;
|
|
315763
|
+
this.otelMetrics = options.otelMetrics;
|
|
315764
|
+
this.forwarderUrl = options.forwarderUrl?.trim();
|
|
315765
|
+
this.deadLetterPath = options.deadLetterPath?.trim();
|
|
315766
|
+
this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
315767
|
+
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
315768
|
+
this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
315769
|
+
this.forwarderTimeoutMs = options.forwarderTimeoutMs ?? DEFAULT_FORWARDER_TIMEOUT_MS;
|
|
315770
|
+
this.enabled = options.enabled;
|
|
315771
|
+
this.forwarderAuthToken = process.env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined;
|
|
315772
|
+
if (!this.enabled) {
|
|
315773
|
+
log.info("Broker execution archive disabled", { enabled: false });
|
|
315774
|
+
return;
|
|
315775
|
+
}
|
|
315776
|
+
validateForwarderUrl(this.forwarderUrl);
|
|
315777
|
+
if (!this.deadLetterPath) {
|
|
315778
|
+
throw new Error("Broker execution archive is enabled but CEX_BROKER_ARCHIVE_DEAD_LETTER_PATH is missing");
|
|
315779
|
+
}
|
|
315671
315780
|
try {
|
|
315672
|
-
|
|
315673
|
-
|
|
315674
|
-
|
|
315675
|
-
exchange: input.exchange,
|
|
315676
|
-
symbol: input.symbol
|
|
315677
|
-
});
|
|
315678
|
-
archiver.enqueue(buildSubscribeStreamArchiveRow({
|
|
315679
|
-
tags,
|
|
315680
|
-
subscriptionType: input.subscriptionType,
|
|
315681
|
-
streamPayload: input.streamPayload,
|
|
315682
|
-
secretLiterals: input.secretLiterals
|
|
315683
|
-
}));
|
|
315684
|
-
} catch (archiveError) {
|
|
315685
|
-
log.warn("Failed to archive subscribe stream event", {
|
|
315686
|
-
error: archiveError
|
|
315687
|
-
});
|
|
315781
|
+
this.deadLetterFd = openSync(this.deadLetterPath, "a", 384);
|
|
315782
|
+
} catch {
|
|
315783
|
+
throw new Error("Broker execution archive cannot open CEX_BROKER_ARCHIVE_DEAD_LETTER_PATH for append");
|
|
315688
315784
|
}
|
|
315689
|
-
});
|
|
315690
|
-
}
|
|
315691
|
-
function archiveTransferEventInBackground(archiver, input) {
|
|
315692
|
-
if (!archiver?.isEnabled()) {
|
|
315693
|
-
return;
|
|
315694
|
-
}
|
|
315695
|
-
queueMicrotask(() => {
|
|
315696
315785
|
try {
|
|
315697
|
-
|
|
315698
|
-
|
|
315699
|
-
|
|
315700
|
-
|
|
315701
|
-
|
|
315702
|
-
|
|
315786
|
+
this.flushTimer = setInterval(() => {
|
|
315787
|
+
this.flush();
|
|
315788
|
+
}, this.flushIntervalMs);
|
|
315789
|
+
this.flushTimer.unref?.();
|
|
315790
|
+
log.info("Broker execution archive enabled", {
|
|
315791
|
+
enabled: true,
|
|
315792
|
+
otel_mirror_enabled: Boolean(this.otelLogs?.isOtelEnabled())
|
|
315703
315793
|
});
|
|
315704
|
-
|
|
315705
|
-
|
|
315706
|
-
|
|
315794
|
+
} catch (error) {
|
|
315795
|
+
this.closeLossJournal();
|
|
315796
|
+
throw error;
|
|
315707
315797
|
}
|
|
315708
|
-
}
|
|
315709
|
-
|
|
315710
|
-
|
|
315711
|
-
|
|
315712
|
-
|
|
315798
|
+
}
|
|
315799
|
+
static disabled() {
|
|
315800
|
+
return new BrokerExecutionArchiver({ enabled: false });
|
|
315801
|
+
}
|
|
315802
|
+
static create(options) {
|
|
315803
|
+
return new BrokerExecutionArchiver({ ...options, enabled: true });
|
|
315804
|
+
}
|
|
315805
|
+
getDeploymentId() {
|
|
315806
|
+
return this.deploymentId;
|
|
315807
|
+
}
|
|
315808
|
+
isEnabled() {
|
|
315809
|
+
return this.enabled && !this.closed;
|
|
315810
|
+
}
|
|
315811
|
+
canPersistMarketMetadataSnapshot() {
|
|
315812
|
+
return this.isEnabled();
|
|
315813
|
+
}
|
|
315814
|
+
canPersistAccountBalanceSnapshots() {
|
|
315815
|
+
return this.isEnabled() && Boolean(this.forwarderUrl);
|
|
315816
|
+
}
|
|
315817
|
+
enqueue(row) {
|
|
315818
|
+
if (!this.enabled || this.closed) {
|
|
315713
315819
|
return;
|
|
315714
315820
|
}
|
|
315715
|
-
|
|
315716
|
-
|
|
315717
|
-
|
|
315718
|
-
|
|
315719
|
-
|
|
315720
|
-
exchange: input.exchange,
|
|
315721
|
-
accountSelector: input.accountSelector,
|
|
315722
|
-
assetSymbol,
|
|
315723
|
-
transaction,
|
|
315724
|
-
normalized
|
|
315725
|
-
})) {
|
|
315726
|
-
continue;
|
|
315821
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
315822
|
+
const shedRow = this.queue[0];
|
|
315823
|
+
if (shedRow) {
|
|
315824
|
+
this.appendLossRecords([shedRow], "queue_shed");
|
|
315825
|
+
this.queue.shift();
|
|
315727
315826
|
}
|
|
315728
|
-
|
|
315729
|
-
|
|
315730
|
-
|
|
315731
|
-
assetSymbol,
|
|
315732
|
-
brokerObservedTimestamp,
|
|
315733
|
-
transfer: {
|
|
315734
|
-
eventKind: "withdrawal",
|
|
315735
|
-
lifecycleAction: "observe_withdrawal",
|
|
315736
|
-
status: normalized.status,
|
|
315737
|
-
amount: normalized.amount,
|
|
315738
|
-
address: normalized.address,
|
|
315739
|
-
network: normalized.network,
|
|
315740
|
-
externalId: normalized.externalId,
|
|
315741
|
-
txid: normalized.txid,
|
|
315742
|
-
resultIndex,
|
|
315743
|
-
feeAmount: normalized.feeAmount,
|
|
315744
|
-
feeCurrency: normalized.feeCurrency,
|
|
315745
|
-
exchangeTimestamp: normalized.exchangeTimestamp,
|
|
315746
|
-
payload: transaction
|
|
315747
|
-
}
|
|
315827
|
+
this.stats.shed += 1;
|
|
315828
|
+
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
315829
|
+
table: shedRow?.table ?? "unknown"
|
|
315748
315830
|
});
|
|
315831
|
+
const now3 = Date.now();
|
|
315832
|
+
if (now3 - this.lastShedWarnAtMs >= SHED_WARN_INTERVAL_MS) {
|
|
315833
|
+
log.warn("Archive queue full: shedding oldest rows", {
|
|
315834
|
+
shed_total: this.stats.shed,
|
|
315835
|
+
queue_max: this.maxQueueSize,
|
|
315836
|
+
table: shedRow?.table ?? "unknown"
|
|
315837
|
+
});
|
|
315838
|
+
this.lastShedWarnAtMs = now3;
|
|
315839
|
+
}
|
|
315749
315840
|
}
|
|
315750
|
-
|
|
315751
|
-
|
|
315752
|
-
|
|
315841
|
+
this.queue.push(row);
|
|
315842
|
+
this.stats.enqueued += 1;
|
|
315843
|
+
this.recordArchiveMetric("cex_archive_rows_enqueued_total", {
|
|
315844
|
+
table: row.table
|
|
315753
315845
|
});
|
|
315846
|
+
if (this.queue.length >= this.batchSize) {
|
|
315847
|
+
this.flush();
|
|
315848
|
+
}
|
|
315754
315849
|
}
|
|
315755
|
-
|
|
315756
|
-
|
|
315757
|
-
if (!archiver?.canPersistMarketMetadataSnapshot()) {
|
|
315758
|
-
return;
|
|
315850
|
+
enqueueInBackground(row) {
|
|
315851
|
+
queueMicrotask(() => this.enqueue(row));
|
|
315759
315852
|
}
|
|
315760
|
-
|
|
315761
|
-
|
|
315762
|
-
if (typeof fetchOrderBook !== "function") {
|
|
315763
|
-
return;
|
|
315764
|
-
}
|
|
315765
|
-
const orderBook = await fetchOrderBook.call(broker, input.symbol, 5);
|
|
315766
|
-
const record = asRecord(orderBook);
|
|
315767
|
-
const snapshot = {
|
|
315768
|
-
action: input.action,
|
|
315769
|
-
symbol: input.symbol,
|
|
315770
|
-
bids: record?.bids,
|
|
315771
|
-
asks: record?.asks,
|
|
315772
|
-
timestamp: record?.timestamp ?? Date.now(),
|
|
315773
|
-
datetime: record?.datetime,
|
|
315774
|
-
nonce: record?.nonce
|
|
315775
|
-
};
|
|
315776
|
-
const tags = buildCommonArchiveTags({
|
|
315777
|
-
deploymentId: archiver.getDeploymentId(),
|
|
315778
|
-
accountSelector: input.accountSelector,
|
|
315779
|
-
exchange: input.exchange,
|
|
315780
|
-
symbol: input.symbol,
|
|
315781
|
-
brokerObservedTimestamp: input.brokerObservedTimestamp
|
|
315782
|
-
});
|
|
315783
|
-
const row = buildMarketMetadataSnapshotRow({
|
|
315784
|
-
tags,
|
|
315785
|
-
clientOrderId: input.clientOrderId,
|
|
315786
|
-
orderId: input.orderId,
|
|
315787
|
-
makerActionId: input.makerActionId,
|
|
315788
|
-
idempotencyId: input.idempotencyId,
|
|
315789
|
-
marketSnapshot: snapshot
|
|
315790
|
-
});
|
|
315791
|
-
archiver.enqueue(row);
|
|
315792
|
-
const hash4 = row.row.market_metadata_hash;
|
|
315793
|
-
return typeof hash4 === "string" ? hash4 : undefined;
|
|
315794
|
-
} catch (archiveError) {
|
|
315795
|
-
log.warn("Failed to capture market metadata snapshot", {
|
|
315796
|
-
error: archiveError
|
|
315797
|
-
});
|
|
315798
|
-
return;
|
|
315799
|
-
}
|
|
315800
|
-
}
|
|
315801
|
-
// src/helpers/broker-execution-archive/withdrawal-observation-tracker.ts
|
|
315802
|
-
var DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES = 1e4;
|
|
315803
|
-
var VENUE_LIFECYCLE_EVIDENCE_FIELDS = [
|
|
315804
|
-
"updated",
|
|
315805
|
-
"updatedAt",
|
|
315806
|
-
"updated_at",
|
|
315807
|
-
"updateTime",
|
|
315808
|
-
"update_time",
|
|
315809
|
-
"updateTimestamp",
|
|
315810
|
-
"lastUpdateTimestamp",
|
|
315811
|
-
"completed",
|
|
315812
|
-
"completedAt",
|
|
315813
|
-
"completed_at",
|
|
315814
|
-
"completeTime",
|
|
315815
|
-
"complete_time",
|
|
315816
|
-
"completionTime",
|
|
315817
|
-
"successTime"
|
|
315818
|
-
];
|
|
315819
|
-
function fingerprintEvidenceValue(value) {
|
|
315820
|
-
if (value === undefined || value === null) {
|
|
315821
|
-
return value;
|
|
315822
|
-
}
|
|
315823
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
315824
|
-
return value;
|
|
315825
|
-
}
|
|
315826
|
-
if (typeof value === "bigint") {
|
|
315827
|
-
return value.toString();
|
|
315828
|
-
}
|
|
315829
|
-
try {
|
|
315830
|
-
return JSON.stringify(value);
|
|
315831
|
-
} catch {
|
|
315832
|
-
return String(value);
|
|
315833
|
-
}
|
|
315834
|
-
}
|
|
315835
|
-
function venueLifecycleEvidence(transaction) {
|
|
315836
|
-
const record = asRecord(transaction);
|
|
315837
|
-
const info = asRecord(record?.info);
|
|
315838
|
-
const evidence = [];
|
|
315839
|
-
for (const [scope, source] of [
|
|
315840
|
-
["transaction", record],
|
|
315841
|
-
["info", info]
|
|
315842
|
-
]) {
|
|
315843
|
-
for (const field of VENUE_LIFECYCLE_EVIDENCE_FIELDS) {
|
|
315844
|
-
const value = source?.[field];
|
|
315845
|
-
if (value !== undefined && value !== null) {
|
|
315846
|
-
evidence.push([scope, field, fingerprintEvidenceValue(value)]);
|
|
315847
|
-
}
|
|
315848
|
-
}
|
|
315849
|
-
}
|
|
315850
|
-
return evidence;
|
|
315851
|
-
}
|
|
315852
|
-
function observationFingerprint(observation) {
|
|
315853
|
-
const { normalized, transaction } = observation;
|
|
315854
|
-
return JSON.stringify({
|
|
315855
|
-
status: normalized.status ?? null,
|
|
315856
|
-
txid: normalized.txid ?? null,
|
|
315857
|
-
amount: normalized.amount ?? null,
|
|
315858
|
-
feeAmount: normalized.feeAmount ?? null,
|
|
315859
|
-
feeCurrency: normalized.feeCurrency ?? null,
|
|
315860
|
-
address: normalized.address ?? null,
|
|
315861
|
-
network: normalized.network ?? null,
|
|
315862
|
-
exchangeTimestamp: normalized.exchangeTimestamp ?? null,
|
|
315863
|
-
venueLifecycleEvidence: venueLifecycleEvidence(transaction)
|
|
315864
|
-
});
|
|
315865
|
-
}
|
|
315866
|
-
|
|
315867
|
-
class WithdrawalObservationTracker {
|
|
315868
|
-
#fingerprints = new Map;
|
|
315869
|
-
#maxEntries;
|
|
315870
|
-
#missingIdSequence = 0n;
|
|
315871
|
-
constructor(options) {
|
|
315872
|
-
const maxEntries = options?.maxEntries ?? DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES;
|
|
315873
|
-
this.#maxEntries = Number.isFinite(maxEntries) ? Math.max(1, Math.floor(maxEntries)) : DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES;
|
|
315874
|
-
}
|
|
315875
|
-
shouldArchive(observation) {
|
|
315876
|
-
const externalId = observation.normalized.externalId?.trim();
|
|
315877
|
-
const identity = JSON.stringify([
|
|
315878
|
-
observation.exchange.trim().toLowerCase() || "unknown",
|
|
315879
|
-
observation.accountSelector?.trim() || "unknown",
|
|
315880
|
-
observation.assetSymbol?.trim().toUpperCase() || "unknown",
|
|
315881
|
-
externalId || `missing:${++this.#missingIdSequence}`
|
|
315882
|
-
]);
|
|
315883
|
-
const fingerprint = observationFingerprint(observation);
|
|
315884
|
-
if (this.#fingerprints.get(identity) === fingerprint) {
|
|
315885
|
-
return false;
|
|
315886
|
-
}
|
|
315887
|
-
this.#fingerprints.delete(identity);
|
|
315888
|
-
this.#fingerprints.set(identity, fingerprint);
|
|
315889
|
-
while (this.#fingerprints.size > this.#maxEntries) {
|
|
315890
|
-
const oldest = this.#fingerprints.keys().next().value;
|
|
315891
|
-
if (oldest === undefined)
|
|
315892
|
-
break;
|
|
315893
|
-
this.#fingerprints.delete(oldest);
|
|
315894
|
-
}
|
|
315895
|
-
return true;
|
|
315896
|
-
}
|
|
315897
|
-
getSize() {
|
|
315898
|
-
return this.#fingerprints.size;
|
|
315899
|
-
}
|
|
315900
|
-
}
|
|
315901
|
-
// src/helpers/broker-execution-archive/writer.ts
|
|
315902
|
-
var import_api_logs2 = __toESM(require_src7(), 1);
|
|
315903
|
-
import { request as httpRequest2 } from "node:http";
|
|
315904
|
-
import { request as httpsRequest2 } from "node:https";
|
|
315905
|
-
|
|
315906
|
-
// src/helpers/market-data-archive/types.ts
|
|
315907
|
-
var MARKET_ARCHIVE_TABLES = new Set([
|
|
315908
|
-
"market_data.orderbook_snapshots",
|
|
315909
|
-
"market_data.candles",
|
|
315910
|
-
"market_data.cex_stream_events",
|
|
315911
|
-
"market_data.cex_ticker_events",
|
|
315912
|
-
"market_data.cex_trades"
|
|
315913
|
-
]);
|
|
315914
|
-
function isMarketArchiveTable(table) {
|
|
315915
|
-
return MARKET_ARCHIVE_TABLES.has(table);
|
|
315916
|
-
}
|
|
315917
|
-
|
|
315918
|
-
// src/helpers/broker-execution-archive/writer.ts
|
|
315919
|
-
var BROKER_EXECUTION_ARCHIVE_TABLES = new Set([
|
|
315920
|
-
"broker_execution.order_events",
|
|
315921
|
-
"broker_execution.market_metadata_snapshots",
|
|
315922
|
-
"broker_execution.transfer_events",
|
|
315923
|
-
"broker_execution.fill_events"
|
|
315924
|
-
]);
|
|
315925
|
-
function isBrokerExecutionArchiveTable(table) {
|
|
315926
|
-
return BROKER_EXECUTION_ARCHIVE_TABLES.has(table);
|
|
315927
|
-
}
|
|
315928
|
-
var DEFAULT_MAX_QUEUE_SIZE = 1e4;
|
|
315929
|
-
var DEFAULT_BATCH_SIZE = 10;
|
|
315930
|
-
var DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
315931
|
-
var DEFAULT_FORWARDER_TIMEOUT_MS = 3000;
|
|
315932
|
-
var DEFAULT_ARCHIVE_FORWARDER_PATH = "/archive";
|
|
315933
|
-
var DEFAULT_ARCHIVE_FORWARDER_PORT = 8090;
|
|
315934
|
-
function isArchiveOtelLogsEnabled() {
|
|
315935
|
-
return process.env.CEX_BROKER_ARCHIVE_OTEL_LOGS_ENABLED === "true";
|
|
315936
|
-
}
|
|
315937
|
-
function resolveArchiveForwarderUrlFromEnv() {
|
|
315938
|
-
const explicit = process.env.CEX_BROKER_ARCHIVE_FORWARDER_URL?.trim();
|
|
315939
|
-
if (explicit) {
|
|
315940
|
-
return explicit;
|
|
315941
|
-
}
|
|
315942
|
-
const host = process.env.CEX_BROKER_ARCHIVE_FORWARDER_HOST?.trim() || process.env.CEX_BROKER_CLICKHOUSE_HOST?.trim();
|
|
315943
|
-
if (!host) {
|
|
315944
|
-
return;
|
|
315945
|
-
}
|
|
315946
|
-
const protocol = process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL || "http";
|
|
315947
|
-
const port = parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FORWARDER_PORT, DEFAULT_ARCHIVE_FORWARDER_PORT);
|
|
315948
|
-
const path = process.env.CEX_BROKER_ARCHIVE_FORWARDER_PATH?.trim() || DEFAULT_ARCHIVE_FORWARDER_PATH;
|
|
315949
|
-
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
315950
|
-
return `${protocol}://${host}:${port}${normalizedPath}`;
|
|
315951
|
-
}
|
|
315952
|
-
|
|
315953
|
-
class BrokerExecutionArchiver {
|
|
315954
|
-
deploymentId;
|
|
315955
|
-
otelLogs;
|
|
315956
|
-
otelMetrics;
|
|
315957
|
-
forwarderUrl;
|
|
315958
|
-
maxQueueSize;
|
|
315959
|
-
batchSize;
|
|
315960
|
-
flushIntervalMs;
|
|
315961
|
-
forwarderTimeoutMs;
|
|
315962
|
-
queue = [];
|
|
315963
|
-
stats = {
|
|
315964
|
-
enqueued: 0,
|
|
315965
|
-
shed: 0,
|
|
315966
|
-
flushed: 0,
|
|
315967
|
-
forwarderFailures: 0
|
|
315968
|
-
};
|
|
315969
|
-
flushTimer = null;
|
|
315970
|
-
flushInFlight = null;
|
|
315971
|
-
closed = false;
|
|
315972
|
-
loggedMissingMarketForwarder = false;
|
|
315973
|
-
enabled;
|
|
315974
|
-
forwarderAuthToken;
|
|
315975
|
-
constructor(options) {
|
|
315976
|
-
this.deploymentId = options.deploymentId?.trim() || process.env.CEX_BROKER_DEPLOYMENT_ID?.trim() || "unknown";
|
|
315977
|
-
this.otelLogs = options.otelLogs;
|
|
315978
|
-
this.otelMetrics = options.otelMetrics;
|
|
315979
|
-
this.forwarderUrl = options.forwarderUrl?.trim();
|
|
315980
|
-
this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
315981
|
-
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
315982
|
-
this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
315983
|
-
this.forwarderTimeoutMs = options.forwarderTimeoutMs ?? DEFAULT_FORWARDER_TIMEOUT_MS;
|
|
315984
|
-
this.enabled = options.enabled;
|
|
315985
|
-
this.forwarderAuthToken = process.env.CEX_BROKER_ARCHIVE_FORWARDER_TOKEN?.trim() || undefined;
|
|
315986
|
-
if (this.enabled) {
|
|
315987
|
-
this.flushTimer = setInterval(() => {
|
|
315988
|
-
this.flush().catch((error) => {
|
|
315989
|
-
log.warn("Broker execution archive flush failed", { error });
|
|
315990
|
-
});
|
|
315991
|
-
}, this.flushIntervalMs);
|
|
315992
|
-
this.flushTimer.unref?.();
|
|
315993
|
-
}
|
|
315994
|
-
}
|
|
315995
|
-
static disabled() {
|
|
315996
|
-
return new BrokerExecutionArchiver({ enabled: false });
|
|
315997
|
-
}
|
|
315998
|
-
static create(options) {
|
|
315999
|
-
const hasSink = Boolean(options.otelLogs?.isOtelEnabled()) || Boolean(options.forwarderUrl);
|
|
316000
|
-
const enabled = process.env.CEX_BROKER_ARCHIVE_ENABLED !== "false" && hasSink;
|
|
316001
|
-
return new BrokerExecutionArchiver({ ...options, enabled });
|
|
316002
|
-
}
|
|
316003
|
-
getDeploymentId() {
|
|
316004
|
-
return this.deploymentId;
|
|
316005
|
-
}
|
|
316006
|
-
isEnabled() {
|
|
316007
|
-
return this.enabled && !this.closed && (Boolean(this.otelLogs) || Boolean(this.forwarderUrl));
|
|
316008
|
-
}
|
|
316009
|
-
canPersistMarketMetadataSnapshot() {
|
|
316010
|
-
return this.isEnabled() && (Boolean(this.otelLogs?.isOtelEnabled()) || Boolean(this.forwarderUrl));
|
|
316011
|
-
}
|
|
316012
|
-
canPersistAccountBalanceSnapshots() {
|
|
316013
|
-
return this.isEnabled() && Boolean(this.forwarderUrl);
|
|
316014
|
-
}
|
|
316015
|
-
enqueue(row) {
|
|
316016
|
-
if (!this.enabled || this.closed || !this.otelLogs && !this.forwarderUrl) {
|
|
316017
|
-
return;
|
|
316018
|
-
}
|
|
316019
|
-
if (isMarketArchiveTable(row.table) && !this.forwarderUrl) {
|
|
316020
|
-
if (!this.loggedMissingMarketForwarder) {
|
|
316021
|
-
this.loggedMissingMarketForwarder = true;
|
|
316022
|
-
log.warn("Market data archive row dropped: configure CEX_BROKER_ARCHIVE_FORWARDER_URL or CEX_BROKER_CLICKHOUSE_HOST", { table: row.table });
|
|
316023
|
-
}
|
|
316024
|
-
return;
|
|
316025
|
-
}
|
|
316026
|
-
if (row.table === "broker_account.balance_snapshots" && !this.forwarderUrl) {
|
|
316027
|
-
return;
|
|
316028
|
-
}
|
|
316029
|
-
if (this.queue.length >= this.maxQueueSize) {
|
|
316030
|
-
this.queue.shift();
|
|
316031
|
-
this.stats.shed += 1;
|
|
316032
|
-
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
316033
|
-
table: row.table
|
|
316034
|
-
});
|
|
316035
|
-
}
|
|
316036
|
-
this.queue.push(row);
|
|
316037
|
-
this.stats.enqueued += 1;
|
|
316038
|
-
this.recordArchiveMetric("cex_archive_rows_enqueued_total", {
|
|
316039
|
-
table: row.table
|
|
316040
|
-
});
|
|
316041
|
-
if (this.queue.length >= this.batchSize) {
|
|
316042
|
-
this.flush().catch((error) => {
|
|
316043
|
-
log.warn("Broker execution archive flush failed", { error });
|
|
316044
|
-
});
|
|
316045
|
-
}
|
|
316046
|
-
}
|
|
316047
|
-
enqueueInBackground(row) {
|
|
316048
|
-
queueMicrotask(() => this.enqueue(row));
|
|
316049
|
-
}
|
|
316050
|
-
async flush() {
|
|
316051
|
-
if (!this.enabled || this.closed || this.queue.length === 0) {
|
|
315853
|
+
async flush() {
|
|
315854
|
+
if (!this.enabled || this.closed || this.queue.length === 0) {
|
|
316052
315855
|
return;
|
|
316053
315856
|
}
|
|
316054
315857
|
if (this.flushInFlight) {
|
|
@@ -316067,21 +315870,36 @@ class BrokerExecutionArchiver {
|
|
|
316067
315870
|
clearInterval(this.flushTimer);
|
|
316068
315871
|
this.flushTimer = null;
|
|
316069
315872
|
}
|
|
316070
|
-
|
|
316071
|
-
|
|
316072
|
-
|
|
316073
|
-
|
|
316074
|
-
|
|
316075
|
-
|
|
316076
|
-
|
|
316077
|
-
|
|
316078
|
-
const
|
|
316079
|
-
this.queue.length
|
|
316080
|
-
|
|
316081
|
-
|
|
315873
|
+
let closeError;
|
|
315874
|
+
try {
|
|
315875
|
+
while (this.queue.length > 0 || this.flushInFlight) {
|
|
315876
|
+
if (this.flushInFlight) {
|
|
315877
|
+
await this.flushInFlight;
|
|
315878
|
+
continue;
|
|
315879
|
+
}
|
|
315880
|
+
const depthBefore = this.queue.length;
|
|
315881
|
+
const flushed = await this.flushBatch();
|
|
315882
|
+
if (!flushed && this.queue.length >= depthBefore && depthBefore > 0) {
|
|
315883
|
+
const undelivered = [...this.queue];
|
|
315884
|
+
this.appendLossRecords(undelivered, "shutdown_forwarder_failure");
|
|
315885
|
+
this.queue.length = 0;
|
|
315886
|
+
break;
|
|
315887
|
+
}
|
|
316082
315888
|
}
|
|
315889
|
+
} catch (error) {
|
|
315890
|
+
closeError = error;
|
|
316083
315891
|
}
|
|
316084
315892
|
this.closed = true;
|
|
315893
|
+
if (this.deadLetterFd !== undefined) {
|
|
315894
|
+
try {
|
|
315895
|
+
this.closeLossJournal();
|
|
315896
|
+
} catch (error) {
|
|
315897
|
+
closeError ??= error;
|
|
315898
|
+
}
|
|
315899
|
+
}
|
|
315900
|
+
if (closeError) {
|
|
315901
|
+
throw closeError;
|
|
315902
|
+
}
|
|
316085
315903
|
}
|
|
316086
315904
|
getStats() {
|
|
316087
315905
|
return { ...this.stats };
|
|
@@ -316089,15 +315907,60 @@ class BrokerExecutionArchiver {
|
|
|
316089
315907
|
getQueueDepth() {
|
|
316090
315908
|
return this.queue.length;
|
|
316091
315909
|
}
|
|
315910
|
+
closeLossJournal() {
|
|
315911
|
+
if (this.deadLetterFd === undefined) {
|
|
315912
|
+
return;
|
|
315913
|
+
}
|
|
315914
|
+
const fd2 = this.deadLetterFd;
|
|
315915
|
+
try {
|
|
315916
|
+
closeSync(fd2);
|
|
315917
|
+
} catch (error) {
|
|
315918
|
+
throw new BrokerExecutionArchiveDurabilityError("Broker execution archive failed to close the configured CEX_BROKER_ARCHIVE_DEAD_LETTER_PATH loss journal", { cause: error });
|
|
315919
|
+
} finally {
|
|
315920
|
+
this.deadLetterFd = undefined;
|
|
315921
|
+
}
|
|
315922
|
+
}
|
|
316092
315923
|
enforceQueueBound() {
|
|
316093
315924
|
while (this.queue.length > this.maxQueueSize) {
|
|
316094
|
-
const dropped = this.queue
|
|
315925
|
+
const dropped = this.queue[0];
|
|
315926
|
+
if (!dropped) {
|
|
315927
|
+
return;
|
|
315928
|
+
}
|
|
315929
|
+
this.appendLossRecords([dropped], "queue_shed");
|
|
315930
|
+
this.queue.shift();
|
|
316095
315931
|
this.stats.shed += 1;
|
|
316096
315932
|
this.recordArchiveMetric("cex_archive_rows_shed_total", {
|
|
316097
315933
|
table: dropped?.table ?? "unknown"
|
|
316098
315934
|
});
|
|
316099
315935
|
}
|
|
316100
315936
|
}
|
|
315937
|
+
appendLossRecords(rows, reason) {
|
|
315938
|
+
if (rows.length === 0) {
|
|
315939
|
+
return;
|
|
315940
|
+
}
|
|
315941
|
+
if (this.deadLetterFd === undefined) {
|
|
315942
|
+
throw new BrokerExecutionArchiveDurabilityError(`Broker execution archive cannot record ${reason}: dead-letter file is not open`);
|
|
315943
|
+
}
|
|
315944
|
+
const timestamp = new Date().toISOString();
|
|
315945
|
+
const records = rows.map((payload) => ({
|
|
315946
|
+
timestamp,
|
|
315947
|
+
deployment_id: this.deploymentId,
|
|
315948
|
+
reason,
|
|
315949
|
+
payload
|
|
315950
|
+
}));
|
|
315951
|
+
try {
|
|
315952
|
+
const bytes2 = Buffer.from(records.map((record) => JSON.stringify(record)).join(`
|
|
315953
|
+
`) + `
|
|
315954
|
+
`);
|
|
315955
|
+
const written = writeSync(this.deadLetterFd, bytes2);
|
|
315956
|
+
if (written !== bytes2.length) {
|
|
315957
|
+
throw new Error(`wrote ${written} of ${bytes2.length} bytes`);
|
|
315958
|
+
}
|
|
315959
|
+
fsyncSync(this.deadLetterFd);
|
|
315960
|
+
} catch (error) {
|
|
315961
|
+
throw new BrokerExecutionArchiveDurabilityError(`Broker execution archive failed to durably record ${reason}; affected row(s) were retained`, { cause: error });
|
|
315962
|
+
}
|
|
315963
|
+
}
|
|
316101
315964
|
async flushBatch() {
|
|
316102
315965
|
const batch = this.queue.splice(0, this.batchSize);
|
|
316103
315966
|
if (batch.length === 0) {
|
|
@@ -316134,111 +315997,407 @@ class BrokerExecutionArchiver {
|
|
|
316134
315997
|
for (const [table, count2] of countByTable) {
|
|
316135
315998
|
this.recordArchiveMetric("cex_archive_rows_flushed_total", { table }, count2);
|
|
316136
315999
|
}
|
|
316137
|
-
this.recordArchiveGauge("cex_archive_last_flush_success", Math.floor(Date.now() / 1000));
|
|
316138
|
-
}
|
|
316139
|
-
async recordArchiveMetric(metricName, labels, value = 1) {
|
|
316140
|
-
try {
|
|
316141
|
-
await this.otelMetrics?.recordCounter(metricName, value, labels);
|
|
316142
|
-
} catch {}
|
|
316000
|
+
this.recordArchiveGauge("cex_archive_last_flush_success", Math.floor(Date.now() / 1000));
|
|
316001
|
+
}
|
|
316002
|
+
async recordArchiveMetric(metricName, labels, value = 1) {
|
|
316003
|
+
try {
|
|
316004
|
+
await this.otelMetrics?.recordCounter(metricName, value, labels);
|
|
316005
|
+
} catch {}
|
|
316006
|
+
}
|
|
316007
|
+
async recordArchiveGauge(metricName, value) {
|
|
316008
|
+
try {
|
|
316009
|
+
await this.otelMetrics?.recordGauge(metricName, value, {});
|
|
316010
|
+
} catch {}
|
|
316011
|
+
}
|
|
316012
|
+
emitOtelLog(entry) {
|
|
316013
|
+
if (!this.otelLogs?.isOtelEnabled()) {
|
|
316014
|
+
return;
|
|
316015
|
+
}
|
|
316016
|
+
try {
|
|
316017
|
+
this.otelLogs.emit({
|
|
316018
|
+
body: entry.table,
|
|
316019
|
+
severityNumber: import_api_logs2.SeverityNumber.INFO,
|
|
316020
|
+
severityText: "INFO",
|
|
316021
|
+
attributes: flattenArchiveAttributes(redactArchiveErrorForOtel(entry))
|
|
316022
|
+
});
|
|
316023
|
+
} catch (error) {
|
|
316024
|
+
log.warn("Broker execution archive OTLP emit failed", { error });
|
|
316025
|
+
}
|
|
316026
|
+
}
|
|
316027
|
+
postToForwarder(batch) {
|
|
316028
|
+
if (!this.forwarderUrl || batch.length === 0) {
|
|
316029
|
+
return Promise.resolve();
|
|
316030
|
+
}
|
|
316031
|
+
const body = JSON.stringify({
|
|
316032
|
+
source: "broker_write",
|
|
316033
|
+
deployment_id: this.deploymentId,
|
|
316034
|
+
rows: batch
|
|
316035
|
+
});
|
|
316036
|
+
const url2 = new URL(this.forwarderUrl);
|
|
316037
|
+
const doRequest = url2.protocol === "http:" ? httpRequest2 : httpsRequest2;
|
|
316038
|
+
const headers = {
|
|
316039
|
+
"content-type": "application/json",
|
|
316040
|
+
"content-length": Buffer.byteLength(body)
|
|
316041
|
+
};
|
|
316042
|
+
if (this.forwarderAuthToken) {
|
|
316043
|
+
headers.authorization = `Bearer ${this.forwarderAuthToken}`;
|
|
316044
|
+
}
|
|
316045
|
+
return new Promise((resolve, reject) => {
|
|
316046
|
+
const req = doRequest(url2, {
|
|
316047
|
+
method: "POST",
|
|
316048
|
+
headers,
|
|
316049
|
+
timeout: this.forwarderTimeoutMs
|
|
316050
|
+
}, (res) => {
|
|
316051
|
+
res.on("data", () => {});
|
|
316052
|
+
res.on("end", () => {
|
|
316053
|
+
const status = res.statusCode ?? 0;
|
|
316054
|
+
if (status < 200 || status >= 300) {
|
|
316055
|
+
reject(new Error(`Archive forwarder returned ${status} ${res.statusMessage ?? ""}`));
|
|
316056
|
+
return;
|
|
316057
|
+
}
|
|
316058
|
+
resolve();
|
|
316059
|
+
});
|
|
316060
|
+
});
|
|
316061
|
+
req.on("error", reject);
|
|
316062
|
+
req.on("timeout", () => {
|
|
316063
|
+
req.destroy(new Error("Archive forwarder request timed out"));
|
|
316064
|
+
});
|
|
316065
|
+
req.write(body);
|
|
316066
|
+
req.end();
|
|
316067
|
+
});
|
|
316068
|
+
}
|
|
316069
|
+
}
|
|
316070
|
+
function redactArchiveErrorForOtel(entry) {
|
|
316071
|
+
if (entry.table !== "broker_execution.order_events" || typeof entry.row.error_message !== "string" || entry.row.error_message.length === 0) {
|
|
316072
|
+
return entry;
|
|
316073
|
+
}
|
|
316074
|
+
return {
|
|
316075
|
+
...entry,
|
|
316076
|
+
row: { ...entry.row, error_message: REDACTED_ERROR_MESSAGE }
|
|
316077
|
+
};
|
|
316078
|
+
}
|
|
316079
|
+
function flattenArchiveAttributes(entry) {
|
|
316080
|
+
const attributes = {
|
|
316081
|
+
ch_table: entry.table
|
|
316082
|
+
};
|
|
316083
|
+
for (const [key, value] of Object.entries(entry.row)) {
|
|
316084
|
+
if (value === undefined || value === null) {
|
|
316085
|
+
continue;
|
|
316086
|
+
}
|
|
316087
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
316088
|
+
attributes[key] = value;
|
|
316089
|
+
} else {
|
|
316090
|
+
attributes[key] = JSON.stringify(value);
|
|
316091
|
+
}
|
|
316092
|
+
}
|
|
316093
|
+
return attributes;
|
|
316094
|
+
}
|
|
316095
|
+
function createBrokerExecutionArchiverFromEnv(otelLogs, otelMetrics) {
|
|
316096
|
+
if (process.env.CEX_BROKER_ARCHIVE_ENABLED !== "true") {
|
|
316097
|
+
return BrokerExecutionArchiver.disabled();
|
|
316098
|
+
}
|
|
316099
|
+
const forwarderUrl = resolveArchiveForwarderUrlFromEnv();
|
|
316100
|
+
const archiveOtelLogs = isArchiveOtelLogsEnabled() ? otelLogs : undefined;
|
|
316101
|
+
return BrokerExecutionArchiver.create({
|
|
316102
|
+
otelLogs: archiveOtelLogs,
|
|
316103
|
+
otelMetrics,
|
|
316104
|
+
forwarderUrl: forwarderUrl ?? "",
|
|
316105
|
+
deadLetterPath: process.env.CEX_BROKER_ARCHIVE_DEAD_LETTER_PATH?.trim() ?? "",
|
|
316106
|
+
deploymentId: process.env.CEX_BROKER_DEPLOYMENT_ID,
|
|
316107
|
+
maxQueueSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_QUEUE_MAX, DEFAULT_MAX_QUEUE_SIZE),
|
|
316108
|
+
batchSize: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_BATCH_SIZE, DEFAULT_BATCH_SIZE),
|
|
316109
|
+
flushIntervalMs: parsePositiveInt(process.env.CEX_BROKER_ARCHIVE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS)
|
|
316110
|
+
});
|
|
316111
|
+
}
|
|
316112
|
+
function validateForwarderUrl(value) {
|
|
316113
|
+
if (!value) {
|
|
316114
|
+
throw new Error("Broker execution archive is enabled but CEX_BROKER_ARCHIVE_FORWARDER_URL is missing");
|
|
316115
|
+
}
|
|
316116
|
+
let url2;
|
|
316117
|
+
try {
|
|
316118
|
+
url2 = new URL(value);
|
|
316119
|
+
} catch (error) {
|
|
316120
|
+
throw new Error("Broker execution archive requires a valid CEX_BROKER_ARCHIVE_FORWARDER_URL", { cause: error });
|
|
316121
|
+
}
|
|
316122
|
+
if (url2.protocol !== "http:" && url2.protocol !== "https:") {
|
|
316123
|
+
throw new Error("Broker execution archive forwarder URL must use http or https");
|
|
316124
|
+
}
|
|
316125
|
+
return url2;
|
|
316126
|
+
}
|
|
316127
|
+
function parsePositiveInt(value, fallback) {
|
|
316128
|
+
if (!value) {
|
|
316129
|
+
return fallback;
|
|
316130
|
+
}
|
|
316131
|
+
const parsed = Number.parseInt(value, 10);
|
|
316132
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
316133
|
+
}
|
|
316134
|
+
|
|
316135
|
+
// src/helpers/broker-execution-archive/capture.ts
|
|
316136
|
+
function archiveOrderExecutionInBackground(archiver, context2, order, error, options) {
|
|
316137
|
+
if (!archiver?.isEnabled()) {
|
|
316138
|
+
return;
|
|
316139
|
+
}
|
|
316140
|
+
queueMicrotask(() => {
|
|
316141
|
+
try {
|
|
316142
|
+
const telemetry = buildOrderExecutionTelemetry(context2, order, error);
|
|
316143
|
+
const tags = buildCommonArchiveTags({
|
|
316144
|
+
deploymentId: archiver.getDeploymentId(),
|
|
316145
|
+
accountSelector: context2.accountLabel,
|
|
316146
|
+
exchange: context2.cex,
|
|
316147
|
+
symbol: telemetry.symbol,
|
|
316148
|
+
brokerObservedTimestamp: telemetry.brokerObservedTimestamp
|
|
316149
|
+
});
|
|
316150
|
+
archiver.enqueue(buildOrderEventArchiveRow({
|
|
316151
|
+
tags,
|
|
316152
|
+
action: context2.action,
|
|
316153
|
+
telemetry,
|
|
316154
|
+
errorDetail: context2.action === "CreateOrder" && error !== undefined ? sanitizeErrorDetail(error, { includeCode: true }) : undefined,
|
|
316155
|
+
marketMetadataHash: options?.marketMetadataHash
|
|
316156
|
+
}));
|
|
316157
|
+
} catch (archiveError) {
|
|
316158
|
+
rethrowArchiveDurabilityError(archiveError);
|
|
316159
|
+
log.warn("Failed to archive order execution", { error: archiveError });
|
|
316160
|
+
}
|
|
316161
|
+
});
|
|
316162
|
+
}
|
|
316163
|
+
function archiveSubscribeStreamInBackground(archiver, input) {
|
|
316164
|
+
if (!archiver?.isEnabled()) {
|
|
316165
|
+
return;
|
|
316166
|
+
}
|
|
316167
|
+
queueMicrotask(() => {
|
|
316168
|
+
try {
|
|
316169
|
+
const tags = buildCommonArchiveTags({
|
|
316170
|
+
deploymentId: archiver.getDeploymentId(),
|
|
316171
|
+
accountSelector: input.accountSelector,
|
|
316172
|
+
exchange: input.exchange,
|
|
316173
|
+
symbol: input.symbol
|
|
316174
|
+
});
|
|
316175
|
+
archiver.enqueue(buildSubscribeStreamArchiveRow({
|
|
316176
|
+
tags,
|
|
316177
|
+
subscriptionType: input.subscriptionType,
|
|
316178
|
+
streamPayload: input.streamPayload,
|
|
316179
|
+
secretLiterals: input.secretLiterals
|
|
316180
|
+
}));
|
|
316181
|
+
} catch (archiveError) {
|
|
316182
|
+
rethrowArchiveDurabilityError(archiveError);
|
|
316183
|
+
log.warn("Failed to archive subscribe stream event", {
|
|
316184
|
+
error: archiveError
|
|
316185
|
+
});
|
|
316186
|
+
}
|
|
316187
|
+
});
|
|
316188
|
+
}
|
|
316189
|
+
function archiveTransferEventInBackground(archiver, input) {
|
|
316190
|
+
if (!archiver?.isEnabled()) {
|
|
316191
|
+
return;
|
|
316192
|
+
}
|
|
316193
|
+
queueMicrotask(() => {
|
|
316194
|
+
try {
|
|
316195
|
+
const tags = buildCommonArchiveTags({
|
|
316196
|
+
deploymentId: archiver.getDeploymentId(),
|
|
316197
|
+
accountSelector: input.accountSelector,
|
|
316198
|
+
exchange: input.exchange,
|
|
316199
|
+
symbol: input.assetSymbol,
|
|
316200
|
+
brokerObservedTimestamp: input.brokerObservedTimestamp
|
|
316201
|
+
});
|
|
316202
|
+
archiver.enqueue(buildTransferEventArchiveRow({ tags, transfer: input.transfer }));
|
|
316203
|
+
} catch (archiveError) {
|
|
316204
|
+
rethrowArchiveDurabilityError(archiveError);
|
|
316205
|
+
log.warn("Failed to archive transfer event", { error: archiveError });
|
|
316206
|
+
}
|
|
316207
|
+
});
|
|
316208
|
+
}
|
|
316209
|
+
function archiveWithdrawalObservationsInBackground(archiver, tracker, input) {
|
|
316210
|
+
try {
|
|
316211
|
+
if (!archiver?.isEnabled() || !Array.isArray(input.transactions)) {
|
|
316212
|
+
return;
|
|
316213
|
+
}
|
|
316214
|
+
const brokerObservedTimestamp = new Date().toISOString();
|
|
316215
|
+
for (const [resultIndex, transaction] of input.transactions.entries()) {
|
|
316216
|
+
const normalized = normalizeCcxtTransactionForArchive(transaction);
|
|
316217
|
+
const assetSymbol = normalized.assetSymbol;
|
|
316218
|
+
if (!tracker.shouldArchive({
|
|
316219
|
+
exchange: input.exchange,
|
|
316220
|
+
accountSelector: input.accountSelector,
|
|
316221
|
+
assetSymbol,
|
|
316222
|
+
transaction,
|
|
316223
|
+
normalized
|
|
316224
|
+
})) {
|
|
316225
|
+
continue;
|
|
316226
|
+
}
|
|
316227
|
+
archiveTransferEventInBackground(archiver, {
|
|
316228
|
+
exchange: input.exchange,
|
|
316229
|
+
accountSelector: input.accountSelector,
|
|
316230
|
+
assetSymbol,
|
|
316231
|
+
brokerObservedTimestamp,
|
|
316232
|
+
transfer: {
|
|
316233
|
+
eventKind: "withdrawal",
|
|
316234
|
+
lifecycleAction: "observe_withdrawal",
|
|
316235
|
+
status: normalized.status,
|
|
316236
|
+
amount: normalized.amount,
|
|
316237
|
+
address: normalized.address,
|
|
316238
|
+
network: normalized.network,
|
|
316239
|
+
externalId: normalized.externalId,
|
|
316240
|
+
txid: normalized.txid,
|
|
316241
|
+
resultIndex,
|
|
316242
|
+
feeAmount: normalized.feeAmount,
|
|
316243
|
+
feeCurrency: normalized.feeCurrency,
|
|
316244
|
+
exchangeTimestamp: normalized.exchangeTimestamp,
|
|
316245
|
+
payload: transaction
|
|
316246
|
+
}
|
|
316247
|
+
});
|
|
316248
|
+
}
|
|
316249
|
+
} catch (archiveError) {
|
|
316250
|
+
rethrowArchiveDurabilityError(archiveError);
|
|
316251
|
+
log.warn("Failed to archive withdrawal observations", {
|
|
316252
|
+
error: archiveError
|
|
316253
|
+
});
|
|
316143
316254
|
}
|
|
316144
|
-
|
|
316145
|
-
|
|
316146
|
-
|
|
316147
|
-
|
|
316255
|
+
}
|
|
316256
|
+
async function captureMarketMetadataSnapshot(archiver, broker, input) {
|
|
316257
|
+
if (!archiver?.canPersistMarketMetadataSnapshot()) {
|
|
316258
|
+
return;
|
|
316148
316259
|
}
|
|
316149
|
-
|
|
316150
|
-
|
|
316260
|
+
try {
|
|
316261
|
+
const fetchOrderBook = broker.fetchOrderBook;
|
|
316262
|
+
if (typeof fetchOrderBook !== "function") {
|
|
316151
316263
|
return;
|
|
316152
316264
|
}
|
|
316153
|
-
|
|
316154
|
-
|
|
316155
|
-
|
|
316156
|
-
|
|
316157
|
-
|
|
316158
|
-
|
|
316159
|
-
|
|
316160
|
-
|
|
316161
|
-
|
|
316162
|
-
|
|
316163
|
-
}
|
|
316164
|
-
postToForwarder(batch) {
|
|
316165
|
-
if (!this.forwarderUrl || batch.length === 0) {
|
|
316166
|
-
return Promise.resolve();
|
|
316167
|
-
}
|
|
316168
|
-
const body = JSON.stringify({
|
|
316169
|
-
source: "broker_write",
|
|
316170
|
-
deployment_id: this.deploymentId,
|
|
316171
|
-
rows: batch
|
|
316172
|
-
});
|
|
316173
|
-
const url2 = new URL(this.forwarderUrl);
|
|
316174
|
-
const doRequest = url2.protocol === "http:" ? httpRequest2 : httpsRequest2;
|
|
316175
|
-
const headers = {
|
|
316176
|
-
"content-type": "application/json",
|
|
316177
|
-
"content-length": Buffer.byteLength(body)
|
|
316265
|
+
const orderBook = await fetchOrderBook.call(broker, input.symbol, 5);
|
|
316266
|
+
const record = asRecord(orderBook);
|
|
316267
|
+
const snapshot = {
|
|
316268
|
+
action: input.action,
|
|
316269
|
+
symbol: input.symbol,
|
|
316270
|
+
bids: record?.bids,
|
|
316271
|
+
asks: record?.asks,
|
|
316272
|
+
timestamp: record?.timestamp ?? Date.now(),
|
|
316273
|
+
datetime: record?.datetime,
|
|
316274
|
+
nonce: record?.nonce
|
|
316178
316275
|
};
|
|
316179
|
-
|
|
316180
|
-
|
|
316181
|
-
|
|
316182
|
-
|
|
316183
|
-
|
|
316184
|
-
|
|
316185
|
-
|
|
316186
|
-
|
|
316187
|
-
|
|
316188
|
-
|
|
316189
|
-
|
|
316190
|
-
|
|
316191
|
-
|
|
316192
|
-
|
|
316193
|
-
|
|
316194
|
-
|
|
316195
|
-
|
|
316196
|
-
|
|
316197
|
-
|
|
316198
|
-
|
|
316199
|
-
|
|
316200
|
-
|
|
316201
|
-
});
|
|
316202
|
-
req.write(body);
|
|
316203
|
-
req.end();
|
|
316276
|
+
const tags = buildCommonArchiveTags({
|
|
316277
|
+
deploymentId: archiver.getDeploymentId(),
|
|
316278
|
+
accountSelector: input.accountSelector,
|
|
316279
|
+
exchange: input.exchange,
|
|
316280
|
+
symbol: input.symbol,
|
|
316281
|
+
brokerObservedTimestamp: input.brokerObservedTimestamp
|
|
316282
|
+
});
|
|
316283
|
+
const row = buildMarketMetadataSnapshotRow({
|
|
316284
|
+
tags,
|
|
316285
|
+
clientOrderId: input.clientOrderId,
|
|
316286
|
+
orderId: input.orderId,
|
|
316287
|
+
makerActionId: input.makerActionId,
|
|
316288
|
+
idempotencyId: input.idempotencyId,
|
|
316289
|
+
marketSnapshot: snapshot
|
|
316290
|
+
});
|
|
316291
|
+
archiver.enqueue(row);
|
|
316292
|
+
const hash4 = row.row.market_metadata_hash;
|
|
316293
|
+
return typeof hash4 === "string" ? hash4 : undefined;
|
|
316294
|
+
} catch (archiveError) {
|
|
316295
|
+
rethrowArchiveDurabilityError(archiveError);
|
|
316296
|
+
log.warn("Failed to capture market metadata snapshot", {
|
|
316297
|
+
error: archiveError
|
|
316204
316298
|
});
|
|
316299
|
+
return;
|
|
316205
316300
|
}
|
|
316206
316301
|
}
|
|
316207
|
-
|
|
316208
|
-
|
|
316209
|
-
|
|
316210
|
-
|
|
316211
|
-
|
|
316212
|
-
|
|
316213
|
-
|
|
316214
|
-
|
|
316215
|
-
|
|
316216
|
-
|
|
316217
|
-
|
|
316218
|
-
|
|
316302
|
+
// src/helpers/broker-execution-archive/withdrawal-observation-tracker.ts
|
|
316303
|
+
var DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES = 1e4;
|
|
316304
|
+
var VENUE_LIFECYCLE_EVIDENCE_FIELDS = [
|
|
316305
|
+
"updated",
|
|
316306
|
+
"updatedAt",
|
|
316307
|
+
"updated_at",
|
|
316308
|
+
"updateTime",
|
|
316309
|
+
"update_time",
|
|
316310
|
+
"updateTimestamp",
|
|
316311
|
+
"lastUpdateTimestamp",
|
|
316312
|
+
"completed",
|
|
316313
|
+
"completedAt",
|
|
316314
|
+
"completed_at",
|
|
316315
|
+
"completeTime",
|
|
316316
|
+
"complete_time",
|
|
316317
|
+
"completionTime",
|
|
316318
|
+
"successTime"
|
|
316319
|
+
];
|
|
316320
|
+
function fingerprintEvidenceValue(value) {
|
|
316321
|
+
if (value === undefined || value === null) {
|
|
316322
|
+
return value;
|
|
316323
|
+
}
|
|
316324
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
316325
|
+
return value;
|
|
316326
|
+
}
|
|
316327
|
+
if (typeof value === "bigint") {
|
|
316328
|
+
return value.toString();
|
|
316329
|
+
}
|
|
316330
|
+
try {
|
|
316331
|
+
return JSON.stringify(value);
|
|
316332
|
+
} catch {
|
|
316333
|
+
return String(value);
|
|
316334
|
+
}
|
|
316335
|
+
}
|
|
316336
|
+
function venueLifecycleEvidence(transaction) {
|
|
316337
|
+
const record = asRecord(transaction);
|
|
316338
|
+
const info = asRecord(record?.info);
|
|
316339
|
+
const evidence = [];
|
|
316340
|
+
for (const [scope, source] of [
|
|
316341
|
+
["transaction", record],
|
|
316342
|
+
["info", info]
|
|
316343
|
+
]) {
|
|
316344
|
+
for (const field of VENUE_LIFECYCLE_EVIDENCE_FIELDS) {
|
|
316345
|
+
const value = source?.[field];
|
|
316346
|
+
if (value !== undefined && value !== null) {
|
|
316347
|
+
evidence.push([scope, field, fingerprintEvidenceValue(value)]);
|
|
316348
|
+
}
|
|
316219
316349
|
}
|
|
316220
316350
|
}
|
|
316221
|
-
return
|
|
316351
|
+
return evidence;
|
|
316222
316352
|
}
|
|
316223
|
-
function
|
|
316224
|
-
const
|
|
316225
|
-
|
|
316226
|
-
|
|
316227
|
-
|
|
316228
|
-
|
|
316229
|
-
|
|
316230
|
-
|
|
316231
|
-
|
|
316232
|
-
|
|
316233
|
-
|
|
316353
|
+
function observationFingerprint(observation) {
|
|
316354
|
+
const { normalized, transaction } = observation;
|
|
316355
|
+
return JSON.stringify({
|
|
316356
|
+
status: normalized.status ?? null,
|
|
316357
|
+
txid: normalized.txid ?? null,
|
|
316358
|
+
amount: normalized.amount ?? null,
|
|
316359
|
+
feeAmount: normalized.feeAmount ?? null,
|
|
316360
|
+
feeCurrency: normalized.feeCurrency ?? null,
|
|
316361
|
+
address: normalized.address ?? null,
|
|
316362
|
+
network: normalized.network ?? null,
|
|
316363
|
+
exchangeTimestamp: normalized.exchangeTimestamp ?? null,
|
|
316364
|
+
venueLifecycleEvidence: venueLifecycleEvidence(transaction)
|
|
316234
316365
|
});
|
|
316235
316366
|
}
|
|
316236
|
-
|
|
316237
|
-
|
|
316238
|
-
|
|
316367
|
+
|
|
316368
|
+
class WithdrawalObservationTracker {
|
|
316369
|
+
#fingerprints = new Map;
|
|
316370
|
+
#maxEntries;
|
|
316371
|
+
#missingIdSequence = 0n;
|
|
316372
|
+
constructor(options) {
|
|
316373
|
+
const maxEntries = options?.maxEntries ?? DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES;
|
|
316374
|
+
this.#maxEntries = Number.isFinite(maxEntries) ? Math.max(1, Math.floor(maxEntries)) : DEFAULT_WITHDRAWAL_OBSERVATION_TRACKER_MAX_ENTRIES;
|
|
316375
|
+
}
|
|
316376
|
+
shouldArchive(observation) {
|
|
316377
|
+
const externalId = observation.normalized.externalId?.trim();
|
|
316378
|
+
const identity = JSON.stringify([
|
|
316379
|
+
observation.exchange.trim().toLowerCase() || "unknown",
|
|
316380
|
+
observation.accountSelector?.trim() || "unknown",
|
|
316381
|
+
observation.assetSymbol?.trim().toUpperCase() || "unknown",
|
|
316382
|
+
externalId || `missing:${++this.#missingIdSequence}`
|
|
316383
|
+
]);
|
|
316384
|
+
const fingerprint = observationFingerprint(observation);
|
|
316385
|
+
if (this.#fingerprints.get(identity) === fingerprint) {
|
|
316386
|
+
return false;
|
|
316387
|
+
}
|
|
316388
|
+
this.#fingerprints.delete(identity);
|
|
316389
|
+
this.#fingerprints.set(identity, fingerprint);
|
|
316390
|
+
while (this.#fingerprints.size > this.#maxEntries) {
|
|
316391
|
+
const oldest = this.#fingerprints.keys().next().value;
|
|
316392
|
+
if (oldest === undefined)
|
|
316393
|
+
break;
|
|
316394
|
+
this.#fingerprints.delete(oldest);
|
|
316395
|
+
}
|
|
316396
|
+
return true;
|
|
316397
|
+
}
|
|
316398
|
+
getSize() {
|
|
316399
|
+
return this.#fingerprints.size;
|
|
316239
316400
|
}
|
|
316240
|
-
const parsed = Number.parseInt(value, 10);
|
|
316241
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
316242
316401
|
}
|
|
316243
316402
|
// src/helpers/account-balance-archive-poller.ts
|
|
316244
316403
|
var DEFAULT_CONFIG = {
|
|
@@ -316332,6 +316491,7 @@ class AccountBalanceArchivePoller {
|
|
|
316332
316491
|
this.params.metrics?.recordGauge("cex_account_balance_poll_last_success_timestamp_seconds", Math.floor(successMs / 1000), labels);
|
|
316333
316492
|
this.#recordFreshness(labels, successMs, successMs);
|
|
316334
316493
|
} catch (error) {
|
|
316494
|
+
rethrowArchiveDurabilityError(error);
|
|
316335
316495
|
this.params.metrics?.recordCounter("cex_account_balance_poll_failures_total", 1, labels);
|
|
316336
316496
|
const now3 = Date.now();
|
|
316337
316497
|
const lastSuccess = this.#lastSuccessMs.get(this.#targetKey(target));
|
|
@@ -316361,6 +316521,7 @@ class AccountBalanceArchivePoller {
|
|
|
316361
316521
|
try {
|
|
316362
316522
|
await this.pollAllOnce();
|
|
316363
316523
|
} catch (error) {
|
|
316524
|
+
rethrowArchiveDurabilityError(error);
|
|
316364
316525
|
log.error("Account balance archive poller tick failed", error);
|
|
316365
316526
|
} finally {
|
|
316366
316527
|
if (!this.#stopped) {
|
|
@@ -316424,6 +316585,7 @@ class FillArchivePoller {
|
|
|
316424
316585
|
try {
|
|
316425
316586
|
await this.pollTrackedOnce();
|
|
316426
316587
|
} catch (error) {
|
|
316588
|
+
rethrowArchiveDurabilityError(error);
|
|
316427
316589
|
log.error("Fill archive poller tick failed", error);
|
|
316428
316590
|
} finally {
|
|
316429
316591
|
this.#running = false;
|
|
@@ -316843,32 +317005,6 @@ var grpc14 = __toESM(require_src3(), 1);
|
|
|
316843
317005
|
// src/handlers/execute-action/deposit.ts
|
|
316844
317006
|
var grpc3 = __toESM(require_src3(), 1);
|
|
316845
317007
|
|
|
316846
|
-
// src/helpers/shared/errors.ts
|
|
316847
|
-
var MAX_ERROR_DETAIL_LENGTH = 512;
|
|
316848
|
-
function getErrorMessage(error) {
|
|
316849
|
-
return error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
316850
|
-
}
|
|
316851
|
-
function errorClassName(error) {
|
|
316852
|
-
if (!(error instanceof Error)) {
|
|
316853
|
-
return;
|
|
316854
|
-
}
|
|
316855
|
-
const name = error.constructor?.name || error.name;
|
|
316856
|
-
return name && name !== "Error" ? name : undefined;
|
|
316857
|
-
}
|
|
316858
|
-
function sanitizeErrorDetail(error) {
|
|
316859
|
-
const className = errorClassName(error);
|
|
316860
|
-
const message = getErrorMessage(error);
|
|
316861
|
-
const detail = className ? `${className}: ${message}` : message;
|
|
316862
|
-
return detail.replace(/\s+/g, " ").trim().slice(0, MAX_ERROR_DETAIL_LENGTH);
|
|
316863
|
-
}
|
|
316864
|
-
function safeLogError(context2, error) {
|
|
316865
|
-
try {
|
|
316866
|
-
log.error(context2, { error });
|
|
316867
|
-
} catch {
|
|
316868
|
-
console.error(context2, error);
|
|
316869
|
-
}
|
|
316870
|
-
}
|
|
316871
|
-
|
|
316872
317008
|
// src/helpers/treasury-discovery.ts
|
|
316873
317009
|
function callArgs(args, params) {
|
|
316874
317010
|
const argsArray = Array.isArray(args) ? [...args] : [];
|
|
@@ -331395,7 +331531,8 @@ async function handleCreateOrder(ctx) {
|
|
|
331395
331531
|
archiveOrderExecutionInBackground(brokerArchiver, createOrderContext, order, undefined, { marketMetadataHash });
|
|
331396
331532
|
ctx.wrappedCallback(null, { result: JSON.stringify({ ...order }) });
|
|
331397
331533
|
} catch (error48) {
|
|
331398
|
-
|
|
331534
|
+
rethrowArchiveDurabilityError(error48);
|
|
331535
|
+
safeLogRedactedError("Order Creation failed", error48);
|
|
331399
331536
|
const failedCreateContext = {
|
|
331400
331537
|
action: "CreateOrder",
|
|
331401
331538
|
cex: cex3,
|
|
@@ -332669,8 +332806,8 @@ class BinanceSpotUserDataStream {
|
|
|
332669
332806
|
}
|
|
332670
332807
|
if ("status" in message && typeof message.status === "number" && message.status !== 200) {
|
|
332671
332808
|
const errorMessage = message.error?.msg ?? message.error?.message ?? `Binance user-data request failed with status ${message.status}`;
|
|
332672
|
-
const
|
|
332673
|
-
this.fail(new Error(typeof
|
|
332809
|
+
const errorCode2 = message.error?.code;
|
|
332810
|
+
this.fail(new Error(typeof errorCode2 === "number" ? `${errorMessage} (code ${errorCode2})` : errorMessage));
|
|
332674
332811
|
return;
|
|
332675
332812
|
}
|
|
332676
332813
|
if (!("event" in message) || !message.event) {
|
|
@@ -332822,19 +332959,26 @@ class OhlcvBarTracker {
|
|
|
332822
332959
|
if (!firstBar || !lastBar) {
|
|
332823
332960
|
return [];
|
|
332824
332961
|
}
|
|
332825
|
-
|
|
332962
|
+
const lastOpenTimeMs = this.lastOpenTimeMs;
|
|
332963
|
+
if (lastOpenTimeMs !== null && lastBar.openTimeMs < lastOpenTimeMs) {
|
|
332964
|
+
return [];
|
|
332965
|
+
}
|
|
332966
|
+
const barsToProcess = lastOpenTimeMs === null ? bars : bars.filter((bar) => bar.openTimeMs >= lastOpenTimeMs);
|
|
332967
|
+
const firstBarToProcess = barsToProcess[0];
|
|
332968
|
+
const lastBarToProcess = barsToProcess[barsToProcess.length - 1];
|
|
332969
|
+
if (!firstBarToProcess || !lastBarToProcess) {
|
|
332826
332970
|
return [];
|
|
332827
332971
|
}
|
|
332828
332972
|
const candidates = [];
|
|
332829
|
-
if (this.lastBar !== null &&
|
|
332973
|
+
if (this.lastBar !== null && lastOpenTimeMs !== null && lastOpenTimeMs < firstBarToProcess.openTimeMs) {
|
|
332830
332974
|
candidates.push({
|
|
332831
332975
|
bar: this.lastBar,
|
|
332832
332976
|
isClosed: true,
|
|
332833
332977
|
brokerVersion
|
|
332834
332978
|
});
|
|
332835
332979
|
}
|
|
332836
|
-
for (let index2 = 0;index2 <
|
|
332837
|
-
const bar =
|
|
332980
|
+
for (let index2 = 0;index2 < barsToProcess.length - 1; index2 += 1) {
|
|
332981
|
+
const bar = barsToProcess[index2];
|
|
332838
332982
|
if (bar) {
|
|
332839
332983
|
candidates.push({
|
|
332840
332984
|
bar,
|
|
@@ -332844,12 +332988,12 @@ class OhlcvBarTracker {
|
|
|
332844
332988
|
}
|
|
332845
332989
|
}
|
|
332846
332990
|
candidates.push({
|
|
332847
|
-
bar:
|
|
332991
|
+
bar: lastBarToProcess,
|
|
332848
332992
|
isClosed: false,
|
|
332849
332993
|
brokerVersion
|
|
332850
332994
|
});
|
|
332851
|
-
this.lastOpenTimeMs =
|
|
332852
|
-
this.lastBar =
|
|
332995
|
+
this.lastOpenTimeMs = lastBarToProcess.openTimeMs;
|
|
332996
|
+
this.lastBar = lastBarToProcess;
|
|
332853
332997
|
return candidates;
|
|
332854
332998
|
}
|
|
332855
332999
|
}
|
|
@@ -333261,6 +333405,7 @@ function archiveOrderbookInBackground(archiver, otelMetrics, input, options) {
|
|
|
333261
333405
|
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
333262
333406
|
}
|
|
333263
333407
|
} catch (error48) {
|
|
333408
|
+
rethrowArchiveDurabilityError(error48);
|
|
333264
333409
|
log.warn("Failed to archive orderbook snapshot", { error: error48 });
|
|
333265
333410
|
}
|
|
333266
333411
|
});
|
|
@@ -333288,6 +333433,7 @@ function archiveOhlcvInBackground(archiver, otelMetrics, tracker, input) {
|
|
|
333288
333433
|
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
333289
333434
|
}
|
|
333290
333435
|
} catch (error48) {
|
|
333436
|
+
rethrowArchiveDurabilityError(error48);
|
|
333291
333437
|
log.warn("Failed to archive OHLCV candle", { error: error48 });
|
|
333292
333438
|
}
|
|
333293
333439
|
});
|
|
@@ -333314,6 +333460,7 @@ function archiveMarketRowsInBackground(archiver, otelMetrics, stream4, input, en
|
|
|
333314
333460
|
recordWatchMetric(otelMetrics, "cex_watch_frames_archived_total", labels);
|
|
333315
333461
|
}
|
|
333316
333462
|
} catch (error48) {
|
|
333463
|
+
rethrowArchiveDurabilityError(error48);
|
|
333317
333464
|
log.warn(`Failed to archive ${stream4} market data`, { error: error48 });
|
|
333318
333465
|
}
|
|
333319
333466
|
});
|
|
@@ -333349,7 +333496,6 @@ function resolveOhlcvBootstrapLimit(optionValue) {
|
|
|
333349
333496
|
}
|
|
333350
333497
|
return Math.min(parsed, MAX_OHLCV_BOOTSTRAP_LIMIT);
|
|
333351
333498
|
}
|
|
333352
|
-
|
|
333353
333499
|
// src/helpers/market-data-archive/ohlcv-history.ts
|
|
333354
333500
|
function supportsFetchOhlcv(broker) {
|
|
333355
333501
|
const fetchOHLCV = broker.fetchOHLCV;
|
|
@@ -333834,7 +333980,7 @@ function createSubscribeHandler(deps) {
|
|
|
333834
333980
|
}
|
|
333835
333981
|
}
|
|
333836
333982
|
while (ohlcvStreamActive && !isStreamClosed()) {
|
|
333837
|
-
const data = await broker.
|
|
333983
|
+
const data = await broker.watchOHLCV(resolvedSymbol, timeframe);
|
|
333838
333984
|
const receivedTimestamp = Date.now();
|
|
333839
333985
|
if (!await writeSubscribeFrame(call, isStreamClosed, {
|
|
333840
333986
|
data: JSON.stringify(data),
|