transfer from monorepo

This commit is contained in:
Laura Abro
2025-04-24 10:24:42 -03:00
parent a2175785d5
commit 1c6fc5540b
95 changed files with 7110 additions and 0 deletions

View File

@ -0,0 +1,36 @@
export function isValidAnthropicApiKey(key: string) {
const regex = /^sk-ant-[a-zA-Z0-9_-]{32,}$/;
return regex.test(key);
}
export async function checkAnthropicAPIKey(apiKey: string) {
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-3-opus-20240229', // or a cheaper model
max_tokens: 1, // minimal usage
messages: [{ role: 'user', content: 'Hi' }],
}),
});
if (response.status === 200) {
console.log('✅ API key is valid and has credit.');
return true;
} else {
const data = await response.json().catch(() => ({}));
if (response.status === 401) {
console.log('❌ Invalid API key.');
} else if (response.status === 403 && data.error?.message?.includes('billing')) {
console.log('❌ API key has no credit or is not authorized.');
} else {
console.log('⚠️ Unexpected error:', data);
}
return false;
}
}

View File

@ -0,0 +1,57 @@
import dotenv from "dotenv";
dotenv.config();
export const status = {
ISSUE_FAILED_TO_BE_SUMMARIZED: "Issue failed to be summarized",
ISSUE_SUCCESSFULLY_SUMMARIZED: "Issue successfully summarized",
NO_ISSUES_PENDING_TO_BE_SUMMARIZED: "No issues pending to be summarized",
ROUND_LESS_THAN_OR_EQUAL_TO_1: "Round <= 1",
NO_ORCA_CLIENT: "No orca client",
NO_CHOSEN_AS_ISSUE_SUMMARIZER: "No chosen as issue summarizer",
UNKNOWN_ERROR: "Unknown error",
STAR_ISSUE_FAILED: "Star issue failed",
GITHUB_CHECK_FAILED: "GitHub check failed",
ANTHROPIC_API_KEY_INVALID: "Anthropic API key invalid",
ANTHROPIC_API_KEY_NO_CREDIT: "Anthropic API key has no credit",
NO_DATA_FOR_THIS_ROUND: "No data for this round",
ISSUE_FAILED_TO_ADD_PR_TO_SUMMARIZER_TODO: "Issue failed to add PR to summarizer todo",
}
export const errorMessage = {
ISSUE_FAILED_TO_BE_SUMMARIZED: "We couldn't summarize this issue. Please try again later.",
ISSUE_SUCCESSFULLY_SUMMARIZED: "The issue was successfully summarized.",
NO_ISSUES_PENDING_TO_BE_SUMMARIZED: "There are no issues waiting to be summarized at this time.",
ROUND_LESS_THAN_OR_EQUAL_TO_1: "This operation requires a round number greater than 1.",
NO_ORCA_CLIENT: "The Orca client is not available.",
NO_CHOSEN_AS_ISSUE_SUMMARIZER: "You haven't been selected as an issue summarizer.",
UNKNOWN_ERROR: "An unexpected error occurred. Please try again later.",
STAR_ISSUE_FAILED: "We couldn't star the issue. Please try again later.",
GITHUB_CHECK_FAILED: "The GitHub check failed. Please verify your GitHub Key.",
ANTHROPIC_API_KEY_INVALID: "The Anthropic API Key is not valid. Please check your API key.",
ANTHROPIC_API_KEY_NO_CREDIT: "Your Anthropic API key has no remaining credits.",
NO_DATA_FOR_THIS_ROUND: "There is no data available for this round.",
ISSUE_FAILED_TO_ADD_PR_TO_SUMMARIZER_TODO: "We couldn't add the PR to the summarizer todo list.",
}
export const actionMessage = {
ISSUE_FAILED_TO_BE_SUMMARIZED: "We couldn't summarize this issue. Please try again later.",
ISSUE_SUCCESSFULLY_SUMMARIZED: "The issue was successfully summarized.",
NO_ISSUES_PENDING_TO_BE_SUMMARIZED: "There are no issues waiting to be summarized at this time.",
ROUND_LESS_THAN_OR_EQUAL_TO_1: "This operation requires a round number greater than 1.",
NO_ORCA_CLIENT: "Please click Orca icon to connect your Orca Pod.",
NO_CHOSEN_AS_ISSUE_SUMMARIZER: "You haven't been selected as an issue summarizer.",
UNKNOWN_ERROR: "An unexpected error occurred. Please try again later.",
STAR_ISSUE_FAILED: "We couldn't star the issue. Please try again later.",
GITHUB_CHECK_FAILED: "Please go to the env variable page to update your GitHub Key.",
ANTHROPIC_API_KEY_INVALID: "Please follow the guide under task description page to set up your Anthropic API key correctly.",
ANTHROPIC_API_KEY_NO_CREDIT: "Please add credits to continue.",
NO_DATA_FOR_THIS_ROUND: "There is no data available for this round.",
ISSUE_FAILED_TO_ADD_PR_TO_SUMMARIZER_TODO: "We couldn't add the PR to the summarizer todo list. Please try again later.",
}
/*********************THE CONSTANTS THAT PROD/TEST ARE DIFFERENT *********************/
export const defaultBountyMarkdownFile = "https://raw.githubusercontent.com/koii-network/prometheus-swarm-bounties/master/README.md"
export const customReward = 400*10**9 // This should be in ROE!
export const middleServerUrl = "https://ooww84kco0s0cs808w8cg804.dev.koii.network"

