@openzeppelin/relayer-plugin-channels 0.17.0 → 0.19.0
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/plugin/config.d.ts +2 -0
- package/dist/plugin/config.d.ts.map +1 -1
- package/dist/plugin/config.js +16 -0
- package/dist/plugin/constants.d.ts +11 -7
- package/dist/plugin/constants.d.ts.map +1 -1
- package/dist/plugin/constants.js +18 -9
- package/dist/plugin/handler.d.ts +14 -0
- package/dist/plugin/handler.d.ts.map +1 -1
- package/dist/plugin/handler.js +49 -6
- package/dist/plugin/management.js +11 -14
- package/dist/plugin/pool.d.ts +14 -5
- package/dist/plugin/pool.d.ts.map +1 -1
- package/dist/plugin/pool.js +98 -38
- package/dist/plugin/submit.d.ts +2 -1
- package/dist/plugin/submit.d.ts.map +1 -1
- package/dist/plugin/submit.js +5 -3
- package/package.json +1 -1
package/dist/plugin/config.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface ChannelAccountsConfig {
|
|
|
19
19
|
inclusionFeeLimited: number;
|
|
20
20
|
sequenceNumberCacheMaxAgeMs: number;
|
|
21
21
|
minSignatureExpirationLedgerBuffer: number;
|
|
22
|
+
globalTimeoutMs: number;
|
|
23
|
+
pollingTimeoutMs: number;
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
26
|
* Load configuration from environment variables
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/plugin/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,wFAAwF;IACxF,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,2BAA2B,EAAE,MAAM,CAAC;IACpC,kCAAkC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/plugin/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,wFAAwF;IACxF,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,2BAA2B,EAAE,MAAM,CAAC;IACpC,kCAAkC,EAAE,MAAM,CAAC;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAoID;;GAEG;AACH,wBAAgB,UAAU,IAAI,qBAAqB,CA6BlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAE3E"}
|
package/dist/plugin/config.js
CHANGED
|
@@ -117,6 +117,20 @@ function parseAllowedFundRelayerIds() {
|
|
|
117
117
|
}
|
|
118
118
|
return ids;
|
|
119
119
|
}
|
|
120
|
+
function parseGlobalTimeoutMs() {
|
|
121
|
+
const raw = process.env.PLUGIN_GLOBAL_TIMEOUT_MS;
|
|
122
|
+
if (!raw)
|
|
123
|
+
return constants_1.TIMEOUT.DEFAULT_GLOBAL_TIMEOUT_MS;
|
|
124
|
+
const n = Number(raw);
|
|
125
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : constants_1.TIMEOUT.DEFAULT_GLOBAL_TIMEOUT_MS;
|
|
126
|
+
}
|
|
127
|
+
function parsePollingTimeoutMs() {
|
|
128
|
+
const raw = process.env.PLUGIN_POLLING_TIMEOUT_MS;
|
|
129
|
+
if (!raw)
|
|
130
|
+
return constants_1.POLLING.DEFAULT_TIMEOUT_MS;
|
|
131
|
+
const n = Number(raw);
|
|
132
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : constants_1.POLLING.DEFAULT_TIMEOUT_MS;
|
|
133
|
+
}
|
|
120
134
|
function parseContractCapacityRatio() {
|
|
121
135
|
const raw = process.env.CONTRACT_CAPACITY_RATIO;
|
|
122
136
|
if (!raw)
|
|
@@ -154,6 +168,8 @@ function loadConfig() {
|
|
|
154
168
|
inclusionFeeLimited: parseInclusionFee('INCLUSION_FEE_LIMITED', DEFAULT_INCLUSION_FEE_LIMITED),
|
|
155
169
|
sequenceNumberCacheMaxAgeMs: parseSequenceNumberCacheMaxAge(),
|
|
156
170
|
minSignatureExpirationLedgerBuffer: parseMinAuthExpiryLedgerBuffer(),
|
|
171
|
+
globalTimeoutMs: parseGlobalTimeoutMs(),
|
|
172
|
+
pollingTimeoutMs: parsePollingTimeoutMs(),
|
|
157
173
|
};
|
|
158
174
|
}
|
|
159
175
|
/**
|
|
@@ -23,11 +23,15 @@ export declare const CONFIG: {
|
|
|
23
23
|
readonly DEFAULT_MIN_SIGNATURE_EXPIRATION_LEDGER_BUFFER: 2;
|
|
24
24
|
};
|
|
25
25
|
export declare const POOL: {
|
|
26
|
-
readonly
|
|
27
|
-
readonly
|
|
28
|
-
readonly
|
|
29
|
-
readonly
|
|
30
|
-
|
|
26
|
+
readonly CLAIM_LOCK_TTL_SECONDS: 3;
|
|
27
|
+
readonly ACQUIRE_MAX_SPINS: 12;
|
|
28
|
+
readonly ACQUIRE_BASE_DELAY_MS: 25;
|
|
29
|
+
readonly ACQUIRE_MAX_DELAY_MS: 500;
|
|
30
|
+
readonly MAX_CLAIMS_PER_SPIN: 3;
|
|
31
|
+
readonly CHANNEL_COOLDOWN_MS: 6000;
|
|
32
|
+
readonly LRU_KEY_TTL_SECONDS: 86400;
|
|
33
|
+
};
|
|
34
|
+
export declare const RELAYER_INFO_CACHE_TTL_SECONDS = 1800;
|
|
31
35
|
export declare const TIME: {
|
|
32
36
|
readonly MAX_TIME_BOUND_OFFSET_SECONDS: 60;
|
|
33
37
|
};
|
|
@@ -41,12 +45,12 @@ export declare const SIMULATION: {
|
|
|
41
45
|
readonly MIN_SIGNATURE_EXPIRATION_LEDGER_BUFFER: 2;
|
|
42
46
|
};
|
|
43
47
|
export declare const TIMEOUT: {
|
|
44
|
-
readonly
|
|
48
|
+
readonly DEFAULT_GLOBAL_TIMEOUT_MS: 30000;
|
|
45
49
|
readonly BUFFER_MS: 2000;
|
|
46
50
|
};
|
|
47
51
|
export declare const POLLING: {
|
|
48
52
|
readonly INTERVAL_MS: 1000;
|
|
49
|
-
readonly
|
|
53
|
+
readonly DEFAULT_TIMEOUT_MS: 25000;
|
|
50
54
|
};
|
|
51
55
|
export declare const FEE: {
|
|
52
56
|
readonly NON_SOROBAN_FEE: 100000;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/plugin/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,eAAO,MAAM,WAAW;;;;;;;;;;CAUd,CAAC;AAGX,eAAO,MAAM,MAAM;;;;;;;CAOT,CAAC;AAGX,eAAO,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/plugin/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,eAAO,MAAM,WAAW;;;;;;;;;;CAUd,CAAC;AAGX,eAAO,MAAM,MAAM;;;;;;;CAOT,CAAC;AAGX,eAAO,MAAM,IAAI;;;;;;;;CAcP,CAAC;AAGX,eAAO,MAAM,8BAA8B,OAAQ,CAAC;AAGpD,eAAO,MAAM,IAAI;;CAEP,CAAC;AAGX,eAAO,MAAM,UAAU;;;;;;IAMrB,uLAAuL;;CAE/K,CAAC;AAGX,eAAO,MAAM,OAAO;;;CAGV,CAAC;AAGX,eAAO,MAAM,OAAO;;;CAGV,CAAC;AAEX,eAAO,MAAM,GAAG;;CAGN,CAAC"}
|
package/dist/plugin/constants.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Centralized constants for the channel accounts plugin.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.FEE = exports.POLLING = exports.TIMEOUT = exports.SIMULATION = exports.TIME = exports.POOL = exports.CONFIG = exports.HTTP_STATUS = void 0;
|
|
8
|
+
exports.FEE = exports.POLLING = exports.TIMEOUT = exports.SIMULATION = exports.TIME = exports.RELAYER_INFO_CACHE_TTL_SECONDS = exports.POOL = exports.CONFIG = exports.HTTP_STATUS = void 0;
|
|
9
9
|
// HTTP Status Codes
|
|
10
10
|
exports.HTTP_STATUS = {
|
|
11
11
|
BAD_REQUEST: 400,
|
|
@@ -29,13 +29,22 @@ exports.CONFIG = {
|
|
|
29
29
|
};
|
|
30
30
|
// Pool Constants
|
|
31
31
|
exports.POOL = {
|
|
32
|
-
// TTL
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
// Per-channel claim lock TTL — must exceed worst-case callback latency
|
|
33
|
+
// to prevent TTL expiry allowing a second worker into the same claim section
|
|
34
|
+
CLAIM_LOCK_TTL_SECONDS: 3,
|
|
35
|
+
// Retry policy: exponential backoff with full jitter
|
|
36
|
+
ACQUIRE_MAX_SPINS: 12,
|
|
37
|
+
ACQUIRE_BASE_DELAY_MS: 25,
|
|
38
|
+
ACQUIRE_MAX_DELAY_MS: 500,
|
|
39
|
+
// Max claim-lock attempts per spin (batch size)
|
|
40
|
+
MAX_CLAIMS_PER_SPIN: 3,
|
|
41
|
+
// Hard-block cooldown for uncertain-outcome channels (~1 Stellar ledger with margin)
|
|
42
|
+
CHANNEL_COOLDOWN_MS: 6000,
|
|
43
|
+
// Housekeeping TTL for per-channel LRU keys
|
|
44
|
+
LRU_KEY_TTL_SECONDS: 86400,
|
|
38
45
|
};
|
|
46
|
+
// Relayer info cache — address and network_type are effectively immutable
|
|
47
|
+
exports.RELAYER_INFO_CACHE_TTL_SECONDS = 1800; // 30 minutes
|
|
39
48
|
// Time Constants
|
|
40
49
|
exports.TIME = {
|
|
41
50
|
MAX_TIME_BOUND_OFFSET_SECONDS: 60,
|
|
@@ -52,13 +61,13 @@ exports.SIMULATION = {
|
|
|
52
61
|
};
|
|
53
62
|
// Global plugin timeout budget
|
|
54
63
|
exports.TIMEOUT = {
|
|
55
|
-
|
|
64
|
+
DEFAULT_GLOBAL_TIMEOUT_MS: 30000,
|
|
56
65
|
BUFFER_MS: 2000,
|
|
57
66
|
};
|
|
58
67
|
// Polling for transactionWait
|
|
59
68
|
exports.POLLING = {
|
|
60
69
|
INTERVAL_MS: 1000,
|
|
61
|
-
|
|
70
|
+
DEFAULT_TIMEOUT_MS: 25000,
|
|
62
71
|
};
|
|
63
72
|
exports.FEE = {
|
|
64
73
|
// For non-Soroban txs: 100,000 stroops (0.01 XLM) per Stellar best practice
|
package/dist/plugin/handler.d.ts
CHANGED
|
@@ -5,7 +5,20 @@
|
|
|
5
5
|
* Orchestrates the transaction processing pipeline using channel accounts with fee bumping.
|
|
6
6
|
*/
|
|
7
7
|
import { PluginContext } from '@openzeppelin/relayer-sdk';
|
|
8
|
+
import type { Relayer } from '@openzeppelin/relayer-sdk';
|
|
8
9
|
import { Transaction, xdr } from '@stellar/stellar-sdk';
|
|
10
|
+
/** Subset of relayer metadata used by the plugin (address + network_type). */
|
|
11
|
+
type CachedRelayerInfo = {
|
|
12
|
+
address: string;
|
|
13
|
+
network_type: string;
|
|
14
|
+
};
|
|
15
|
+
/** @internal Exported for test isolation only. */
|
|
16
|
+
export declare function clearRelayerInfoCache(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Return cached relayer info or fetch from the API and cache the result.
|
|
19
|
+
* Returns null if the relayer has no address (misconfigured).
|
|
20
|
+
*/
|
|
21
|
+
export declare function getCachedRelayerInfo(network: string, relayerId: string, relayer: Relayer): Promise<CachedRelayerInfo | null>;
|
|
9
22
|
/**
|
|
10
23
|
* Extracts func and auth from an unsigned Soroban transaction.
|
|
11
24
|
* Returns null if the transaction is not a single invokeHostFunction operation.
|
|
@@ -18,4 +31,5 @@ export declare function extractFuncAuthFromUnsignedXdr(tx: Transaction): {
|
|
|
18
31
|
* Main plugin handler exported for OpenZeppelin Relayer
|
|
19
32
|
*/
|
|
20
33
|
export declare function handler(context: PluginContext): Promise<any>;
|
|
34
|
+
export {};
|
|
21
35
|
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/plugin/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/plugin/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,EAA4B,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAQnF,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAOxD,8EAA8E;AAC9E,KAAK,iBAAiB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAUnE,kDAAkD;AAClD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAanC;AAsBD;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,WAAW,GACd;IAAE,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,CAAA;CAAE,GAAG,IAAI,CAkB1E;AAyTD;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAElE"}
|
package/dist/plugin/handler.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* Orchestrates the transaction processing pipeline using channel accounts with fee bumping.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.clearRelayerInfoCache = clearRelayerInfoCache;
|
|
10
|
+
exports.getCachedRelayerInfo = getCachedRelayerInfo;
|
|
9
11
|
exports.extractFuncAuthFromUnsignedXdr = extractFuncAuthFromUnsignedXdr;
|
|
10
12
|
exports.handler = handler;
|
|
11
13
|
const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
|
|
@@ -21,6 +23,35 @@ const fee_1 = require("./fee");
|
|
|
21
23
|
const tx_1 = require("./tx");
|
|
22
24
|
const fee_tracking_1 = require("./fee-tracking");
|
|
23
25
|
const sequence_1 = require("./sequence");
|
|
26
|
+
/**
|
|
27
|
+
* In-memory cache for relayer info. Avoids a remote API call (getRelayer → HTTP GET)
|
|
28
|
+
* on every request while a channel lock is held. Keyed by `${network}:${relayerId}`.
|
|
29
|
+
* Entries expire after RELAYER_INFO_CACHE_TTL_SECONDS; stale entries are evicted on miss.
|
|
30
|
+
*/
|
|
31
|
+
const relayerInfoCache = new Map();
|
|
32
|
+
/** @internal Exported for test isolation only. */
|
|
33
|
+
function clearRelayerInfoCache() {
|
|
34
|
+
relayerInfoCache.clear();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Return cached relayer info or fetch from the API and cache the result.
|
|
38
|
+
* Returns null if the relayer has no address (misconfigured).
|
|
39
|
+
*/
|
|
40
|
+
async function getCachedRelayerInfo(network, relayerId, relayer) {
|
|
41
|
+
const cacheKey = `${network}:${relayerId}`;
|
|
42
|
+
const cached = relayerInfoCache.get(cacheKey);
|
|
43
|
+
if (cached && cached.expiresAt > Date.now())
|
|
44
|
+
return cached.info;
|
|
45
|
+
// Evict stale entry so it doesn't linger in memory
|
|
46
|
+
if (cached)
|
|
47
|
+
relayerInfoCache.delete(cacheKey);
|
|
48
|
+
const info = await relayer.getRelayer();
|
|
49
|
+
if (!info?.address)
|
|
50
|
+
return null;
|
|
51
|
+
const entry = { address: info.address, network_type: info.network_type };
|
|
52
|
+
relayerInfoCache.set(cacheKey, { info: entry, expiresAt: Date.now() + constants_1.RELAYER_INFO_CACHE_TTL_SECONDS * 1000 });
|
|
53
|
+
return entry;
|
|
54
|
+
}
|
|
24
55
|
function getApiKey(headers, headerName) {
|
|
25
56
|
const values = headers[headerName];
|
|
26
57
|
return values?.[0]?.trim() || undefined;
|
|
@@ -74,7 +105,7 @@ async function handleXdrSubmit(xdrStr, ctx, skipWait) {
|
|
|
74
105
|
contractId,
|
|
75
106
|
isLimited: contractId ? ctx.acquireOptions.limitedContracts.has(contractId) : false,
|
|
76
107
|
};
|
|
77
|
-
return (0, submit_1.submitWithFeeBumpAndWait)(ctx.fundRelayer, validated.toXDR(), ctx.network, maxFee, ctx.api, ctx.startTime, ctx.tracker, submitContext, skipWait);
|
|
108
|
+
return (0, submit_1.submitWithFeeBumpAndWait)(ctx.fundRelayer, validated.toXDR(), ctx.network, maxFee, ctx.api, ctx.startTime, ctx.tracker, submitContext, skipWait, ctx.config);
|
|
78
109
|
}
|
|
79
110
|
async function handleFuncAuthSubmit(func, auth, ctx, skipWait) {
|
|
80
111
|
// Simulate once — used for both read-only detection and transaction assembly
|
|
@@ -90,10 +121,11 @@ async function handleFuncAuthSubmit(func, auth, ctx, skipWait) {
|
|
|
90
121
|
};
|
|
91
122
|
}
|
|
92
123
|
let poolLock;
|
|
124
|
+
let needsCooldown = false;
|
|
93
125
|
try {
|
|
94
126
|
poolLock = await ctx.pool.acquire(ctx.acquireOptions);
|
|
95
127
|
const channelRelayer = ctx.api.useRelayer(poolLock.relayerId);
|
|
96
|
-
const channelInfo = await channelRelayer
|
|
128
|
+
const channelInfo = await getCachedRelayerInfo(ctx.network, poolLock.relayerId, channelRelayer);
|
|
97
129
|
console.log(`[channels] Acquired channel: ${poolLock.relayerId}`);
|
|
98
130
|
if (!channelInfo || !channelInfo.address) {
|
|
99
131
|
throw (0, relayer_sdk_1.pluginError)('Channel relayer not found', {
|
|
@@ -123,9 +155,8 @@ async function handleFuncAuthSubmit(func, auth, ctx, skipWait) {
|
|
|
123
155
|
isLimited: contractId ? ctx.acquireOptions.limitedContracts.has(contractId) : false,
|
|
124
156
|
};
|
|
125
157
|
try {
|
|
126
|
-
const result = await (0, submit_1.submitWithFeeBumpAndWait)(ctx.fundRelayer, signedTx.toXDR(), ctx.network, maxFee, ctx.api, ctx.startTime, ctx.tracker, submitContext, skipWait);
|
|
158
|
+
const result = await (0, submit_1.submitWithFeeBumpAndWait)(ctx.fundRelayer, signedTx.toXDR(), ctx.network, maxFee, ctx.api, ctx.startTime, ctx.tracker, submitContext, skipWait, ctx.config);
|
|
127
159
|
if (result.status === 'pending' || result.status === 'sent' || result.status === 'submitted') {
|
|
128
|
-
// extend lock and clear sequence
|
|
129
160
|
console.log(`[channels]: extending lock and clearing sequence`);
|
|
130
161
|
await ctx.pool.extendLock(poolLock);
|
|
131
162
|
await (0, sequence_1.clearSequence)(ctx.kv, ctx.network, channelInfo.address);
|
|
@@ -135,7 +166,9 @@ async function handleFuncAuthSubmit(func, auth, ctx, skipWait) {
|
|
|
135
166
|
await (0, sequence_1.commitSequence)(ctx.kv, ctx.network, channelInfo.address, sequence);
|
|
136
167
|
}
|
|
137
168
|
else {
|
|
169
|
+
// Unknown status — uncertain outcome → release with cooldown
|
|
138
170
|
await (0, sequence_1.clearSequence)(ctx.kv, ctx.network, channelInfo.address);
|
|
171
|
+
needsCooldown = true;
|
|
139
172
|
}
|
|
140
173
|
return result;
|
|
141
174
|
}
|
|
@@ -146,12 +179,22 @@ async function handleFuncAuthSubmit(func, auth, ctx, skipWait) {
|
|
|
146
179
|
await ctx.pool.extendLock(poolLock);
|
|
147
180
|
poolLock = undefined; // skip release in finally
|
|
148
181
|
}
|
|
182
|
+
else if (error.code === 'ONCHAIN_FAILED') {
|
|
183
|
+
// Sequence was consumed (tx landed on chain) — cooldown prevents
|
|
184
|
+
// next acquirer from hitting stale RPC and reusing old sequence
|
|
185
|
+
needsCooldown = true;
|
|
186
|
+
}
|
|
149
187
|
throw error;
|
|
150
188
|
}
|
|
151
189
|
}
|
|
152
190
|
finally {
|
|
153
191
|
if (poolLock) {
|
|
154
|
-
|
|
192
|
+
if (needsCooldown) {
|
|
193
|
+
await ctx.pool.releaseWithCooldown(poolLock);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
await ctx.pool.release(poolLock);
|
|
197
|
+
}
|
|
155
198
|
}
|
|
156
199
|
}
|
|
157
200
|
}
|
|
@@ -212,7 +255,7 @@ async function channelAccounts(context) {
|
|
|
212
255
|
hash: stellar.hash ?? null,
|
|
213
256
|
};
|
|
214
257
|
}
|
|
215
|
-
const fundInfo = await fundRelayer
|
|
258
|
+
const fundInfo = await getCachedRelayerInfo(config.network, fundRelayerId, fundRelayer);
|
|
216
259
|
if (!fundInfo || !fundInfo.address) {
|
|
217
260
|
throw (0, relayer_sdk_1.pluginError)('Fund relayer not found', {
|
|
218
261
|
code: 'RELAYER_UNAVAILABLE',
|
|
@@ -171,12 +171,8 @@ async function setChannelAccounts(kv, network, payload) {
|
|
|
171
171
|
const current = await readStoredRelayerIds(kv, listKey);
|
|
172
172
|
// Check for locked removals
|
|
173
173
|
const removals = current.filter((id) => !relayerIds.includes(id));
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
if (await isRelayerIdLocked(kv, network, id)) {
|
|
177
|
-
locked.push(id);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
174
|
+
const lockedSet = await getLockedRelayerIds(kv, network);
|
|
175
|
+
const locked = removals.filter((id) => lockedSet.has(id));
|
|
180
176
|
if (locked.length > 0) {
|
|
181
177
|
throw (0, relayer_sdk_1.pluginError)('Locked relayer IDs cannot be removed', {
|
|
182
178
|
code: 'LOCKED_CONFLICT',
|
|
@@ -211,12 +207,12 @@ async function getPoolStats(config, kv) {
|
|
|
211
207
|
status: constants_1.HTTP_STATUS.INTERNAL_SERVER_ERROR,
|
|
212
208
|
});
|
|
213
209
|
}
|
|
214
|
-
// 2. Check
|
|
210
|
+
// 2. Check locks via single listKeys scan — best-effort
|
|
215
211
|
let locked;
|
|
216
212
|
let available;
|
|
217
213
|
try {
|
|
218
|
-
const
|
|
219
|
-
locked =
|
|
214
|
+
const lockedSet = await getLockedRelayerIds(kv, network);
|
|
215
|
+
locked = relayerIds.filter((id) => lockedSet.has(id)).length;
|
|
220
216
|
available = relayerIds.length - locked;
|
|
221
217
|
}
|
|
222
218
|
catch {
|
|
@@ -268,16 +264,17 @@ async function readStoredRelayerIds(kv, key) {
|
|
|
268
264
|
});
|
|
269
265
|
}
|
|
270
266
|
}
|
|
271
|
-
async function
|
|
272
|
-
const
|
|
267
|
+
async function getLockedRelayerIds(kv, network) {
|
|
268
|
+
const prefix = `${network}:channel:in-use:`;
|
|
273
269
|
try {
|
|
274
|
-
|
|
270
|
+
const keys = await kv.listKeys(`${prefix}*`);
|
|
271
|
+
return new Set(keys.map((k) => k.slice(prefix.length)));
|
|
275
272
|
}
|
|
276
273
|
catch (error) {
|
|
277
|
-
throw (0, relayer_sdk_1.pluginError)('KV error while checking relayer
|
|
274
|
+
throw (0, relayer_sdk_1.pluginError)('KV error while checking relayer locks', {
|
|
278
275
|
code: 'KV_ERROR',
|
|
279
276
|
status: constants_1.HTTP_STATUS.INTERNAL_SERVER_ERROR,
|
|
280
|
-
details: {
|
|
277
|
+
details: { prefix, pattern: `${prefix}*`, message: error instanceof Error ? error.message : String(error) },
|
|
281
278
|
});
|
|
282
279
|
}
|
|
283
280
|
}
|
package/dist/plugin/pool.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* KV-backed, stateless channel pool.
|
|
5
5
|
* - Membership comes from KV: <network>:channel:relayer-ids
|
|
6
6
|
* - Per-relayer locks with tokens: <network>:channel:in-use:<relayerId>
|
|
7
|
-
* - Uses
|
|
7
|
+
* - Uses per-channel claim locks to make acquire safe across workers.
|
|
8
|
+
* - LRU ordering via per-channel keys: <network>:channel:lru:<relayerId>
|
|
8
9
|
*/
|
|
9
10
|
import { PluginKVStore } from '@openzeppelin/relayer-sdk';
|
|
10
11
|
export type PoolLock = {
|
|
@@ -18,21 +19,29 @@ export type AcquireOptions = {
|
|
|
18
19
|
};
|
|
19
20
|
export declare class ChannelPool {
|
|
20
21
|
private readonly network;
|
|
21
|
-
private readonly globalLockKey;
|
|
22
22
|
private readonly channelLockTtlSec;
|
|
23
|
-
private readonly mutexTtlSec;
|
|
24
23
|
private readonly kv;
|
|
25
24
|
constructor(network: 'testnet' | 'mainnet', kv: PluginKVStore, lockTtlSeconds: number);
|
|
26
25
|
/** Acquire a relayerId with a token lock */
|
|
27
26
|
acquire(options: AcquireOptions): Promise<PoolLock>;
|
|
28
|
-
|
|
29
|
-
private
|
|
27
|
+
/** Try to claim one channel from a batch of candidates */
|
|
28
|
+
private tryClaimBatch;
|
|
29
|
+
/** Attempt to claim a single channel under its per-channel lock */
|
|
30
|
+
private tryClaimChannel;
|
|
30
31
|
/** Extend the lock TTL if we still own it (e.g. after WAIT_TIMEOUT) */
|
|
31
32
|
extendLock(lock: PoolLock, ttlSec?: number): Promise<void>;
|
|
32
33
|
/** Release the lock if we own it */
|
|
33
34
|
release(lock: PoolLock): Promise<void>;
|
|
35
|
+
/** Release with cooldown: keeps lock alive with short TTL to hard-block the channel. */
|
|
36
|
+
releaseWithCooldown(lock: PoolLock, cooldownMs?: 6000): Promise<void>;
|
|
34
37
|
private membershipKey;
|
|
35
38
|
private lockKey;
|
|
39
|
+
private claimKey;
|
|
40
|
+
private lruKey;
|
|
41
|
+
/** Read per-channel LRU timestamps in parallel (partial failures keep successful reads) */
|
|
42
|
+
private readLruMap;
|
|
43
|
+
/** Fire-and-forget LRU timestamp update for a single channel */
|
|
44
|
+
private updateLru;
|
|
36
45
|
private getRelayerIdsFromKV;
|
|
37
46
|
private getPoolCapacityDetails;
|
|
38
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/plugin/pool.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/plugin/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAC;AAIvE,MAAM,MAAM,QAAQ,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAUF,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgB;gBAEvB,OAAO,EAAE,SAAS,GAAG,SAAS,EAAE,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM;IAMrF,4CAA4C;IACtC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8DzD,0DAA0D;YAC5C,aAAa;IAQ3B,mEAAmE;YACrD,eAAe;IAuB7B,uEAAuE;IACjE,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBhE,oCAAoC;IAC9B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5C,wFAAwF;IAClF,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,OAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAa/F,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,MAAM;IAId,2FAA2F;YAC7E,UAAU;IASxB,gEAAgE;IAChE,OAAO,CAAC,SAAS;YAMH,mBAAmB;YAYnB,sBAAsB;CAUrC"}
|
package/dist/plugin/pool.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* KV-backed, stateless channel pool.
|
|
6
6
|
* - Membership comes from KV: <network>:channel:relayer-ids
|
|
7
7
|
* - Per-relayer locks with tokens: <network>:channel:in-use:<relayerId>
|
|
8
|
-
* - Uses
|
|
8
|
+
* - Uses per-channel claim locks to make acquire safe across workers.
|
|
9
|
+
* - LRU ordering via per-channel keys: <network>:channel:lru:<relayerId>
|
|
9
10
|
*/
|
|
10
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -19,21 +20,52 @@ class ChannelPool {
|
|
|
19
20
|
constructor(network, kv, lockTtlSeconds) {
|
|
20
21
|
this.network = network;
|
|
21
22
|
this.kv = kv;
|
|
22
|
-
this.globalLockKey = `${this.network}:channel-pool-lock`;
|
|
23
23
|
this.channelLockTtlSec = lockTtlSeconds;
|
|
24
|
-
this.mutexTtlSec = constants_1.POOL.MUTEX_TTL_SECONDS;
|
|
25
24
|
}
|
|
26
25
|
/** Acquire a relayerId with a token lock */
|
|
27
26
|
async acquire(options) {
|
|
28
|
-
const maxSpins = constants_1.POOL.
|
|
27
|
+
const maxSpins = constants_1.POOL.ACQUIRE_MAX_SPINS;
|
|
28
|
+
// --- Read state ONCE for the entire retry loop ---
|
|
29
|
+
let ids = await this.getRelayerIdsFromKV();
|
|
30
|
+
if (ids.length === 0) {
|
|
31
|
+
throw (0, relayer_sdk_1.pluginError)('No channel accounts configured. Use the management API to set channel accounts.', {
|
|
32
|
+
code: 'NO_CHANNELS_CONFIGURED',
|
|
33
|
+
status: constants_1.HTTP_STATUS.SERVICE_UNAVAILABLE,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (options.contractId && options.limitedContracts.has(options.contractId)) {
|
|
37
|
+
ids = filterChannelsForLimitedContract(ids, options.capacityRatio);
|
|
38
|
+
}
|
|
39
|
+
// Shuffle all candidates; batch size scales so every spin covers an
|
|
40
|
+
// equal slice of the pool, guaranteeing full coverage across all spins.
|
|
41
|
+
const candidates = ids.slice();
|
|
42
|
+
shuffle(candidates);
|
|
43
|
+
// Read LRU for a small prefix only (capped at maxSpins * POOL.MAX_CLAIMS_PER_SPIN) to guide initial ordering.
|
|
44
|
+
// The rest stay in random shuffled order — no extra Redis reads.
|
|
45
|
+
const lruSampleSize = Math.min(candidates.length, maxSpins * constants_1.POOL.MAX_CLAIMS_PER_SPIN);
|
|
46
|
+
const lruMap = await this.readLruMap(candidates.slice(0, lruSampleSize));
|
|
47
|
+
const prefix = candidates.slice(0, lruSampleSize);
|
|
48
|
+
prefix.sort((a, b) => (lruMap[a] ?? 0) - (lruMap[b] ?? 0));
|
|
49
|
+
for (let j = 0; j < prefix.length; j++)
|
|
50
|
+
candidates[j] = prefix[j];
|
|
51
|
+
// Batch size: cover the full pool across maxSpins iterations
|
|
52
|
+
const batchSize = Math.max(constants_1.POOL.MAX_CLAIMS_PER_SPIN, Math.ceil(candidates.length / maxSpins));
|
|
53
|
+
let offset = 0;
|
|
29
54
|
for (let i = 0; i < maxSpins; i++) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
continue;
|
|
55
|
+
if (offset >= candidates.length) {
|
|
56
|
+
// Full sweep done — reshuffle for next sweep
|
|
57
|
+
offset = 0;
|
|
58
|
+
shuffle(candidates);
|
|
35
59
|
}
|
|
36
|
-
|
|
60
|
+
const batch = candidates.slice(offset, offset + batchSize);
|
|
61
|
+
offset += batchSize;
|
|
62
|
+
const result = await this.tryClaimBatch(batch);
|
|
63
|
+
if (result)
|
|
64
|
+
return result;
|
|
65
|
+
// Exponential backoff with full jitter
|
|
66
|
+
const baseDelay = Math.min(constants_1.POOL.ACQUIRE_MAX_DELAY_MS, constants_1.POOL.ACQUIRE_BASE_DELAY_MS * Math.pow(2, i));
|
|
67
|
+
const jitter = Math.floor(Math.random() * baseDelay);
|
|
68
|
+
await sleep(jitter);
|
|
37
69
|
}
|
|
38
70
|
const diagnostics = await this.getPoolCapacityDetails(options, maxSpins);
|
|
39
71
|
console.warn('[channels] Pool capacity exhausted', diagnostics);
|
|
@@ -43,35 +75,27 @@ class ChannelPool {
|
|
|
43
75
|
details: diagnostics,
|
|
44
76
|
});
|
|
45
77
|
}
|
|
46
|
-
|
|
47
|
-
async
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
let ids = await this.getRelayerIdsFromKV();
|
|
53
|
-
if (ids.length === 0) {
|
|
54
|
-
throw (0, relayer_sdk_1.pluginError)('No channel accounts configured. Use the management API to set channel accounts.', {
|
|
55
|
-
code: 'NO_CHANNELS_CONFIGURED',
|
|
56
|
-
status: constants_1.HTTP_STATUS.SERVICE_UNAVAILABLE,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
// If contract is limited, filter to allowed subset
|
|
60
|
-
if (options.contractId && options.limitedContracts.has(options.contractId)) {
|
|
61
|
-
ids = filterChannelsForLimitedContract(ids, options.capacityRatio);
|
|
78
|
+
/** Try to claim one channel from a batch of candidates */
|
|
79
|
+
async tryClaimBatch(batch) {
|
|
80
|
+
for (const candidate of batch) {
|
|
81
|
+
const result = await this.tryClaimChannel(candidate);
|
|
82
|
+
if (result)
|
|
83
|
+
return result;
|
|
62
84
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/** Attempt to claim a single channel under its per-channel lock */
|
|
88
|
+
async tryClaimChannel(relayerId) {
|
|
89
|
+
return this.kv.withLock(this.claimKey(relayerId), async () => {
|
|
90
|
+
// Double-check: another worker may have claimed it between our scan and this lock
|
|
91
|
+
if (await this.kv.exists(this.lockKey(relayerId)))
|
|
92
|
+
return null;
|
|
69
93
|
const token = randomToken();
|
|
70
|
-
|
|
71
|
-
|
|
94
|
+
await this.kv.set(this.lockKey(relayerId), { token, lockedAt: new Date().toISOString() }, { ttlSec: this.channelLockTtlSec });
|
|
95
|
+
// Fire-and-forget: LRU is best-effort, don't hold the claim lock for it
|
|
96
|
+
this.updateLru(relayerId);
|
|
72
97
|
return { relayerId, token };
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
98
|
+
}, { ttlSec: constants_1.POOL.CLAIM_LOCK_TTL_SECONDS, onBusy: 'skip' });
|
|
75
99
|
}
|
|
76
100
|
/** Extend the lock TTL if we still own it (e.g. after WAIT_TIMEOUT) */
|
|
77
101
|
async extendLock(lock, ttlSec) {
|
|
@@ -99,12 +123,47 @@ class ChannelPool {
|
|
|
99
123
|
// ignore release errors
|
|
100
124
|
}
|
|
101
125
|
}
|
|
126
|
+
/** Release with cooldown: keeps lock alive with short TTL to hard-block the channel. */
|
|
127
|
+
async releaseWithCooldown(lock, cooldownMs = constants_1.POOL.CHANNEL_COOLDOWN_MS) {
|
|
128
|
+
try {
|
|
129
|
+
const key = this.lockKey(lock.relayerId);
|
|
130
|
+
const current = await this.kv.get(key);
|
|
131
|
+
if (current?.token === lock.token) {
|
|
132
|
+
const cooldownSec = Math.max(1, Math.ceil(cooldownMs / 1000));
|
|
133
|
+
await this.kv.set(key, current, { ttlSec: cooldownSec });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// ignore
|
|
138
|
+
}
|
|
139
|
+
}
|
|
102
140
|
membershipKey() {
|
|
103
141
|
return `${this.network}:channel:relayer-ids`;
|
|
104
142
|
}
|
|
105
143
|
lockKey(relayerId) {
|
|
106
144
|
return `${this.network}:channel:in-use:${relayerId}`;
|
|
107
145
|
}
|
|
146
|
+
claimKey(relayerId) {
|
|
147
|
+
return `${this.network}:channel:claim:${relayerId}`;
|
|
148
|
+
}
|
|
149
|
+
lruKey(relayerId) {
|
|
150
|
+
return `${this.network}:channel:lru:${relayerId}`;
|
|
151
|
+
}
|
|
152
|
+
/** Read per-channel LRU timestamps in parallel (partial failures keep successful reads) */
|
|
153
|
+
async readLruMap(ids) {
|
|
154
|
+
const lruMap = {};
|
|
155
|
+
const results = await Promise.allSettled(ids.map((id) => this.kv.get(this.lruKey(id))));
|
|
156
|
+
results.forEach((r, i) => {
|
|
157
|
+
lruMap[ids[i]] = r.status === 'fulfilled' ? (r.value?.ts ?? 0) : 0;
|
|
158
|
+
});
|
|
159
|
+
return lruMap;
|
|
160
|
+
}
|
|
161
|
+
/** Fire-and-forget LRU timestamp update for a single channel */
|
|
162
|
+
updateLru(relayerId) {
|
|
163
|
+
this.kv.set(this.lruKey(relayerId), { ts: Date.now() }, { ttlSec: constants_1.POOL.LRU_KEY_TTL_SECONDS }).catch((err) => {
|
|
164
|
+
console.debug('[channels] failed to update LRU key', err);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
108
167
|
async getRelayerIdsFromKV() {
|
|
109
168
|
try {
|
|
110
169
|
const doc = await this.kv.get(this.membershipKey());
|
|
@@ -121,7 +180,7 @@ class ChannelPool {
|
|
|
121
180
|
async getPoolCapacityDetails(options, maxSpins) {
|
|
122
181
|
const isLimited = !!(options.contractId && options.limitedContracts.has(options.contractId));
|
|
123
182
|
return {
|
|
124
|
-
reason: isLimited ? 'limited_contract_capacity' : '
|
|
183
|
+
reason: isLimited ? 'limited_contract_capacity' : 'all_channels_busy_or_claim_contention',
|
|
125
184
|
contractId: options.contractId,
|
|
126
185
|
capacityRatio: options.capacityRatio,
|
|
127
186
|
maxSpins,
|
|
@@ -169,8 +228,9 @@ function simpleHash(str) {
|
|
|
169
228
|
*/
|
|
170
229
|
function filterChannelsForLimitedContract(ids, ratio) {
|
|
171
230
|
const k = Math.max(1, Math.floor(ratio * ids.length));
|
|
231
|
+
const hashes = new Map(ids.map((id) => [id, simpleHash(id)]));
|
|
172
232
|
return ids
|
|
173
233
|
.slice()
|
|
174
|
-
.sort((a, b) =>
|
|
234
|
+
.sort((a, b) => hashes.get(a) - hashes.get(b) || a.localeCompare(b))
|
|
175
235
|
.slice(0, k);
|
|
176
236
|
}
|
package/dist/plugin/submit.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Transaction } from '@stellar/stellar-sdk';
|
|
7
7
|
import { Relayer, PluginAPI } from '@openzeppelin/relayer-sdk';
|
|
8
|
+
import type { ChannelAccountsConfig } from './config';
|
|
8
9
|
import { ChannelAccountsResponse } from './types';
|
|
9
10
|
import { FeeTracker } from './fee-tracking';
|
|
10
11
|
export interface SubmitContext {
|
|
@@ -25,7 +26,7 @@ export declare function signWithChannelAndFund(transaction: Transaction, channel
|
|
|
25
26
|
/**
|
|
26
27
|
* Submit transaction with fee bump and wait for confirmation
|
|
27
28
|
*/
|
|
28
|
-
export declare function submitWithFeeBumpAndWait(fundRelayer: Relayer, signedXdr: string, network: 'testnet' | 'mainnet', maxFee: number, api: PluginAPI, startTime: number, tracker?: FeeTracker, context?: SubmitContext, skipWait?: boolean): Promise<ChannelAccountsResponse>;
|
|
29
|
+
export declare function submitWithFeeBumpAndWait(fundRelayer: Relayer, signedXdr: string, network: 'testnet' | 'mainnet', maxFee: number, api: PluginAPI, startTime: number, tracker?: FeeTracker, context?: SubmitContext, skipWait?: boolean, config?: Pick<ChannelAccountsConfig, 'globalTimeoutMs' | 'pollingTimeoutMs'>): Promise<ChannelAccountsResponse>;
|
|
29
30
|
/** Try to decode a transaction result XDR from the reason string */
|
|
30
31
|
export declare function decodeTransactionResult(reason: string): DecodedTransactionResult | null;
|
|
31
32
|
export declare function buildStellarLabTransactionUrl(network: 'testnet' | 'mainnet', txHash: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/plugin/submit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAO,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAEL,OAAO,EAGP,SAAS,EACV,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,wBAAwB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,OAAO,EACvB,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,GAAG,SAAS,EAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,aAAa,EACvB,QAAQ,CAAC,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/plugin/submit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAO,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAEL,OAAO,EAGP,SAAS,EACV,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,wBAAwB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,OAAO,EACvB,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,GAAG,SAAS,EAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,aAAa,EACvB,QAAQ,CAAC,EAAE,OAAO,EAClB,MAAM,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,iBAAiB,GAAG,kBAAkB,CAAC,GAC3E,OAAO,CAAC,uBAAuB,CAAC,CAmHlC;AASD,oEAAoE;AACpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAkCvF;AAeD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAepG;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOrD"}
|
package/dist/plugin/submit.js
CHANGED
|
@@ -42,7 +42,7 @@ async function signWithChannelAndFund(transaction, channelRelayer, _fundRelayer,
|
|
|
42
42
|
/**
|
|
43
43
|
* Submit transaction with fee bump and wait for confirmation
|
|
44
44
|
*/
|
|
45
|
-
async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee, api, startTime, tracker, context, skipWait) {
|
|
45
|
+
async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee, api, startTime, tracker, context, skipWait, config) {
|
|
46
46
|
// Submit with fee bump
|
|
47
47
|
console.debug(`[channels] Sending fee bump tx: network=${network}, maxFee=${maxFee}, xdr_len=${signedXdr.length}`);
|
|
48
48
|
const payload = {
|
|
@@ -66,8 +66,10 @@ async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee,
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
// Compute dynamic timeout from remaining global budget
|
|
69
|
+
const globalTimeout = config?.globalTimeoutMs ?? constants_1.TIMEOUT.DEFAULT_GLOBAL_TIMEOUT_MS;
|
|
70
|
+
const pollingTimeout = config?.pollingTimeoutMs ?? constants_1.POLLING.DEFAULT_TIMEOUT_MS;
|
|
69
71
|
const elapsedMs = Date.now() - startTime;
|
|
70
|
-
const remainingMs =
|
|
72
|
+
const remainingMs = globalTimeout - constants_1.TIMEOUT.BUFFER_MS - elapsedMs;
|
|
71
73
|
if (remainingMs <= 0) {
|
|
72
74
|
throw (0, relayer_sdk_1.pluginError)('No time remaining for transaction wait', {
|
|
73
75
|
code: 'WAIT_TIMEOUT',
|
|
@@ -79,7 +81,7 @@ async function submitWithFeeBumpAndWait(fundRelayer, signedXdr, network, maxFee,
|
|
|
79
81
|
},
|
|
80
82
|
});
|
|
81
83
|
}
|
|
82
|
-
const timeout = Math.min(remainingMs,
|
|
84
|
+
const timeout = Math.min(remainingMs, pollingTimeout);
|
|
83
85
|
console.debug(`[channels] Transaction wait: elapsed=${elapsedMs}ms, timeout=${timeout}ms`);
|
|
84
86
|
// Wait for confirmation
|
|
85
87
|
try {
|