Skip to content

Commit

Permalink
feat: add env var for community validator key / mnemonic + send weigh…
Browse files Browse the repository at this point in the history
…ts to chain
  • Loading branch information
steinerkelvin committed Oct 11, 2024
1 parent 9df068c commit 3e1d1b7
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 34 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ JWT_SECRET='your-jwt-secret'
# The Discord API token and endpoint used to send notifications to Discord.
DISCORD_API_TOKEN="your-discord-api-token"
DISCORD_API_ENDPOINT="your-discord-api-endpoint"

# Comrads
COMMUNITY_VALIDATOR_MNEMONIC=""
15 changes: 15 additions & 0 deletions apps/commune-worker/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { z } from 'zod';

const envSchema = z.object({
COMMUNITY_VALIDATOR_MNEMONIC: z.string().min(1),
});

const result = envSchema.safeParse(process.env);

if (!result.success) {
console.error('❌ Invalid environment variables:');
console.error(JSON.stringify(result.error.format(), null, 2));
process.exit(1);
}

export const env = result.data;
54 changes: 34 additions & 20 deletions apps/commune-worker/src/weights.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import { bigintDivision } from "@commune-ts/subspace/utils";
import { assert } from "tsafe";

/** Related to weights computation */
Expand Down Expand Up @@ -53,33 +54,44 @@ export function calcFinalWeights(
}

