Skip to content

Commit

Permalink
implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabib committed Feb 4, 2025
1 parent 72fc21a commit 9d6c8bc
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 18 deletions.
8 changes: 6 additions & 2 deletions packages/nns/src/canisters/governance/request.converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1017,17 +1017,21 @@ export const fromListNeurons = ({
neuronIds,
includeEmptyNeurons,
includePublicNeurons,
pageNumber,
pageSize,
}: {
neuronIds?: NeuronId[];
includeEmptyNeurons?: boolean;
includePublicNeurons?: boolean;
pageNumber?: bigint;
pageSize?: bigint;
}): RawListNeurons => ({
neuron_ids: BigUint64Array.from(neuronIds ?? []),
include_neurons_readable_by_caller: neuronIds ? false : true,
include_empty_neurons_readable_by_caller: toNullable(includeEmptyNeurons),
include_public_neurons_in_full_neurons: toNullable(includePublicNeurons),
page_number: [],
page_size: [],
page_number: toNullable(pageNumber),
page_size: toNullable(pageSize),
});

export const fromManageNeuron = ({
Expand Down
114 changes: 98 additions & 16 deletions packages/nns/src/governance.canister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Principal } from "@dfinity/principal";
import {
assertPercentageNumber,
createServices,
fromDefinedNullable,
fromNullable,
isNullish,
nonNullish,
Expand Down Expand Up @@ -142,6 +143,9 @@ export class GovernanceCanister {
* it is fetched using a query call.
*
* The backend treats `includeEmptyNeurons` as false if absent.
*
* The response might be paginated. In this case, all pages will be fetched in parallel and
* combined into a single response.
*/
public listNeurons = async ({
certified = true,
Expand All @@ -154,29 +158,107 @@ export class GovernanceCanister {
includeEmptyNeurons?: boolean;
includePublicNeurons?: boolean;
}): Promise<NeuronInfo[]> => {
const rawRequest = fromListNeurons({
neuronIds,
includeEmptyNeurons,
includePublicNeurons,
});
// The Ledger app version 2.4.9 does not support
// include_empty_neurons_readable_by_caller nor include_public_neurons_in_full_neurons,
// even when the field is absent,
// so we use the old service (which does not have these fields) if possible,
// in case the call will be signed by the Ledger device. We only have a
// certified version of the old service.
const useOldMethod =
isNullish(includeEmptyNeurons) &&
isNullish(includePublicNeurons) &&
certified;
const service = useOldMethod
? this.oldListNeuronsCertifiedService
: this.getGovernanceService(certified);
const raw_response = await service.list_neurons(rawRequest);

if (useOldMethod) {
return this.fetchNeuronsWithOldMethodAndNoPagination({
neuronIds,
});
}

// Fetch the first page to get the total number of pages
const { neurons: firstPageNeurons, totalPages = 1n } =
await this.fetchNeuronsPage({
certified,
neuronIds,
includeEmptyNeurons,
includePublicNeurons,
});

// https://github.com/dfinity/ic/blob/de17b0d718d6f279e9da8cd0f1b5de17036a6102/rs/nns/governance/api/src/ic_nns_governance.pb.v1.rs#L3543
if (totalPages === 1n) return firstPageNeurons;

const pagePromises = [];
for (let pageNumber = 1n; pageNumber <= totalPages; pageNumber++) {
pagePromises.push(
this.fetchNeuronsPage({
certified,
neuronIds,
includeEmptyNeurons,
includePublicNeurons,
pageNumber,
}),
);
}

const pageResults = await Promise.all(pagePromises);
const allNeurons = firstPageNeurons.concat(
...pageResults.map((result) => result.neurons),
);

return allNeurons;
};

// The Ledger app version 2.4.9 does not support
// include_empty_neurons_readable_by_caller nor include_public_neurons_in_full_neurons,
// even when the field is absent,
// so we use the old service (which does not have these fields) if possible,
// in case the call will be signed by the Ledger device. We only have a
// certified version of the old service.
private fetchNeuronsWithOldMethodAndNoPagination = async ({
neuronIds,
}: {
neuronIds?: NeuronId[];
}): Promise<NeuronInfo[]> => {
const rawRequest = fromListNeurons({
neuronIds,
});

const service = this.oldListNeuronsCertifiedService;
const rawResponse = await service.list_neurons(rawRequest);

return toArrayOfNeuronInfo({
response: raw_response,
response: rawResponse,
canisterId: this.canisterId,
});
};

private fetchNeuronsPage = async ({
certified,
neuronIds,
includeEmptyNeurons,
includePublicNeurons,
pageNumber,
}: {
certified: boolean;
neuronIds?: NeuronId[];
includeEmptyNeurons?: boolean;
includePublicNeurons?: boolean;
pageNumber?: bigint;
}): Promise<{ neurons: NeuronInfo[]; totalPages: bigint }> => {
// https://github.com/dfinity/ic/blob/59c4b87a337f1bd52a076c0f3e99acf155b79803/rs/nns/governance/src/governance.rs#L223
const PAGE_SIZE = 500n;

const rawRequest = fromListNeurons({
neuronIds,
includeEmptyNeurons,
includePublicNeurons,
pageNumber,
pageSize: PAGE_SIZE,
});

const service = this.getGovernanceService(certified);
const rawResponse = await service.list_neurons(rawRequest);
const neurons = toArrayOfNeuronInfo({
response: rawResponse,
canisterId: this.canisterId,
});
const totalPages = fromDefinedNullable(rawResponse.total_pages_available);

return { neurons, totalPages };
};

/**
Expand Down

0 comments on commit 9d6c8bc

Please sign in to comment.