update types

This commit is contained in:
Fouad
2021-07-22 12:00:46 +01:00
parent c8a02e1354
commit 793dbdef7e
3 changed files with 428 additions and 356 deletions
+2 -5
View File
@@ -1,15 +1,12 @@
import {Alert} from '@material-ui/lab';
import { getDisplayExecGasFee } from "../common/helpers";
type ExecFeeNoticeProps = {
name: string
}
const ExecFeeNotice = (props: ExecFeeNoticeProps) => {
const ExecFeeNotice = ({name}: {name: string}) => {
return (
<Alert severity="info">
The gas fee for
<strong> {props.name} </strong>
<strong> {name} </strong>
{`is ${getDisplayExecGasFee()}`}
</Alert>
)
+425 -350
View File
@@ -1,380 +1,455 @@
import React from 'react';
import Grid from '@material-ui/core/Grid';
import TextField from '@material-ui/core/TextField';
import { Button, Checkbox, FormControlLabel, InputAdornment } from "@material-ui/core";
import React from "react";
import Grid from "@material-ui/core/Grid";
import TextField from "@material-ui/core/TextField";
import {
Button,
Checkbox,
FormControlLabel,
InputAdornment,
} from "@material-ui/core";
import bs58 from "bs58";
import semver from "semver"
import semver from "semver";
import { NodeType } from "../../common/node";
import { theme } from "../../lib/theme";
import { basicRawCoinValueValidation, makeBasicStyle, validateRawPort } from "../../common/helpers";
import {
basicRawCoinValueValidation,
makeBasicStyle,
validateRawPort,
} from "../../common/helpers";
import { Coin, nativeToPrintable } from "@nymproject/nym-validator-client";
import { DENOM } from "../../pages/_app";
import { printableBalanceToNative } from "@nymproject/nym-validator-client/dist/currency";
import { BondingInformation } from "./NodeBond";
const DEFAULT_MIX_PORT = 1789
const DEFAULT_VERLOC_PORT = 1790
const DEFAULT_HTTP_API_PORT = 8000
const DEFAULT_CLIENTS_PORT = 9000
const DEFAULT_MIX_PORT = 1789;
const DEFAULT_VERLOC_PORT = 1790;
const DEFAULT_HTTP_API_PORT = 8000;
const DEFAULT_CLIENTS_PORT = 9000;
type BondNodeFormProps = {
type: NodeType
minimumMixnodeBond: Coin,
minimumGatewayBond: Coin,
onSubmit: (event: any) => void
}
type TBondNodeFormProps = {
type: NodeType;
minimumMixnodeBond: Coin;
minimumGatewayBond: Coin;
onSubmit: (event: any) => void;
};
export default function BondNodeForm(props: BondNodeFormProps) {
const classes = makeBasicStyle(theme);
type TFormInput = {
amount: string;
host: string;
http_api_port: string;
mixPort: string;
verlocPort: string;
sphinxKey: string;
identityKey: string;
version: string;
location?: string;
clientsPort?: string;
httpApiPort?: string;
};
const [validity, setValidity] = React.useState({
validAmount: true,
validSphinxKey: true,
validIdentityKey: true,
validHost: true,
validVersion: true,
validLocation: true,
validMixPort: true,
export default function BondNodeForm(props: TBondNodeFormProps) {
const classes = makeBasicStyle(theme);
// this should have probably be somehow split to be subclasses of the validity matrix
// the above is more true now as more fields are added. This looks kinda disgusting...
// mixnode-specific:
validVerlocPort: true,
validHttpApiPort: true,
const [validity, setValidity] = React.useState({
validAmount: true,
validSphinxKey: true,
validIdentityKey: true,
validHost: true,
validVersion: true,
validLocation: true,
validMixPort: true,
// gateway-specific:
validClientsPort: true,
})
// this should have probably be somehow split to be subclasses of the validity matrix
// the above is more true now as more fields are added. This looks kinda disgusting...
// mixnode-specific:
validVerlocPort: true,
validHttpApiPort: true,
const [advancedShown, setAdvancedShown] = React.useState(false)
// gateway-specific:
validClientsPort: true,
});
const handleCheckboxToggle = () => {
setAdvancedShown((prevSet) => !prevSet);
const [advancedShown, setAdvancedShown] = React.useState(false);
const handleCheckboxToggle = () => {
setAdvancedShown((prevSet) => !prevSet);
};
const validateForm = ({
amount,
sphinxKey,
identityKey,
host,
version,
verlocPort,
location,
mixPort,
httpApiPort,
clientsPort,
}: TFormInput): boolean => {
let validAmount = validateAmount(amount);
let validSphinxKey = validateKey(sphinxKey);
let validIdentityKey = validateKey(identityKey);
let validHost = validateHost(host);
let validVersion = validateVersion(version);
let validLocation =
props.type == NodeType.Gateway ? validateLocation(location) : true;
let newValidity = {
validAmount: validAmount,
validSphinxKey: validSphinxKey,
validIdentityKey: validIdentityKey,
validHost: validHost,
validVersion: validVersion,
validLocation: validLocation,
};
if (advancedShown) {
let validMixPort = validateRawPort(mixPort);
let validVerlocPort =
props.type == NodeType.Mixnode ? validateRawPort(verlocPort) : true;
let validHttpApiPort =
props.type == NodeType.Mixnode ? validateRawPort(httpApiPort) : true;
let validClientsPort =
props.type == NodeType.Gateway ? validateRawPort(clientsPort) : true;
newValidity = {
...newValidity,
...{
validMixPort: validMixPort,
validVerlocPort: validVerlocPort,
validHttpApiPort: validHttpApiPort,
validClientsPort: validClientsPort,
},
};
}
setValidity((previousState) => {
return { ...previousState, ...newValidity };
});
const validateForm = (event: any): boolean => {
let validAmount = validateAmount(event.target.amount.value);
let validSphinxKey = validateKey(event.target.sphinxkey.value);
let validIdentityKey = validateKey(event.target.identity.value);
let validHost = validateHost(event.target.host.value);
let validVersion = validateVersion(event.target.version.value);
// just AND everything together
const reducer = (acc, current) => acc && current;
return Object.entries(newValidity)
.map((entry) => entry[1])
.reduce(reducer, true);
};
let validLocation = (props.type == NodeType.Gateway) ? validateLocation(event.target.location.value) : true;
const validateAmount = (rawValue: string): boolean => {
// tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc
if (!basicRawCoinValueValidation(rawValue)) {
return false;
}
let newValidity = {
validAmount: validAmount,
validSphinxKey: validSphinxKey,
validIdentityKey: validIdentityKey,
validHost: validHost,
validVersion: validVersion,
// this conversion seems really iffy but I'm not sure how to better approach it
let nativeValueString = printableBalanceToNative(rawValue);
let nativeValue = parseInt(nativeValueString);
if (props.type == NodeType.Mixnode) {
return nativeValue >= parseInt(props.minimumMixnodeBond.amount);
} else {
return nativeValue >= parseInt(props.minimumGatewayBond.amount);
}
};
validLocation: validLocation,
}
const validateKey = (key: string): boolean => {
// it must be a valid base58 key
try {
const bytes = bs58.decode(key);
// of length 32
return bytes.length === 32;
} catch {
return false;
}
};
if (advancedShown) {
let validMixPort = validateRawPort(event.target.mixPort.value)
let validVerlocPort = (props.type == NodeType.Mixnode) ? validateRawPort(event.target.verlocPort.value) : true;
let validHttpApiPort = (props.type == NodeType.Mixnode) ? validateRawPort(event.target.httpApiPort.value) : true;
let validClientsPort = (props.type == NodeType.Gateway) ? validateRawPort(event.target.clientsPort.value) : true;
const validateHost = (host: string): boolean => {
// I don't think that proper checks are in scope of the change here
// what would need to be checked is whether one of the following is true:
// - host is an ipv4 address
// - host is an ipv6 address
// - host is a valid hostname
newValidity = {
...newValidity, ...{
validMixPort: validMixPort,
validVerlocPort: validVerlocPort,
validHttpApiPort: validHttpApiPort,
validClientsPort: validClientsPort,
// so at least perform the dumbest possible checks
// ipv4 needs 4 dot-separated octets
// ipv6 can have multiple possible representations, but it needs to contain at least two colons
// a hostname (in this case) needs to have a top level domain present
const dot_occurrences = host.split(".").length - 1;
const colon_occurrences = host.split(":").length - 1;
if (dot_occurrences == 3) {
// possible ipv4
// make sure it has no ports attached!
return colon_occurrences == 0;
} else if (colon_occurrences >= 2) {
// possible ipv6
return true;
} else if (dot_occurrences >= 1) {
// possible hostname
// make sure it has no ports attached!
return colon_occurrences == 0;
}
return false;
};
const validateVersion = (version: string): boolean => {
// check if its a valid semver
return semver.valid(version) && semver.minor(version) >= 11;
};
const validateLocation = (location: string): boolean => {
// right now only perform the stupid check of whether the user copy-pasted the tooltip... (with or without brackets)
return !location.trim().includes("physical location of your node");
};
const constructMixnodeBondingInfo = ({
amount,
host,
httpApiPort,
mixPort,
verlocPort,
sphinxKey,
identityKey,
version,
}: TFormInput): BondingInformation => {
return {
amount,
nodeDetails: {
host,
http_api_port: advancedShown
? parseInt(httpApiPort)
: DEFAULT_HTTP_API_PORT,
mix_port: advancedShown ? parseInt(mixPort) : DEFAULT_MIX_PORT,
verloc_port: advancedShown ? parseInt(verlocPort) : DEFAULT_VERLOC_PORT,
sphinx_key: sphinxKey,
identity_key: identityKey,
version,
},
};
};
const constructGatewayBondingInfo = (
data: TFormInput
): BondingInformation => {
return {
amount: data.amount,
nodeDetails: {
host: data.host,
mix_port: advancedShown ? parseInt(data.mixPort) : DEFAULT_MIX_PORT,
clients_port: advancedShown
? parseInt(data.clientsPort)
: DEFAULT_CLIENTS_PORT,
sphinx_key: data.sphinxKey,
identity_key: data.identityKey,
version: data.version,
location: data.location,
},
};
};
const submitForm = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const target = event.target as unknown as TFormInput;
if (validateForm(target)) {
if (props.type == NodeType.Mixnode) {
return props.onSubmit(constructMixnodeBondingInfo(target));
} else {
return props.onSubmit(constructGatewayBondingInfo(target));
}
}
};
let minimumBond = props.minimumMixnodeBond;
if (props.type == NodeType.Gateway) {
minimumBond = props.minimumGatewayBond;
}
// if this whole interface wasn't to be completely redone in a month time, I would have definitely redone the form
// but I guess it's fine for time being
return (
<form onSubmit={submitForm}>
<Grid container spacing={3}>
<Grid item xs={12} sm={8}>
<TextField
required
id="amount"
name="amount"
label={`Amount to bond (minimum ${nativeToPrintable(
minimumBond.amount
)} ${minimumBond.denom})`}
error={!validity.validAmount}
{...(!validity.validAmount
? {
helperText: `Enter a valid bond amount (minimum ${nativeToPrintable(
minimumBond.amount
)})`,
}
}
}
: {})}
fullWidth
InputProps={{
endAdornment: (
<InputAdornment position="end">{DENOM}</InputAdornment>
),
}}
/>
</Grid>
setValidity((previousState) => {
return {...previousState, ...newValidity}
});
<Grid item xs={12}>
<TextField
error={!validity.validIdentityKey}
required
id="identity"
name="identity"
label="Identity key"
fullWidth
/>
</Grid>
<Grid item xs={12}>
<TextField
error={!validity.validSphinxKey}
required
id="sphinxKey"
name="sphinxKey"
label="Sphinx key"
fullWidth
{...(!validity.validSphinxKey
? { helperText: "Enter a valid sphinx key" }
: {})}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
error={!validity.validHost}
required
id="host"
name="host"
label="Host"
fullWidth
{...(!validity.validHost
? { helperText: "Enter a valid IP or a hostname (without port)" }
: {})}
/>
</Grid>
// just AND everything together
const reducer = (acc, current) => acc && current;
return Object.entries(newValidity).map((entry) => entry[1]).reduce(reducer, true)
}
{/* if it's a gateway - get location */}
<Grid item xs={12} sm={6}>
{props.type === NodeType.Gateway && (
<TextField
error={!validity.validLocation}
required
id="location"
name="location"
label="Location"
fullWidth
{...(!validity.validLocation
? { helperText: "Enter a valid location of your node" }
: {})}
/>
)}
</Grid>
const validateAmount = (rawValue: string): boolean => {
// tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc
if (!basicRawCoinValueValidation(rawValue)) {
return false
}
// this conversion seems really iffy but I'm not sure how to better approach it
let nativeValueString = printableBalanceToNative(rawValue)
let nativeValue = parseInt(nativeValueString)
if (props.type == NodeType.Mixnode) {
return nativeValue >= parseInt(props.minimumMixnodeBond.amount)
} else {
return nativeValue >= parseInt(props.minimumGatewayBond.amount)
}
}
const validateKey = (key: string): boolean => {
// it must be a valid base58 key
try {
const bytes = bs58.decode(key);
// of length 32
return bytes.length === 32
} catch {
return false
}
}
const validateHost = (host: string): boolean => {
// I don't think that proper checks are in scope of the change here
// what would need to be checked is whether one of the following is true:
// - host is an ipv4 address
// - host is an ipv6 address
// - host is a valid hostname
// so at least perform the dumbest possible checks
// ipv4 needs 4 dot-separated octets
// ipv6 can have multiple possible representations, but it needs to contain at least two colons
// a hostname (in this case) needs to have a top level domain present
const dot_occurrences = host.split('.').length - 1
const colon_occurrences = host.split(':').length - 1
if (dot_occurrences == 3) {
// possible ipv4
// make sure it has no ports attached!
return colon_occurrences == 0
} else if (colon_occurrences >= 2) {
// possible ipv6
return true
} else if (dot_occurrences >= 1) {
// possible hostname
// make sure it has no ports attached!
return colon_occurrences == 0
}
return false
}
const validateVersion = (version: string): boolean => {
// check if its a valid semver
return semver.valid(version) && semver.minor(version) >= 11
}
const validateLocation = (location: string): boolean => {
// right now only perform the stupid check of whether the user copy-pasted the tooltip... (with or without brackets)
return !location.trim().includes("physical location of your node")
}
const constructMixnodeBondingInfo = (event: any): BondingInformation => {
return {
amount: event.target.amount.value,
nodeDetails: {
host: event.target.host.value,
http_api_port: advancedShown ? parseInt(event.target.httpApiPort.value) : DEFAULT_HTTP_API_PORT,
mix_port: advancedShown ? parseInt(event.target.mixPort.value) : DEFAULT_MIX_PORT,
verloc_port: advancedShown ? parseInt(event.target.verlocPort.value) : DEFAULT_VERLOC_PORT,
sphinx_key: event.target.sphinxkey.value,
identity_key: event.target.identity.value,
version: event.target.version.value,
}
}
}
const constructGatewayBondingInfo = (event: any): BondingInformation => {
return {
amount: event.target.amount.value,
nodeDetails: {
host: event.target.host.value,
mix_port: advancedShown ? parseInt(event.target.mixPort.value) : DEFAULT_MIX_PORT,
clients_port: advancedShown ? parseInt(event.target.clientsPort.value) : DEFAULT_CLIENTS_PORT,
sphinx_key: event.target.sphinxkey.value,
identity_key: event.target.identity.value,
version: event.target.version.value,
location: event.target.location.value
}
}
}
const submitForm = (event: any) => {
event.preventDefault()
if (validateForm(event)) {
if (props.type == NodeType.Mixnode) {
return props.onSubmit(constructMixnodeBondingInfo(event))
} else {
return props.onSubmit(constructGatewayBondingInfo(event))
}
}
}
let minimumBond = props.minimumMixnodeBond;
if (props.type == NodeType.Gateway) {
minimumBond = props.minimumGatewayBond
}
// if this whole interface wasn't to be completely redone in a month time, I would have definitely redone the form
// but I guess it's fine for time being
return (
<form onSubmit={submitForm}>
<Grid container spacing={3}>
<Grid item xs={12} sm={8}>
<TextField
required
id="amount"
name="amount"
label={`Amount to bond (minimum ${nativeToPrintable(minimumBond.amount)} ${minimumBond.denom})`}
error={!validity.validAmount}
{...(!validity.validAmount ? {helperText: `Enter a valid bond amount (minimum ${nativeToPrintable(minimumBond.amount)})`} : {})}
fullWidth
InputProps={{
endAdornment:
<InputAdornment position="end">{DENOM}</InputAdornment>
}}
/>
</Grid>
<Grid item xs={12}>
<TextField
error={!validity.validIdentityKey}
required
id="identity"
name="identity"
label="Identity key"
fullWidth
/>
</Grid>
<Grid item xs={12}>
<TextField
error={!validity.validSphinxKey}
required
id="sphinxkey"
name="sphinxkey"
label="Sphinx key"
fullWidth
{...(!validity.validSphinxKey ? {helperText: "Enter a valid sphinx key"} : {})}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
error={!validity.validHost}
required
id="host"
name="host"
label="Host"
fullWidth
{...(!validity.validHost ? {helperText: "Enter a valid IP or a hostname (without port)"} : {})}
/>
</Grid>
{/* if it's a gateway - get location */}
<Grid item xs={12} sm={6}>{
props.type === NodeType.Gateway &&
<TextField
error={!validity.validLocation}
required
id="location"
name="location"
label="Location"
fullWidth
{...(!validity.validLocation ? {helperText: "Enter a valid location of your node"} : {})}
/>
}
</Grid>
<Grid item xs={12} sm={6}>
<TextField
error={!validity.validVersion}
required
id="version"
name="version"
label="Version"
fullWidth
{...(!validity.validVersion ? {helperText: "Enter a valid version (min. 0.11.0), like 0.11.0"} : {})}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={
<Checkbox
checked={advancedShown}
onChange={handleCheckboxToggle}
/>
}
label="Show advanced options"
/>
</Grid>
{advancedShown &&
<React.Fragment>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validMixPort}
variant="outlined"
id="mixPort"
name="mixPort"
label="Mix Port"
fullWidth
defaultValue={DEFAULT_MIX_PORT}
{...(!validity.validMixPort ? {helperText: "Enter a valid version, like 0.10.0"} : {})}
/>
</Grid>
{/*yes, I also hate so many layers of indentation here*/}
{props.type === NodeType.Mixnode ? (
<React.Fragment>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validVerlocPort}
variant="outlined"
id="verlocPort"
name="verlocPort"
label="Verloc Port"
fullWidth
defaultValue={DEFAULT_VERLOC_PORT}
/>
</Grid>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validHttpApiPort}
variant="outlined"
id="httpApiPort"
name="httpApiPort"
label="HTTP API Port"
fullWidth
defaultValue={DEFAULT_HTTP_API_PORT}
/>
</Grid>
</React.Fragment>
) : (
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validClientsPort}
variant="outlined"
id="clientsPort"
name="clientsPort"
label="client WS API Port"
fullWidth
defaultValue={DEFAULT_CLIENTS_PORT}
/>
</Grid>
)}
</React.Fragment>
<Grid item xs={12} sm={6}>
<TextField
error={!validity.validVersion}
required
id="version"
name="version"
label="Version"
fullWidth
{...(!validity.validVersion
? {
helperText:
"Enter a valid version (min. 0.11.0), like 0.11.0",
}
: {})}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={
<Checkbox
checked={advancedShown}
onChange={handleCheckboxToggle}
/>
}
label="Show advanced options"
/>
</Grid>
{advancedShown && (
<React.Fragment>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validMixPort}
variant="outlined"
id="mixPort"
name="mixPort"
label="Mix Port"
fullWidth
defaultValue={DEFAULT_MIX_PORT}
{...(!validity.validMixPort
? { helperText: "Enter a valid version, like 0.10.0" }
: {})}
/>
</Grid>
<div className={classes.buttons}>
<Button
variant="contained"
color="primary"
type="submit"
className={classes.button}
>
Bond
</Button>
</div>
</form>
);
}
{/*yes, I also hate so many layers of indentation here*/}
{props.type === NodeType.Mixnode ? (
<React.Fragment>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validVerlocPort}
variant="outlined"
id="verlocPort"
name="verlocPort"
label="Verloc Port"
fullWidth
defaultValue={DEFAULT_VERLOC_PORT}
/>
</Grid>
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validHttpApiPort}
variant="outlined"
id="httpApiPort"
name="httpApiPort"
label="HTTP API Port"
fullWidth
defaultValue={DEFAULT_HTTP_API_PORT}
/>
</Grid>
</React.Fragment>
) : (
<Grid item xs={12} sm={4}>
<TextField
error={!validity.validClientsPort}
variant="outlined"
id="clientsPort"
name="clientsPort"
label="client WS API Port"
fullWidth
defaultValue={DEFAULT_CLIENTS_PORT}
/>
</Grid>
)}
</React.Fragment>
)}
</Grid>
<div className={classes.buttons}>
<Button
variant="contained"
color="primary"
type="submit"
className={classes.button}
>
Bond
</Button>
</div>
</form>
);
}
+1 -1
View File
@@ -150,7 +150,7 @@ const BondNode = () => {
<React.Fragment>
<main className={classes.layout}>
<Paper className={classes.paper}>
<ExecFeeNotice name={"bonding"}/>
<ExecFeeNotice name="bonding"/>
<Typography component="h1" variant="h4" align="center" className={classes.wrapper}>
Bond a {nodeType}
</Typography>