View File

@ -0,0 +1,96 @@
import { namespaceWrapper } from "@_koii/namespace-wrapper";
import { getFile } from "./ipfs";
/**
* Filter out ineligible nodes from the distribution list
* @param distributionList Raw distribution list from namespace
* @param submissions List of submissions for the round
* @returns Filtered distribution list containing only eligible nodes
*/
async function filterIneligibleNodes(
distributionList: Record<string, number>,
roundNumber: number,
): Promise<Record<string, any>> {
const filteredDistributionList: Record<string, any> = {};
if (Object.keys(distributionList).length === 0) {
console.log("Distribution list is empty, skipping filterIneligibleNodes");
return filteredDistributionList;
}
const taskSubmissionInfo = await namespaceWrapper.getTaskSubmissionInfo(roundNumber);
if (!taskSubmissionInfo) {
console.log("Task submission info is null, skipping filterIneligibleNodes");
return filteredDistributionList;
}
const submissions = taskSubmissionInfo.submissions;
for (const [stakingKey, amount] of Object.entries(distributionList)) {
const numericAmount = amount as number;
// Skip if amount is zero or negative (failed audit)
if (numericAmount <= 0) {
console.log("Skipping staking key:", stakingKey, "Amount:", numericAmount);
continue;
}
// Find corresponding submission
const submissionCID = submissions[roundNumber][stakingKey]["submission_value"];
const submission = await getFile(submissionCID);
// Skip if no submission found
if (!submission) {
console.log("No submission found, skipping staking key:", stakingKey);
continue;
}
const submissionData = JSON.parse(submission);
console.log("Staking key:", stakingKey, "Submission data:", submissionData);
const payload = await namespaceWrapper.verifySignature(submissionData.signature, stakingKey);
console.log("Payload:", payload);
const payloadData = JSON.parse(payload.data || "{}");
// Skip if submission has no PR URL or is a dummy submission
if (!payloadData.prUrl || payloadData.prUrl === "none") {
continue;
}
// Node is eligible, include in filtered list
filteredDistributionList[stakingKey] = payloadData;
}
console.log("Filtered distribution list:", filteredDistributionList);
return filteredDistributionList;
}
export async function getDistributionList(roundNumber: number): Promise<Record<string, any> | null> {
try {
const taskDistributionInfo = await namespaceWrapper.getTaskDistributionInfo(roundNumber);
if (!taskDistributionInfo) {
console.log("Task distribution info is null, skipping task");
return null;
}
const distribution = taskDistributionInfo.distribution_rewards_submission[roundNumber];
const leaderStakingKey = Object.keys(distribution)[0];
console.log("Fetching distribution list for round", roundNumber, "with leader staking key", leaderStakingKey);
const distributionList = await namespaceWrapper.getDistributionList(leaderStakingKey, roundNumber);
if (!distributionList) {
console.log("Distribution list is null, skipping task");
return null;
}
console.log("Raw distribution list:", distributionList);
const parsedDistributionList: Record<string, number> = JSON.parse(distributionList);
return await filterIneligibleNodes(parsedDistributionList, roundNumber);
} catch (error) {
console.error("Error fetching distribution list:", error);
return null;
}
}

