Skip to content

Commit

Permalink
Merge pull request #312 from NodeFactoryIo/beacon-assign
Browse files Browse the repository at this point in the history
Beacon assign
  • Loading branch information
BeroBurny committed Nov 23, 2020
2 parents 8820c31 + 51f376d commit b5bdbb2
Show file tree
Hide file tree
Showing 17 changed files with 229 additions and 27 deletions.
1 change: 1 addition & 0 deletions src/renderer/constants/routes.ts
Expand Up @@ -5,6 +5,7 @@ export class Routes {
public static VALIDATOR_DETAILS = "/details/:publicKey";
public static BEACON_NODES = "/beacon-nodes";
public static ADD_BEACON_NODE = "/add-beacon-node/:validatorKey";
public static ASSIGN_BEACON_NODE = "/assign-beacon-node/:validatorKey";
public static ONBOARD_ROUTE_EVALUATE = (step: OnBoardingRoutes): string => `/onboard/${step}`;
}

Expand Down
2 changes: 2 additions & 0 deletions src/renderer/containers/Application.tsx
Expand Up @@ -8,6 +8,7 @@ import {AddBeaconNodeContainer} from "./AddBeaconNode/AddBeaconNode";
import {BeaconNodesContainer} from "./BeaconNodes/BeaconNodes";
import {DashboardContainer} from "./Dashboard/DashboardContainer";
import {ValidatorDetailsContainer} from "./ValidatorDetails/ValidatorDetailsContainer";
import {AssignBeaconNode} from "./AssignBeaconNode/AssignBeaconNode";

