Skip to content

Commit

Permalink
Feat: Add Epoch committee (#1258)
Browse files Browse the repository at this point in the history
* fix: add Epoch page

* feat: Add committee to epoch page

* feat: add api committee route

* fix: hook request param
  • Loading branch information
brancoder authored Mar 6, 2024
1 parent 6c9442e commit 82b4c21
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 0 deletions.
11 changes: 11 additions & 0 deletions api/src/models/api/nova/IEpochCommitteeRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface IEpochCommitteeRequest {
/**
* The network to search on.
*/
network: string;

/**
* The epoch index to get the committee for.
*/
epochIndex: string;
}
8 changes: 8 additions & 0 deletions api/src/models/api/nova/IEpochCommitteeResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { CommitteeResponse } from "@iota/sdk-nova";
import { IResponse } from "./IResponse";

export interface IEpochCommitteeResponse extends IResponse {
committeeResponse?: CommitteeResponse;
}
1 change: 1 addition & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,4 +293,5 @@ export const routes: IRoute[] = [
func: "get",
},
{ path: "/nova/slot/:network/:slotIndex", method: "get", folder: "nova/slot", func: "get" },
{ path: "/nova/epoch/committee/:network/:epochIndex", method: "get", folder: "nova/epoch/committee", func: "get" },
];
30 changes: 30 additions & 0 deletions api/src/routes/nova/epoch/committee/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ServiceFactory } from "../../../../factories/serviceFactory";
import { IEpochCommitteeRequest } from "../../../../models/api/nova/IEpochCommitteeRequest";
import { IEpochCommitteeResponse } from "../../../../models/api/nova/IEpochCommitteeResponse";
import { IConfiguration } from "../../../../models/configuration/IConfiguration";
import { NOVA } from "../../../../models/db/protocolVersion";
import { NetworkService } from "../../../../services/networkService";
import { NovaApiService } from "../../../../services/nova/novaApiService";
import { ValidationHelper } from "../../../../utils/validationHelper";

/**
* Fetch the committee from the network.
* @param _ The configuration.
* @param request The request.
* @returns The response.
*/
export async function get(_: IConfiguration, request: IEpochCommitteeRequest): Promise<IEpochCommitteeResponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
ValidationHelper.oneOf(request.network, networks, "network");
ValidationHelper.numberFromString(request.epochIndex, "epochIndex");

const networkConfig = networkService.get(request.network);

if (networkConfig.protocolVersion !== NOVA) {
return {};
}

const novaApiService = ServiceFactory.get<NovaApiService>(`api-service-${networkConfig.network}`);
return novaApiService.getEpochCommittee(Number(request.epochIndex));
}
20 changes: 20 additions & 0 deletions api/src/services/nova/novaApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { IBlockResponse } from "../../models/api/nova/IBlockResponse";
import { ICongestionResponse } from "../../models/api/nova/ICongestionResponse";
import { IDelegationDetailsResponse } from "../../models/api/nova/IDelegationDetailsResponse";
import { IDelegationWithDetails } from "../../models/api/nova/IDelegationWithDetails";
import { IEpochCommitteeResponse } from "../../models/api/nova/IEpochCommitteeResponse";
import { INftDetailsResponse } from "../../models/api/nova/INftDetailsResponse";
import { IOutputDetailsResponse } from "../../models/api/nova/IOutputDetailsResponse";
import { IRewardsResponse } from "../../models/api/nova/IRewardsResponse";
Expand Down Expand Up @@ -442,6 +443,25 @@ export class NovaApiService {
}
}

/**
* Get the epoch committee.
* @param epochIndex The epoch index to get the committee for.
* @returns The epoch committee.
*/
public async getEpochCommittee(epochIndex: number): Promise<IEpochCommitteeResponse> {
try {
const response = await this.client.getCommittee(epochIndex);

if (response) {
return {
committeeResponse: response,
};
}
} catch (e) {
logger.error(`Failed fetching committee for epoch index ${epochIndex}. Cause: ${e}`);
}
}

