Skip to content

Commit

Permalink
ADD: getSigningKeysWithQueueInfo (#2148)
Browse files Browse the repository at this point in the history
* ADD: getSigningKeysWithQueueInfo

* FIX: BigInt
  • Loading branch information
M4r71nW authored Dec 17, 2024
1 parent 20e443b commit 154e90c
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 3 deletions.
3 changes: 3 additions & 0 deletions launcher/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ module.exports = {
"vue/no-v-for-template-key": "off",
"no-empty": ["error", { allowEmptyCatch: true }],
},
globals: {
BigInt: true,
},
};
171 changes: 169 additions & 2 deletions launcher/src/backend/web3/CSM.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,170 @@ async function getSigningKeys(contract, nodeOperatorId, startIndex, keysCount) {
}
}

async function getDepositQueue(contract) {
try {
if (!contract) {
throw new Error("Contract is not initialized.");
}
const queueInfo = await contract.methods.depositQueue().call();

const head = Number(queueInfo[0]);
const tail = Number(queueInfo[1]);

return { head, tail };
} catch (error) {
log.error("Error calling getDepositQueue:", error);
return null;
}
}

async function getDepositQueueItem(contract, index) {
try {
if (!contract) {
throw new Error("Contract is not initialized.");
}
// Fetch the batch item from the contract
const batchItem = await contract.methods.depositQueueItem(index).call();

// Function to extract batch information
const extractBatchInfo = (batch) => ({
nodeId: (batch >> 192n) & ((1n << 64n) - 1n), // Extract the 64 bits for nodeId
keysCount: (batch >> 128n) & ((1n << 64n) - 1n), // Extract the next 64 bits for keysCount
nextBatch: batch & ((1n << 128n) - 1n), // Extract the lower 128 bits for nextBatch
});

// Extract details from the batch item
const { nodeId, keysCount, nextBatch } = extractBatchInfo(BigInt(batchItem));

// Return extracted information
return { nodeId, keysCount, nextBatch };
} catch (error) {
log.error("Error calling getDepositQueueItem:", error);
return null;
}
}

/**
* Retrieves signing keys with deposit queue information for a given Node Operator.
*
* @async
* @function getSigningKeysWithQueueInfo
* @param {Object} monitoring - Monitoring Object
* @returns {Promise<Array<{key: string, queuePosition: bigint}>> | Promise<null>}
* Returns an array of signing keys with queue positions or null on failure.
* @throws {Error} Logs and returns `null` if:
* - The RPC tunnel could not be opened.
* - The contract or other necessary data could not be retrieved.
* - Any other unexpected error occurs.
*/
async function getSigningKeysWithQueueInfo(monitoring) {
try {
// Open RPC tunnel
log.info("Opening RPC tunnel...");
await monitoring.openRpcTunnel();

// look up Node Operator ID
log.info("Get Node Operator ID from LCOM");
const lcomServices = await monitoring.getServiceInfos("LCOMService");
if (lcomServices.length < 1) {
throw new Error("LCOM service not found");
}
const nodeOperatorId = lcomServices.find((s) => s.config.env.NO_ID).config.env.NO_ID;
if (!nodeOperatorId) {
throw new Error("Node Operator ID not found in LCOM Config");
}
log.info("Node Operator ID:", nodeOperatorId);

// Initialize the contract
const contract = await getContract();
if (!contract) {
log.error("Failed to initialize contract.");
return null;
}

// Check if the node is in sync
const isSynced = await getSyncStatus();
if (!isSynced) {
log.info("Node is currently syncing...");
return null;
}

// Check if the Node Operator is active
const isActive = await isNodeOperatorActive(contract, nodeOperatorId);
if (!isActive) {
log.info("Node Operator is not active.");
return null;
}

// Retrieve enqueued count
const enqueuedCount = await getNodeOperatorInfo(contract, nodeOperatorId);
if (enqueuedCount === null || enqueuedCount <= 0) {
log.info("No enqueued validators for this Node Operator.");
}

// Retrieve the number of non-withdrawn keys
const numberOfNoneWithdrawnKeys = await getNoneWithdrawnKeys(contract, nodeOperatorId);
if (numberOfNoneWithdrawnKeys === null || numberOfNoneWithdrawnKeys <= 0) {
log.info("No non-withdrawn keys available.");
return null;
}

// Retrieve the signing keys
const signingKeys = await getSigningKeys(contract, nodeOperatorId, 0, numberOfNoneWithdrawnKeys);
if (!signingKeys) {
log.info("Failed to retrieve signing keys.");
return null;
}

// Initialize queue data
const queueData = await getDepositQueue(contract);
if (!queueData) {
log.error("Failed to retrieve deposit queue data.");
return null;
}
const { head, tail } = queueData;

// Prepare results
const signingKeysWithQueueInfo = signingKeys.map((key) => ({ key, queuePosition: 0 })); // Default queuePosition to 0

if (enqueuedCount > 0) {
let remainingKeysToMark = Number(enqueuedCount);

// Traverse the deposit queue from tail to head
for (let index = tail; index >= head && remainingKeysToMark > 0; index--) {
const queueItem = await getDepositQueueItem(contract, index);

if (queueItem.nodeId === BigInt(nodeOperatorId)) {
const keysCountInQueue = Number(queueItem.keysCount);

// Calculate queue position
const queuePosition = queueItem.nextBatch - BigInt(head);

// Determine range of keys to mark in reverse order
const startIndex = signingKeysWithQueueInfo.length - remainingKeysToMark;
const endIndex = startIndex + keysCountInQueue;

for (let i = endIndex - 1; i >= startIndex; i--) {
signingKeysWithQueueInfo[i].queuePosition = queuePosition;
}

// Reduce the remaining keys to mark
remainingKeysToMark -= keysCountInQueue;
}
}
}

// Return signing keys with queue information
return signingKeysWithQueueInfo;
} catch (error) {
log.error("Error in getSigningKeysWithQueueInfo:", error);
return null;
} finally {
log.info("Closing RPC tunnel...");
await monitoring.closeRpcTunnel();
}
}