View File

@ -0,0 +1,161 @@
import { defaultBountyMarkdownFile } from "./constant";
interface BountyIssue {
githubUrl: string;
projectName: string;
bountyTask: string;
description: string;
bountyAmount: string;
bountyType: string;
transactionHash: string;
status: string;
}
export async function getExistingIssues(): Promise<BountyIssue[]> {
try {
// read from the bounty markdown file
// console.log('Fetching markdown file from:', defaultBountyMarkdownFile);
const bountyMarkdownFile = await fetch(defaultBountyMarkdownFile);
const bountyMarkdownFileText = await bountyMarkdownFile.text();
// console.log('Raw markdown content:', bountyMarkdownFileText);
const bountyMarkdownFileLines = bountyMarkdownFileText.split("\n");
// console.log('Number of lines:', bountyMarkdownFileLines.length);
const issues: BountyIssue[] = [];
let isTableStarted = false;
for (const line of bountyMarkdownFileLines) {
// Skip empty lines
if (line.trim() === '') {
// console.log('Skipping empty line');
continue;
}
// console.log('Processing line:', line);
// Skip the title line starting with #
if (line.startsWith('#')) {
// console.log('Found title line:', line);
continue;
}
// Skip the header and separator lines
if (line.startsWith('|') && line.includes('GitHub URL')) {
//console.log('Found header line');
continue;
}
if (line.startsWith('|') && line.includes('-----')) {
// console.log('Found separator line');
continue;
}
// Process table rows
if (line.startsWith('|')) {
isTableStarted = true;
// Remove first and last | and split by |
const cells = line.slice(1, -1).split('|').map(cell => cell.trim());
// console.log('Parsed cells:', cells);
// Extract GitHub URL and name from markdown link format [name](url)
const githubUrlMatch = cells[0].match(/\[(.*?)\]\((.*?)\)/);
// console.log('GitHub URL match:', githubUrlMatch);
const projectName = githubUrlMatch ? githubUrlMatch[1] : '';
const githubUrl = githubUrlMatch ? githubUrlMatch[2] : '';
const issue: BountyIssue = {
githubUrl,
projectName,
bountyTask: cells[1],
description: cells[3],
bountyAmount: cells[4],
bountyType: cells[5],
transactionHash: cells[6],
status: cells[7]
};
// console.log('Created issue object:', issue);
issues.push(issue);
}
}
// Filter all issues with status "Initialized" && Bounty Task is Document & Summarize
console.log('Final parsed issues number:', issues.length);
return issues
} catch (error) {
// console.error('Error processing markdown:', error);
throw error;
}
}
export async function getInitializedDocumentSummarizeIssues(issues: BountyIssue[]) {
return issues.filter(issue => issue.status === "Initialized" && issue.bountyTask === "Document & Summarize");
}
// async function main(){
// const existingIssues = await getExistingIssues();
// const transactionHashs = [
// "51680569890c40efa0f1f891044db219",
// "21a7021da88a4092af014702da7638cb",
// "befcf8d281074e3e934d8947c02ecb6f",
// "a1db701bbda24a45b573e58840d9b31c",
// "4ab503566a1142b1a3a9b406849839c9",
// "7f6fb74e4b6a41b0af805ca3f6c9ea15",
// "878af0d284c7460394b6d6e1090119be",
// "64d90b6f891d4ea385c8f6ad81808103",
// "6f7522b2e2374d4ca4f92bcf1f694bec",
// "e85201ae9ed9417e8c56216bb44cd78b",
// "d2ca259ef6ce4129a786677d919aad24",
// "6ce684318aab4356b76ba64e87b31be7",
// "d94d07647b1b42819d9bf629f5624ae1",
// "60aa8f04dd314c14b30e5ac2957bd9f8",
// "b7e21455e41b4626b5015b7bf39ff190",
// "5e7109ed4dd94373958eda2416337ad3",
// "2d647d3ab2c5465890939315ada47fd7",
// "51ade1ba2f6341e99aa6ec56b1a00f27",
// "a74f5e80238a4582aa444c18e9d5d66f",
// "8390a3143a8445f196a124605e524f3d",
// "26b712f341ca457d86db67ecd841c438",
// "0ec98ba1e7174eef87772df8356bab0d",
// "2737c33bff8c4490b7e5f53a5f5da580",
// "e5b9b714d5694680a56cfa77361f3477",
// "afb1bbbf1c074d28bef5fa216008cd6b",
// "b40da8c53a644a6e898e3314e08c10ea",
// "6a2f743c0497427ea4cd3cadb785b166",
// "ce390111854b4a4b980b5e1e3f7c2f0e",
// "c1b54e7a8dfd40be873051dd64bae5c4",
// "7dcda8e5969c45e08f9a8887d8c39d10",
// "fc11382529644d55b95fc2264e40436f",
// "7c145db039b64edba719e81dd398b37e",
// "c92b4920b25540a692c3b8e12215f0e0",
// "cebbf4e2310d4a11ac44321823ddb373",
// "5ae707005d0e413cb9feb9bdadc1e987",
// "d28f92643c2548338d3e49144bc66afc",
// "bd18484224c24fc786a5171e9d06cd50",
// "f0605ea0f9524572bbe5bf4e72597476",
// "62e6303c57334f72ada393bfa9e7aacc",
// "f4ee9168804c4b01932ac76cc32d1f13",
// "d4a95e2d35db47d28a208309019b1925",
// "014425adc1b8447ab34d7d8104e91cf0"
// ]
// const initializedDocumentSummarizeIssues = existingIssues.filter((issue) => transactionHashs.includes(issue.transactionHash));
// if (initializedDocumentSummarizeIssues.length == 0) {
// console.log("No issues pending to be summarized");
// return;
// }
// console.log("Initialized Document & Summarize issues number:", initializedDocumentSummarizeIssues.length);
// }
// async function main() {
// try {
// const existingIssues = await getInitializedDocumentSummarizeIssues();
// console.log('Initialized Document & Summarize issues number:', existingIssues.length);
// } catch (error) {
// console.error('Error in main:', error);
// }
// }
// main();

