Skip to content

Commit

Permalink
Migrated fellows to use People chain and PAPI (#131)
Browse files Browse the repository at this point in the history
- Migrated the chain from polkadot to `People Chain`.
  - Resolves #130 and resolves polkadot-fellows/runtimes#394
- Upgraded project to use PAPI (it now has a `.papi` directory).
  - Removed slim node docker image and bundled everything together
- Polkadot-API depends on some dependencies that `ncc` is not bundling
together so we need to keep the `node_modules` (like
`@polkadot-api/metadata-compatibility`)
 - Updated to version `2.6.0`

---------

Co-authored-by: cornholio <[email protected]>
  • Loading branch information
Bullrich and mutantcornholio authored Jul 29, 2024
1 parent 4d1f40d commit b010f2e
Show file tree
Hide file tree
Showing 11 changed files with 1,475 additions and 635 deletions.
3 changes: 3 additions & 0 deletions .papi/descriptors/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!.gitignore
!package.json
24 changes: 24 additions & 0 deletions .papi/descriptors/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": "0.1.0-autogenerated.14257783844949306470",
"name": "@polkadot-api/descriptors",
"files": [
"dist"
],
"exports": {
".": {
"module": "./dist/index.mjs",
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"browser": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"sideEffects": false,
"peerDependencies": {
"polkadot-api": "*"
}
}
Binary file added .papi/metadata/collectives.scale
Binary file not shown.
Binary file added .papi/metadata/people.scale
Binary file not shown.
14 changes: 14 additions & 0 deletions .papi/polkadot-api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 0,
"descriptorPath": ".papi/descriptors",
"entries": {
"collectives": {
"wsUrl": "wss://polkadot-collectives-rpc.polkadot.io",
"metadata": ".papi/metadata/collectives.scale"
},
"people": {
"wsUrl": "wss://polkadot-people-rpc.polkadot.io",
"metadata": ".papi/metadata/people.scale"
}
}
}
7 changes: 2 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ WORKDIR /action

COPY .yarn/ ./.yarn/
COPY package.json yarn.lock .yarnrc.yml ./
COPY .papi ./.papi

RUN yarn install --immutable

COPY . .

RUN yarn run build

FROM node:22-slim

COPY --from=Builder /action/dist /action

ENTRYPOINT ["node", "/action/index.js"]
ENTRYPOINT ["node", "/action/dist/index.js"]
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ outputs:

runs:
using: 'docker'
image: 'docker://ghcr.io/paritytech/review-bot/action:2.5.0'
image: 'docker://ghcr.io/paritytech/review-bot/action:2.6.0'
14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "review-bot",
"version": "2.5.0",
"version": "2.6.0",
"description": "Have custom review rules for PRs with auto assignment",
"main": "src/index.ts",
"scripts": {
Expand All @@ -9,7 +9,8 @@
"cli": "ncc build src/cli.ts -o dist-cli && node dist-cli",
"test": "jest",
"fix": "npx eslint --fix 'src/**/*.ts' && npx prettier --write 'src/**/*.{ts,yml}'",
"lint": "npx eslint 'src/**/*.ts' && npx prettier --check 'src/**/*.{ts,yml}'"
"lint": "npx eslint 'src/**/*.ts' && npx prettier --check 'src/**/*.{ts,yml}'",
"postinstall": "papi"
},
"engines": {
"node": ">=22.0.0"
Expand Down Expand Up @@ -38,9 +39,14 @@
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@eng-automation/js": "^2.2.0",
"@polkadot/api": "^11.3.1",
"@polkadot-api/descriptors": "file:.papi/descriptors",
"joi": "^17.13.1",
"polkadot-api": "^0.12.0",
"smoldot": "^2.0.29",
"yaml": "^2.3.4"
},
"packageManager": "[email protected]"
"packageManager": "[email protected]",
"resolutions": {
"@polkadot-api/descriptors": "portal:./.papi/descriptors"
}
}
186 changes: 117 additions & 69 deletions src/polkadot/fellows.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { ApiPromise, WsProvider } from "@polkadot/api";
import { collectives, people } from "@polkadot-api/descriptors";
import { createClient, SS58String, TypedApi } from "polkadot-api";
import { chainSpec as polkadotChainSpec } from "polkadot-api/chains/polkadot";
import { chainSpec as collectivesChainSpec } from "polkadot-api/chains/polkadot_collectives";
import { chainSpec as peopleChainSpec } from "polkadot-api/chains/polkadot_people";
import { getSmProvider } from "polkadot-api/sm-provider";
import { start } from "smoldot";

