From c21dadcd9eb09b8255d2f1122b49b1176594dca3 Mon Sep 17 00:00:00 2001 From: Jaco Date: Thu, 10 Jun 2021 10:05:40 +0300 Subject: [PATCH] Optimize crowdloan refresh (delta only on events) (#5549) * Optimize crowdloan refresh (delata only on events) * Adjust own contributions --- packages/page-assets/src/useAssetIds.ts | 2 +- .../src/Crowdloan/useContributions.ts | 101 +++++++++++++----- packages/page-parachains/src/useFunds.ts | 2 +- packages/page-parachains/src/useHrmp.ts | 2 +- packages/page-parachains/src/useOwnedIds.ts | 2 +- packages/page-parachains/src/useProposals.ts | 4 +- .../page-parachains/src/useUpcomingIds.ts | 2 +- packages/page-treasury/src/useTipHashes.ts | 2 +- packages/react-hooks/src/useEventTrigger.ts | 36 +++++-- 9 files changed, 111 insertions(+), 42 deletions(-) diff --git a/packages/page-assets/src/useAssetIds.ts b/packages/page-assets/src/useAssetIds.ts index 85c96435a0..7202ca6f8e 100644 --- a/packages/page-assets/src/useAssetIds.ts +++ b/packages/page-assets/src/useAssetIds.ts @@ -16,5 +16,5 @@ export default function useAssetIds (): AssetId[] | undefined { const { api } = useApi(); const trigger = useEventTrigger([api.events.assets.Created, api.events.assets.Destroyed]); - return useMapKeys(api.query.assets.asset, { at: trigger, transform: extractAssetIds }); + return useMapKeys(api.query.assets.asset, { at: trigger.blockHash, transform: extractAssetIds }); } diff --git a/packages/page-parachains/src/Crowdloan/useContributions.ts b/packages/page-parachains/src/Crowdloan/useContributions.ts index 24beab0537..80a08f0310 100644 --- a/packages/page-parachains/src/Crowdloan/useContributions.ts +++ b/packages/page-parachains/src/Crowdloan/useContributions.ts @@ -1,6 +1,7 @@ // Copyright 2017-2021 @polkadot/app-parachains authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { ApiPromise } from '@polkadot/api'; import type { StorageKey } from '@polkadot/types'; import type { Balance, EventRecord, ParaId } from '@polkadot/types/interfaces'; @@ -23,7 +24,7 @@ const NO_CONTRIB: Contributions = { myContributions: {} }; -function extractContributors (allAccountsHex: string[], keys: StorageKey[], ss58Format?: number): Contributions { +function extractContributors (keys: StorageKey[], allAccountsHex: string[], ss58Format?: number): Contributions { const contributorsHex = keys.map((k) => k.toHex()); const myAccountsHex = contributorsHex.filter((c) => allAccountsHex.includes(c)); @@ -35,13 +36,36 @@ function extractContributors (allAccountsHex: string[], keys: StorageKey[], ss58 }; } +function extractDelta (api: ApiPromise, events: EventRecord[], prev: Contributions, allAccountsHex: string[], ss58Format?: number): Contributions { + const removed = events + .filter(({ event }) => api.events.crowdloan.Withdrew.is(event)) + .map(({ event: { data: [accountId] } }) => accountId.toHex()); + const contributorsHex = prev.contributorsHex.filter((h) => !removed.includes(h)); + + events + .filter(({ event }) => api.events.crowdloan.Contributed.is(event)) + .forEach(({ event: { data: [accountId] } }): void => { + contributorsHex.push(accountId.toHex()); + }); + + const myAccountsHex = contributorsHex.filter((c) => allAccountsHex.includes(c)); + + return { + contributorsHex, + myAccounts: myAccountsHex.map((a) => encodeAddress(a, ss58Format)), + myAccountsHex, + myContributions: {} + }; +} + function useMyContributions (paraId: ParaId, childKey: string, keys: string[]): Record { const { api } = useApi(); const { allAccountsHex } = useAccounts(); const [state, setState] = useState>({}); const trigger = useEventTrigger([ - api.events.crowdloan.Contributed + api.events.crowdloan.Contributed, + api.events.crowdloan.Withdrew ], useCallback( ({ event: { data: [accountId, eventParaId] } }: EventRecord) => eventParaId.eq(paraId) && @@ -75,44 +99,69 @@ function useMyContributions (paraId: ParaId, childKey: string, keys: string[]): export default function useContributions (paraId: ParaId, childKey: string): Contributions { const { api } = useApi(); const { allAccountsHex } = useAccounts(); - const [state, setState] = useState(() => NO_CONTRIB); + const [state, _setState] = useState(() => NO_CONTRIB); const myContributions = useMyContributions(paraId, childKey, state.myAccountsHex); - const trigger = useEventTrigger([ - api.events.crowdloan.Contributed, - api.events.crowdloan.Withdrew, + // these trigger a full refresh of the keys + const triggerAll = useEventTrigger([ + api.events.crowdloan.Dissolved, api.events.crowdloan.AllRefunded, api.events.crowdloan.PartiallyRefunded ], useCallback( - ({ event: { data } }: EventRecord) => - ((data.length === 1 - ? data[0] // AllRefunded, PartiallyRefunded [ParaId] - : data[1] // Contributed, Withdrew [AccountId, ParaId, Balance] - ) as ParaId).eq(paraId), + ({ event: { data: [eventParaId] } }: EventRecord) => + eventParaId.eq(paraId), [paraId] )); + // these trigger a delta adjustment + const triggerDelta = useEventTrigger([ + api.events.crowdloan.Contributed, + api.events.crowdloan.Withdrew + ], useCallback( + ({ event: { data: [, eventParaId] } }: EventRecord) => + eventParaId.eq(paraId), + [paraId] + )); + + const setState = useCallback( + (fn: (prev: Contributions) => Contributions) => + _setState((prev) => { + const { contributorsHex, myAccounts, myAccountsHex } = fn(prev); + + return { + ...prev, + contributorsHex, + ...(prev.myAccounts.length === myAccounts.length + ? {} + : { myAccounts, myAccountsHex } + ) + }; + }), + [] + ); + useEffect((): void => { - trigger && + triggerAll && api.rpc.childstate .getKeys(childKey, '0x') - .then((keys) => setState((prev) => { - const { contributorsHex, myAccounts, myAccountsHex } = extractContributors(allAccountsHex, keys, api.registry.chainSS58); - - return { - ...prev, - contributorsHex, - ...(prev.myAccounts.length === myAccounts.length - ? {} - : { myAccounts, myAccountsHex } - ) - }; - })) + .then((keys) => + setState(() => + extractContributors(keys, allAccountsHex, api.registry.chainSS58) + ) + ) .catch(console.error); - }, [allAccountsHex, api, childKey, trigger]); + }, [allAccountsHex, api, childKey, setState, triggerAll]); useEffect((): void => { - setState((prev) => ({ + triggerDelta && triggerDelta.events.length && + setState((prev) => + extractDelta(api, triggerDelta.events, prev, allAccountsHex, api.registry.chainSS58) + ); + }, [api, allAccountsHex, setState, triggerDelta]); + + useEffect((): void => { + // no deltas, set directly here + _setState((prev) => ({ ...prev, myContributions })); diff --git a/packages/page-parachains/src/useFunds.ts b/packages/page-parachains/src/useFunds.ts index e0cb51314a..cd36edf81d 100644 --- a/packages/page-parachains/src/useFunds.ts +++ b/packages/page-parachains/src/useFunds.ts @@ -167,7 +167,7 @@ export default function useFunds (): Campaigns { const { api } = useApi(); const bestNumber = useBestNumber(); const trigger = useEventTrigger([api.events.crowdloan?.Created]); - const paraIds = useMapKeys(api.query.crowdloan?.funds, { at: trigger, transform: extractFundIds }); + const paraIds = useMapKeys(api.query.crowdloan?.funds, { at: trigger.blockHash, transform: extractFundIds }); const campaigns = useCall(api.query.crowdloan?.funds.multi, [paraIds], optFundMulti); const leases = useCall(api.query.slots.leases.multi, [paraIds], optLeaseMulti); const [result, setResult] = useState(EMPTY); diff --git a/packages/page-parachains/src/useHrmp.ts b/packages/page-parachains/src/useHrmp.ts index 6498ed74a7..832d31a655 100644 --- a/packages/page-parachains/src/useHrmp.ts +++ b/packages/page-parachains/src/useHrmp.ts @@ -35,7 +35,7 @@ export default function useHrmp (): AllChannels | undefined { (api.events.parasHrmp || api.events.hrmp)?.OpenChannelAccepted, (api.events.parasHrmp || api.events.hrmp)?.ChannelClosed ]); - const channelIds = useMapKeys((api.query.parasHrmp || api.query.hrmp)?.hrmpChannels, { at: trigger, transform: extractChannelIds }); + const channelIds = useMapKeys((api.query.parasHrmp || api.query.hrmp)?.hrmpChannels, { at: trigger.blockHash, transform: extractChannelIds }); return useCall(channelIds && (api.query.parasHrmp || api.query.hrmp)?.hrmpChannels.multi, [channelIds], optChannels); } diff --git a/packages/page-parachains/src/useOwnedIds.ts b/packages/page-parachains/src/useOwnedIds.ts index 090c6e4101..00499b76df 100644 --- a/packages/page-parachains/src/useOwnedIds.ts +++ b/packages/page-parachains/src/useOwnedIds.ts @@ -55,7 +55,7 @@ export default function useOwnedIds (): OwnedId[] { const { api } = useApi(); const { allAccounts } = useAccounts(); const trigger = useEventTrigger([api.events.registrar.Registered, api.events.registrar.Reserved]); - const unfiltered = useMapEntries(api.query.registrar.paras, { at: trigger, transform: extractIds }); + const unfiltered = useMapEntries(api.query.registrar.paras, { at: trigger.blockHash, transform: extractIds }); const hashes = useCall(api.query.paras.currentCodeHash.multi, [unfiltered ? unfiltered.ids : []], hashesOption); return useMemo( diff --git a/packages/page-parachains/src/useProposals.ts b/packages/page-parachains/src/useProposals.ts index 262644ab5a..9c17cdc3c7 100644 --- a/packages/page-parachains/src/useProposals.ts +++ b/packages/page-parachains/src/useProposals.ts @@ -35,8 +35,8 @@ export default function useProposals (): Proposals | undefined { const { api } = useApi(); const mountedRef = useIsMountedRef(); const trigger = useEventTrigger([api.events.proposeParachain?.ProposeParachain]); - const proposalIds = useMapKeys(api.query.proposeParachain?.proposals, { at: trigger, transform: extractProposalIds }); - const scheduled = useMapEntries(api.query.proposeParachain?.scheduledProposals, { at: trigger, transform: extractScheduled }); + const proposalIds = useMapKeys(api.query.proposeParachain?.proposals, { at: trigger.blockHash, transform: extractProposalIds }); + const scheduled = useMapEntries(api.query.proposeParachain?.scheduledProposals, { at: trigger.blockHash, transform: extractScheduled }); const [sessionIndex, approvedIds] = useCallMulti([ api.query.session.currentIndex, api.query.proposeParachain?.approvedProposals diff --git a/packages/page-parachains/src/useUpcomingIds.ts b/packages/page-parachains/src/useUpcomingIds.ts index a17fcb044e..a0755c5ff5 100644 --- a/packages/page-parachains/src/useUpcomingIds.ts +++ b/packages/page-parachains/src/useUpcomingIds.ts @@ -28,5 +28,5 @@ export default function useUpomingIds (): ParaId[] | undefined { const { api } = useApi(); const trigger = useEventTrigger([api.events.session.NewSession, api.events.registrar.Registered]); - return useMapEntries(api.query.paras.paraLifecycles, { at: trigger, transform: extractIds }); + return useMapEntries(api.query.paras.paraLifecycles, { at: trigger.blockHash, transform: extractIds }); } diff --git a/packages/page-treasury/src/useTipHashes.ts b/packages/page-treasury/src/useTipHashes.ts index ee8404b9b4..d99189651b 100644 --- a/packages/page-treasury/src/useTipHashes.ts +++ b/packages/page-treasury/src/useTipHashes.ts @@ -14,5 +14,5 @@ export default function useTipHashes (): string[] | undefined { const { api } = useApi(); const trigger = useEventTrigger([api.events.tips?.NewTip, api.events.tips?.TipClosed, api.events.tips?.TipRetracted]); - return useMapKeys((api.query.tips || api.query.treasury)?.tips, { at: trigger, transform: extractHashes }); + return useMapKeys((api.query.tips || api.query.treasury)?.tips, { at: trigger.blockHash, transform: extractHashes }); } diff --git a/packages/react-hooks/src/useEventTrigger.ts b/packages/react-hooks/src/useEventTrigger.ts index f43868d4a1..3b6e7d7049 100644 --- a/packages/react-hooks/src/useEventTrigger.ts +++ b/packages/react-hooks/src/useEventTrigger.ts @@ -13,21 +13,41 @@ import { useIsMountedRef } from './useIsMountedRef'; type EventCheck = AugmentedEvent<'promise'> | false | undefined | null; +interface Result { + blockHash: string; + events: EventRecord[]; +} + +const EMPTY_RESULT: Result = { + blockHash: '', + events: [] +}; + const IDENTITY_FILTER = () => true; -export function useEventTrigger (checks: EventCheck[], filter: (record: EventRecord) => boolean = IDENTITY_FILTER): string { +export function useEventTrigger (_checks: EventCheck[], filter: (record: EventRecord) => boolean = IDENTITY_FILTER): Result { const { api } = useApi(); - const [trigger, setTrigger] = useState('0'); + const [state, setState] = useState(() => EMPTY_RESULT); + const [checks] = useState(() => _checks); const mountedRef = useIsMountedRef(); const eventRecords = useCall>(api.query.system.events); useEffect((): void => { - mountedRef.current && eventRecords && eventRecords.filter((r) => - r.event && - checks.some((c) => c && c.is(r.event)) && - filter(r) - ).length && setTrigger(eventRecords.createdAtHash?.toHex() || ''); + if (mountedRef.current && eventRecords) { + const events = eventRecords.filter((r) => + r.event && + checks.some((c) => c && c.is(r.event)) && + filter(r) + ); + + if (events.length) { + setState({ + blockHash: eventRecords.createdAtHash?.toHex() || '', + events + }); + } + } }, [eventRecords, checks, filter, mountedRef]); - return trigger; + return state; }