View File

@ -0,0 +1,36 @@
export async function checkGitHub(username: string, token: string) {
// 1. Check username
const userRes = await fetch(`https://api.github.com/users/${username}`);
const isUsernameValid = userRes.status === 200;
// 2. Check token
const tokenRes = await fetch('https://api.github.com/user', {
headers: {
Authorization: `token ${token}`,
},
});
const isTokenValid = tokenRes.status === 200;
const isIdentityValid = await checkGitHubIdentity(username, token);
return isIdentityValid&&isUsernameValid&&isTokenValid
}
async function checkGitHubIdentity(username: string, token: string) {
const res = await fetch('https://api.github.com/user', {
headers: {
Authorization: `token ${token}`,
Accept: 'application/vnd.github.v3+json',
},
});
if (res.status !== 200) {
return false
}
const data = await res.json();
if (data.login.toLowerCase() !== username.toLowerCase()) {
return false
}
return true
}

34
worker/src/utils/ipfs.ts Normal file
View File

@ -0,0 +1,34 @@
import { namespaceWrapper } from "@_koii/namespace-wrapper";
import { KoiiStorageClient } from "@_koii/storage-task-sdk";
import fs from "fs";
export async function storeFile(data: any, filename: string = "submission.json"): Promise<string> {
// Create a new instance of the Koii Storage Client
const client = KoiiStorageClient.getInstance({});
const basePath = await namespaceWrapper.getBasePath();
try {
// Write the data to a temp file
fs.writeFileSync(`${basePath}/${filename}`, typeof data === "string" ? data : JSON.stringify(data));
// Get the user staking account, to be used for signing the upload request
const userStaking = await namespaceWrapper.getSubmitterAccount();
if (!userStaking) {
throw new Error("No staking keypair found");
}
// Upload the file to IPFS and get the CID
const { cid } = await client.uploadFile(`${basePath}/${filename}`, userStaking);
return cid;
} catch (error) {
throw error;
} finally {
// Delete the temp file
fs.unlinkSync(`${basePath}/${filename}`);
}
}
export async function getFile(cid: string, filename: string = "submission.json"): Promise<string> {
const storageClient = KoiiStorageClient.getInstance({});
const fileBlob = await storageClient.getFile(cid, filename);
return await fileBlob.text();
}

