Skip to content

Commit 154e90c

Browse files
authored
ADD: getSigningKeysWithQueueInfo (#2148)
* ADD: getSigningKeysWithQueueInfo * FIX: BigInt
1 parent 20e443b commit 154e90c

File tree

4 files changed

+181
-3
lines changed

4 files changed

+181
-3
lines changed

launcher/.eslintrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ module.exports = {
1212
"vue/no-v-for-template-key": "off",
1313
"no-empty": ["error", { allowEmptyCatch: true }],
1414
},
15+
globals: {
16+
BigInt: true,
17+
},
1518
};

launcher/src/backend/web3/CSM.js

Lines changed: 169 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,170 @@ async function getSigningKeys(contract, nodeOperatorId, startIndex, keysCount) {
144144
}
145145
}
146146

147+
async function getDepositQueue(contract) {
148+
try {
149+
if (!contract) {
150+
throw new Error("Contract is not initialized.");
151+
}
152+
const queueInfo = await contract.methods.depositQueue().call();
153+
154+
const head = Number(queueInfo[0]);
155+
const tail = Number(queueInfo[1]);
156+
157+
return { head, tail };
158+
} catch (error) {
159+
log.error("Error calling getDepositQueue:", error);
160+
return null;
161+
}
162+
}
163+
164+
async function getDepositQueueItem(contract, index) {
165+
try {
166+
if (!contract) {
167+
throw new Error("Contract is not initialized.");
168+
}
169+
// Fetch the batch item from the contract
170+
const batchItem = await contract.methods.depositQueueItem(index).call();
171+
172+
// Function to extract batch information
173+
const extractBatchInfo = (batch) => ({
174+
nodeId: (batch >> 192n) & ((1n << 64n) - 1n), // Extract the 64 bits for nodeId
175+
keysCount: (batch >> 128n) & ((1n << 64n) - 1n), // Extract the next 64 bits for keysCount
176+
nextBatch: batch & ((1n << 128n) - 1n), // Extract the lower 128 bits for nextBatch
177+
});
178+
179+
// Extract details from the batch item
180+
const { nodeId, keysCount, nextBatch } = extractBatchInfo(BigInt(batchItem));
181+
182+
// Return extracted information
183+
return { nodeId, keysCount, nextBatch };
184+
} catch (error) {
185+
log.error("Error calling getDepositQueueItem:", error);
186+
return null;
187+
}
188+
}
189+
190+
/**
191+
* Retrieves signing keys with deposit queue information for a given Node Operator.
192+
*
193+
* @async
194+
* @function getSigningKeysWithQueueInfo
195+
* @param {Object} monitoring - Monitoring Object
196+
* @returns {Promise<Array<{key: string, queuePosition: bigint}>> | Promise<null>}
197+
* Returns an array of signing keys with queue positions or null on failure.
198+
* @throws {Error} Logs and returns `null` if:
199+
* - The RPC tunnel could not be opened.
200+
* - The contract or other necessary data could not be retrieved.
201+
* - Any other unexpected error occurs.
202+
*/
203+
async function getSigningKeysWithQueueInfo(monitoring) {
204+
try {
205+
// Open RPC tunnel
206+
log.info("Opening RPC tunnel...");
207+
await monitoring.openRpcTunnel();
208+
209+
// look up Node Operator ID
210+
log.info("Get Node Operator ID from LCOM");
211+
const lcomServices = await monitoring.getServiceInfos("LCOMService");
212+
if (lcomServices.length < 1) {
213+
throw new Error("LCOM service not found");
214+
}
215+
const nodeOperatorId = lcomServices.find((s) => s.config.env.NO_ID).config.env.NO_ID;
216+
if (!nodeOperatorId) {
217+
throw new Error("Node Operator ID not found in LCOM Config");
218+
}
219+
log.info("Node Operator ID:", nodeOperatorId);
220+
221+
// Initialize the contract
222+
const contract = await getContract();
223+
if (!contract) {
224+
log.error("Failed to initialize contract.");
225+
return null;
226+
}
227+
228+
// Check if the node is in sync
229+
const isSynced = await getSyncStatus();
230+
if (!isSynced) {
231+
log.info("Node is currently syncing...");
232+
return null;
233+
}
234+
235+
// Check if the Node Operator is active
236+
const isActive = await isNodeOperatorActive(contract, nodeOperatorId);
237+
if (!isActive) {
238+
log.info("Node Operator is not active.");
239+
return null;
240+
}
241+
242+
// Retrieve enqueued count
243+
const enqueuedCount = await getNodeOperatorInfo(contract, nodeOperatorId);
244+
if (enqueuedCount === null || enqueuedCount <= 0) {
245+
log.info("No enqueued validators for this Node Operator.");
246+
}
247+
248+
// Retrieve the number of non-withdrawn keys
249+
const numberOfNoneWithdrawnKeys = await getNoneWithdrawnKeys(contract, nodeOperatorId);
250+
if (numberOfNoneWithdrawnKeys === null || numberOfNoneWithdrawnKeys <= 0) {
251+
log.info("No non-withdrawn keys available.");
252+
return null;
253+
}
254+
255+
// Retrieve the signing keys
256+
const signingKeys = await getSigningKeys(contract, nodeOperatorId, 0, numberOfNoneWithdrawnKeys);
257+
if (!signingKeys) {
258+
log.info("Failed to retrieve signing keys.");
259+
return null;
260+
}
261+
262+
// Initialize queue data
263+
const queueData = await getDepositQueue(contract);
264+
if (!queueData) {
265+
log.error("Failed to retrieve deposit queue data.");
266+
return null;
267+
}
268+
const { head, tail } = queueData;
269+
270+
// Prepare results
271+
const signingKeysWithQueueInfo = signingKeys.map((key) => ({ key, queuePosition: 0 })); // Default queuePosition to 0
272+
273+
if (enqueuedCount > 0) {
274+
let remainingKeysToMark = Number(enqueuedCount);
275+
276+
// Traverse the deposit queue from tail to head
277+
for (let index = tail; index >= head && remainingKeysToMark > 0; index--) {
278+
const queueItem = await getDepositQueueItem(contract, index);
279+
280+
if (queueItem.nodeId === BigInt(nodeOperatorId)) {
281+
const keysCountInQueue = Number(queueItem.keysCount);
282+
283+
// Calculate queue position
284+
const queuePosition = queueItem.nextBatch - BigInt(head);
285+
286+
// Determine range of keys to mark in reverse order
287+
const startIndex = signingKeysWithQueueInfo.length - remainingKeysToMark;
288+
const endIndex = startIndex + keysCountInQueue;
289+
290+
for (let i = endIndex - 1; i >= startIndex; i--) {
291+
signingKeysWithQueueInfo[i].queuePosition = queuePosition;
292+
}
293+
294+
// Reduce the remaining keys to mark
295+
remainingKeysToMark -= keysCountInQueue;
296+
}
297+
}
298+
}
299+
300+
// Return signing keys with queue information
301+
return signingKeysWithQueueInfo;
302+
} catch (error) {
303+
log.error("Error in getSigningKeysWithQueueInfo:", error);
304+
return null;
305+
} finally {
306+
log.info("Closing RPC tunnel...");
307+
await monitoring.closeRpcTunnel();
308+
}
309+
}
310+
147311
/**
148312
* Checks for matching signing keys of a specified Node Operator.
149313
*
@@ -154,7 +318,7 @@ async function getSigningKeys(contract, nodeOperatorId, startIndex, keysCount) {
154318
*
155319
* @async
156320
* @function checkSigningKeys
157-
* @param {number} nodeOperatorId - The ID of the Node Operator whose signing keys are to be checked.
321+
* @param {Object} monitoring - Monitoring Object.
158322
* @param {string[]} keysArray - An array of signing keys in hexadecimal format to be matched against the Node Operator's keys.
159323
* @returns {Promise<string[]|boolean|null>} A promise that resolves to:
160324
* - An array of matching signing keys (in hexadecimal format) if any matches are found.
@@ -254,4 +418,7 @@ async function checkSigningKeys(keysArray, monitoring) {
254418
}
255419
}
256420

257-
export default checkSigningKeys;
421+
export default {
422+
checkSigningKeys,
423+
getSigningKeysWithQueueInfo,
424+
};

launcher/src/background.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ProtocolHandler } from "./backend/CustomUrlProtocol.js";
1818
import path from "path";
1919
import { readFileSync, existsSync, mkdirSync, renameSync, readdir, rmSync } from "fs";
2020
import url from "url";
21-
import checkSigningKeys from "./backend/web3/CSM.js";
21+
import { checkSigningKeys, getSigningKeysWithQueueInfo } from "./backend/web3/CSM.js";
2222
const isDevelopment = process.env.NODE_ENV !== "production";
2323
const nodeConnection = new NodeConnection();
2424
const storageService = new StorageService();
@@ -848,6 +848,10 @@ ipcMain.handle("getCSMQueue", async (event, args) => {
848848
return await checkSigningKeys(args.keysArray, monitoring);
849849
});
850850

851+
ipcMain.handle("getSigningKeysWithQueueInfo", async () => {
852+
return await getSigningKeysWithQueueInfo(monitoring);
853+
});
854+
851855
ipcMain.handle("getObolClusterInformation", async (event, args) => {
852856
return await monitoring.getObolClusterInformation(args.serviceID);
853857
});

launcher/src/store/ControlService.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,10 @@ class ControlService extends EventEmitter {
748748
return this.promiseIpc.send("getCSMQueue", { keysArray });
749749
}
750750

751+
async getSigningKeysWithQueueInfo() {
752+
return this.promiseIpc.send("getSigningKeysWithQueueInfo");
753+
}
754+
751755
async getObolClusterInformation(serviceID) {
752756
return this.promiseIpc.send("getObolClusterInformation", { serviceID });
753757
}

0 commit comments

Comments
 (0)