const Application = (): ReactElement => (
<div className='cg-app'>
Expand All @@ -18,6 +19,7 @@ const Application = (): ReactElement => (
<Route path={Routes.VALIDATOR_DETAILS} component={ValidatorDetailsContainer} />
<Route path={Routes.BEACON_NODES} component={BeaconNodesContainer} />
<Route path={Routes.ADD_BEACON_NODE} component={AddBeaconNodeContainer} />
<Route path={Routes.ASSIGN_BEACON_NODE} component={AssignBeaconNode} />
<Redirect from='/' to={Routes.DASHBOARD_ROUTE} />
</Switch>
</Router>
Expand Down
49 changes: 49 additions & 0 deletions src/renderer/containers/AssignBeaconNode/AssignBeaconNode.tsx
@@ -0,0 +1,49 @@
import React, {useState} from "react";
import OnBoardModal from "../Onboard/OnBoardModal";
import {Background} from "../../components/Background/Background";
import {useHistory} from "react-router-dom";
import {ButtonPrimary} from "../../components/Button/ButtonStandard";
import {Dropdown} from "../../components/Dropdown/Dropdown";
import {useDispatch, useSelector} from "react-redux";
import {getBeaconKeys} from "../../ducks/beacon/selectors";
import {setValidatorBeaconNode} from "../../ducks/validator/actions";
import {useParams} from "react-router";
import {Routes} from "../../constants/routes";

export const AssignBeaconNode: React.FC = () => {
const {validatorKey} = useParams();
const history = useHistory();

const [beaconIndex, setBeaconIndex] = useState<number>(0);
const beaconNodes = useSelector(getBeaconKeys);
const dispatch = useDispatch();

const onChange = (selected: number): void => {
setBeaconIndex(selected);
};

const onSubmit = (): void => {
dispatch(setValidatorBeaconNode(validatorKey, beaconNodes[beaconIndex]));
history.push(Routes.DASHBOARD_ROUTE);
};

return (
<Background>
<OnBoardModal history={history} currentStep={0}>
<h1>Validator Beacon Node</h1>
<p>Assigns Beacon Node to Validator.</p>
<br />

<div className='dropdown-input-container'>
<Dropdown options={beaconNodes} current={beaconIndex} onChange={onChange} />
</div>

<span className='submit-button-container'>
<ButtonPrimary buttonId='assign' onClick={onSubmit}>
ASSIGN
</ButtonPrimary>
</span>
</OnBoardModal>
</Background>
);
};
10 changes: 10 additions & 0 deletions src/renderer/containers/AssignBeaconNode/assignBeaconNode.scss
@@ -0,0 +1,10 @@
.dropdown-input-container {
display: flex;
flex-direction: column;
align-self: flex-start;
width: 100%;

h3 {
margin-right: 0.2rem;
}
}
24 changes: 9 additions & 15 deletions src/renderer/containers/Validator/Validator.tsx
@@ -1,6 +1,5 @@
import React, {useEffect, useState} from "react";
import {useSelector, useDispatch} from "react-redux";
import {useHistory} from "react-router";
import {PasswordPrompt} from "../../components/Prompt/PasswordPrompt";
import {Routes} from "../../constants/routes";

Expand All @@ -18,8 +17,9 @@ import {
stopActiveValidatorService,
startNewValidatorService,
} from "../../ducks/validator/actions";
import {getSelectedNetwork, getBeaconNodes} from "../../ducks/network/selectors";
import {getValidator} from "../../ducks/validator/selectors";
import {getSelectedNetwork} from "../../ducks/network/selectors";
import {getValidator, getValidatorBeaconNodes} from "../../ducks/validator/selectors";
import {Link} from "react-router-dom";

export interface IValidatorSimpleProps {
publicKey: string;
Expand All @@ -31,12 +31,8 @@ export interface IValidatorSimpleProps {
export const Validator: React.FunctionComponent<IValidatorSimpleProps> = (props: IValidatorSimpleProps) => {
const [askPassword, setAskPassword] = useState<string>(null);
const dispatch = useDispatch();
const history = useHistory();
const network = useSelector(getSelectedNetwork);
const validatorBeaconNodes = useSelector(getBeaconNodes);
const nodes = Object.prototype.hasOwnProperty.call(validatorBeaconNodes, props.publicKey)
? validatorBeaconNodes[props.publicKey]
: [];
const nodes = useSelector((state: IRootState) => getValidatorBeaconNodes(state, props));
const validator = useSelector((state: IRootState) => getValidator(state, props));

const isLoaded = !!validator;
Expand All @@ -47,10 +43,6 @@ export const Validator: React.FunctionComponent<IValidatorSimpleProps> = (props:
dispatch(updateValidatorChainData(props.publicKey));
}, [props.publicKey]);

const onAddButtonClick = (): void => {
history.push(Routes.ADD_BEACON_NODE.replace(":validatorKey", props.publicKey));
};

const renderBeaconNodes = (): React.ReactElement => {
return (
<div className='validator-nodes'>
Expand All @@ -65,14 +57,16 @@ export const Validator: React.FunctionComponent<IValidatorSimpleProps> = (props:
onClick={props.onBeaconNodeClick(node.url)}
title={node.localDockerId ? "Local Docker container" : "Remote Beacon node"}
url={node.url}
isSyncing={node.isSyncing}
value={node.currentSlot || "N/A"}
isSyncing={false}
value={"N/A"}
/>
</div>
))}
</div>

<AddButton onClick={onAddButtonClick} />
<Link to={Routes.ASSIGN_BEACON_NODE.replace(":validatorKey", props.publicKey)}>
<AddButton />
</Link>
</div>
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion src/renderer/ducks/beacon/selectors.ts
@@ -1,6 +1,10 @@
import {IRootState} from "../reducers";
import {IBeaconState} from "./slice";
import {IBeaconState, IBeaconDictionary} from "./slice";

export const getBeacons = (state: IRootState): IBeaconState => state.beacons;

export const getBeaconDictionary = (state: IRootState): IBeaconDictionary => state.beacons.beacons;

export const getBeaconKeys = (state: IRootState): string[] => state.beacons.keys;

export const getHasBeacons = (state: IRootState): boolean => !!state.beacons.keys.length;
2 changes: 1 addition & 1 deletion src/renderer/ducks/beacon/slice.ts
Expand Up @@ -6,7 +6,7 @@ type Beacon = {
localDockerId?: string;
};

interface IBeaconDictionary {
export interface IBeaconDictionary {
[key: string]: Beacon;
}

Expand Down
7 changes: 7 additions & 0 deletions src/renderer/ducks/validator/actions.ts
Expand Up @@ -11,6 +11,7 @@ export const {
loadValidatorStatus,
removeValidator,
stopValidatorService,
storeValidatorBeaconNodes,
} = validatorSlice.actions;

export const loadValidatorsAction = createAction("validator/loadValidatorsAction");
Expand All @@ -31,6 +32,12 @@ export const removeActiveValidator = createAction<RemoveActiveValidator>(
(publicKey: string, validatorIndex: number) => ({payload: publicKey, meta: validatorIndex}),
);

type SetValidatorBeaconNode = (publicKey: string, beaconNode: string) => {payload: string; meta: string};
export const setValidatorBeaconNode = createAction<SetValidatorBeaconNode>(
"validator/setValidatorBeaconNode",
(publicKey: string, beaconNode: string) => ({payload: beaconNode, meta: publicKey}),
);

export const updateValidatorChainData = createAction<string>("validator/updateValidatorChainData");

export const updateValidatorsFromChain = createAction<string[]>("validator/updateValidatorsFromChain");
Expand Down
43 changes: 33 additions & 10 deletions src/renderer/ducks/validator/sagas.ts
Expand Up @@ -26,6 +26,8 @@ import {
addNewValidator,
updateValidatorStatus,
loadValidatorsAction,
setValidatorBeaconNode,
storeValidatorBeaconNodes,
} from "./actions";
import {ICGKeystore} from "../../services/keystore";
import {loadValidatorBeaconNodes, unsubscribeToBlockListening} from "../network/actions";
Expand All @@ -38,6 +40,7 @@ import * as logger from "electron-log";
import {getAuthAccount} from "../auth/selectors";
import {getBeaconNodes} from "../network/selectors";
import {getValidators} from "./selectors";
import {ValidatorBeaconNodes} from "../../models/validatorBeaconNodes";

interface IValidatorServices {
[validatorAddress: string]: Validator;
Expand All @@ -46,21 +49,27 @@ interface IValidatorServices {
const validatorServices: IValidatorServices = {};

function* loadValidatorsSaga(): Generator<
SelectEffect | PutEffect | Promise<ICGKeystore[]>,
SelectEffect | PutEffect | Promise<ICGKeystore[]> | Promise<ValidatorBeaconNodes[]> | Promise<IValidator[]>,
void,
ICGKeystore[] & (CGAccount | null)
ICGKeystore[] & (CGAccount | null) & ValidatorBeaconNodes[] & IValidator[]
> {
const auth: CGAccount | null = yield select(getAuthAccount);
if (auth) {
const validators: ICGKeystore[] = yield auth.loadValidators();
const validatorArray: IValidator[] = validators.map((keyStore, index) => ({
name: keyStore.getName() ?? `Validator - ${index}`,
status: undefined,
publicKey: keyStore.getPublicKey(),
network: auth.getValidatorNetwork(keyStore.getPublicKey()),
keystore: keyStore,
isRunning: undefined,
}));
const validatorArray: IValidator[] = yield Promise.all(
validators.map(async (keyStore, index) => {
const beaconNodes = await database.validatorBeaconNodes.get(keyStore.getPublicKey());
return {
name: keyStore.getName() ?? `Validator - ${index}`,
status: undefined,
publicKey: keyStore.getPublicKey(),
network: auth.getValidatorNetwork(keyStore.getPublicKey()),
keystore: keyStore,
isRunning: undefined,
beaconNodes: beaconNodes?.nodes || [],
};
}),
);
yield put(loadValidators(validatorArray));
}
}
Expand All @@ -74,6 +83,7 @@ export function* addNewValidatorSaga(action: ReturnType<typeof addNewValidator>)
keystore,
status: undefined,
isRunning: false,
beaconNodes: [],
};

yield put(addValidator(validator));
Expand Down Expand Up @@ -173,6 +183,18 @@ function* stopService(action: ReturnType<typeof stopActiveValidatorService>): Ge
yield put(stopValidatorService(publicKey));
}

function* setValidatorBeacon({
payload,
meta,
}: ReturnType<typeof setValidatorBeaconNode>): Generator<
PutEffect | Promise<ValidatorBeaconNodes>,
void,
ValidatorBeaconNodes
> {
const beaconNodes = yield database.validatorBeaconNodes.upsert(meta, [payload]);
yield put(storeValidatorBeaconNodes(beaconNodes.nodes, meta));
}

export function* validatorSagaWatcher(): Generator {
yield all([
takeEvery(loadValidatorsAction, loadValidatorsSaga),
Expand All @@ -183,5 +205,6 @@ export function* validatorSagaWatcher(): Generator {
takeEvery(updateValidatorStatus, loadValidatorStatusSaga),
takeEvery(startNewValidatorService, startService),
takeEvery(stopActiveValidatorService, stopService),
takeEvery(setValidatorBeaconNode, setValidatorBeacon),
]);
}
7 changes: 7 additions & 0 deletions src/renderer/ducks/validator/selectors.ts
Expand Up @@ -3,6 +3,7 @@ import {createSelector} from "@reduxjs/toolkit";
import {getPublicKeyFromProps} from "../fromProps";
import {IByPublicKey} from "./slice";
import {getSelectedNetwork} from "../network/selectors";
import {getBeaconDictionary} from "../beacon/selectors";

export const getValidators = (state: IRootState): IByPublicKey => state.validators.byPublicKey;

Expand All @@ -18,3 +19,9 @@ export const getNetworkValidators = createSelector(
export const getValidator = createSelector(getValidators, getPublicKeyFromProps, (validators, key) => validators[key]);

export const getValidatorNetwork = createSelector(getValidator, (validator) => validator?.network);

export const getValidatorBeaconNodes = createSelector(
getValidator,
getBeaconDictionary,
(validator, beacons) => validator?.beaconNodes.map((url) => beacons[url]).filter((beacon) => !!beacon) || [],
);
10 changes: 10 additions & 0 deletions src/renderer/ducks/validator/slice.ts
Expand Up @@ -13,6 +13,7 @@ export interface IValidator {
balance?: bigint;
keystore: ICGKeystore;
isRunning: boolean;
beaconNodes: string[];
}

export interface IValidatorComplete extends IValidator {
Expand Down Expand Up @@ -74,6 +75,15 @@ export const validatorSlice = createSlice({
meta: publicKey,
}),
},
storeValidatorBeaconNodes: {
reducer: (state, action: PayloadAction<string[], string, string>): void => {
state.byPublicKey[action.meta].beaconNodes = action.payload;
},
prepare: (beaconNodes: string[], publicKey: string): {payload: string[]; meta: string} => ({
payload: beaconNodes,
meta: publicKey,
}),
},
stopValidatorService: (state, action: PayloadAction<string>): void => {
state.byPublicKey[action.payload].isRunning = false;
},
Expand Down
9 changes: 9 additions & 0 deletions src/renderer/models/types/validatorBeaconNodes.ts
@@ -0,0 +1,9 @@
import {ContainerType, ListType} from "@chainsafe/ssz";
import {StringType} from "./basic";
import {ValidatorBeaconNodes} from "../validatorBeaconNodes";

export const ValidatorBeaconNodesType = new ContainerType<ValidatorBeaconNodes>({
fields: {
nodes: new ListType({elementType: new StringType(), limit: 1}),
},
});
30 changes: 30 additions & 0 deletions src/renderer/models/validatorBeaconNodes.ts
@@ -0,0 +1,30 @@
interface IValidatorBeaconNodes {
nodes: string[];
}

export class ValidatorBeaconNodes implements IValidatorBeaconNodes {
public nodes: string[] = [];

public static createNodes(nodes: string[]): ValidatorBeaconNodes | null {
const list = new ValidatorBeaconNodes();
nodes.forEach((url) => list.addNode(url));

return list;
}

// Add new node to the list that has unique values
public addNode(url: string): void {
if (!this.nodes.some((beaconUrl) => beaconUrl === url)) {
this.nodes.push(url);
}
}

public removeNode(url: string): boolean {
const index = this.nodes.findIndex((beaconUrl) => beaconUrl !== url);
if (index !== -1) {
this.nodes.splice(index, 1);
return true;
}
return false;
}
}
3 changes: 3 additions & 0 deletions src/renderer/services/db/api/database.ts
Expand Up @@ -7,6 +7,7 @@ import {ValidatorAttestationsRepository} from "./repositories/validator/attestat
import {ValidatorBlocksRepository} from "./repositories/validator/blocks";
import {ValidatorNetworkRepository} from "./repositories/validator/network";
import {BeaconsRepository} from "./repositories/beacons";
import {ValidatorBeaconNodesRepository} from "./repositories/validatorBeaconNodes";

interface IValidatorDB {
attestations: ValidatorAttestationsRepository;
Expand All @@ -20,6 +21,7 @@ export class CGDatabase extends DatabaseService {
public validator: IValidatorDB;
public settings: SettingsRepository;
public beacons: BeaconsRepository;
public validatorBeaconNodes: ValidatorBeaconNodesRepository;

public constructor(opts: IDatabaseApiOptions) {
super(opts);
Expand All @@ -32,6 +34,7 @@ export class CGDatabase extends DatabaseService {
};
this.settings = new SettingsRepository(this.db);
this.beacons = new BeaconsRepository(this.db);
this.validatorBeaconNodes = new ValidatorBeaconNodesRepository(this.db);
}
}

Expand Down

0 comments on commit b5bdbb2

Please sign in to comment.