Unverified Commit ffa23843 authored by Hanwen Cheng's avatar Hanwen Cheng Committed by GitHub
Browse files

refactor: regroup the paths into their own network. (#575)

* fix: add backward compatibility for kusama account with different genesisHash

* refactor: regroup the paths into their own network

* fix logic error
parent ccdee715
......@@ -23,10 +23,13 @@ import Address from './Address';
import TouchableItem from './TouchableItem';
import AccountPrefixedTitle from './AccountPrefixedTitle';
import {
isSubstrateNetworkParams,
isUnknownNetworkParams
} from 'types/networkSpecsTypes';
import {
defaultNetworkKey,
NETWORK_LIST,
NetworkProtocols,
UnknownNetworkKeys
} from 'constants/networkSpecs';
import colors from 'styles/colors';
......@@ -45,6 +48,7 @@ export default function PathCard({
identity,
path,
name,
networkKey,
testID,
titlePrefix
}: {
......@@ -52,6 +56,7 @@ export default function PathCard({
identity: Identity;
path: string;
name?: string;
networkKey?: string;
testID?: string;
titlePrefix?: string;
}): React.ReactElement {
......@@ -60,11 +65,12 @@ export default function PathCard({
const address = getAddressWithPath(path, identity);
const isUnknownAddress = address === '';
const networkKey = getNetworkKeyByPath(path);
const network =
networkKey === UnknownNetworkKeys.UNKNOWN && !isUnknownAddress
const computedNetworkKey =
networkKey || getNetworkKeyByPath(path, identity.meta.get(path)!);
const networkParams =
computedNetworkKey === UnknownNetworkKeys.UNKNOWN && !isUnknownAddress
? NETWORK_LIST[defaultNetworkKey]
: NETWORK_LIST[networkKey];
: NETWORK_LIST[computedNetworkKey];
const nonSubstrateCard = (
<View testID={testID}>
......@@ -77,21 +83,25 @@ export default function PathCard({
}}
/>
<View style={styles.content}>
<AccountIcon address={address} network={network} style={styles.icon} />
<AccountIcon
address={address}
network={networkParams}
style={styles.icon}
/>
<View style={styles.desc}>
<View>
<Text style={[fontStyles.t_regular, { color: colors.bg_text_sec }]}>
{network.title}
{networkParams.title}
</Text>
</View>
<AccountPrefixedTitle title={pathName!} titlePrefix={titlePrefix} />
<Address address={address} protocol={network.protocol} />
<Address address={address} protocol={networkParams.protocol} />
</View>
<View
style={[
styles.footer,
{
backgroundColor: network.color
backgroundColor: networkParams.color
}
]}
/>
......@@ -109,7 +119,7 @@ export default function PathCard({
<View style={[styles.content, styles.contentDer]}>
<AccountIcon
address={address}
network={network}
network={networkParams}
style={styles.icon}
/>
<View style={styles.desc}>
......@@ -133,8 +143,8 @@ export default function PathCard({
</View>
);
return network.protocol === NetworkProtocols.SUBSTRATE ||
network.protocol === NetworkProtocols.UNKNOWN
return isSubstrateNetworkParams(networkParams) ||
isUnknownNetworkParams(networkParams)
? substrateDerivationCard
: nonSubstrateCard;
}
......
......@@ -23,6 +23,8 @@ import {
UnknownNetworkParams
} from 'types/networkSpecsTypes';
export const unknownNetworkPathId = '';
export const NetworkProtocols: {
[key: string]: NetworkProtocol;
} = Object.freeze({
......@@ -74,12 +76,12 @@ export const SubstrateNetworkKeys: {
const unknownNetworkBase: { [key: string]: UnknownNetworkParams } = {
[UnknownNetworkKeys.UNKNOWN]: {
color: colors.bg_alert,
order: 0,
pathId: '',
order: 99,
pathId: unknownNetworkPathId,
prefix: 2,
protocol: NetworkProtocols.UNKNOWN,
secondaryColor: colors.card_bgSolid,
title: 'Custom network'
title: 'Unknown network'
}
};
......
......@@ -53,8 +53,9 @@ import { NetworkCard } from 'components/AccountCard';
import {
NetworkParams,
SubstrateNetworkParams,
isEthereumNetworkParams,
isSubstrateNetworkParams,
isEthereumNetworkParams
isUnknownNetworkParams
} from 'types/networkSpecsTypes';
const excludedNetworks = [
......@@ -142,7 +143,7 @@ function AccountNetworkChooser({
const filterNetworkKeys = ([networkKey]: [string, any]): boolean => {
const shouldExclude = excludedNetworks.includes(networkKey);
if (isNew && !shouldExclude) return true;
const availableNetworks = getExistedNetworkKeys(currentIdentity!);
if (shouldShowMoreNetworks) {
if (shouldExclude) return false;
return !availableNetworks.includes(networkKey);
......@@ -240,9 +241,15 @@ function AccountNetworkChooser({
}
} else {
const paths = Array.from(currentIdentity!.meta.keys());
if (isSubstrateNetworkParams(networkParams)) {
const listedPaths = getPathsWithSubstrateNetworkKey(paths, networkKey);
if (listedPaths.length === 0)
if (
isSubstrateNetworkParams(networkParams) ||
isUnknownNetworkParams(networkParams)
) {
const listedPaths = getPathsWithSubstrateNetworkKey(
currentIdentity!,
networkKey
);
if (listedPaths.length === 0 && isSubstrateNetworkParams(networkParams))
return await deriveSubstrateNetworkRootPath(
networkKey,
networkParams
......@@ -261,6 +268,7 @@ function AccountNetworkChooser({
if (identities.length === 0) return showOnboardingMessage();
if (!currentIdentity) return showNoCurrentIdentityMessage();
const availableNetworks = getExistedNetworkKeys(currentIdentity!);
const networkList = Object.entries(NETWORK_LIST).filter(filterNetworkKeys);
networkList.sort(sortNetworkKeys);
......
......@@ -23,11 +23,7 @@ import { NavigationAccountProps } from 'types/props';
import { withAccountStore } from 'utils/HOC';
import TextInput from 'components/TextInput';
import ButtonMainAction from 'components/ButtonMainAction';
import {
getNetworkKey,
getNetworkKeyByPath,
validateDerivedPath
} from 'utils/identitiesUtils';
import { getNetworkKey, validateDerivedPath } from 'utils/identitiesUtils';
import { navigateToPathsList, unlockSeedPhrase } from 'utils/navigationHelpers';
import { alertPathDerivationError } from 'utils/alertUtils';
import Separator from 'components/Separator';
......@@ -48,22 +44,21 @@ function PathDerivation({
const [modalVisible, setModalVisible] = useState(false);
const pathNameInput = useRef<TextInput>(null);
const parentPath = route.params.parentPath;
const [customNetworkKey, setCustomNetworkKey] = useState(() => {
const parentNetworkKey = getNetworkKey(
parentPath,
accounts.state.currentIdentity!
);
return parentNetworkKey === UnknownNetworkKeys.UNKNOWN
const parentNetworkKey = useMemo(
() => getNetworkKey(parentPath, accounts.state.currentIdentity!),
[parentPath, accounts.state.currentIdentity]
);
const [customNetworkKey, setCustomNetworkKey] = useState(
parentNetworkKey === UnknownNetworkKeys.UNKNOWN
? defaultNetworkKey
: parentNetworkKey;
});
const completePath = `${parentPath}${derivationPath}`;
const pathIndicatedNetworkKey = useMemo(
(): string => getNetworkKeyByPath(completePath),
[completePath]
: parentNetworkKey
);
const isCustomNetwork =
pathIndicatedNetworkKey === UnknownNetworkKeys.UNKNOWN;
const completePath = `${parentPath}${derivationPath}`;
const enableCustomNetwork = parentPath === '';
const currentNetworkKey = enableCustomNetwork
? customNetworkKey
: parentNetworkKey;
const onPathDerivation = async (): Promise<void> => {
if (!validateDerivedPath(derivationPath)) {
......@@ -73,11 +68,11 @@ function PathDerivation({
const derivationSucceed = await accounts.deriveNewPath(
completePath,
seedPhrase,
isCustomNetwork ? customNetworkKey : pathIndicatedNetworkKey,
currentNetworkKey,
keyPairsName
);
if (derivationSucceed) {
navigateToPathsList(navigation, pathIndicatedNetworkKey);
navigateToPathsList(navigation, currentNetworkKey);
} else {
setIsPathValid(false);
alertPathDerivationError();
......@@ -117,7 +112,7 @@ function PathDerivation({
testID={testIDs.PathDerivation.nameInput}
value={keyPairsName}
/>
{isCustomNetwork && (
{enableCustomNetwork && (
<NetworkSelector
networkKey={customNetworkKey}
setVisible={setModalVisible}
......@@ -128,6 +123,7 @@ function PathDerivation({
identity={accounts.state.currentIdentity!}
name={keyPairsName}
path={completePath}
networkKey={currentNetworkKey}
/>
<ButtonMainAction
......@@ -138,7 +134,7 @@ function PathDerivation({
testID={testIDs.PathDerivation.deriveButton}
onPress={onPathDerivation}
/>
{isCustomNetwork && (
{enableCustomNetwork && (
<NetworkOptions
setNetworkKey={setCustomNetworkKey}
visible={modalVisible}
......
......@@ -34,7 +34,6 @@ import QrView from 'components/QrView';
import {
getAddressWithPath,
getNetworkKey,
getNetworkKeyByPath,
getPathName,
getPathsWithSubstrateNetworkKey,
isSubstratePath
......@@ -76,17 +75,15 @@ export function PathDetailsView({
alertDeleteAccount('this account', async () => {
await unlockSeedPhrase(navigation);
const deleteSucceed = await accounts.deletePath(path);
const paths = Array.from(accounts.state.currentIdentity!.meta.keys());
const pathIndicatedNetworkKey = getNetworkKeyByPath(path);
if (deleteSucceed) {
if (isSubstratePath(path)) {
const listedPaths = getPathsWithSubstrateNetworkKey(
paths,
pathIndicatedNetworkKey
accounts.state.currentIdentity!,
networkKey
);
const hasOtherPaths = listedPaths.length > 0;
hasOtherPaths
? navigateToPathsList(navigation, pathIndicatedNetworkKey)
? navigateToPathsList(navigation, networkKey)
: navigation.navigate('AccountNetworkChooser');
} else {
navigation.navigate('AccountNetworkChooser');
......
......@@ -20,14 +20,13 @@ import { Text, View } from 'react-native';
import { PathDetailsView } from './PathDetails';
import { SafeAreaScrollViewContainer } from 'components/SafeAreaContainer';
import {
NETWORK_LIST,
NetworkProtocols,
UnknownNetworkKeys
} from 'constants/networkSpecs';
import { NETWORK_LIST, UnknownNetworkKeys } from 'constants/networkSpecs';
import testIDs from 'e2e/testIDs';
import { PathGroup } from 'types/identityTypes';
import { isEthereumNetworkParams } from 'types/networkSpecsTypes';
import {
isEthereumNetworkParams,
isUnknownNetworkParams
} from 'types/networkSpecsTypes';
import { NavigationAccountProps } from 'types/props';
import { withAccountStore } from 'utils/HOC';
import {
......@@ -52,12 +51,13 @@ function PathsList({
const { currentIdentity } = accounts.state;
const isEthereumPath = isEthereumNetworkParams(networkParams);
const isUnknownNetworkPath =
networkParams.protocol === NetworkProtocols.UNKNOWN;
const isUnknownNetworkPath = isUnknownNetworkParams(networkParams);
const pathsGroups = useMemo((): PathGroup[] | null => {
if (!currentIdentity || isEthereumPath) return null;
const paths = Array.from(currentIdentity.meta.keys());
const listedPaths = getPathsWithSubstrateNetworkKey(paths, networkKey);
const listedPaths = getPathsWithSubstrateNetworkKey(
currentIdentity,
networkKey
);
return groupPaths(listedPaths);
}, [currentIdentity, isEthereumPath, networkKey]);
......
......@@ -270,10 +270,9 @@ export default class AccountsStore extends Container<AccountsStoreState> {
//assume it is an accountId
if (networkKey !== UnknownNetworkKeys.UNKNOWN) {
derivedAccount = this.getAccountFromIdentity(accountId);
} else {
//TODO remove this function when we enable networkSpecs update and remove KUSAMA_CC2 network.
derivedAccount = this.getAccountFromIdentity(address);
}
//TODO backward support for user who has create account in known network for an unknown network. removed after offline network update
derivedAccount = derivedAccount || this.getAccountFromIdentity(address);
if (derivedAccount instanceof Object)
return { ...derivedAccount, isLegacy: false };
......@@ -516,9 +515,11 @@ export default class AccountsStore extends Container<AccountsStoreState> {
async deletePath(path: string): Promise<boolean> {
if (this.state.currentIdentity === null) return false;
const updatedCurrentIdentity = deepCopyIdentity(this.state.currentIdentity);
const { address } = updatedCurrentIdentity.meta.get(path)!;
const pathMeta = updatedCurrentIdentity.meta.get(path)!;
updatedCurrentIdentity.meta.delete(path);
updatedCurrentIdentity.addresses.delete(getAddressKeyByPath(address, path));
updatedCurrentIdentity.addresses.delete(
getAddressKeyByPath(path, pathMeta)
);
try {
await this.setState({
......
import { NetworkProtocols } from 'constants/networkSpecs';
import { NetworkProtocols, unknownNetworkPathId } from 'constants/networkSpecs';
export type NetworkProtocol = 'ethereum' | 'substrate' | 'unknown';
......@@ -47,9 +47,9 @@ export function isSubstrateNetworkParams(
| UnknownNetworkParams
| EthereumNetworkParams
): networkParams is SubstrateNetworkParams {
const { protocol, pathId } = networkParams as SubstrateNetworkParams;
return (
(networkParams as SubstrateNetworkParams).protocol ===
NetworkProtocols.SUBSTRATE
protocol === NetworkProtocols.SUBSTRATE && pathId !== unknownNetworkPathId
);
}
......@@ -71,8 +71,10 @@ export function isUnknownNetworkParams(
| UnknownNetworkParams
| EthereumNetworkParams
): networkParams is UnknownNetworkParams {
const { protocol, pathId } = networkParams as SubstrateNetworkParams;
return (
(networkParams as UnknownNetworkParams).protocol ===
NetworkProtocols.UNKNOWN
(protocol === NetworkProtocols.SUBSTRATE &&
pathId === unknownNetworkPathId) ||
protocol === NetworkProtocols.UNKNOWN
);
}
......@@ -14,15 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import {
NetworkProtocols,
NETWORK_LIST,
SubstrateNetworkKeys
} from 'constants/networkSpecs';
import { NETWORK_LIST, SubstrateNetworkKeys } from 'constants/networkSpecs';
import { UnlockedAccount } from 'types/identityTypes';
import {
EthereumNetworkParams,
isSubstrateNetworkParams
isSubstrateNetworkParams,
isUnknownNetworkParams
} from 'types/networkSpecsTypes';
import { ValidSeed } from 'types/utilTypes';
......@@ -50,7 +47,7 @@ export function generateAccountId({
if (isSubstrateNetworkParams(networkParams)) {
const { genesisHash } = networkParams;
return `${protocol}:${address}:${genesisHash ?? ''}`;
} else if (protocol === NetworkProtocols.UNKNOWN) {
} else if (isUnknownNetworkParams(networkParams)) {
return `substrate:${address}`;
} else {
const { ethereumChainId } = networkParams as EthereumNetworkParams;
......
......@@ -27,6 +27,7 @@ import {
} from 'constants/networkSpecs';
import {
Account,
AccountMeta,
FoundAccount,
FoundLegacyAccount,
Identity,
......@@ -51,7 +52,7 @@ export function isLegacyFoundAccount(
return foundAccount.isLegacy;
}
const extractPathId = (path: string): string | null => {
export const extractPathId = (path: string): string | null => {
const matchNetworkPath = path.match(pathsRegex.networkPath);
if (!matchNetworkPath) return null;
return removeSlash(matchNetworkPath[0]);
......@@ -79,10 +80,18 @@ export const extractAddressFromAccountId = (id: string): string => {
return address;
};
export const getAddressKeyByPath = (address: string, path: string): string =>
isSubstratePath(path)
export const getAddressKeyByPath = (
path: string,
pathMeta: AccountMeta
): string => {
const address = pathMeta.address;
return isSubstratePath(path)
? address
: generateAccountId({ address, networkKey: getNetworkKeyByPath(path) });
: generateAccountId({
address,
networkKey: getNetworkKeyByPath(path, pathMeta)
});
};
export function emptyIdentity(): Identity {
return {
......@@ -138,22 +147,37 @@ export const deepCopyIdentity = (identity: Identity): Identity =>
deserializeIdentity(serializeIdentity(identity));
export const getPathsWithSubstrateNetworkKey = (
paths: string[],
identity: Identity,
networkKey: string
): string[] => {
if (networkKey === UnknownNetworkKeys.UNKNOWN) {
const pathIdList = Object.values(SUBSTRATE_NETWORK_LIST).map(
networkParams => networkParams.pathId
);
return paths.filter(path => {
const pathId = extractPathId(path);
if (!isSubstratePath(path)) return false;
return !pathId || !pathIdList.includes(pathId);
});
}
return paths.filter(
path => extractPathId(path) === SUBSTRATE_NETWORK_LIST[networkKey].pathId
);
const pathEntries = Array.from(identity.meta.entries());
const isUnknownNetworkKey = networkKey === 'unknown';
const targetPathId = SUBSTRATE_NETWORK_LIST[networkKey]?.pathId;
const knownPathIds = Object.values(SUBSTRATE_NETWORK_LIST).map(v => v.pathId);
const pathReducer = (
groupedPaths: string[],
[path, pathMeta]: [string, AccountMeta]
): string[] => {
let pathId;
if (!isSubstratePath(path)) return groupedPaths;
if (pathMeta.hasOwnProperty('networkPathId')) {
pathId = pathMeta.networkPathId;
} else {
pathId = extractPathId(path);
}
if (!isUnknownNetworkKey) {
if (pathId === targetPathId) {
groupedPaths.push(path);
}
} else {
if (pathId && !knownPathIds.includes(pathId)) {
groupedPaths.push(path);
}
}
return groupedPaths;
};
return pathEntries.reduce(pathReducer, []);
};
const getNetworkKeyByPathId = (pathId: string): string => {
......@@ -167,17 +191,19 @@ const getNetworkKeyByPathId = (pathId: string): string => {
export const getNetworkKey = (path: string, identity: Identity): string => {
if (identity.meta.has(path)) {
const networkPathId = identity.meta.get(path)!.networkPathId;
if (networkPathId) return getNetworkKeyByPathId(networkPathId);
return getNetworkKeyByPath(path, identity.meta.get(path)!);
}
return getNetworkKeyByPath(path);
return UnknownNetworkKeys.UNKNOWN;
};
export const getNetworkKeyByPath = (path: string): string => {
export const getNetworkKeyByPath = (
path: string,
pathMeta: AccountMeta
): string => {
if (!isSubstratePath(path) && NETWORK_LIST.hasOwnProperty(path)) {
return path;
}
const pathId = extractPathId(path);
const pathId = pathMeta.networkPathId || extractPathId(path);
if (!pathId) return UnknownNetworkKeys.UNKNOWN;
return getNetworkKeyByPathId(pathId);
......@@ -236,16 +262,19 @@ export const unlockIdentitySeed = async (
};
export const getExistedNetworkKeys = (identity: Identity): string[] => {
const pathsList = Array.from(identity.addresses.values());
const networkKeysSet = pathsList.reduce((networksSet, path) => {
let networkKey;
if (isSubstratePath(path)) {
networkKey = getNetworkKeyByPath(path);
} else {
networkKey = path;
}
return { ...networksSet, [networkKey]: true };
}, {});
const pathEntries = Array.from(identity.meta.entries());
const networkKeysSet = pathEntries.reduce(
(networksSet, [path, pathMeta]: [string, AccountMeta]) => {
let networkKey;
if (isSubstratePath(path)) {
networkKey = getNetworkKeyByPath(path, pathMeta);
} else {
networkKey = path;
}
return { ...networksSet, [networkKey]: true };
},
{}
);
return Object.keys(networkKeysSet);
};
......
......@@ -55,6 +55,7 @@ const raw = [
expectName: 'funding2',
isKusamaPath: true,
name: '',
networkPathId: 'westend',
path: '//kusama//funding/2'
},
{
......@@ -128,6 +129,7 @@ const metaMap = raw.reduce((acc, v) => {
address: v.address,
createdAt: 1573142786972,
name: v.name,
networkPathId: v.networkPathId,
updatedAt: 1573142786972
};
acc.set(v.path, meta);
......@@ -234,22 +236,26 @@ describe('IdentitiesUtils', () => {
expect(networkKeys).toEqual([
EthereumNetworkKeys.FRONTIER,
SubstrateNetworkKeys.KUSAMA,
SubstrateNetworkKeys.WESTEND,
UnknownNetworkKeys.UNKNOWN,
SubstrateNetworkKeys.POLKADOT
]);
});
it('get networkKey correctly by path', () => {
expect(getNetworkKeyByPath('')).toEqual(UnknownNetworkKeys.UNKNOWN);
expect(getNetworkKeyByPath('//kusama')).toEqual(
const getNetworkKeyByPathTest = (path: string): string => {
return getNetworkKeyByPath(path, testIdentities[0].meta.get(path));
};
expect(getNetworkKeyByPathTest('')).toEqual(UnknownNetworkKeys.UNKNOWN);
expect(getNetworkKeyByPathTest('//kusama')).toEqual(
SubstrateNetworkKeys.KUSAMA
);
expect(getNetworkKeyByPath('//kusama//derived//anything')).toEqual(
expect(getNetworkKeyByPathTest('//kusama//funding/1')).toEqual(
SubstrateNetworkKeys.KUSAMA
);
expect(getNetworkKeyByPath('1')).toEqual(EthereumNetworkKeys.FRONTIER);
expect(getNetworkKeyByPath('//anything/could/be')).toEqual(
UnknownNetworkKeys.UNKNOWN
expect(getNetworkKeyByPathTest('//kusama//funding/2')).toEqual(
SubstrateNetworkKeys.WESTEND
);
expect(getNetworkKeyByPathTest('1')).toEqual(EthereumNetworkKeys.FRONTIER);
});
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!