265
worker/src/utils/leader.ts Normal file
View File

@ -0,0 +1,265 @@
import { namespaceWrapper, TASK_ID } from "@_koii/namespace-wrapper";
import { getFile } from "./ipfs";
import seedrandom from "seedrandom";
export async function fetchRoundSubmissionGitHubRepoOwner(
roundNumber: number,
submitterPublicKey: string,
): Promise<string | null> {
try {
const taskSubmissionInfo = await namespaceWrapper.getTaskSubmissionInfo(roundNumber);
if (!taskSubmissionInfo) {
console.error("NO TASK SUBMISSION INFO");
return null;
}
const submissions = taskSubmissionInfo.submissions;
// This should only have one round
const lastRound = Object.keys(submissions).pop();
if (!lastRound) {
return null;
}
const lastRoundSubmissions = submissions[lastRound];
const lastRoundSubmitterSubmission = lastRoundSubmissions[submitterPublicKey];
console.log("lastRoundSubmitterSubmission", { lastRoundSubmitterSubmission });
if (!lastRoundSubmitterSubmission) {
return null;
}
const cid = lastRoundSubmitterSubmission.submission_value;
const submissionString = await getFile(cid);
const submission = JSON.parse(submissionString);
console.log({ submission });
// verify the signature of the submission
const signaturePayload = await namespaceWrapper.verifySignature(submission.signature, submitterPublicKey);
console.log({ signaturePayload });
// verify the signature payload
if (signaturePayload.error || !signaturePayload.data) {
console.error("INVALID SIGNATURE");
return null;
}
const data = JSON.parse(signaturePayload.data);
if (data.taskId !== TASK_ID || data.stakingKey !== submitterPublicKey) {
console.error("INVALID SIGNATURE DATA");
return null;
}
if (!data.githubUsername) {
console.error("NO GITHUB USERNAME");
console.log("data", { data });
return null;
}
return data.githubUsername;
} catch (error) {
console.error("FETCH LAST ROUND SUBMISSION GITHUB REPO OWNER ERROR:", error);
return null;
}
}
export async function selectShortestDistance(keys: string[], submitterPublicKey: string): Promise<string> {
let shortestDistance = Infinity;
let closestKey = "";
for (const key of keys) {
const distance = knnDistance(submitterPublicKey, key);
if (distance < shortestDistance) {
shortestDistance = distance;
closestKey = key;
}
}
return closestKey;
}
async function getSubmissionInfo(roundNumber: number): Promise<any> {
try {
return await namespaceWrapper.getTaskSubmissionInfo(roundNumber);
} catch (error) {
console.error("GET SUBMISSION INFO ERROR:", error);
return null;
}
}
function calculatePublicKeyFrequency(submissions: any): Record<string, number> {
const frequency: Record<string, number> = {};
for (const round in submissions) {
for (const publicKey in submissions[round]) {
if (frequency[publicKey]) {
frequency[publicKey]++;
} else {
frequency[publicKey] = 1;
}
}
}
return frequency;
}
function handleAuditTrigger(submissionAuditTrigger: any): Set<string> {
const auditTriggerKeys = new Set<string>();
for (const round in submissionAuditTrigger) {
for (const publicKey in submissionAuditTrigger[round]) {
auditTriggerKeys.add(publicKey);
}
}
return auditTriggerKeys;
}
async function selectLeaderKey(
sortedKeys: string[],
leaderNumber: number,
submitterPublicKey: string,
submissionPublicKeysFrequency: Record<string, number>,
): Promise<string> {
const topValue = sortedKeys[leaderNumber - 1];
const count = sortedKeys.filter(
(key) => submissionPublicKeysFrequency[key] >= submissionPublicKeysFrequency[topValue],
).length;
if (count >= leaderNumber) {
const rng = seedrandom(String(TASK_ID));
const guaranteedKeys = sortedKeys.filter(
(key) => submissionPublicKeysFrequency[key] > submissionPublicKeysFrequency[topValue],
);
const randomKeys = sortedKeys
.filter((key) => submissionPublicKeysFrequency[key] === submissionPublicKeysFrequency[topValue])
.sort(() => rng() - 0.5)
.slice(0, leaderNumber - guaranteedKeys.length);
const keys = [...guaranteedKeys, ...randomKeys];
return await selectShortestDistance(keys, submitterPublicKey);
} else {
const keys = sortedKeys.slice(0, leaderNumber);
return await selectShortestDistance(keys, submitterPublicKey);
}
}
export async function getRandomNodes(roundNumber: number, numberOfNodes: number): Promise<string[]> {
console.log("Getting random nodes for round:", roundNumber, "with number of nodes:", numberOfNodes);
const lastRoundSubmission = await getSubmissionInfo(roundNumber - 1);
console.log("Last round submission:", lastRoundSubmission);
if (!lastRoundSubmission) {
return [];
}
const lastRoundSubmissions = lastRoundSubmission.submissions;
console.log("Last round submissions:", lastRoundSubmissions);
// Get the last round number
const lastRound = Object.keys(lastRoundSubmissions).pop();
if (!lastRound) {
return [];
}
// Get the submissions for that round
const submissions = lastRoundSubmissions[lastRound];
console.log("Submissions:", submissions);
const availableKeys = Object.keys(submissions);
console.log("Available keys:", availableKeys);
// If we have fewer submissions than requested nodes, return all available submissions
if (availableKeys.length <= numberOfNodes) {
return availableKeys;
}
const seed = TASK_ID + roundNumber.toString() || "default" + roundNumber;
const rng = seedrandom(seed);
// Use the keys from the submissions object
const randomKeys = availableKeys.sort(() => rng() - 0.5).slice(0, numberOfNodes);
console.log("Random keys:", randomKeys);
return randomKeys;
}
// Helper function that finds the leader for a specific round
async function getLeaderForRound(
roundNumber: number,
maxLeaderNumber: number,
submitterPublicKey: string,
): Promise<{ chosenKey: string | null; leaderNode: string | null }> {
if (roundNumber <= 0) {
return { chosenKey: null, leaderNode: null };
}
const submissionPublicKeysFrequency: Record<string, number> = {};
const submissionAuditTriggerKeys = new Set<string>();
for (let i = 1; i < 5; i++) {
const taskSubmissionInfo = await getSubmissionInfo(roundNumber - i);
console.log({ taskSubmissionInfo });
if (taskSubmissionInfo) {
const submissions = taskSubmissionInfo.submissions;
const frequency = calculatePublicKeyFrequency(submissions);
Object.assign(submissionPublicKeysFrequency, frequency);
const auditTriggerKeys = handleAuditTrigger(taskSubmissionInfo.submissions_audit_trigger);
auditTriggerKeys.forEach((key) => submissionAuditTriggerKeys.add(key));
}
}
const keysNotInAuditTrigger = Object.keys(submissionPublicKeysFrequency).filter(
(key) => !submissionAuditTriggerKeys.has(key),
);
const sortedKeys = keysNotInAuditTrigger.sort(
(a, b) => submissionPublicKeysFrequency[b] - submissionPublicKeysFrequency[a],
);
console.log({ sortedKeys });
let chosenKey = null;
const leaderNumber = sortedKeys.length < maxLeaderNumber ? sortedKeys.length : maxLeaderNumber;
chosenKey = await selectLeaderKey(sortedKeys, leaderNumber, submitterPublicKey, submissionPublicKeysFrequency);
// Find GitHub username for the chosen key
for (let i = 1; i < 5; i++) {
const githubUsername = await fetchRoundSubmissionGitHubRepoOwner(roundNumber - i, chosenKey);
if (githubUsername) {
return { chosenKey, leaderNode: githubUsername };
}
}
return { chosenKey, leaderNode: null };
}
export async function getLeaderNode({
roundNumber,
leaderNumber = 5,
submitterPublicKey,
}: {
roundNumber: number;
leaderNumber?: number;
submitterPublicKey: string;
}): Promise<{ isLeader: boolean; leaderNode: string | null }> {
// Find leader for current round
const currentLeader = await getLeaderForRound(roundNumber, leaderNumber, submitterPublicKey);
console.log({ currentLeader });
if (currentLeader.chosenKey === submitterPublicKey) {
// If we're the leader, get the leader from 3 rounds ago
const previousLeader = await getLeaderForRound(roundNumber - 3, leaderNumber, submitterPublicKey);
console.log({ previousLeader });
return { isLeader: true, leaderNode: previousLeader.leaderNode };
}
// Not the leader, return the current leader's info
return { isLeader: false, leaderNode: currentLeader.leaderNode };
}
function base58ToNumber(char: string): number {
const base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
return base58Chars.indexOf(char);
}
function knnDistance(a: string, b: string): number {
if (a.length !== b.length) {
throw new Error("Strings must be of the same length for KNN distance calculation.");
}
const truncatedA = a.slice(0, 30);
const truncatedB = b.slice(0, 30);
let distance = 0;
for (let i = 0; i < truncatedA.length; i++) {
const numA = base58ToNumber(truncatedA[i]);
const numB = base58ToNumber(truncatedB[i]);
distance += Math.abs(numA - numB);
}
return distance;
}