import { ActionLogger, TeamApi } from "../github/types";

Expand All @@ -10,83 +15,126 @@ export class PolkadotFellows implements TeamApi {

constructor(private readonly logger: ActionLogger) {}

private async fetchAllFellows(): Promise<Map<string, number>> {
let api: ApiPromise;
this.logger.debug("Connecting to collective parachain");
// we connect to the collective rpc node
const wsProvider = new WsProvider("wss://polkadot-collectives-rpc.polkadot.io");
api = await ApiPromise.create({ provider: wsProvider });
try {
// We fetch all the members
const membersObj = await api.query.fellowshipCollective.members.entries();

// We iterate over the fellow data and convert them into usable values
const fellows: FellowData[] = [];
for (const [key, rank] of membersObj) {
// @ts-ignore
const [address]: [string] = key.toHuman();
fellows.push({ address, ...(rank.toHuman() as object) } as FellowData);
private async getGhHandle(
address: SS58String,
peopleApi: TypedApi<typeof people>,
logger: ActionLogger,
): Promise<string | undefined> {
logger.debug(`Fetching identity of '${address}'`);

const identityOf = await peopleApi.query.Identity.IdentityOf.getValue(address);

if (identityOf) {
const [identity] = identityOf;
const github = identity.info.github.value;

if (!github) {
logger.debug(`'${address}' does not have an additional field named 'github'`);
return;
}
this.logger.debug(JSON.stringify(fellows));

// Once we obtained this information, we disconnect this api.
await api.disconnect();

this.logger.debug("Connecting to relay parachain.");
// We connect to the relay chain
api = await ApiPromise.create({ provider: new WsProvider("wss://rpc.polkadot.io") });

// We iterate over the different members and extract their data
const users: Map<string, number> = new Map<string, number>();
for (const fellow of fellows) {
this.logger.debug(`Fetching identity of '${fellow.address}', rank: ${fellow.rank}`);
const identityQuery = await api.query.identity.identityOf(fellow.address);
// If the identity is null, we check if there is a super identity.
if (identityQuery.isEmpty) {
this.logger.debug("Identity is null. Checking for super identity");
const superIdentity = (await api.query.identity.superOf(fellow.address)).toHuman() as
| [string, { Raw: string }]
| undefined;
if (superIdentity && superIdentity[0]) {
this.logger.debug(`${fellow.address} has a super identity: ${superIdentity[0]}. Adding it to the array`);
fellows.push({ address: superIdentity[0], rank: fellow.rank });
} else {
this.logger.debug("No super identity found. Skipping");
}
continue;
}

const [fellowData] = identityQuery.toHuman() as [Record<string, unknown>, unknown];
const handle = github.asText().replace("@", "") as string;

if (handle) {
logger.info(`Found github handle for '${address}': '${handle}'`);
} else {
logger.debug(`'${address}' does not have a GitHub handle`);
return;
}
return handle;
}

logger.debug(`Identity of '${address}' is null. Checking for super identity`);

// @ts-ignore
const additional = fellowData.info?.additional as [{ Raw: string }, { Raw: string }][] | undefined;
const superIdentityAddress = (await peopleApi.query.Identity.SuperOf.getValue(address))?.[0];

// If it does not have additional data (GitHub handle goes here) we ignore it
if (!additional || additional.length < 1) {
this.logger.debug("Additional data is null. Skipping");
continue;
}
if (superIdentityAddress) {
logger.debug(`'${address}' has a super identity: '${superIdentityAddress}'. Fetching that identity`);
return await this.getGhHandle(superIdentityAddress, peopleApi, logger);
} else {
logger.debug(`No superidentity for ${address} found.`);
return undefined;
}
}

private async fetchAllFellows(logger: ActionLogger): Promise<Map<string, number>> {
logger.info("Initializing smoldot");
const smoldot = start();

for (const additionalData of additional) {
const [key, value] = additionalData;
// We verify that they have an additional data of the key "github"
// If it has a handle defined, we push it into the array
if (key?.Raw && key?.Raw === "github" && value?.Raw && value?.Raw.length > 0) {
this.logger.debug(`Found handles: '${value.Raw}'`);
// We add it to the array and remove the @ if they add it to the handle
users.set(value.Raw.replace("@", ""), fellow.rank);
}
try {
// Create smoldot chain with Polkadot Relay Chain
const smoldotRelayChain = await smoldot.addChain({
chainSpec: polkadotChainSpec,
});

// Add the people chain to smoldot
const peopleParachain = await smoldot.addChain({
chainSpec: peopleChainSpec,
potentialRelayChains: [smoldotRelayChain],
});

// Initialize the smoldot provider
const jsonRpcProvider = getSmProvider(peopleParachain);
logger.info("Initializing the people client");
const peopleClient = createClient(jsonRpcProvider);

// Get the types for the people client
const peopleApi = peopleClient.getTypedApi(people);

logger.info("Initializing the collectives client");

const collectiveRelayChain = await smoldot.addChain({
chainSpec: collectivesChainSpec,
potentialRelayChains: [smoldotRelayChain],
});
const collectiveJsonRpcProvider = getSmProvider(collectiveRelayChain);
logger.info("Initializing the relay client");
const collectivesClient = createClient(collectiveJsonRpcProvider);
const collectivesApi = collectivesClient.getTypedApi(collectives);

// Pull the members of the FellowshipCollective
const memberEntries = await collectivesApi.query.FellowshipCollective.Members.getEntries();

// We no longer need the collective client, so let's destroy it
collectivesClient.destroy();

// Build the Array of FellowData and filter out candidates (zero rank members)
const fellows: FellowData[] = memberEntries
.map(({ keyArgs: [address], value: rank }) => {
return { address, rank };
})
.filter(({ rank }) => rank > 0);
logger.debug(JSON.stringify(fellows));

// Let's now pull the GH handles of the fellows
const users = await Promise.all(
fellows.map(async ({ address, rank }) => {
return {
address,
rank,
githubHandle: await this.getGhHandle(address, peopleApi, logger),
};
}),
);
logger.info(`Found users: ${JSON.stringify(Array.from(users.entries()))}`);

const userMap: Map<string, number> = new Map<string, number>();

for (const { githubHandle, rank } of users) {
if (githubHandle) {
userMap.set(githubHandle, rank);
}
}

this.logger.info(`Found users: ${JSON.stringify(Array.from(users.entries()))}`);
// We are now done with the relay client
peopleClient.destroy();

return users;
return userMap;
} catch (error) {
this.logger.error(error as Error);
logger.error(error as Error);
throw error;
} finally {
await api.disconnect();
await smoldot.terminate();
}
}

Expand All @@ -96,7 +144,7 @@ export class PolkadotFellows implements TeamApi {

if (this.fellowsCache.size < 1) {
this.logger.debug("Cache not found. Fetching fellows.");
this.fellowsCache = await this.fetchAllFellows();
this.fellowsCache = await this.fetchAllFellows(this.logger);
}

return Array.from(this.fellowsCache.entries());
Expand All @@ -108,7 +156,7 @@ export class PolkadotFellows implements TeamApi {

if (this.fellowsCache.size < 1) {
this.logger.debug("Cache not found. Fetching fellows.");
this.fellowsCache = await this.fetchAllFellows();
this.fellowsCache = await this.fetchAllFellows(this.logger);
}
const users: string[] = [];
for (const [user, rank] of this.fellowsCache) {
Expand Down
8 changes: 5 additions & 3 deletions src/test/fellows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { mock, mockClear, MockProxy } from "jest-mock-extended";
import { ActionLogger, TeamApi } from "../github/types";
import { PolkadotFellows } from "../polkadot/fellows";

const timeout = 25_000;
const timeout = 60_000;

describe("CAPI test", () => {
let fellows: TeamApi;
Expand All @@ -29,11 +29,13 @@ describe("CAPI test", () => {
async () => {
const members = await fellows.getTeamMembers("2");
expect(members.length).toBeGreaterThan(0);
expect(logger.debug).toHaveBeenCalledWith("Connecting to collective parachain");
expect(logger.info).toHaveBeenCalledWith("Initializing the people client");
expect(logger.info).toHaveBeenCalledWith("Initializing the collectives client");
mockClear(logger);
const members2 = await fellows.getTeamMembers("2");
expect(members2.length).toBeGreaterThan(0);
expect(logger.debug).not.toHaveBeenCalledWith("Connecting to collective parachain");
expect(logger.info).not.toHaveBeenCalledWith("Initializing the people client");
expect(logger.info).not.toHaveBeenCalledWith("Initializing the collectives client");
},
timeout,
);
Expand Down
Loading

0 comments on commit b010f2e

Please sign in to comment.