@hyperlane-xyz/rebalancer 0.1.2 → 1.0.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/README.md +134 -14
- package/dist/config/RebalancerConfig.d.ts +2 -2
- package/dist/config/RebalancerConfig.d.ts.map +1 -1
- package/dist/config/RebalancerConfig.js +4 -3
- package/dist/config/RebalancerConfig.js.map +1 -1
- package/dist/config/RebalancerConfig.test.js +434 -163
- package/dist/config/RebalancerConfig.test.js.map +1 -1
- package/dist/config/types.d.ts +1650 -290
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +124 -46
- package/dist/config/types.js.map +1 -1
- package/dist/core/Rebalancer.d.ts +14 -7
- package/dist/core/Rebalancer.d.ts.map +1 -1
- package/dist/core/Rebalancer.js +168 -99
- package/dist/core/Rebalancer.js.map +1 -1
- package/dist/core/Rebalancer.test.d.ts +2 -0
- package/dist/core/Rebalancer.test.d.ts.map +1 -0
- package/dist/core/Rebalancer.test.js +391 -0
- package/dist/core/Rebalancer.test.js.map +1 -0
- package/dist/core/RebalancerService.d.ts +16 -2
- package/dist/core/RebalancerService.d.ts.map +1 -1
- package/dist/core/RebalancerService.js +164 -21
- package/dist/core/RebalancerService.js.map +1 -1
- package/dist/core/RebalancerService.test.d.ts +2 -0
- package/dist/core/RebalancerService.test.d.ts.map +1 -0
- package/dist/core/RebalancerService.test.js +809 -0
- package/dist/core/RebalancerService.test.js.map +1 -0
- package/dist/factories/RebalancerContextFactory.d.ts +11 -0
- package/dist/factories/RebalancerContextFactory.d.ts.map +1 -1
- package/dist/factories/RebalancerContextFactory.js +60 -13
- package/dist/factories/RebalancerContextFactory.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/interfaces/IMonitor.d.ts +6 -8
- package/dist/interfaces/IMonitor.d.ts.map +1 -1
- package/dist/interfaces/IMonitor.js.map +1 -1
- package/dist/interfaces/IRebalancer.d.ts +20 -4
- package/dist/interfaces/IRebalancer.d.ts.map +1 -1
- package/dist/interfaces/IStrategy.d.ts +18 -2
- package/dist/interfaces/IStrategy.d.ts.map +1 -1
- package/dist/metrics/Metrics.d.ts +4 -2
- package/dist/metrics/Metrics.d.ts.map +1 -1
- package/dist/metrics/Metrics.js +21 -1
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/metrics/scripts/metrics.d.ts +2 -0
- package/dist/metrics/scripts/metrics.d.ts.map +1 -1
- package/dist/metrics/scripts/metrics.js +12 -0
- package/dist/metrics/scripts/metrics.js.map +1 -1
- package/dist/monitor/Monitor.d.ts +8 -3
- package/dist/monitor/Monitor.d.ts.map +1 -1
- package/dist/monitor/Monitor.js +75 -15
- package/dist/monitor/Monitor.js.map +1 -1
- package/dist/strategy/BaseStrategy.d.ts +51 -5
- package/dist/strategy/BaseStrategy.d.ts.map +1 -1
- package/dist/strategy/BaseStrategy.js +199 -19
- package/dist/strategy/BaseStrategy.js.map +1 -1
- package/dist/strategy/CollateralDeficitStrategy.d.ts +65 -0
- package/dist/strategy/CollateralDeficitStrategy.d.ts.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.js +245 -0
- package/dist/strategy/CollateralDeficitStrategy.js.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.test.d.ts +2 -0
- package/dist/strategy/CollateralDeficitStrategy.test.d.ts.map +1 -0
- package/dist/strategy/CollateralDeficitStrategy.test.js +364 -0
- package/dist/strategy/CollateralDeficitStrategy.test.js.map +1 -0
- package/dist/strategy/CompositeStrategy.d.ts +18 -0
- package/dist/strategy/CompositeStrategy.d.ts.map +1 -0
- package/dist/strategy/CompositeStrategy.js +63 -0
- package/dist/strategy/CompositeStrategy.js.map +1 -0
- package/dist/strategy/CompositeStrategy.test.d.ts +2 -0
- package/dist/strategy/CompositeStrategy.test.d.ts.map +1 -0
- package/dist/strategy/CompositeStrategy.test.js +265 -0
- package/dist/strategy/CompositeStrategy.test.js.map +1 -0
- package/dist/strategy/MinAmountStrategy.d.ts +12 -5
- package/dist/strategy/MinAmountStrategy.d.ts.map +1 -1
- package/dist/strategy/MinAmountStrategy.js +23 -14
- package/dist/strategy/MinAmountStrategy.js.map +1 -1
- package/dist/strategy/MinAmountStrategy.test.js +88 -20
- package/dist/strategy/MinAmountStrategy.test.js.map +1 -1
- package/dist/strategy/StrategyFactory.d.ts +15 -6
- package/dist/strategy/StrategyFactory.d.ts.map +1 -1
- package/dist/strategy/StrategyFactory.js +48 -10
- package/dist/strategy/StrategyFactory.js.map +1 -1
- package/dist/strategy/StrategyFactory.test.js +2 -2
- package/dist/strategy/StrategyFactory.test.js.map +1 -1
- package/dist/strategy/WeightedStrategy.d.ts +13 -4
- package/dist/strategy/WeightedStrategy.d.ts.map +1 -1
- package/dist/strategy/WeightedStrategy.js +18 -6
- package/dist/strategy/WeightedStrategy.js.map +1 -1
- package/dist/strategy/WeightedStrategy.test.js +108 -18
- package/dist/strategy/WeightedStrategy.test.js.map +1 -1
- package/dist/strategy/index.d.ts +2 -0
- package/dist/strategy/index.d.ts.map +1 -1
- package/dist/strategy/index.js +2 -0
- package/dist/strategy/index.js.map +1 -1
- package/dist/test/helpers.d.ts +93 -3
- package/dist/test/helpers.d.ts.map +1 -1
- package/dist/test/helpers.js +267 -10
- package/dist/test/helpers.js.map +1 -1
- package/dist/tracking/ActionTracker.d.ts +49 -0
- package/dist/tracking/ActionTracker.d.ts.map +1 -0
- package/dist/tracking/ActionTracker.js +422 -0
- package/dist/tracking/ActionTracker.js.map +1 -0
- package/dist/tracking/ActionTracker.test.d.ts +2 -0
- package/dist/tracking/ActionTracker.test.d.ts.map +1 -0
- package/dist/tracking/ActionTracker.test.js +637 -0
- package/dist/tracking/ActionTracker.test.js.map +1 -0
- package/dist/tracking/IActionTracker.d.ts +101 -0
- package/dist/tracking/IActionTracker.d.ts.map +1 -0
- package/dist/tracking/IActionTracker.js +2 -0
- package/dist/tracking/IActionTracker.js.map +1 -0
- package/dist/tracking/InflightContextAdapter.d.ts +18 -0
- package/dist/tracking/InflightContextAdapter.d.ts.map +1 -0
- package/dist/tracking/InflightContextAdapter.js +35 -0
- package/dist/tracking/InflightContextAdapter.js.map +1 -0
- package/dist/tracking/InflightContextAdapter.test.d.ts +2 -0
- package/dist/tracking/InflightContextAdapter.test.d.ts.map +1 -0
- package/dist/tracking/InflightContextAdapter.test.js +172 -0
- package/dist/tracking/InflightContextAdapter.test.js.map +1 -0
- package/dist/tracking/index.d.ts +7 -0
- package/dist/tracking/index.d.ts.map +1 -0
- package/dist/tracking/index.js +6 -0
- package/dist/tracking/index.js.map +1 -0
- package/dist/tracking/store/IStore.d.ts +41 -0
- package/dist/tracking/store/IStore.d.ts.map +1 -0
- package/dist/tracking/store/IStore.js +2 -0
- package/dist/tracking/store/IStore.js.map +1 -0
- package/dist/tracking/store/InMemoryStore.d.ts +21 -0
- package/dist/tracking/store/InMemoryStore.d.ts.map +1 -0
- package/dist/tracking/store/InMemoryStore.js +40 -0
- package/dist/tracking/store/InMemoryStore.js.map +1 -0
- package/dist/tracking/store/InMemoryStore.test.d.ts +2 -0
- package/dist/tracking/store/InMemoryStore.test.d.ts.map +1 -0
- package/dist/tracking/store/InMemoryStore.test.js +290 -0
- package/dist/tracking/store/InMemoryStore.test.js.map +1 -0
- package/dist/tracking/store/index.d.ts +3 -0
- package/dist/tracking/store/index.d.ts.map +1 -0
- package/dist/tracking/store/index.js +2 -0
- package/dist/tracking/store/index.js.map +1 -0
- package/dist/tracking/types.d.ts +43 -0
- package/dist/tracking/types.d.ts.map +1 -0
- package/dist/tracking/types.js +2 -0
- package/dist/tracking/types.js.map +1 -0
- package/dist/utils/ExplorerClient.d.ts +39 -1
- package/dist/utils/ExplorerClient.d.ts.map +1 -1
- package/dist/utils/ExplorerClient.js +205 -2
- package/dist/utils/ExplorerClient.js.map +1 -1
- package/dist/utils/balanceUtils.js +2 -2
- package/dist/utils/balanceUtils.js.map +1 -1
- package/dist/utils/balanceUtils.test.js +1 -0
- package/dist/utils/balanceUtils.test.js.map +1 -1
- package/dist/utils/bridgeUtils.d.ts +1 -3
- package/dist/utils/bridgeUtils.d.ts.map +1 -1
- package/dist/utils/bridgeUtils.js +1 -5
- package/dist/utils/bridgeUtils.js.map +1 -1
- package/dist/utils/bridgeUtils.test.js +3 -14
- package/dist/utils/bridgeUtils.test.js.map +1 -1
- package/package.json +11 -9
- package/src/config/RebalancerConfig.test.ts +459 -163
- package/src/config/RebalancerConfig.ts +5 -3
- package/src/config/types.ts +159 -52
- package/src/core/Rebalancer.test.ts +632 -0
- package/src/core/Rebalancer.ts +247 -157
- package/src/core/RebalancerService.test.ts +1144 -0
- package/src/core/RebalancerService.ts +245 -23
- package/src/factories/RebalancerContextFactory.ts +115 -14
- package/src/index.ts +16 -4
- package/src/interfaces/IMonitor.ts +15 -8
- package/src/interfaces/IRebalancer.ts +22 -4
- package/src/interfaces/IStrategy.ts +23 -2
- package/src/metrics/Metrics.ts +26 -5
- package/src/metrics/scripts/metrics.ts +14 -0
- package/src/monitor/Monitor.ts +109 -22
- package/src/strategy/BaseStrategy.ts +316 -26
- package/src/strategy/CollateralDeficitStrategy.test.ts +551 -0
- package/src/strategy/CollateralDeficitStrategy.ts +390 -0
- package/src/strategy/CompositeStrategy.test.ts +405 -0
- package/src/strategy/CompositeStrategy.ts +102 -0
- package/src/strategy/MinAmountStrategy.test.ts +189 -88
- package/src/strategy/MinAmountStrategy.ts +44 -13
- package/src/strategy/StrategyFactory.test.ts +2 -2
- package/src/strategy/StrategyFactory.ts +91 -8
- package/src/strategy/WeightedStrategy.test.ts +187 -72
- package/src/strategy/WeightedStrategy.ts +41 -7
- package/src/strategy/index.ts +2 -0
- package/src/test/helpers.ts +418 -14
- package/src/tracking/ActionTracker.test.ts +783 -0
- package/src/tracking/ActionTracker.ts +647 -0
- package/src/tracking/IActionTracker.ts +140 -0
- package/src/tracking/InflightContextAdapter.test.ts +203 -0
- package/src/tracking/InflightContextAdapter.ts +42 -0
- package/src/tracking/index.ts +36 -0
- package/src/tracking/store/IStore.ts +48 -0
- package/src/tracking/store/InMemoryStore.test.ts +338 -0
- package/src/tracking/store/InMemoryStore.ts +58 -0
- package/src/tracking/store/index.ts +2 -0
- package/src/tracking/types.ts +74 -0
- package/src/utils/ExplorerClient.ts +266 -3
- package/src/utils/balanceUtils.test.ts +1 -0
- package/src/utils/balanceUtils.ts +2 -2
- package/src/utils/bridgeUtils.test.ts +3 -15
- package/src/utils/bridgeUtils.ts +0 -10
- package/dist/core/WithInflightGuard.d.ts +0 -20
- package/dist/core/WithInflightGuard.d.ts.map +0 -1
- package/dist/core/WithInflightGuard.js +0 -47
- package/dist/core/WithInflightGuard.js.map +0 -1
- package/dist/core/WithInflightGuard.test.d.ts +0 -2
- package/dist/core/WithInflightGuard.test.d.ts.map +0 -1
- package/dist/core/WithInflightGuard.test.js +0 -64
- package/dist/core/WithInflightGuard.test.js.map +0 -1
- package/dist/core/WithSemaphore.d.ts +0 -22
- package/dist/core/WithSemaphore.d.ts.map +0 -1
- package/dist/core/WithSemaphore.js +0 -67
- package/dist/core/WithSemaphore.js.map +0 -1
- package/dist/core/WithSemaphore.test.d.ts +0 -2
- package/dist/core/WithSemaphore.test.d.ts.map +0 -1
- package/dist/core/WithSemaphore.test.js +0 -83
- package/dist/core/WithSemaphore.test.js.map +0 -1
- package/src/core/WithInflightGuard.test.ts +0 -131
- package/src/core/WithInflightGuard.ts +0 -67
- package/src/core/WithSemaphore.test.ts +0 -111
- package/src/core/WithSemaphore.ts +0 -92
|
@@ -2,11 +2,37 @@ import type { Logger } from 'pino';
|
|
|
2
2
|
|
|
3
3
|
export type InflightRebalanceQueryParams = {
|
|
4
4
|
bridges: string[];
|
|
5
|
-
|
|
5
|
+
routersByDomain: Record<number, string>; // Domain ID → router address (derive routers and domains from this)
|
|
6
6
|
txSender: string;
|
|
7
7
|
limit?: number;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
export type UserTransferQueryParams = {
|
|
11
|
+
routersByDomain: Record<number, string>; // Domain ID → router address (derive routers and domains from this)
|
|
12
|
+
excludeTxSender: string; // Rebalancer address to exclude
|
|
13
|
+
limit?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type RebalanceActionQueryParams = {
|
|
17
|
+
bridges: string[]; // Bridge contract addresses
|
|
18
|
+
routersByDomain: Record<number, string>; // Domain ID → router address (derive routers and domains from this)
|
|
19
|
+
rebalancerAddress: string; // Only include rebalancer's txs
|
|
20
|
+
limit?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ExplorerMessage = {
|
|
24
|
+
msg_id: string;
|
|
25
|
+
origin_domain_id: number;
|
|
26
|
+
destination_domain_id: number;
|
|
27
|
+
sender: string;
|
|
28
|
+
recipient: string;
|
|
29
|
+
origin_tx_hash: string;
|
|
30
|
+
origin_tx_sender: string;
|
|
31
|
+
origin_tx_recipient: string;
|
|
32
|
+
is_delivered: boolean;
|
|
33
|
+
message_body: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
10
36
|
export class ExplorerClient {
|
|
11
37
|
constructor(private readonly baseUrl: string) {}
|
|
12
38
|
|
|
@@ -14,15 +40,43 @@ export class ExplorerClient {
|
|
|
14
40
|
return addr.replace(/^0x/i, '\\x').toLowerCase();
|
|
15
41
|
}
|
|
16
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Normalize all hex fields in Explorer response from PostgreSQL bytea format (\\x) to standard hex (0x)
|
|
45
|
+
*/
|
|
46
|
+
private normalizeExplorerMessage(msg: any): ExplorerMessage {
|
|
47
|
+
const normalizeHex = (hex: string): string => {
|
|
48
|
+
if (!hex) return hex;
|
|
49
|
+
return hex.startsWith('\\x') ? '0x' + hex.slice(2) : hex;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
msg_id: normalizeHex(msg.msg_id),
|
|
54
|
+
origin_domain_id: msg.origin_domain_id,
|
|
55
|
+
destination_domain_id: msg.destination_domain_id,
|
|
56
|
+
sender: normalizeHex(msg.sender),
|
|
57
|
+
recipient: normalizeHex(msg.recipient),
|
|
58
|
+
origin_tx_hash: normalizeHex(msg.origin_tx_hash),
|
|
59
|
+
origin_tx_sender: normalizeHex(msg.origin_tx_sender),
|
|
60
|
+
origin_tx_recipient: normalizeHex(msg.origin_tx_recipient),
|
|
61
|
+
is_delivered: msg.is_delivered,
|
|
62
|
+
message_body: normalizeHex(msg.message_body),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
17
66
|
async hasUndeliveredRebalance(
|
|
18
67
|
params: InflightRebalanceQueryParams,
|
|
19
68
|
logger: Logger,
|
|
20
69
|
): Promise<boolean> {
|
|
21
|
-
const { bridges,
|
|
70
|
+
const { bridges, routersByDomain, txSender, limit = 5 } = params;
|
|
71
|
+
|
|
72
|
+
// Derive routers and domains from routersByDomain
|
|
73
|
+
const routers = Object.values(routersByDomain);
|
|
74
|
+
const domains = Object.keys(routersByDomain).map(Number);
|
|
22
75
|
|
|
23
76
|
const variables = {
|
|
24
77
|
senders: bridges.map((a) => this.toBytea(a)),
|
|
25
78
|
recipients: bridges.map((a) => this.toBytea(a)),
|
|
79
|
+
originTxRecipients: routers.map((a) => this.toBytea(a)),
|
|
26
80
|
originDomains: domains,
|
|
27
81
|
destDomains: domains,
|
|
28
82
|
txSenders: [this.toBytea(txSender)],
|
|
@@ -35,6 +89,7 @@ export class ExplorerClient {
|
|
|
35
89
|
query InflightRebalancesForRoute(
|
|
36
90
|
$senders: [bytea!],
|
|
37
91
|
$recipients: [bytea!],
|
|
92
|
+
$originTxRecipients: [bytea!],
|
|
38
93
|
$originDomains: [Int!],
|
|
39
94
|
$destDomains: [Int!],
|
|
40
95
|
$txSenders: [bytea!],
|
|
@@ -46,6 +101,7 @@ export class ExplorerClient {
|
|
|
46
101
|
{ is_delivered: { _eq: false } },
|
|
47
102
|
{ sender: { _in: $senders } },
|
|
48
103
|
{ recipient: { _in: $recipients } },
|
|
104
|
+
{ origin_tx_recipient: { _in: $originTxRecipients } },
|
|
49
105
|
{ origin_domain_id: { _in: $originDomains } },
|
|
50
106
|
{ destination_domain_id: { _in: $destDomains } },
|
|
51
107
|
{ origin_tx_sender: { _in: $txSenders } }
|
|
@@ -61,7 +117,9 @@ export class ExplorerClient {
|
|
|
61
117
|
recipient
|
|
62
118
|
origin_tx_hash
|
|
63
119
|
origin_tx_sender
|
|
120
|
+
origin_tx_recipient
|
|
64
121
|
is_delivered
|
|
122
|
+
message_body
|
|
65
123
|
}
|
|
66
124
|
}`;
|
|
67
125
|
|
|
@@ -94,6 +152,211 @@ export class ExplorerClient {
|
|
|
94
152
|
|
|
95
153
|
logger.debug({ rows }, 'Explorer query rows');
|
|
96
154
|
|
|
97
|
-
|
|
155
|
+
// Post-query validation: verify each message's origin domain matches the expected router
|
|
156
|
+
const validatedRows = rows.filter((msg: any) => {
|
|
157
|
+
const expectedRouter = routersByDomain[msg.origin_domain_id];
|
|
158
|
+
if (!expectedRouter) return false;
|
|
159
|
+
const normalizedMsgRouter = msg.origin_tx_recipient?.startsWith('\\x')
|
|
160
|
+
? '0x' + msg.origin_tx_recipient.slice(2)
|
|
161
|
+
: msg.origin_tx_recipient;
|
|
162
|
+
return (
|
|
163
|
+
normalizedMsgRouter?.toLowerCase() === expectedRouter.toLowerCase()
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
logger.debug(
|
|
168
|
+
{ totalRows: rows.length, validatedRows: validatedRows.length },
|
|
169
|
+
'Post-query validation results',
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
return validatedRows.length > 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Query inflight user transfers from the Explorer.
|
|
177
|
+
* Returns transfers where sender/recipient are routers, excluding rebalancer's own transactions.
|
|
178
|
+
*/
|
|
179
|
+
async getInflightUserTransfers(
|
|
180
|
+
params: UserTransferQueryParams,
|
|
181
|
+
logger: Logger,
|
|
182
|
+
): Promise<ExplorerMessage[]> {
|
|
183
|
+
const { routersByDomain, excludeTxSender, limit = 100 } = params;
|
|
184
|
+
|
|
185
|
+
// Derive routers and domains from routersByDomain
|
|
186
|
+
const routers = Object.values(routersByDomain);
|
|
187
|
+
const domains = Object.keys(routersByDomain).map(Number);
|
|
188
|
+
|
|
189
|
+
const variables = {
|
|
190
|
+
senders: routers.map((a) => this.toBytea(a)),
|
|
191
|
+
recipients: routers.map((a) => this.toBytea(a)),
|
|
192
|
+
originDomains: domains,
|
|
193
|
+
destDomains: domains,
|
|
194
|
+
excludeTxSender: this.toBytea(excludeTxSender),
|
|
195
|
+
limit,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
logger.debug({ variables }, 'Explorer getInflightUserTransfers query');
|
|
199
|
+
|
|
200
|
+
const query = `
|
|
201
|
+
query InflightUserTransfers(
|
|
202
|
+
$senders: [bytea!],
|
|
203
|
+
$recipients: [bytea!],
|
|
204
|
+
$originDomains: [Int!],
|
|
205
|
+
$destDomains: [Int!],
|
|
206
|
+
$excludeTxSender: bytea!,
|
|
207
|
+
$limit: Int = 100
|
|
208
|
+
) {
|
|
209
|
+
message_view(
|
|
210
|
+
where: {
|
|
211
|
+
_and: [
|
|
212
|
+
{ is_delivered: { _eq: false } },
|
|
213
|
+
{ sender: { _in: $senders } },
|
|
214
|
+
{ recipient: { _in: $recipients } },
|
|
215
|
+
{ origin_domain_id: { _in: $originDomains } },
|
|
216
|
+
{ destination_domain_id: { _in: $destDomains } },
|
|
217
|
+
{ origin_tx_sender: { _neq: $excludeTxSender } }
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
order_by: { origin_tx_id: desc }
|
|
221
|
+
limit: $limit
|
|
222
|
+
) {
|
|
223
|
+
msg_id
|
|
224
|
+
origin_domain_id
|
|
225
|
+
destination_domain_id
|
|
226
|
+
sender
|
|
227
|
+
recipient
|
|
228
|
+
origin_tx_hash
|
|
229
|
+
origin_tx_sender
|
|
230
|
+
origin_tx_recipient
|
|
231
|
+
is_delivered
|
|
232
|
+
message_body
|
|
233
|
+
}
|
|
234
|
+
}`;
|
|
235
|
+
|
|
236
|
+
const res = await fetch(this.baseUrl, {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: { 'Content-Type': 'application/json' },
|
|
239
|
+
body: JSON.stringify({ query, variables }),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
logger.debug(
|
|
243
|
+
{ status: res.status },
|
|
244
|
+
'Explorer getInflightUserTransfers response',
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
if (!res.ok) {
|
|
248
|
+
let errorDetails: string;
|
|
249
|
+
try {
|
|
250
|
+
const errorJson = await res.json();
|
|
251
|
+
errorDetails = JSON.stringify(errorJson);
|
|
252
|
+
} catch (_e) {
|
|
253
|
+
try {
|
|
254
|
+
errorDetails = await res.text();
|
|
255
|
+
} catch (_textError) {
|
|
256
|
+
errorDetails = 'Unable to read response body';
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
throw new Error(`Explorer query failed: ${res.status} ${errorDetails}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const json = await res.json();
|
|
263
|
+
const messages = json?.data?.message_view ?? [];
|
|
264
|
+
return messages.map((msg: any) => this.normalizeExplorerMessage(msg));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Query inflight rebalance actions from the Explorer.
|
|
269
|
+
* Returns messages where sender/recipient are bridges, tx sender is the rebalancer,
|
|
270
|
+
* and origin_tx_recipient is one of this warp route's routers.
|
|
271
|
+
*/
|
|
272
|
+
async getInflightRebalanceActions(
|
|
273
|
+
params: RebalanceActionQueryParams,
|
|
274
|
+
logger: Logger,
|
|
275
|
+
): Promise<ExplorerMessage[]> {
|
|
276
|
+
const { bridges, routersByDomain, rebalancerAddress, limit = 100 } = params;
|
|
277
|
+
|
|
278
|
+
// Derive routers and domains from routersByDomain
|
|
279
|
+
const routers = Object.values(routersByDomain);
|
|
280
|
+
const domains = Object.keys(routersByDomain).map(Number);
|
|
281
|
+
|
|
282
|
+
const variables = {
|
|
283
|
+
senders: bridges.map((a) => this.toBytea(a)),
|
|
284
|
+
recipients: bridges.map((a) => this.toBytea(a)),
|
|
285
|
+
originTxRecipients: routers.map((a) => this.toBytea(a)),
|
|
286
|
+
originDomains: domains,
|
|
287
|
+
destDomains: domains,
|
|
288
|
+
txSender: this.toBytea(rebalancerAddress),
|
|
289
|
+
limit,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
logger.debug({ variables }, 'Explorer getInflightRebalanceActions query');
|
|
293
|
+
|
|
294
|
+
const query = `
|
|
295
|
+
query InflightRebalanceActions(
|
|
296
|
+
$senders: [bytea!],
|
|
297
|
+
$recipients: [bytea!],
|
|
298
|
+
$originTxRecipients: [bytea!],
|
|
299
|
+
$originDomains: [Int!],
|
|
300
|
+
$destDomains: [Int!],
|
|
301
|
+
$txSender: bytea!,
|
|
302
|
+
$limit: Int = 100
|
|
303
|
+
) {
|
|
304
|
+
message_view(
|
|
305
|
+
where: {
|
|
306
|
+
_and: [
|
|
307
|
+
{ is_delivered: { _eq: false } },
|
|
308
|
+
{ sender: { _in: $senders } },
|
|
309
|
+
{ recipient: { _in: $recipients } },
|
|
310
|
+
{ origin_tx_recipient: { _in: $originTxRecipients } },
|
|
311
|
+
{ origin_domain_id: { _in: $originDomains } },
|
|
312
|
+
{ destination_domain_id: { _in: $destDomains } },
|
|
313
|
+
{ origin_tx_sender: { _eq: $txSender } }
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
order_by: { origin_tx_id: desc }
|
|
317
|
+
limit: $limit
|
|
318
|
+
) {
|
|
319
|
+
msg_id
|
|
320
|
+
origin_domain_id
|
|
321
|
+
destination_domain_id
|
|
322
|
+
sender
|
|
323
|
+
recipient
|
|
324
|
+
origin_tx_hash
|
|
325
|
+
origin_tx_sender
|
|
326
|
+
origin_tx_recipient
|
|
327
|
+
is_delivered
|
|
328
|
+
message_body
|
|
329
|
+
}
|
|
330
|
+
}`;
|
|
331
|
+
|
|
332
|
+
const res = await fetch(this.baseUrl, {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: { 'Content-Type': 'application/json' },
|
|
335
|
+
body: JSON.stringify({ query, variables }),
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
logger.debug(
|
|
339
|
+
{ status: res.status },
|
|
340
|
+
'Explorer getInflightRebalanceActions response',
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
let errorDetails: string;
|
|
345
|
+
try {
|
|
346
|
+
const errorJson = await res.json();
|
|
347
|
+
errorDetails = JSON.stringify(errorJson);
|
|
348
|
+
} catch (_e) {
|
|
349
|
+
try {
|
|
350
|
+
errorDetails = await res.text();
|
|
351
|
+
} catch (_textError) {
|
|
352
|
+
errorDetails = 'Unable to read response body';
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
throw new Error(`Explorer query failed: ${res.status} ${errorDetails}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const json = await res.json();
|
|
359
|
+
const messages = json?.data?.message_view ?? [];
|
|
360
|
+
return messages.map((msg: any) => this.normalizeExplorerMessage(msg));
|
|
98
361
|
}
|
|
99
362
|
}
|
|
@@ -26,7 +26,7 @@ export function getRawBalances(
|
|
|
26
26
|
|
|
27
27
|
// Ignore tokens that are not in the provided chains list
|
|
28
28
|
if (!chainSet.has(token.chainName)) {
|
|
29
|
-
logger.
|
|
29
|
+
logger.debug(
|
|
30
30
|
{
|
|
31
31
|
context: getRawBalances.name,
|
|
32
32
|
chain: token.chainName,
|
|
@@ -40,7 +40,7 @@ export function getRawBalances(
|
|
|
40
40
|
|
|
41
41
|
// Ignore tokens that are not collateralized or are otherwise ineligible
|
|
42
42
|
if (!isCollateralizedTokenEligibleForRebalancing(token)) {
|
|
43
|
-
logger.
|
|
43
|
+
logger.debug(
|
|
44
44
|
{
|
|
45
45
|
context: getRawBalances.name,
|
|
46
46
|
chain: token.chainName,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { expect } from 'chai';
|
|
2
|
-
import { pino } from 'pino';
|
|
3
2
|
|
|
4
3
|
import { type ChainMap } from '@hyperlane-xyz/sdk';
|
|
5
4
|
|
|
@@ -8,29 +7,24 @@ import {
|
|
|
8
7
|
getBridgeConfig,
|
|
9
8
|
} from './bridgeUtils.js';
|
|
10
9
|
|
|
11
|
-
const testLogger = pino({ level: 'silent' });
|
|
12
|
-
|
|
13
10
|
describe('bridgeConfig', () => {
|
|
14
11
|
it('should return the base bridge config when no overrides exist', () => {
|
|
15
12
|
const bridges: ChainMap<BridgeConfigWithOverride> = {
|
|
16
13
|
chain1: {
|
|
17
14
|
bridge: '0x1234567890123456789012345678901234567890',
|
|
18
15
|
bridgeMinAcceptedAmount: 1000,
|
|
19
|
-
bridgeIsWarp: true,
|
|
20
16
|
},
|
|
21
17
|
chain2: {
|
|
22
18
|
bridge: '0x0987654321098765432109876543210987654321',
|
|
23
19
|
bridgeMinAcceptedAmount: 2000,
|
|
24
|
-
bridgeIsWarp: true,
|
|
25
20
|
},
|
|
26
21
|
};
|
|
27
22
|
|
|
28
|
-
const result = getBridgeConfig(bridges, 'chain1', 'chain2'
|
|
23
|
+
const result = getBridgeConfig(bridges, 'chain1', 'chain2');
|
|
29
24
|
|
|
30
25
|
expect(result).to.deep.equal({
|
|
31
26
|
bridge: '0x1234567890123456789012345678901234567890',
|
|
32
27
|
bridgeMinAcceptedAmount: 1000,
|
|
33
|
-
bridgeIsWarp: true,
|
|
34
28
|
});
|
|
35
29
|
});
|
|
36
30
|
|
|
@@ -39,7 +33,6 @@ describe('bridgeConfig', () => {
|
|
|
39
33
|
chain1: {
|
|
40
34
|
bridge: '0x1234567890123456789012345678901234567890',
|
|
41
35
|
bridgeMinAcceptedAmount: 1000,
|
|
42
|
-
bridgeIsWarp: true,
|
|
43
36
|
override: {
|
|
44
37
|
chain2: {
|
|
45
38
|
bridgeMinAcceptedAmount: 5000,
|
|
@@ -49,16 +42,14 @@ describe('bridgeConfig', () => {
|
|
|
49
42
|
chain2: {
|
|
50
43
|
bridge: '0x0987654321098765432109876543210987654321',
|
|
51
44
|
bridgeMinAcceptedAmount: 2000,
|
|
52
|
-
bridgeIsWarp: true,
|
|
53
45
|
},
|
|
54
46
|
};
|
|
55
47
|
|
|
56
|
-
const result = getBridgeConfig(bridges, 'chain1', 'chain2'
|
|
48
|
+
const result = getBridgeConfig(bridges, 'chain1', 'chain2');
|
|
57
49
|
|
|
58
50
|
expect(result).to.deep.equal({
|
|
59
51
|
bridge: '0x1234567890123456789012345678901234567890',
|
|
60
52
|
bridgeMinAcceptedAmount: 5000,
|
|
61
|
-
bridgeIsWarp: true,
|
|
62
53
|
});
|
|
63
54
|
});
|
|
64
55
|
|
|
@@ -67,7 +58,6 @@ describe('bridgeConfig', () => {
|
|
|
67
58
|
chain1: {
|
|
68
59
|
bridge: '0x1234567890123456789012345678901234567890',
|
|
69
60
|
bridgeMinAcceptedAmount: 1000,
|
|
70
|
-
bridgeIsWarp: true,
|
|
71
61
|
override: {
|
|
72
62
|
chain2: {
|
|
73
63
|
bridge: '0xABCDEF0123456789ABCDEF0123456789ABCDEF01',
|
|
@@ -77,16 +67,14 @@ describe('bridgeConfig', () => {
|
|
|
77
67
|
chain2: {
|
|
78
68
|
bridge: '0x0987654321098765432109876543210987654321',
|
|
79
69
|
bridgeMinAcceptedAmount: 2000,
|
|
80
|
-
bridgeIsWarp: true,
|
|
81
70
|
},
|
|
82
71
|
};
|
|
83
72
|
|
|
84
|
-
const result = getBridgeConfig(bridges, 'chain1', 'chain2'
|
|
73
|
+
const result = getBridgeConfig(bridges, 'chain1', 'chain2');
|
|
85
74
|
|
|
86
75
|
expect(result).to.deep.equal({
|
|
87
76
|
bridge: '0xABCDEF0123456789ABCDEF0123456789ABCDEF01',
|
|
88
77
|
bridgeMinAcceptedAmount: 1000,
|
|
89
|
-
bridgeIsWarp: true,
|
|
90
78
|
});
|
|
91
79
|
});
|
|
92
80
|
});
|
package/src/utils/bridgeUtils.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { type Logger } from 'pino';
|
|
2
|
-
|
|
3
1
|
import type { ChainMap, ChainName } from '@hyperlane-xyz/sdk';
|
|
4
2
|
|
|
5
3
|
export type BridgeConfigWithOverride = BridgeConfig & {
|
|
@@ -9,7 +7,6 @@ export type BridgeConfigWithOverride = BridgeConfig & {
|
|
|
9
7
|
export type BridgeConfig = {
|
|
10
8
|
bridge: string;
|
|
11
9
|
bridgeMinAcceptedAmount: string | number;
|
|
12
|
-
bridgeIsWarp: boolean;
|
|
13
10
|
};
|
|
14
11
|
|
|
15
12
|
/**
|
|
@@ -23,15 +20,8 @@ export function getBridgeConfig(
|
|
|
23
20
|
bridges: ChainMap<BridgeConfigWithOverride>,
|
|
24
21
|
fromChain: ChainName,
|
|
25
22
|
toChain: ChainName,
|
|
26
|
-
logger: Logger,
|
|
27
23
|
): BridgeConfig {
|
|
28
24
|
const fromConfig = bridges[fromChain];
|
|
29
|
-
|
|
30
|
-
if (!fromConfig) {
|
|
31
|
-
logger.error({ fromChain }, 'Bridge config not found');
|
|
32
|
-
throw new Error(`Bridge config not found for chain ${fromChain}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
25
|
const routeSpecificOverrides = fromConfig.override?.[toChain];
|
|
36
26
|
|
|
37
27
|
// Create a new object with the properties from bridgeConfig, excluding the overrides property
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { Logger } from 'pino';
|
|
2
|
-
import { type ChainMetadataManager } from '@hyperlane-xyz/sdk';
|
|
3
|
-
import { type RebalancerConfig } from '../config/RebalancerConfig.js';
|
|
4
|
-
import type { IRebalancer } from '../interfaces/IRebalancer.js';
|
|
5
|
-
import type { RebalancingRoute } from '../interfaces/IStrategy.js';
|
|
6
|
-
import { type ExplorerClient } from '../utils/ExplorerClient.js';
|
|
7
|
-
/**
|
|
8
|
-
* Prevents rebalancing if there are inflight rebalances for the warp route.
|
|
9
|
-
*/
|
|
10
|
-
export declare class WithInflightGuard implements IRebalancer {
|
|
11
|
-
private readonly config;
|
|
12
|
-
private readonly rebalancer;
|
|
13
|
-
private readonly explorer;
|
|
14
|
-
private readonly txSender;
|
|
15
|
-
private readonly chainManager;
|
|
16
|
-
private readonly logger;
|
|
17
|
-
constructor(config: RebalancerConfig, rebalancer: IRebalancer, explorer: ExplorerClient, txSender: string, chainManager: ChainMetadataManager, logger: Logger);
|
|
18
|
-
rebalance(routes: RebalancingRoute[]): Promise<void>;
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=WithInflightGuard.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WithInflightGuard.d.ts","sourceRoot":"","sources":["../../src/core/WithInflightGuard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;GAEG;AACH,qBAAa,iBAAkB,YAAW,WAAW;IAIjD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAP/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAGb,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,WAAW,EACvB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,oBAAoB,EACnD,MAAM,EAAE,MAAM;IAKV,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAwC3D"}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prevents rebalancing if there are inflight rebalances for the warp route.
|
|
3
|
-
*/
|
|
4
|
-
export class WithInflightGuard {
|
|
5
|
-
config;
|
|
6
|
-
rebalancer;
|
|
7
|
-
explorer;
|
|
8
|
-
txSender;
|
|
9
|
-
chainManager;
|
|
10
|
-
logger;
|
|
11
|
-
constructor(config, rebalancer, explorer, txSender, chainManager, logger) {
|
|
12
|
-
this.config = config;
|
|
13
|
-
this.rebalancer = rebalancer;
|
|
14
|
-
this.explorer = explorer;
|
|
15
|
-
this.txSender = txSender;
|
|
16
|
-
this.chainManager = chainManager;
|
|
17
|
-
this.logger = logger.child({ class: WithInflightGuard.name });
|
|
18
|
-
}
|
|
19
|
-
async rebalance(routes) {
|
|
20
|
-
// Always enforce the inflight guard
|
|
21
|
-
if (routes.length === 0) {
|
|
22
|
-
return this.rebalancer.rebalance(routes);
|
|
23
|
-
}
|
|
24
|
-
const chains = Object.keys(this.config.strategyConfig.chains);
|
|
25
|
-
const bridges = chains.map((chain) => this.config.strategyConfig.chains[chain].bridge);
|
|
26
|
-
const domains = chains.map((chain) => this.chainManager.getDomainId(chain));
|
|
27
|
-
let hasInflightRebalances = false;
|
|
28
|
-
try {
|
|
29
|
-
hasInflightRebalances = await this.explorer.hasUndeliveredRebalance({
|
|
30
|
-
bridges,
|
|
31
|
-
domains: Array.from(new Set(domains)),
|
|
32
|
-
txSender: this.txSender,
|
|
33
|
-
limit: 5,
|
|
34
|
-
}, this.logger);
|
|
35
|
-
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
this.logger.error({ status: e.status, body: e.body }, 'Explorer inflight query failed');
|
|
38
|
-
throw e;
|
|
39
|
-
}
|
|
40
|
-
if (hasInflightRebalances) {
|
|
41
|
-
this.logger.info('Inflight rebalance detected via Explorer; skipping this cycle');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
return this.rebalancer.rebalance(routes);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=WithInflightGuard.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WithInflightGuard.js","sourceRoot":"","sources":["../../src/core/WithInflightGuard.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,OAAO,iBAAiB;IAIT;IACA;IACA;IACA;IACA;IAPF,MAAM,CAAS;IAEhC,YACmB,MAAwB,EACxB,UAAuB,EACvB,QAAwB,EACxB,QAAgB,EAChB,YAAkC,EACnD,MAAc;QALG,WAAM,GAAN,MAAM,CAAkB;QACxB,eAAU,GAAV,UAAU,CAAa;QACvB,aAAQ,GAAR,QAAQ,CAAgB;QACxB,aAAQ,GAAR,QAAQ,CAAQ;QAChB,iBAAY,GAAZ,YAAY,CAAsB;QAGnD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAA0B;QACxC,oCAAoC;QACpC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CACxB,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAC3D,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAE5E,IAAI,qBAAqB,GAAG,KAAK,CAAC;QAClC,IAAI,CAAC;YACH,qBAAqB,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CACjE;gBACE,OAAO;gBACP,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;gBACrC,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,KAAK,EAAE,CAAC;aACT,EACD,IAAI,CAAC,MAAM,CACZ,CAAC;QACJ,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAClC,gCAAgC,CACjC,CAAC;YACF,MAAM,CAAC,CAAC;QACV,CAAC;QAED,IAAI,qBAAqB,EAAE,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,+DAA+D,CAChE,CAAC;YACF,OAAO;QACT,CAAC;QAED,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WithInflightGuard.test.d.ts","sourceRoot":"","sources":["../../src/core/WithInflightGuard.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import chai, { expect } from 'chai';
|
|
2
|
-
import chaiAsPromised from 'chai-as-promised';
|
|
3
|
-
import { ethers } from 'ethers';
|
|
4
|
-
import { pino } from 'pino';
|
|
5
|
-
import Sinon from 'sinon';
|
|
6
|
-
import { chainMetadata } from '@hyperlane-xyz/registry';
|
|
7
|
-
import { ChainMetadataManager } from '@hyperlane-xyz/sdk';
|
|
8
|
-
import { MockRebalancer, buildTestConfig } from '../test/helpers.js';
|
|
9
|
-
import { ExplorerClient } from '../utils/ExplorerClient.js';
|
|
10
|
-
import { WithInflightGuard } from './WithInflightGuard.js';
|
|
11
|
-
chai.use(chaiAsPromised);
|
|
12
|
-
const testLogger = pino({ level: 'silent' });
|
|
13
|
-
describe('WithInflightGuard', () => {
|
|
14
|
-
it('forwards empty routes without calling Explorer', async () => {
|
|
15
|
-
const config = buildTestConfig();
|
|
16
|
-
const rebalancer = new MockRebalancer();
|
|
17
|
-
const rebalanceSpy = Sinon.spy(rebalancer, 'rebalance');
|
|
18
|
-
const explorer = new ExplorerClient('http://localhost');
|
|
19
|
-
const explorerSpy = Sinon.stub(explorer, 'hasUndeliveredRebalance');
|
|
20
|
-
const guard = new WithInflightGuard(config, rebalancer, explorer, ethers.Wallet.createRandom().address, new ChainMetadataManager(chainMetadata), testLogger);
|
|
21
|
-
await guard.rebalance([]);
|
|
22
|
-
expect(explorerSpy.called).to.be.false;
|
|
23
|
-
expect(rebalanceSpy.calledOnce).to.be.true;
|
|
24
|
-
expect(rebalanceSpy.calledWith([])).to.be.true;
|
|
25
|
-
});
|
|
26
|
-
it('calls underlying rebalancer when no inflight is detected', async () => {
|
|
27
|
-
const config = buildTestConfig({}, ['ethereum', 'arbitrum']);
|
|
28
|
-
const routes = [{ origin: 'ethereum' }];
|
|
29
|
-
const rebalancer = new MockRebalancer();
|
|
30
|
-
const rebalanceSpy = Sinon.spy(rebalancer, 'rebalance');
|
|
31
|
-
const explorer = new ExplorerClient('http://localhost');
|
|
32
|
-
const explorerSpy = Sinon.stub(explorer, 'hasUndeliveredRebalance').resolves(false);
|
|
33
|
-
const guard = new WithInflightGuard(config, rebalancer, explorer, ethers.Wallet.createRandom().address, new ChainMetadataManager(chainMetadata), testLogger);
|
|
34
|
-
await guard.rebalance(routes);
|
|
35
|
-
expect(explorerSpy.calledOnce).to.be.true;
|
|
36
|
-
expect(rebalanceSpy.calledOnce).to.be.true;
|
|
37
|
-
expect(rebalanceSpy.calledWith(routes)).to.be.true;
|
|
38
|
-
});
|
|
39
|
-
it('skips rebalancing when inflight is detected', async () => {
|
|
40
|
-
const config = buildTestConfig({}, ['ethereum', 'arbitrum']);
|
|
41
|
-
const routes = [{ origin: 'ethereum' }];
|
|
42
|
-
const rebalancer = new MockRebalancer();
|
|
43
|
-
const rebalanceSpy = Sinon.spy(rebalancer, 'rebalance');
|
|
44
|
-
const explorer = new ExplorerClient('http://localhost');
|
|
45
|
-
const explorerSpy = Sinon.stub(explorer, 'hasUndeliveredRebalance').resolves(true);
|
|
46
|
-
const guard = new WithInflightGuard(config, rebalancer, explorer, ethers.Wallet.createRandom().address, new ChainMetadataManager(chainMetadata), testLogger);
|
|
47
|
-
await guard.rebalance(routes);
|
|
48
|
-
expect(explorerSpy.calledOnce).to.be.true;
|
|
49
|
-
expect(rebalanceSpy.called).to.be.false;
|
|
50
|
-
});
|
|
51
|
-
it('propagates explorer query error', async () => {
|
|
52
|
-
const config = buildTestConfig({}, ['ethereum', 'arbitrum']);
|
|
53
|
-
const routes = [{ origin: 'ethereum' }];
|
|
54
|
-
const rebalancer = new MockRebalancer();
|
|
55
|
-
const rebalanceSpy = Sinon.spy(rebalancer, 'rebalance');
|
|
56
|
-
const explorer = new ExplorerClient('http://localhost');
|
|
57
|
-
const explorerSpy = Sinon.stub(explorer, 'hasUndeliveredRebalance').rejects(new Error('Explorer HTTP 405'));
|
|
58
|
-
const guard = new WithInflightGuard(config, rebalancer, explorer, ethers.Wallet.createRandom().address, new ChainMetadataManager(chainMetadata), testLogger);
|
|
59
|
-
await expect(guard.rebalance(routes)).to.be.rejectedWith('Explorer HTTP 405');
|
|
60
|
-
expect(explorerSpy.calledOnce).to.be.true;
|
|
61
|
-
expect(rebalanceSpy.called).to.be.false;
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
//# sourceMappingURL=WithInflightGuard.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WithInflightGuard.test.js","sourceRoot":"","sources":["../../src/core/WithInflightGuard.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { Logger } from 'pino';
|
|
2
|
-
import { type RebalancerConfig } from '../config/RebalancerConfig.js';
|
|
3
|
-
import type { IRebalancer } from '../interfaces/IRebalancer.js';
|
|
4
|
-
import type { RebalancingRoute } from '../interfaces/IStrategy.js';
|
|
5
|
-
/**
|
|
6
|
-
* Prevents frequent rebalancing operations while bridges complete.
|
|
7
|
-
*/
|
|
8
|
-
export declare class WithSemaphore implements IRebalancer {
|
|
9
|
-
private readonly config;
|
|
10
|
-
private readonly rebalancer;
|
|
11
|
-
private waitUntil;
|
|
12
|
-
private executing;
|
|
13
|
-
private readonly logger;
|
|
14
|
-
constructor(config: RebalancerConfig, rebalancer: IRebalancer, logger: Logger);
|
|
15
|
-
/**
|
|
16
|
-
* Rebalance with timing control
|
|
17
|
-
* @param routes - Routes to process
|
|
18
|
-
*/
|
|
19
|
-
rebalance(routes: RebalancingRoute[]): Promise<void>;
|
|
20
|
-
private getHighestLockTime;
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=WithSemaphore.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WithSemaphore.d.ts","sourceRoot":"","sources":["../../src/core/WithSemaphore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE;;GAEG;AACH,qBAAa,aAAc,YAAW,WAAW;IAQ7C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAP7B,OAAO,CAAC,SAAS,CAAa;IAE9B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAGb,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,WAAW,EACxC,MAAM,EAAE,MAAM;IAKhB;;;OAGG;IACG,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA+C1D,OAAO,CAAC,kBAAkB;CAgB3B"}
|