/**
* Normalize module weights to integer percentages.
* Normalize weights to kinda arbitrary small integers. They need to fit in
* a u16 which is what Subspace accepts as vote values.
*/
export function normalizeModuleWeights(
module_weights: Map<Key, bigint>,
): Map<string, bigint> {
const module_key_arr: string[] = [];
const module_weight_arr: bigint[] = [];
export function normalizeWeightsForVote(
weights: Map<Key, bigint>,
): Map<string, number> {
const SCALE = 2n << 8n;

let max_weight = 0n;
for (const [module_key, weight] of module_weights.entries()) {
module_key_arr.push(module_key);
module_weight_arr.push(weight);
for (const weight of weights.values()) {
if (weight > max_weight) max_weight = weight;
}

const normalized_weights: bigint[] = [];
for (const weight of module_weight_arr) {
normalized_weights.push((weight * 100n) / max_weight);
const result = new Map<string, number>();
for (const [module_key, weight] of weights.entries()) {
const normalized = (weight * SCALE) / max_weight;
result.set(module_key, Number(normalized));
}
return result;
}

/**
* Normalize weights to float percentages.
*/
export function normalizeWeightsToPercent(
module_weights: Map<Key, bigint>,
): Map<Key, number> {
let total_weight = 0n;
for (const weight of module_weights.values()) {
total_weight += weight;
}

const result = new Map<string, number>();
for (const [module_key, weight] of module_weights.entries()) {
const normalized = bigintDivision(weight, total_weight);
result.set(module_key, Number(normalized));
}

const result = new Map<string, bigint>();
// for (let i = 0; i < module_key_arr.length; i++) {
module_key_arr.forEach((module_key, i) => {
const nw = normalized_weights[i];
assert(nw != null);
result.set(module_key, nw);
});
return result;
}

Expand Down Expand Up @@ -139,10 +151,12 @@ function _testFinalWeights() {

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const result = calcFinalWeights(user_stakes, user_weight_maps);
const normalized = normalizeModuleWeights(result);
const normalized = normalizeWeightsForVote(result);
const normalizedPerc = normalizeWeightsToPercent(result);

console.log(result);
console.log(normalized);
console.log(normalizedPerc);
}

// _testFinalWeights();
67 changes: 55 additions & 12 deletions apps/commune-worker/src/workers/weight-aggregator.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,54 @@
import type { KeyringPair } from "@polkadot/keyring/types";
import { Keyring } from "@polkadot/api";
import { cryptoWaitReady } from "@polkadot/util-crypto";
import { assert } from "tsafe";

import type { ApiPromise } from "@commune-ts/subspace/queries";
import type { LastBlock } from "@commune-ts/types";
import { and, eq, sql } from "@commune-ts/db";
import { db } from "@commune-ts/db/client";
import { moduleData, userModuleData } from "@commune-ts/db/schema";
import { queryStakeOut } from "@commune-ts/subspace/queries";
import { queryLastBlock, queryStakeOut } from "@commune-ts/subspace/queries";

import { BLOCK_TIME, log, sleep } from "../common";
import { calcFinalWeights, normalizeModuleWeights } from "../weights";
import { env } from "../env";
import {
calcFinalWeights,
normalizeWeightsForVote,
normalizeWeightsToPercent,
} from "../weights";

// TODO: get comrads Substrate key from environment
// TODO: subnets
// TODO: send weights to chain
// TODO: update tables on DB

export async function weightAggregatorWorker(api: ApiPromise) {
await cryptoWaitReady();
const keyring = new Keyring({ type: "sr25519" });
const keypair = keyring.addFromUri(env.COMMUNITY_VALIDATOR_MNEMONIC);

let knownLastBlock: LastBlock | null = null;

while (true) {
try {
await weightCompilerTask(api);
const lastBlock = await queryLastBlock(api);
if (
knownLastBlock != null &&
lastBlock.blockNumber <= knownLastBlock.blockNumber
) {
log(`Block ${lastBlock.blockNumber} already processed, skipping`);
await sleep(BLOCK_TIME / 2);
continue;
}
knownLastBlock = lastBlock;

log(`Block ${lastBlock.blockNumber}: processing`);

await weightAggregatorTask(api, keypair);

await sleep(BLOCK_TIME * 2);

} catch (e) {
log("UNEXPECTED ERROR: ", e);
await sleep(BLOCK_TIME);
Expand All @@ -30,7 +61,10 @@ export async function weightAggregatorWorker(api: ApiPromise) {
* Fetches assigned weights by users and their stakes, to calculate the final
* weights for the community validator.
*/
export async function weightCompilerTask(api: ApiPromise) {
export async function weightAggregatorTask(
api: ApiPromise,
keypair: KeyringPair,
) {
const stakeOutData = await queryStakeOut(
String(process.env.NEXT_PUBLIC_CACHE_PROVIDER_URL),
);
Expand All @@ -40,39 +74,48 @@ export async function weightCompilerTask(api: ApiPromise) {
const weightMap = await getUserWeightMap();

const finalWeights = calcFinalWeights(stakeOutMap, weightMap);
const normalizedWeights = normalizeModuleWeights(finalWeights);
const normalizedVoteWeights = normalizeWeightsForVote(finalWeights);
const normalizedPercWeights = normalizeWeightsToPercent(finalWeights);

// TODO: normalize weights with (sum(weights) == 100%) for DB

console.log(normalizedWeights);
console.log(normalizedVoteWeights);

const uids: number[] = [];
const weights: number[] = [];
for (const [moduleKey, weight] of normalizedWeights) {
for (const [moduleKey, weight] of normalizedVoteWeights) {
const uid = uidMap.get(moduleKey);
if (uid == null) {
console.error(`Module key ${moduleKey} not found in uid map`);
continue;
}
uids.push(uid);
weights.push(Number(weight));
weights.push(weight);
}

setWeights(api, 666, uids, weights);
try {
await setChainWeights(api, keypair, 2, uids, weights);
} catch (err) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
console.error(`Failed to set weights on chain: ${err}`);
return;
}

// TODO: update tables on DB
}

function setWeights(
async function setChainWeights(
api: ApiPromise,
keypair: KeyringPair,
netuid: number,
uids: number[],
weights: number[],
) {
assert(api.tx.subspaceModule != undefined);
assert(api.tx.subspaceModule.setWeights != undefined);
const tx = api.tx.subspaceModule.setWeights(netuid, uids, weights);
// TODO: send weights to chain
const tx = await api.tx.subspaceModule
.setWeights(netuid, uids, weights)
.signAndSend(keypair);
return tx;
}

Expand Down
3 changes: 1 addition & 2 deletions packages/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ export function assertOrThrow(
export function bigintDivision(a: bigint, b: bigint, precision = 8n): number {
if (b === 0n) return NaN;
const base = 10n ** precision;
const baseNum = Number(base);
return (Number(a) * Number(base)) / Number(b) / baseNum;
return Number((a * base) / b) / Number(base);
}

const NANO_MULTIPLIER = new BN("1000000000");
Expand Down

0 comments on commit 3e1d1b7

Please sign in to comment.