Rebag all dislocated (#7289)
* Perform per-loaded bag rebag calculations * Allow global rebag
This commit is contained in:
parent
dade085db6
commit
c339e7166a
@ -12,33 +12,34 @@ import { AddressMini, Spinner } from '@polkadot/react-components';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import Rebag from './Rebag';
|
||||
import Stash from './Stash';
|
||||
import useBagEntries from './useBagEntries';
|
||||
import useBonded from './useBonded';
|
||||
|
||||
interface Props {
|
||||
bagLower: BN;
|
||||
bagUpper: BN;
|
||||
index: number;
|
||||
info: PalletBagsListListBag;
|
||||
lower: BN;
|
||||
stashNodes?: StashNode[];
|
||||
upper: BN;
|
||||
}
|
||||
|
||||
function getRebags (bonded: ListNode[], stashNodes: StashNode[], lower: BN, upper: BN): string[] {
|
||||
function getRebags (bonded: ListNode[], stashNodes: StashNode[], bagUpper: BN, bagLower: BN): string[] {
|
||||
return bonded
|
||||
.filter(({ bonded, stashId }) =>
|
||||
(
|
||||
bonded.gt(upper) ||
|
||||
bonded.lt(lower)
|
||||
bonded.gt(bagUpper) ||
|
||||
bonded.lt(bagLower)
|
||||
) &&
|
||||
stashNodes.every((n) => n.stashId !== stashId)
|
||||
)
|
||||
.map(({ stashId }) => stashId);
|
||||
}
|
||||
|
||||
function Bag ({ info, lower, stashNodes, upper }: Props): React.ReactElement<Props> {
|
||||
function Bag ({ bagLower, bagUpper, info, stashNodes }: Props): React.ReactElement<Props> {
|
||||
const [[headId, trigger], setHeadId] = useState<[AccountId32 | null, number]>([null, 0]);
|
||||
const [, setRebags] = useState<string[]>([]);
|
||||
const [rebags, setRebags] = useState<string[]>([]);
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [isCompleted, list] = useBagEntries(headId, trigger);
|
||||
const bonded = useBonded(list);
|
||||
@ -56,32 +57,28 @@ function Bag ({ info, lower, stashNodes, upper }: Props): React.ReactElement<Pro
|
||||
);
|
||||
}, [bonded, isCompleted, stashNodes]);
|
||||
|
||||
// TODO: Currently not used, but calculated. This is the list of stashes that
|
||||
// should be rebagged (need to provide a rebag modal to move all these at once)
|
||||
useEffect((): void => {
|
||||
// NOTE: There may be some issues if these are indeed rebagged - we need
|
||||
// the StashNode to see if this item is indeed still within this list
|
||||
!isLoading && bonded && stashNodes && setRebags(
|
||||
getRebags(bonded, stashNodes, lower, upper)
|
||||
getRebags(bonded, stashNodes, bagUpper, bagLower)
|
||||
);
|
||||
}, [bonded, isLoading, lower, stashNodes, upper]);
|
||||
}, [bagLower, bagUpper, bonded, isLoading, stashNodes]);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className='number' />
|
||||
<td className='number'><FormatBalance value={upper} /></td>
|
||||
<td className='number'><FormatBalance value={lower} /></td>
|
||||
<td className='number'><FormatBalance value={bagUpper} /></td>
|
||||
<td className='number'><FormatBalance value={bagLower} /></td>
|
||||
<td className='address'>{info.head.isSome && <AddressMini value={info.head} />}</td>
|
||||
<td className='address'>{info.tail.isSome && <AddressMini value={info.tail} />}</td>
|
||||
<td className='address'>
|
||||
{stashNodes?.map(({ stashId }) => (
|
||||
<Stash
|
||||
bagLower={bagLower}
|
||||
bagUpper={bagUpper}
|
||||
isLoading={isLoading}
|
||||
key={stashId}
|
||||
list={bonded}
|
||||
lower={lower}
|
||||
stashId={stashId}
|
||||
upper={upper}
|
||||
/>
|
||||
))}
|
||||
</td>
|
||||
@ -93,6 +90,13 @@ function Bag ({ info, lower, stashNodes, upper }: Props): React.ReactElement<Pro
|
||||
: null
|
||||
}
|
||||
</td>
|
||||
<td className='button'>
|
||||
<Rebag
|
||||
bagLower={bagLower}
|
||||
bagUpper={bagUpper}
|
||||
stashIds={rebags}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
77
packages/page-staking/src/Bags/Rebag.tsx
Normal file
77
packages/page-staking/src/Bags/Rebag.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
|
||||
import { useApi, useToggle, useTxBatch } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import useBagsNodes from './useBagsNodes';
|
||||
|
||||
interface Props {
|
||||
bagLower: BN;
|
||||
bagUpper: BN;
|
||||
stashIds: string[];
|
||||
}
|
||||
|
||||
function Rebag ({ bagUpper, stashIds }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [isVisible, toggleVisible] = useToggle();
|
||||
const map = useBagsNodes(stashIds);
|
||||
const changes = useMemo(
|
||||
() => map && Object
|
||||
.values(map[bagUpper.toString()] || {})
|
||||
.map(({ stashId }) => api.tx.bagsList.rebag(stashId)),
|
||||
[api, bagUpper, map]
|
||||
);
|
||||
const tx = useTxBatch(changes);
|
||||
|
||||
if (!changes || !changes.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon='refresh'
|
||||
label={t<string>('Rebag {{count}}', { replace: { count: changes.length } })}
|
||||
onClick={toggleVisible}
|
||||
/>
|
||||
{isVisible && (
|
||||
<Modal
|
||||
header={t<string>('Rebag dislocated entries')}
|
||||
onClose={toggleVisible}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns hint={t<string>('The account that will submit the rebag transaction.')}>
|
||||
<InputAddress
|
||||
help={t<string>('Select the account you wish to rebag with.')}
|
||||
label={t<string>('rebag from account')}
|
||||
onChange={setAccountId}
|
||||
type='account'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
extrinsic={tx}
|
||||
icon='refresh'
|
||||
isDisabled={!tx}
|
||||
label={t<string>('Rebag')}
|
||||
onStart={toggleVisible}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Rebag);
|
||||
@ -13,12 +13,12 @@ import { useApi } from '@polkadot/react-hooks';
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
bagLower: BN;
|
||||
bagUpper: BN;
|
||||
className?: string;
|
||||
isLoading: boolean;
|
||||
list?: ListNode[];
|
||||
lower: BN;
|
||||
stashId: string;
|
||||
upper: BN;
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
@ -28,7 +28,7 @@ interface Entry {
|
||||
stashInfo: ListNode | null;
|
||||
}
|
||||
|
||||
function findEntry (upper: BN, lower: BN, stashId: string, list: ListNode[] = []): Entry {
|
||||
function findEntry (upper: BN, bagLower: BN, stashId: string, list: ListNode[] = []): Entry {
|
||||
const stashInfo = list.find((o) => o.stashId === stashId) || null;
|
||||
const other = (stashInfo && stashInfo.jump && list.find((o) => o.stashId === stashInfo.jump)) || null;
|
||||
|
||||
@ -36,7 +36,7 @@ function findEntry (upper: BN, lower: BN, stashId: string, list: ListNode[] = []
|
||||
canJump: !!other,
|
||||
canRebag: !!stashInfo && (
|
||||
stashInfo.bonded.gt(upper) ||
|
||||
stashInfo.bonded.lt(lower)
|
||||
stashInfo.bonded.lt(bagLower)
|
||||
),
|
||||
jumpCount: stashInfo && other
|
||||
? (stashInfo.index - other.index)
|
||||
@ -45,12 +45,12 @@ function findEntry (upper: BN, lower: BN, stashId: string, list: ListNode[] = []
|
||||
};
|
||||
}
|
||||
|
||||
function Stash ({ className, isLoading, list, lower, stashId, upper }: Props): React.ReactElement<Props> {
|
||||
function Stash ({ bagLower, bagUpper, className, isLoading, list, stashId }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { canJump, canRebag, jumpCount, stashInfo } = useMemo(
|
||||
() => findEntry(upper, lower, stashId, list),
|
||||
[list, lower, stashId, upper]
|
||||
() => findEntry(bagUpper, bagLower, stashId, list),
|
||||
[bagLower, bagUpper, list, stashId]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -15,17 +15,17 @@ import { useTranslation } from '../translate';
|
||||
interface Props {
|
||||
className?: string;
|
||||
ids?: BN[];
|
||||
nodes?: BagMap;
|
||||
stashNodes?: BagMap;
|
||||
}
|
||||
|
||||
function Summary ({ className = '', ids, nodes }: Props): React.ReactElement<Props> {
|
||||
function Summary ({ className = '', ids, stashNodes }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const total = useCall<BN>(api.query.bagsList.counterForListNodes);
|
||||
|
||||
const myCount = useMemo(
|
||||
() => nodes && Object.values(nodes).reduce((count, n) => count + n.length, 0),
|
||||
[nodes]
|
||||
() => stashNodes && Object.values(stashNodes).reduce((count, n) => count + n.length, 0),
|
||||
[stashNodes]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -38,7 +38,7 @@ function Summary ({ className = '', ids, nodes }: Props): React.ReactElement<Pro
|
||||
</CardSummary>
|
||||
<section>
|
||||
<CardSummary label={t<string>('total nodes')}>
|
||||
{nodes
|
||||
{stashNodes
|
||||
? formatNumber(total)
|
||||
: <Spinner noLabel />
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ function Bags ({ ownStashes }: Props): React.ReactElement<Props> {
|
||||
const [filterIndex, setFilterIndex] = useState(() => stashIds.length ? 0 : 1);
|
||||
const ids = useBagsIds();
|
||||
const list = useBagsList(ids);
|
||||
const nodes = useBagsNodes(stashIds);
|
||||
const stashNodes = useBagsNodes(stashIds);
|
||||
|
||||
const headerRef = useRef([
|
||||
[t('bags')],
|
||||
@ -45,28 +45,32 @@ function Bags ({ ownStashes }: Props): React.ReactElement<Props> {
|
||||
[t('first'), 'address'],
|
||||
[t('last'), 'address'],
|
||||
[t('stashes'), 'address'],
|
||||
[t('count'), 'number']
|
||||
[t('nodes'), 'number'],
|
||||
[undefined, 'mini']
|
||||
]);
|
||||
|
||||
const filterOptions = useRef([
|
||||
{ text: t('My bags'), value: 'mine' },
|
||||
{ text: t('All bags'), value: 'all' }
|
||||
]);
|
||||
const filterOptions = useMemo(
|
||||
() => [
|
||||
{ isDisabled: !stashIds.length, text: t('My bags'), value: 'mine' },
|
||||
{ text: t('All bags'), value: 'all' }
|
||||
],
|
||||
[stashIds, t]
|
||||
);
|
||||
const filtered = useMemo(
|
||||
() => list && nodes && sortNodes(list, nodes, !filterIndex),
|
||||
[filterIndex, list, nodes]
|
||||
() => list && stashNodes && sortNodes(list, stashNodes, !filterIndex),
|
||||
[filterIndex, list, stashNodes]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Summary
|
||||
ids={ids}
|
||||
nodes={nodes}
|
||||
stashNodes={stashNodes}
|
||||
/>
|
||||
<Button.Group>
|
||||
<ToggleGroup
|
||||
onChange={setFilterIndex}
|
||||
options={filterOptions.current}
|
||||
options={filterOptions}
|
||||
value={filterIndex}
|
||||
/>
|
||||
</Button.Group>
|
||||
@ -79,14 +83,14 @@ function Bags ({ ownStashes }: Props): React.ReactElement<Props> {
|
||||
emptySpinner={t<string>('Retrieving all available bags, this will take some time')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{filtered?.map(([{ index, info, key, lower, upper }, nodes]) => (
|
||||
{filtered?.map(([{ bagLower, bagUpper, index, info, key }, stashNodes]) => (
|
||||
<Bag
|
||||
bagLower={bagLower}
|
||||
bagUpper={bagUpper}
|
||||
index={index}
|
||||
info={info}
|
||||
key={key}
|
||||
lower={lower}
|
||||
stashNodes={nodes}
|
||||
upper={upper}
|
||||
stashNodes={stashNodes}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
|
||||
@ -17,11 +17,11 @@ export interface ListNode {
|
||||
}
|
||||
|
||||
export interface BagInfo {
|
||||
bagLower: BN;
|
||||
bagUpper: BN;
|
||||
index: number;
|
||||
info: PalletBagsListListBag;
|
||||
key: string;
|
||||
lower: BN;
|
||||
upper: BN;
|
||||
}
|
||||
|
||||
export type BagMap = Record<string, StashNode[]>;
|
||||
|
||||
@ -17,19 +17,19 @@ const MULTI_OPTS = {
|
||||
.map((id, index): [BN, Option<PalletBagsListListBag>] => [id, opts[index]])
|
||||
.filter(([, o]) => o.isSome)
|
||||
.sort(([a], [b]) => b.cmp(a))
|
||||
.map(([upper, o], index): BagInfo => ({
|
||||
.map(([bagUpper, o], index): BagInfo => ({
|
||||
bagLower: BN_ZERO,
|
||||
bagUpper,
|
||||
index,
|
||||
info: o.unwrap(),
|
||||
key: upper.toString(),
|
||||
lower: BN_ZERO,
|
||||
upper
|
||||
key: bagUpper.toString()
|
||||
}));
|
||||
|
||||
return sorted.map((entry, index) =>
|
||||
(index === (sorted.length - 1))
|
||||
? entry
|
||||
// We could probably use a .add(BN_ONE) here
|
||||
: { ...entry, lower: sorted[index + 1].upper }
|
||||
: { ...entry, bagLower: sorted[index + 1].bagUpper }
|
||||
);
|
||||
},
|
||||
withParamsTransform: true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user