Split notePreimage generation into partial (#8622)

* Split notePreimage generation into partial

* Remove old Add

* Move storageFee to proposal calculation

* s/Proposal/Partial/

* Renames

* Adjust

* Text adjust
This commit is contained in:
Jaco 2022-12-25 13:38:30 +02:00 committed by GitHub
parent 8bf34a4e59
commit de4b94fe43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 216 additions and 141 deletions

View File

@ -1,141 +0,0 @@
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic } from '@polkadot/api/types';
import React, { useEffect, useState } from 'react';
import { Button, Extrinsic, InputAddress, InputBalance, Modal, Static, TxButton } from '@polkadot/react-components';
import { useApi, useToggle } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import { BN, BN_ZERO } from '@polkadot/util';
import { blake2AsHex } from '@polkadot/util-crypto';
import { useTranslation } from '../translate';
interface Props {
className?: string;
imageHash?: string;
}
interface HashState {
encodedHash: string;
encodedLength: number;
encodedProposal: string;
storageFee: BN;
}
const ZERO_HASH = blake2AsHex('');
function Add ({ className, imageHash }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api, apiDefaultTxSudo } = useApi();
const [isAddOpen, toggleAdd] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
const [{ encodedHash, encodedLength, encodedProposal, storageFee }, setHash] = useState<HashState>({ encodedHash: ZERO_HASH, encodedLength: 0, encodedProposal: '', storageFee: BN_ZERO });
const [proposal, setProposal] = useState<SubmittableExtrinsic<'promise'>>();
useEffect((): void => {
// get the proposal in hex format, i.e. we don't want the length prefix
const encodedProposal = (proposal as SubmittableExtrinsic<'promise'>)?.method.toHex() || '';
// we are dealing with hex values here, so subtrack the prefix (+ 2 chars per byte)
const encodedLength = Math.max(0, Math.round((encodedProposal.length - 2) / 2));
// we currently don't have a constant exposed, or rather, maybe somewhere else...
// const storageFee = api.consts.democracy.preimageByteDeposit.mul(
// encodedProposal
// ? new BN((encodedProposal.length - 2) / 2)
// : BN_ZERO
// );
setHash({ encodedHash: blake2AsHex(encodedProposal), encodedLength, encodedProposal, storageFee: BN_ZERO });
}, [api, proposal]);
const isMatched = imageHash
? imageHash === encodedHash
: true;
return (
<>
{isAddOpen && (
<Modal
className={className}
header={t<string>('Submit preimage')}
onClose={toggleAdd}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('This account will pay the fees for the preimage, based on the size thereof.')}>
<InputAddress
help={t<string>('The account you want to register the preimage from')}
label={t<string>('send from account')}
labelExtra={
<Available
label={<span className='label'>{t<string>('transferrable')}</span>}
params={accountId}
/>
}
onChange={setAccountId}
type='account'
/>
</Modal.Columns>
<Modal.Columns hint={
<>
<p>{t<string>('The image (proposal) will be stored on-chain against the hash of the contents.')}</p>
<p>{t<string>('When submitting a proposal the hash needs to be known. Proposals can be submitted with hash-only, but upon dispatch the preimage needs to be available.')}</p>
</>
}
>
<Extrinsic
defaultValue={apiDefaultTxSudo}
label={t<string>('propose')}
onChange={setProposal}
/>
<Static
help={t<string>('The hash of the selected proposal, use it for submitting the proposal')}
label={t<string>('preimage hash')}
value={encodedHash}
withCopy
/>
<Static
help={t<string>('The encoded length of the selected proposal, as available on-chain')}
label={t<string>('preimage length')}
value={encodedLength.toString()}
withCopy
/>
</Modal.Columns>
{!storageFee.isZero() && (
<Modal.Columns hint={t<string>('The calculated storage costs based on the size and the per-bytes fee.')}>
<InputBalance
defaultValue={storageFee}
help={t<string>('The amount reserved to store this image')}
isDisabled
label={t<string>('calculated storage fee')}
/>
</Modal.Columns>
)}
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={accountId}
icon='plus'
isDisabled={!proposal || !accountId || !isMatched || !encodedProposal}
label={t<string>('Submit preimage')}
onStart={toggleAdd}
params={[encodedProposal]}
tx={api.tx.preimage.notePreimage}
/>
</Modal.Actions>
</Modal>
)}
<Button
icon='plus'
label={t<string>('Add preimage')}
onClick={toggleAdd}
/>
</>
);
}
export default React.memo(Add);

View File

