Optimize crowdloan refresh (delta only on events) (#5549)

* Optimize crowdloan refresh (delata only on events)

* Adjust own contributions
This commit is contained in:
Jaco 2021-06-10 10:05:40 +03:00 committed by GitHub
parent ac622aa3d2
commit c21dadcd9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 111 additions and 42 deletions

View File

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

View File

@ -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<string, Balance> {
const { api } = useApi();
const { allAccountsHex } = useAccounts();
const [state, setState] = useState<Record<string, Balance>>({});
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<Contributions>(() => NO_CONTRIB);
const [state, _setState] = useState<Contributions>(() => 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
}));

View File

@ -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<Campaign[]>(api.query.crowdloan?.funds.multi, [paraIds], optFundMulti);
const leases = useCall<ParaId[]>(api.query.slots.leases.multi, [paraIds], optLeaseMulti);
const [result, setResult] = useState<Campaigns>(EMPTY);

View File

@ -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<AllChannels>(channelIds && (api.query.parasHrmp || api.query.hrmp)?.hrmpChannels.multi, [channelIds], optChannels);
}

View File

@ -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<Owned>(api.query.registrar.paras, { at: trigger, transform: extractIds });
const unfiltered = useMapEntries<Owned>(api.query.registrar.paras, { at: trigger.blockHash, transform: extractIds });
const hashes = useCall(api.query.paras.currentCodeHash.multi, [unfiltered ? unfiltered.ids : []], hashesOption);
return useMemo(

View File

@ -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<MultiQuery>([
api.query.session.currentIndex,
api.query.proposeParachain?.approvedProposals

View File

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

View File

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

View File

@ -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<Vec<EventRecord>>(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;
}