Rebag all dislocated (#7289)

* Perform per-loaded bag rebag calculations

* Allow global rebag
This commit is contained in:
Jaco 2022-04-02 10:27:12 +02:00 committed by GitHub
parent dade085db6
commit c339e7166a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 50 deletions

View File

@ -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>
);
}

View 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);

View File

@ -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 (

View File

@ -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 />
}

View File

@ -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>

View File

@ -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[]>;

View File

@ -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