/**
* Checks for matching signing keys of a specified Node Operator.
*
Expand All @@ -154,7 +318,7 @@ async function getSigningKeys(contract, nodeOperatorId, startIndex, keysCount) {
*
* @async
* @function checkSigningKeys
* @param {number} nodeOperatorId - The ID of the Node Operator whose signing keys are to be checked.
* @param {Object} monitoring - Monitoring Object.
* @param {string[]} keysArray - An array of signing keys in hexadecimal format to be matched against the Node Operator's keys.
* @returns {Promise<string[]|boolean|null>} A promise that resolves to:
* - An array of matching signing keys (in hexadecimal format) if any matches are found.
Expand Down Expand Up @@ -254,4 +418,7 @@ async function checkSigningKeys(keysArray, monitoring) {
}
}

export default checkSigningKeys;
export default {
checkSigningKeys,
getSigningKeysWithQueueInfo,
};
6 changes: 5 additions & 1 deletion launcher/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ProtocolHandler } from "./backend/CustomUrlProtocol.js";
import path from "path";
import { readFileSync, existsSync, mkdirSync, renameSync, readdir, rmSync } from "fs";
import url from "url";
import checkSigningKeys from "./backend/web3/CSM.js";
import { checkSigningKeys, getSigningKeysWithQueueInfo } from "./backend/web3/CSM.js";
const isDevelopment = process.env.NODE_ENV !== "production";
const nodeConnection = new NodeConnection();
const storageService = new StorageService();
Expand Down Expand Up @@ -848,6 +848,10 @@ ipcMain.handle("getCSMQueue", async (event, args) => {
return await checkSigningKeys(args.keysArray, monitoring);
});

ipcMain.handle("getSigningKeysWithQueueInfo", async () => {
return await getSigningKeysWithQueueInfo(monitoring);
});

ipcMain.handle("getObolClusterInformation", async (event, args) => {
return await monitoring.getObolClusterInformation(args.serviceID);
});
Expand Down
4 changes: 4 additions & 0 deletions launcher/src/store/ControlService.js
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,10 @@ class ControlService extends EventEmitter {
return this.promiseIpc.send("getCSMQueue", { keysArray });
}

async getSigningKeysWithQueueInfo() {
return this.promiseIpc.send("getSigningKeysWithQueueInfo");
}

async getObolClusterInformation(serviceID) {
return this.promiseIpc.send("getObolClusterInformation", { serviceID });
}
Expand Down

0 comments on commit 154e90c

Please sign in to comment.