@ -0,0 +1,127 @@
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { BN } from '@polkadot/util';
import type { HexString } from '@polkadot/util/types';
import type { HashState } from './types';
import React, { useCallback, useState } from 'react';
import { Extrinsic, InputBalance, Modal, Static } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { BN_ZERO } from '@polkadot/util';
import { blake2AsHex } from '@polkadot/util-crypto';
import { useTranslation } from '../../translate';
interface Props {
className?: string;
onChange: (state: HashState) => void;
}
const EMPTY_HASH = blake2AsHex('');
export const EMPTY_PROPOSAL: HashState = {
encodedHash: EMPTY_HASH,
encodedLength: 0,
encodedProposal: null,
notePreimageTx: null,
storageFee: BN_ZERO
};
function getState (api: ApiPromise, proposal?: SubmittableExtrinsic<'promise'>): HashState {
let encodedHash = EMPTY_HASH;
let encodedProposal: HexString | null = null;
let encodedLength = 0;
let notePreimageTx: SubmittableExtrinsic<'promise'> | null = null;
let storageFee = BN_ZERO;
if (proposal) {
encodedProposal = proposal.method.toHex();
encodedLength = Math.ceil((encodedProposal.length - 2) / 2);
encodedHash = blake2AsHex(encodedProposal);
notePreimageTx = api.tx.preimage.notePreimage(encodedProposal);
// we currently don't have a constant exposed, however match to Substrate
storageFee = ((api.consts.preimage?.baseDeposit || BN_ZERO) as unknown as BN).add(
((api.consts.preimage?.byteDeposit || BN_ZERO) as unknown as BN).muln(encodedLength)
);
}
return {
encodedHash,
encodedLength,
encodedProposal,
notePreimageTx,
storageFee
};
}
function Partial ({ className, onChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api, apiDefaultTxSudo } = useApi();
const [{ encodedHash, encodedLength, storageFee }, setState] = useState<HashState>(EMPTY_PROPOSAL);
const changeState = useCallback(
(state: HashState): void => {
setState(state);
onChange(state);
},
[onChange]
);
const setProposal = useCallback(
(proposal?: SubmittableExtrinsic<'promise'>) =>
changeState(getState(api, proposal)),
[api, changeState]
);
return (
<>
<Modal.Columns
className={className}
hint={
<>
<p>{t<string>('The image (proposal) will be stored on-chain against the hash of the contents.')}</p>
<p>{t<string>('When submitting a proposal the hash needs to be known. Proposals can be submitted with hash-only, but upon dispatch the preimage needs to be available.')}</p>
</>
}
>
<Extrinsic
defaultValue={apiDefaultTxSudo}
label={t<string>('propose')}
onChange={setProposal}
/>
<Static
help={t<string>('The hash of the selected proposal, use it for submitting the proposal')}
label={t<string>('preimage hash')}
value={encodedHash}
withCopy
/>
<Static
help={t<string>('The encoded length of the selected proposal, as available on-chain')}
label={t<string>('preimage length')}
value={encodedLength || '0'}
withCopy
/>
</Modal.Columns>
{!storageFee.isZero() && (
<Modal.Columns
className={className}
hint={t<string>('The calculated storage costs based on the base and the per-bytes fee.')}
>
<InputBalance
defaultValue={storageFee}
help={t<string>('The amount reserved to store this image')}
isDisabled
label={t<string>('calculated storage fee')}
/>
</Modal.Columns>
)}
</>
);
}
export default React.memo(Partial);

View File

@ -0,0 +1,75 @@
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HashState } from './types';
import React, { useState } from 'react';
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import { useTranslation } from '../../translate';
import Proposal, { EMPTY_PROPOSAL } from './Partial';
interface Props {
className?: string;
imageHash?: string;
}
function Add ({ className, imageHash }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [isAddOpen, toggleAdd] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
const [{ encodedHash, notePreimageTx }, setProposal] = useState<HashState>(EMPTY_PROPOSAL);
const isMatched = !imageHash || imageHash === encodedHash;
return (
<>
{isAddOpen && (
<Modal
className={className}
header={t<string>('Submit preimage')}
onClose={toggleAdd}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('This account will pay the fees for the preimage, based on the size thereof.')}>
<InputAddress
help={t<string>('The account you want to register the preimage from')}
label={t<string>('send from account')}
labelExtra={
<Available
label={<span className='label'>{t<string>('transferrable')}</span>}
params={accountId}
/>
}
onChange={setAccountId}
type='account'
/>
</Modal.Columns>
<Proposal onChange={setProposal} />
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={accountId}
extrinsic={notePreimageTx}
icon='plus'
isDisabled={!accountId || !isMatched || !notePreimageTx}
label={t<string>('Submit preimage')}
onStart={toggleAdd}
/>
</Modal.Actions>
</Modal>
)}
<Button
icon='plus'
label={t<string>('Add preimage')}
onClick={toggleAdd}
/>
</>
);
}
export default React.memo(Add);

View File

@ -0,0 +1,14 @@
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { BN } from '@polkadot/util';
import type { HexString } from '@polkadot/util/types';
export interface HashState {
encodedHash: HexString;
encodedLength: number;
encodedProposal?: HexString | null;
notePreimageTx?: SubmittableExtrinsic<'promise'> | null;
storageFee: BN;
}