/**
* Find item on the stardust network.
* @param query The query to use for finding items.
Expand Down
67 changes: 67 additions & 0 deletions client/src/app/routes/nova/EpochPage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
@import "../../../scss/fonts";
@import "../../../scss/mixins";
@import "../../../scss/media-queries";
@import "../../../scss/variables";

.epoch-page {
.validators-page__header {
display: flex;
flex-direction: column;
align-items: flex-start;

.header__title {
margin-bottom: 8px;
}
}

.all-validators__section {
font-family: $metropolis;
margin-top: 40px;
background-color: $gray-1;
border-radius: 8px;

.all-validators__header {
width: fit-content;
margin: 0 auto;
padding: 20px;
}

.all-validators__wrapper {
margin: 0 20px 20px;

.validator-item {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
margin: 0px 12px;
align-items: center;
line-height: 32px;
justify-content: center;
background-color: $gray-3;
border-radius: 4px;

&.table-header {
background-color: $gray-5;
}

&:not(:last-child) {
margin-bottom: 12px;
}

.validator-item__address,
.validator-item__state,
.validator-item__fixed-cost,
.validator-item__pool-stake,
.validator-item__validator-stake,
.validator-item__delegator-stake {
display: flex;
margin: 0 auto;
justify-content: center;
}

.validator-item__address {
width: 140px;
}
}
}
}
}
43 changes: 43 additions & 0 deletions client/src/app/routes/nova/EpochPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { useEpochProgress } from "~/helpers/nova/hooks/useEpochProgress";
import Modal from "~/app/components/Modal";
import NotFound from "~/app/components/NotFound";
import moment from "moment";
import useEpochCommittee from "~/helpers/nova/hooks/useEpochCommittee";
import TruncatedId from "~/app/components/stardust/TruncatedId";
import "./EpochPage.scss";

export interface EpochPageProps {
/**
Expand All @@ -24,6 +27,7 @@ const EpochPage: React.FC<RouteComponentProps<EpochPageProps>> = ({
},
}) => {
const { epochUnixTimeRange, epochProgressPercent, registrationTime } = useEpochProgress(Number(epochIndex));
const { epochCommittee } = useEpochCommittee(network, epochIndex);

if (epochIndex === null || !epochUnixTimeRange || moment().unix() < epochUnixTimeRange.from) {
return <NotFound query={epochIndex} searchTarget="epoch" />;
Expand Down Expand Up @@ -82,6 +86,45 @@ const EpochPage: React.FC<RouteComponentProps<EpochPageProps>> = ({
<div className="label">Registration end:</div>
<div className="value">{registrationTimeRemaining}</div>
</div>
<div className="section--data">
<div className="label">Total pool stake:</div>
<div className="value">{epochCommittee?.totalStake}</div>
</div>
<div className="section--data">
<div className="label">Total validator stake:</div>
<div className="value">{epochCommittee?.totalValidatorStake}</div>
</div>
<div className="section--data">
<div className="label">Total delegated stake:</div>
<div className="value">{Number(epochCommittee?.totalStake) - Number(epochCommittee?.totalValidatorStake)}</div>
</div>
</div>

<div className="section all-validators__section">
<h2 className="all-validators__header">Committee</h2>
<div className="all-validators__wrapper">
<div className="validator-item table-header">
<div className="validator-item__address">Address</div>
<div className="validator-item__fixed-cost">Cost</div>
<div className="validator-item__pool-stake">Pool stake</div>
<div className="validator-item__validator-stake">Validator stake</div>
<div className="validator-item__delegator-stake">Delegated stake</div>
</div>
{epochCommittee?.committee.map((validator, idx) => {
const delegatorStake = Number(validator.poolStake) - Number(validator.validatorStake);
return (
<div className="validator-item" key={`validator-${idx}`}>
<div className="validator-item__address">
<TruncatedId id={validator.address} />
</div>
<div className="validator-item__fixed-cost">{validator.fixedCost}</div>
<div className="validator-item__pool-stake">{validator.poolStake}</div>
<div className="validator-item__validator-stake">{validator.validatorStake}</div>
<div className="validator-item__delegator-stake">{delegatorStake}</div>
</div>
);
})}
</div>
</div>
</div>
</div>
Expand Down
52 changes: 52 additions & 0 deletions client/src/helpers/nova/hooks/useEpochCommittee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { CommitteeResponse } from "@iota/sdk-wasm-nova/web";
import { useEffect, useState } from "react";
import { ServiceFactory } from "~/factories/serviceFactory";
import { useIsMounted } from "~/helpers/hooks/useIsMounted";
import { NOVA } from "~/models/config/protocolVersion";
import { NovaApiClient } from "~/services/nova/novaApiClient";

interface IUseEpochCommittee {
epochCommittee: CommitteeResponse | null;
error: string | undefined;
isLoading: boolean;
}

export default function useEpochCommittee(network: string, epochIndex?: string): IUseEpochCommittee {
const isMounted = useIsMounted();
const [apiClient] = useState(ServiceFactory.get<NovaApiClient>(`api-client-${NOVA}`));
const [epochCommittee, setEpochCommittee] = useState<CommitteeResponse | null>(null);
const [error, setError] = useState<string | undefined>();
const [isLoading, setIsLoading] = useState<boolean>(true);

useEffect(() => {
setIsLoading(true);
setEpochCommittee(null);
if (epochIndex) {
// eslint-disable-next-line no-void
void (async () => {
apiClient
.getEpochCommittee({
network,
epochIndex,
})
.then((response) => {
if (isMounted) {
setEpochCommittee(response.committeeResponse);
setError(response.error);
}
})
.finally(() => {
setIsLoading(false);
});
})();
} else {
setIsLoading(false);
}
}, [epochIndex]);

return {
epochCommittee,
error,
isLoading,
};
}
11 changes: 11 additions & 0 deletions client/src/models/api/nova/IEpochCommitteeRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface IEpochCommitteeRequest {
/**
* The network to search on.
*/
network: string;

