Remove auto validator defaults (#3306)

* Remove auto validator defaults

* Adjust

* Additional cleanups

* Disable nominate on no target validators

* Remove unused next from targets
This commit is contained in:
Jaco Greeff 2020-08-01 12:34:40 +02:00 committed by GitHub
parent 5cafda8bfc
commit ce49da0029
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 30 additions and 183 deletions

View File

@ -15,15 +15,13 @@ import NominatePartial from '../partials/Nominate';
interface Props {
className?: string;
controllerId: string;
next?: string[];
nominating?: string[];
onClose: () => void;
stashId: string;
targets: SortedTargets;
validators?: string[];
}
function Nominate ({ className = '', controllerId, next, nominating, onClose, stashId, targets, validators }: Props): React.ReactElement<Props> | null {
function Nominate ({ className = '', controllerId, nominating, onClose, stashId, targets }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const [{ nominateTx }, setTx] = useState<NominateInfo>({});
@ -37,12 +35,10 @@ function Nominate ({ className = '', controllerId, next, nominating, onClose, st
<NominatePartial
className='nominatePartial'
controllerId={controllerId}
next={next}
nominating={nominating}
onChange={setTx}
stashId={stashId}
targets={targets}
validators={validators}
withSenders
/>
</Modal.Content>

View File

@ -38,7 +38,7 @@ interface Props {
validators?: string[];
}
function Account ({ allSlashes, className = '', info: { controllerId, destination, destinationId, hexSessionIdNext, hexSessionIdQueue, isLoading, isOwnController, isOwnStash, isStashNominating, isStashValidating, nominating, sessionIds, stakingLedger, stashId }, isDisabled, next, targets, validators }: Props): React.ReactElement<Props> {
function Account ({ allSlashes, className = '', info: { controllerId, destination, destinationId, hexSessionIdNext, hexSessionIdQueue, isLoading, isOwnController, isOwnStash, isStashNominating, isStashValidating, nominating, sessionIds, stakingLedger, stashId }, isDisabled, targets }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { queueExtrinsic } = useContext(StatusContext);
@ -133,12 +133,10 @@ function Account ({ allSlashes, className = '', info: { controllerId, destinatio
{isNominateOpen && controllerId && (
<Nominate
controllerId={controllerId}
next={next}
nominating={nominating}
onClose={toggleNominate}
stashId={stashId}
targets={targets}
validators={validators}
/>
)}
{isSetControllerOpen && controllerId && (
@ -326,7 +324,7 @@ function Account ({ allSlashes, className = '', info: { controllerId, destinatio
}
{isStashNominating &&
<Menu.Item
disabled={!isOwnController}
disabled={!isOwnController || !targets.validators?.length}
onClick={toggleNominate}
>
{t<string>('Set nominees')}

View File

@ -16,15 +16,13 @@ import NominatePartial from './partials/Nominate';
interface Props {
isInElection?: boolean;
next?: string[];
targets?: SortedTargets;
validators?: string[];
targets: SortedTargets;
}
const EMPTY_NOMS: string[] = [];
const NUM_STEPS = 2;
function NewNominator ({ isInElection, next, targets, validators }: Props): React.ReactElement<Props> {
function NewNominator ({ isInElection, targets }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [isVisible, toggleVisible] = useToggle();
@ -57,7 +55,7 @@ function NewNominator ({ isInElection, next, targets, validators }: Props): Reac
<>
<Button
icon='plus'
isDisabled={isDisabled}
isDisabled={isDisabled || !targets.validators?.length}
key='new-nominator'
label={t<string>('Nominator')}
onClick={_toggle}
@ -79,12 +77,10 @@ function NewNominator ({ isInElection, next, targets, validators }: Props): Reac
{controllerId && stashId && step === 2 && (
<NominatePartial
controllerId={controllerId}
next={next}
nominating={EMPTY_NOMS}
onChange={setNominateInfo}
stashId={stashId}
targets={targets}
validators={validators}
/>
)}
</Modal.Content>

View File

@ -48,7 +48,7 @@ function extractState (ownStashes: StakerState[]): State {
};
}
function Actions ({ className = '', isInElection, next, ownStashes, targets, validators }: Props): React.ReactElement<Props> {
function Actions ({ className = '', isInElection, ownStashes, targets }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const allSlashes = useAvailableSlashes();
const [{ bondedTotal, foundStashes }, setState] = useState<State>({});
@ -80,9 +80,7 @@ function Actions ({ className = '', isInElection, next, ownStashes, targets, val
<Button.Group>
<NewNominator
isInElection={isInElection}
next={next}
targets={targets}
validators={validators}
/>
<NewValidator isInElection={isInElection} />
<NewStash />
@ -99,9 +97,7 @@ function Actions ({ className = '', isInElection, next, ownStashes, targets, val
info={info}
isDisabled={isInElection}
key={info.stashId}
next={next}
targets={targets}
validators={validators}
/>
))}
</Table>

View File

@ -5,142 +5,40 @@
import { NominateInfo } from './types';
import { SortedTargets } from '../../types';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { AddressMini, InputAddress, InputAddressMulti, Modal, Static, Toggle } from '@polkadot/react-components';
import { getParentAccount } from '@polkadot/react-components/AccountName';
import { InputAddress, InputAddressMulti, Modal } from '@polkadot/react-components';
import { useApi, useFavorites } from '@polkadot/react-hooks';
import { randomAsNumber } from '@polkadot/util-crypto';
import { MAX_NOMINATIONS, MAX_PAYOUTS, STORE_FAVS_BASE } from '../../constants';
import { MAX_NOMINATIONS, STORE_FAVS_BASE } from '../../constants';
import { useTranslation } from '../../translate';
interface Props {
className?: string;
controllerId: string;
next?: string[];
nominating?: string[];
onChange: (info: NominateInfo) => void;
stashId: string;
targets: SortedTargets;
validators: string[];
withSenders?: boolean;
}
interface Selected {
isAutoSelect: boolean;
selected: string[];
}
function autoPick (targets: SortedTargets): string[] {
const parents: string[] = [];
const positions: number[] = [];
return (targets.validators || []).reduce((result: string[], { isElected, isFavorite, key, numNominators, rewardPayout }): string[] => {
if (result.length < MAX_NOMINATIONS) {
if (numNominators && (numNominators < MAX_PAYOUTS) && (isElected || isFavorite) && !rewardPayout.isZero()) {
const parent = getParentAccount(key);
let shouldInclude = isFavorite;
if (!shouldInclude) {
const noParent = !parents.includes(key) && (!parent || !parents.includes(parent));
if (noParent) {
shouldInclude = true;
} else {
const parentIndex = parent
? parents.indexOf(parent)
: -1;
const keyIndex = parents.indexOf(key);
const index = keyIndex !== -1
? keyIndex
: parentIndex;
// 66.6% chance to be replaced by a later one
if (index !== -1 && ((randomAsNumber() % 300) > 100)) {
result[positions[index]] = key;
}
}
}
if (shouldInclude) {
positions[parents.length] = result.length;
parents.push(key);
if (parent) {
positions[parents.length] = result.length;
parents.push(parent);
}
result.push(key);
}
}
}
return result;
}, []);
}
function initialPick (targets: SortedTargets): Selected {
const selected = targets.validators?.length
? autoPick(targets)
: [];
return {
isAutoSelect: selected.length !== 0,
selected
};
}
function Nominate ({ className = '', controllerId, next, nominating, onChange, stashId, targets, validators, withSenders }: Props): React.ReactElement<Props> {
function Nominate ({ className = '', controllerId, nominating, onChange, stashId, targets: { validatorIds = [] }, withSenders }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [favorites] = useFavorites(STORE_FAVS_BASE);
const [{ isAutoSelect, selected }, setSelected] = useState<Selected>(
// nominating?.length
// ? { isAutoSelect: false, selected: nominating }
// : initialPick(targets)
initialPick(targets)
);
const [selected, setSelected] = useState<string[]>(nominating || []);
const [available] = useState<string[]>((): string[] => {
const shortlist = [
// ensure that the favorite is included in the list of stashes
...favorites.filter((acc) => (validators || []).includes(acc) || (next || []).includes(acc)),
...favorites.filter((acc) => validatorIds.includes(acc)),
// make sure the nominee is not in our favorites already
...(nominating || []).filter((acc) => !favorites.includes(acc))
];
return shortlist
.concat(...(validators || []).filter((acc) => !shortlist.includes(acc)))
.concat(...(next || []).filter((acc) => !shortlist.includes(acc)));
.concat(...(validatorIds.filter((acc) => !shortlist.includes(acc))));
});
const [autoSelected, setAutoSelected] = useState<string[]>([]);
const _setSelected = useCallback(
(selected: string[]) => setSelected(({ isAutoSelect }: Selected) => ({
isAutoSelect,
selected
})),
[]
);
const _toggleAutoSelect = useCallback(
(isAutoSelect: boolean) => setSelected(() => ({
isAutoSelect,
selected: isAutoSelect
? autoSelected
: []
})),
[autoSelected]
);
useEffect((): void => {
setAutoSelected(
targets.validators?.length
? autoPick(targets)
: []
);
}, [targets]);
useEffect((): void => {
onChange({
@ -173,53 +71,18 @@ function Nominate ({ className = '', controllerId, next, nominating, onChange, s
)}
<Modal.Columns>
<Modal.Column>
{isAutoSelect
? (
<>
<Static
label={t<string>('auto-selected targets for nomination')}
value={
selected.map((validatorId) => (
<AddressMini
className='addressStatic'
isHighlight={favorites.includes(validatorId)}
key={validatorId}
value={validatorId}
/>
))
}
/>
<article className='warning'>{t<string>('The auto-selection is done on the current profitability of the validators taking your favorites into account. It is adjusted based on the commission and current range of backing for the validator. The calculation may and will change over time, so it is rather a selection based on the current state of the network, not a predictor of future profitability.')}</article>
</>
)
: (
<InputAddressMulti
available={available}
availableLabel={t<string>('candidate accounts')}
defaultValue={nominating}
help={t<string>('Filter available candidates based on name, address or short account index.')}
maxCount={MAX_NOMINATIONS}
onChange={_setSelected}
valueLabel={t<string>('nominated accounts')}
/>
)
}
{autoSelected.length !== 0 && (
<Toggle
className='auto--toggle'
isDisabled={!targets.validators?.length}
label={
isAutoSelect
? t<string>('Use an automatic selection of the currently most profitable validators')
: t<string>('Select targets manually (no auto-selection based on current profitability)')
}
onChange={_toggleAutoSelect}
value={isAutoSelect}
/>
)}
<InputAddressMulti
available={available}
availableLabel={t<string>('candidate accounts')}
defaultValue={nominating}
help={t<string>('Filter available candidates based on name, address or short account index.')}
maxCount={MAX_NOMINATIONS}
onChange={setSelected}
valueLabel={t<string>('nominated accounts')}
/>
</Modal.Column>
<Modal.Column>
<p>{t<string>('Nominators can be selected automatically based on the current on-chain conditions or supplied manually as selected from the list of all currently available validators. In both cases, your favorites appear for the selection.')}</p>
<p>{t<string>('Nominators can be selected manually from the list of all currently available validators. If you have set favorites these will appear for the selection.')}</p>
<p>{t<string>('Once transmitted the new selection will only take effect in 2 eras since the selection criteria for the next era was done at the end of the previous era. Until then, the nominations will show as inactive.')}</p>
</Modal.Column>
</Modal.Columns>

View File

@ -23,7 +23,6 @@ import useOwnNominators from './useOwnNominators';
interface Props {
className?: string;
isInElection: boolean;
next?: string[];
ownStashes?: StakerState[];
stakingOverview?: DeriveStakingOverview;
targets: SortedTargets;
@ -182,11 +181,11 @@ function Targets ({ className = '', isInElection, ownStashes, targets: { calcWit
<Button
icon='check'
isDisabled={!validators?.length || !ownNominators?.length}
label={t<string>('Select best')}
label={t<string>('Most profitable')}
onClick={_selectProfitable}
/>
<Nominate
isDisabled={isInElection}
isDisabled={isInElection || !validators?.length}
ownNominators={ownNominators}
targets={selected}
/>

View File

@ -36,7 +36,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
const { api } = useApi();
const { hasAccounts } = useAccounts();
const { pathname } = useLocation();
const [{ next, validators }, setValidators] = useState<Validators>({});
const [{ next }, setValidators] = useState<Validators>({});
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
const allStashes = useStashIds();
const ownStashes = useOwnStashInfos();
@ -123,7 +123,6 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
<Route path={`${basePath}/targets`}>
<Targets
isInElection={isInElection}
next={next}
ownStashes={ownStashes}
stakingOverview={stakingOverview}
targets={targets}
@ -144,10 +143,8 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
<Actions
className={pathname === `${basePath}/actions` ? '' : 'staking--hidden'}
isInElection={isInElection}
next={next}
ownStashes={ownStashes}
targets={targets}
validators={validators}
/>
<Overview
className={basePath === pathname ? '' : 'staking--hidden'}

View File

@ -67,4 +67,5 @@ export interface SortedTargets {
setCalcWith: (amount?: BN) => void;
totalStaked?: BN;
validators?: ValidatorInfo[];
validatorIds?: string[];
}

View File

@ -139,8 +139,9 @@ function extractInfo (allAccounts: string[], amount: BN = baseBalance(), elected
const [elected, nominators, totalStaked] = extractSingle(allAccounts, amount, electedDerive, favorites, perValidatorReward, true);
const [waiting] = extractSingle(allAccounts, amount, waitingDerive, favorites, perValidatorReward, false);
const validators = sortValidators(elected.concat(waiting));
const validatorIds = validators.map(({ accountId }) => accountId.toString());
return { nominators, totalStaked, validators };
return { nominators, totalStaked, validatorIds, validators };
}
export default function useSortedTargets (favorites: string[]): SortedTargets {