|
5 | 5 | BundleAction, |
6 | 6 | DepositWithBlock, |
7 | 7 | FillStatus, |
| 8 | + FillWithBlock, |
8 | 9 | L1Token, |
9 | 10 | RelayerBalanceReport, |
10 | 11 | RelayerBalanceTable, |
@@ -55,12 +56,21 @@ import { |
55 | 56 | getBinanceApiClient, |
56 | 57 | getBinanceWithdrawalLimits, |
57 | 58 | chainIsEvm, |
| 59 | + getFillStatusPda, |
| 60 | + getKitKeypairFromEvmSigner, |
| 61 | + getRelayDataFromFill, |
58 | 62 | } from "../utils"; |
59 | 63 | import { MonitorClients, updateMonitorClients } from "./MonitorClientHelper"; |
60 | 64 | import { MonitorConfig } from "./MonitorConfig"; |
61 | 65 | import { CombinedRefunds, getImpliedBundleBlockRanges } from "../dataworker/DataworkerUtils"; |
62 | 66 | import { PUBLIC_NETWORKS, TOKEN_EQUIVALENCE_REMAPPING } from "@across-protocol/constants"; |
63 | 67 | import { utils as sdkUtils, arch } from "@across-protocol/sdk"; |
| 68 | +import { |
| 69 | + address, |
| 70 | + fetchEncodedAccount, |
| 71 | + getBase64EncodedWireTransaction, |
| 72 | + signTransactionMessageWithSigners, |
| 73 | +} from "@solana/kit"; |
64 | 74 |
|
65 | 75 | // 60 minutes, which is the length of the challenge window, so if a rebalance takes longer than this to finalize, |
66 | 76 | // then its finalizing after the subsequent challenge period has started, which is sub-optimal. |
@@ -481,6 +491,11 @@ export class Monitor { |
481 | 491 | const l1Tokens = this.getL1TokensForRelayerBalancesReport(); |
482 | 492 | for (const relayer of this.monitorConfig.monitoredRelayers) { |
483 | 493 | for (const chainId of this.monitorChains) { |
| 494 | + // If the monitored relayer address is invalid on the monitored chain (e.g. the monitored relayer is a base58 address while the chain ID is mainnet), |
| 495 | + // then there is no balance to update in this loop. |
| 496 | + if (!relayer.isValidOn(chainId)) { |
| 497 | + continue; |
| 498 | + } |
484 | 499 | const l2ToL1Tokens = this.getL2ToL1TokenMap(l1Tokens, chainId); |
485 | 500 | const l2TokenAddresses = Object.keys(l2ToL1Tokens); |
486 | 501 | const tokenBalances = await this._getBalances( |
@@ -1183,6 +1198,90 @@ export class Monitor { |
1183 | 1198 | } |
1184 | 1199 | } |
1185 | 1200 |
|
| 1201 | + async closePDAs(): Promise<void> { |
| 1202 | + const simulate = process.env["SEND_TRANSACTIONS"] !== "true"; |
| 1203 | + const svmSpokePoolClient = this.clients.spokePoolClients[CHAIN_IDs.SOLANA]; |
| 1204 | + if (!isSVMSpokePoolClient(svmSpokePoolClient)) { |
| 1205 | + return; |
| 1206 | + } |
| 1207 | + const fills: FillWithBlock[] = []; |
| 1208 | + for (const relayers of this.monitorConfig.monitoredRelayers) { |
| 1209 | + if (relayers.isSVM()) { |
| 1210 | + const relayerFills = svmSpokePoolClient.getFillsForRelayer(relayers); |
| 1211 | + fills.push(...relayerFills); |
| 1212 | + } |
| 1213 | + } |
| 1214 | + const spokePoolProgramId = address(svmSpokePoolClient.spokePoolAddress.toBase58()); |
| 1215 | + const signer = await getKitKeypairFromEvmSigner(this.clients.hubPoolClient.hubPool.signer); |
| 1216 | + const svmRpc = svmSpokePoolClient.svmEventsClient.getRpc(); |
| 1217 | + |
| 1218 | + for (const fill of fills) { |
| 1219 | + const relayData = getRelayDataFromFill(fill); |
| 1220 | + const relayDataWithMessageHash = { |
| 1221 | + ...relayData, |
| 1222 | + messageHash: fill.messageHash, |
| 1223 | + }; |
| 1224 | + const fillStatus = await svmSpokePoolClient.relayFillStatus(relayDataWithMessageHash, fill.destinationChainId); |
| 1225 | + // If fill PDA should not be closed, skip. |
| 1226 | + if (!this._shouldCloseFillPDA(fillStatus, fill.fillDeadline, svmSpokePoolClient.getCurrentTime())) { |
| 1227 | + this.logger.info({ |
| 1228 | + at: "Monitor#closePDAs", |
| 1229 | + message: `Not ready to close PDA for fill ${fill.txnRef}`, |
| 1230 | + fill, |
| 1231 | + }); |
| 1232 | + continue; |
| 1233 | + } |
| 1234 | + |
| 1235 | + const fillStatusPda = await getFillStatusPda( |
| 1236 | + spokePoolProgramId, |
| 1237 | + relayDataWithMessageHash, |
| 1238 | + fill.destinationChainId |
| 1239 | + ); |
| 1240 | + // Check if PDA is already closed |
| 1241 | + const fillStatusPdaAccount = await fetchEncodedAccount(svmRpc, fillStatusPda); |
| 1242 | + if (!fillStatusPdaAccount.exists) { |
| 1243 | + continue; |
| 1244 | + } |
| 1245 | + |
| 1246 | + const closePdaInstruction = await arch.svm.createCloseFillPdaInstruction(signer, svmRpc, fillStatusPda); |
| 1247 | + const signedTransaction = await signTransactionMessageWithSigners(closePdaInstruction); |
| 1248 | + const encodedTransaction = getBase64EncodedWireTransaction(signedTransaction); |
| 1249 | + |
| 1250 | + if (simulate) { |
| 1251 | + const result = await svmRpc |
| 1252 | + .simulateTransaction(encodedTransaction, { |
| 1253 | + encoding: "base64", |
| 1254 | + }) |
| 1255 | + .send(); |
| 1256 | + if (result.value.err) { |
| 1257 | + this.logger.error({ |
| 1258 | + at: "Monitor#closePDAs", |
| 1259 | + message: `Failed to close PDA for fill ${fill.txnRef}`, |
| 1260 | + error: result.value.err, |
| 1261 | + }); |
| 1262 | + } |
| 1263 | + continue; |
| 1264 | + } |
| 1265 | + |
| 1266 | + try { |
| 1267 | + await svmRpc |
| 1268 | + .sendTransaction(encodedTransaction, { preflightCommitment: "confirmed", encoding: "base64" }) |
| 1269 | + .send(); |
| 1270 | + |
| 1271 | + this.logger.info({ |
| 1272 | + at: "Monitor#closePDAs", |
| 1273 | + message: `Closed PDA ${fillStatusPda} for fill ${fill.txnRef}`, |
| 1274 | + }); |
| 1275 | + } catch (err) { |
| 1276 | + this.logger.error({ |
| 1277 | + at: "Monitor#closePDAs", |
| 1278 | + message: `Failed to close PDA for fill ${fill.txnRef}`, |
| 1279 | + error: err, |
| 1280 | + }); |
| 1281 | + } |
| 1282 | + } |
| 1283 | + } |
| 1284 | + |
1186 | 1285 | async updateLatestAndFutureRelayerRefunds(relayerBalanceReport: RelayerBalanceReport): Promise<void> { |
1187 | 1286 | const validatedBundleRefunds: CombinedRefunds[] = |
1188 | 1287 | await this.clients.bundleDataClient.getPendingRefundsFromValidBundles(); |
@@ -1384,7 +1483,11 @@ export class Monitor { |
1384 | 1483 | this.spokePoolsBlocks[chainId].endingBlock = endingBlock; |
1385 | 1484 | } else if (isSVMSpokePoolClient(spokePoolClient)) { |
1386 | 1485 | const svmProvider = await spokePoolClient.svmEventsClient.getRpc(); |
1387 | | - const { slot: latestSlot } = await arch.svm.getNearestSlotTime(svmProvider, spokePoolClient.logger); |
| 1486 | + const { slot: latestSlot } = await arch.svm.getNearestSlotTime( |
| 1487 | + svmProvider, |
| 1488 | + { commitment: "confirmed" }, |
| 1489 | + spokePoolClient.logger |
| 1490 | + ); |
1388 | 1491 | const endingBlock = this.monitorConfig.spokePoolsBlocks[chainId]?.endingBlock; |
1389 | 1492 | this.monitorConfig.spokePoolsBlocks[chainId] ??= { startingBlock: undefined, endingBlock: undefined }; |
1390 | 1493 | if (this.monitorConfig.pollingDelay === 0) { |
@@ -1497,4 +1600,8 @@ export class Monitor { |
1497 | 1600 | } |
1498 | 1601 | return false; |
1499 | 1602 | } |
| 1603 | + |
| 1604 | + private _shouldCloseFillPDA(fillStatus: FillStatus, fillDeadline: number, currentTime: number): boolean { |
| 1605 | + return fillStatus === FillStatus.Filled && currentTime > fillDeadline; |
| 1606 | + } |
1500 | 1607 | } |
0 commit comments