/**
* The epoch index to get the committee for.
*/
epochIndex: string;
}
6 changes: 6 additions & 0 deletions client/src/models/api/nova/IEpochCommitteeResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { CommitteeResponse } from "@iota/sdk-wasm-nova/web";
import { IResponse } from "../IResponse";

export interface IEpochCommitteeResponse extends IResponse {
committeeResponse: CommitteeResponse;
}
11 changes: 11 additions & 0 deletions client/src/services/nova/novaApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { ILatestSlotCommitmentResponse } from "~/models/api/nova/ILatestSlotComm
import { IDelegationDetailsResponse } from "~/models/api/nova/IDelegationDetailsResponse";
import { ISlotBlocksRequest } from "~/models/api/nova/ISlotBlocksRequest";
import { ISlotBlocksResponse } from "~/models/api/nova/ISlotBlocksResponse";
import { IEpochCommitteeRequest } from "~/models/api/nova/IEpochCommitteeRequest";
import { IEpochCommitteeResponse } from "~/models/api/nova/IEpochCommitteeResponse";

/**
* Class to handle api communications on nova.
Expand Down Expand Up @@ -251,6 +253,15 @@ export class NovaApiClient extends ApiClient {
);
}

/**
* Get the epoch committee.
* @param request The request to send.
* @returns The response from the request.
*/
public async getEpochCommittee(request: IEpochCommitteeRequest): Promise<IEpochCommitteeResponse> {
return this.callApi<unknown, IEpochCommitteeResponse>(`nova/epoch/committee/${request.network}/${request.epochIndex}`, "get");
}

/**
* Get the stats.
* @param request The request to send.
Expand Down

0 comments on commit 82b4c21

Please sign in to comment.