View File

@ -0,0 +1,40 @@
import { TASK_ID } from "@_koii/namespace-wrapper";
import { getFile } from "./ipfs";
import { Submission } from "@_koii/namespace-wrapper/dist/types";
import { Submitter } from "@_koii/task-manager/dist/types/global";
import { namespaceWrapper } from "@_koii/namespace-wrapper";
export async function submissionJSONSignatureDecode({submission_value, submitterPublicKey, roundNumber}: {submission_value: string, submitterPublicKey: string, roundNumber: number}) {
let submissionString;
try {
console.log("Getting file from IPFS", submission_value);
submissionString = await getFile(submission_value);
console.log("submissionString", submissionString);
} catch (error) {
console.log("error", error);
console.error("INVALID SIGNATURE DATA");
return null;
}
// verify the signature of the submission
const submission = JSON.parse(submissionString);
console.log("submission", submission);
const signaturePayload = await namespaceWrapper.verifySignature(submission.signature, submitterPublicKey);
if (!signaturePayload.data) {
console.error("INVALID SIGNATURE");
return null;
}
const data = JSON.parse(signaturePayload.data);
console.log("signaturePayload", signaturePayload);
console.log("data", data);
if (
data.taskId !== TASK_ID ||
data.roundNumber !== roundNumber ||
data.stakingKey !== submitterPublicKey ||
!data.pubKey ||
!data.prUrl
) {
console.error("INVALID SIGNATURE DATA");
return null;
}
return data;
}