Skip to content

Commit

Permalink
feat: add main network sync controller integration
Browse files Browse the repository at this point in the history
I need to add some integration tests to be a bit more confident in outcome
  • Loading branch information
Prithpal-Sooriya committed Sep 13, 2024
1 parent ccd95c8 commit eef26fd
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 188 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import type {
KeyringControllerUnlockEvent,
KeyringControllerAddNewAccountAction,
} from '@metamask/keyring-controller';
import type { NetworkConfiguration } from '@metamask/network-controller';
import type {
NetworkConfiguration,
NetworkController,
NetworkControllerGetStateAction,
} from '@metamask/network-controller';
import type { HandleSnapRequest } from '@metamask/snaps-controllers';

import { createSnapSignMessageRequest } from '../authentication/auth-snap-requests';
Expand All @@ -35,7 +39,10 @@ import {
mapInternalAccountToUserStorageAccount,
} from './accounts/user-storage';
import { createSHA256Hash } from './encryption';
import { startNetworkSyncing } from './network-syncing/controller-integration';
import {
performMainNetworkSync,
startNetworkSyncing,
} from './network-syncing/controller-integration';
import type {
UserStoragePathWithFeatureAndKey,
UserStoragePathWithFeatureOnly,
Expand All @@ -46,26 +53,32 @@ import {
upsertUserStorage,
} from './services';

// TODO: add external NetworkController event
// Need to listen for when a network gets added
// TODO - replace shimmed interface with actual interfaces once merged
// Waiting on #4698
type NetworkControllerNetworkAddedEvent = {
type: 'NetworkController:networkAdded';
payload: [networkConfiguration: NetworkConfiguration];
};

// TODO: add external NetworkController event
// Need to listen for when a network is updated, or the default rpc/block explorer changes
type NetworkControllerNetworkChangedEvent = {
type: 'NetworkController:networkChanged';
type NetworkControllerNetworkUpdatedEvent = {
type: 'NetworkController:networkUpdated';
payload: [networkConfiguration: NetworkConfiguration];
};

// TODO: add external NetworkController event
// Need to listen for when a network gets deleted
type NetworkControllerNetworkDeletedEvent = {
type: 'NetworkController:networkDeleted';
type NetworkControllerNetworkRemovedEvent = {
type: 'NetworkController:networkRemoved';
payload: [networkConfiguration: NetworkConfiguration];
};
type NetworkControllerAddNetworkAction = {
type: 'NetworkController:addNetwork';
handler: NetworkController['addNetwork'];
};
type NetworkControllerUpdateNetworkAction = {
type: 'NetworkController:updateNetwork';
handler: NetworkController['updateNetwork'];
};
type NetworkControllerRemoveNetworkAction = {
type: 'NetworkController:removeNetwork';
handler: NetworkController['removeNetwork'];
};

// TODO: fix external dependencies
export declare type NotificationServicesControllerDisableNotificationServices =
Expand Down Expand Up @@ -173,10 +186,15 @@ export type AllowedActions =
// Metamask Notifications
| NotificationServicesControllerDisableNotificationServices
| NotificationServicesControllerSelectIsNotificationServicesEnabled
// Account syncing
// Account Syncing
| AccountsControllerListAccountsAction
| AccountsControllerUpdateAccountMetadataAction
| KeyringControllerAddNewAccountAction;
| KeyringControllerAddNewAccountAction
// Network Syncing
| NetworkControllerGetStateAction
| NetworkControllerAddNetworkAction
| NetworkControllerUpdateNetworkAction
| NetworkControllerRemoveNetworkAction;

// Messenger events
export type UserStorageControllerStateChangeEvent = ControllerStateChangeEvent<
Expand Down Expand Up @@ -207,8 +225,8 @@ export type AllowedEvents =
| AccountsControllerAccountRenamedEvent
// Network Syncing Events
| NetworkControllerNetworkAddedEvent
| NetworkControllerNetworkChangedEvent
| NetworkControllerNetworkDeletedEvent;
| NetworkControllerNetworkUpdatedEvent
| NetworkControllerNetworkRemovedEvent;

// Messenger
export type UserStorageControllerMessenger = RestrictedControllerMessenger<
Expand All @@ -232,6 +250,13 @@ export default class UserStorageController extends BaseController<
UserStorageControllerState,
UserStorageControllerMessenger
> {
// This is replaced with the actual value in the constructor
// We will remove this once the feature will be released
#env = {
isAccountSyncingEnabled: false,
isNetworkSyncingEnabled: false,
};

#auth = {
getBearerToken: async () => {
return await this.messagingSystem.call(
Expand Down Expand Up @@ -260,17 +285,12 @@ export default class UserStorageController extends BaseController<
};

#accounts = {
// This is replaced with the actual value in the constructor
// We will remove this once the feature will be released
isAccountSyncingEnabled: false,
isAccountSyncingInProgress: false,
canSync: () => {
try {
this.#assertProfileSyncingEnabled();

return (
this.#accounts.isAccountSyncingEnabled && this.#auth.isAuthEnabled()
);
return this.#env.isAccountSyncingEnabled && this.#auth.isAuthEnabled();
} catch {
return false;
}
Expand Down Expand Up @@ -406,9 +426,8 @@ export default class UserStorageController extends BaseController<
state: { ...defaultState, ...state },
});

this.#accounts.isAccountSyncingEnabled = Boolean(
env?.isAccountSyncingEnabled,
);
this.#env.isAccountSyncingEnabled = Boolean(env?.isAccountSyncingEnabled);
this.#env.isNetworkSyncingEnabled = Boolean(env?.isNetworkSyncingEnabled);

this.getMetaMetricsState = getMetaMetricsState;
this.#keyringController.setupLockedStateSubscriptions();
Expand All @@ -417,18 +436,10 @@ export default class UserStorageController extends BaseController<
this.#accounts.setupAccountSyncingSubscriptions();

// Network Syncing
if (env?.isNetworkSyncingEnabled) {
if (this.#env.isNetworkSyncingEnabled) {
startNetworkSyncing({
messenger,
getStorageConfig: async () => {
const { storageKey, bearerToken } =
await this.#getStorageKeyAndBearerToken();
return {
storageKey,
bearerToken,
nativeScryptCrypto: this.#nativeScryptCrypto,
};
},
getStorageConfig: this.#getStorageOptions,
});
}
}
Expand Down Expand Up @@ -479,6 +490,20 @@ export default class UserStorageController extends BaseController<
);
}

async #getStorageOptions() {
if (!this.state.isProfileSyncingEnabled) {
return null;
}

const { storageKey, bearerToken } =
await this.#getStorageKeyAndBearerToken();
return {
storageKey,
bearerToken,
nativeScryptCrypto: this.#nativeScryptCrypto,
};
}

public async enableProfileSyncing(): Promise<void> {
try {
this.#setIsProfileSyncingUpdateLoading(true);
Expand Down Expand Up @@ -868,4 +893,15 @@ export default class UserStorageController extends BaseController<
);
}
}

async syncNetworks() {
if (!this.#env.isNetworkSyncingEnabled) {
return;
}

await performMainNetworkSync({
messenger: this.messagingSystem,
getStorageConfig: this.#getStorageOptions,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,50 @@ import log from 'loglevel';

import type { UserStorageBaseOptions } from '../services';
import type { UserStorageControllerMessenger } from '../UserStorageController';
import { addNetwork, deleteNetwork, updateNetwork } from './sync-mutations';
import { getAllRemoteNetworks } from './services';
import { findNetworksToUpdate } from './sync-all';
import { batchUpdateNetworks, deleteNetwork } from './sync-mutations';

type SetupNetworkSyncingProps = {
type NetworkSyncingProps = {
messenger: UserStorageControllerMessenger;
getStorageConfig: () => Promise<UserStorageBaseOptions>;
getStorageConfig: () => Promise<UserStorageBaseOptions | null>;
};

/**
* Global in-mem cache to signify that the network syncing is in progress
* Ensures that listeners do not fire during main sync (prevent double requests)
*/
let isMainNetworkSyncInProgress = false;

/**
* Initialize and setup events to listen to for network syncing
* We will be listening to:
* - Remove Event, to indicate that we need to remote network from remote
*
* We will not be listening to:
* - Add/Update events are not required, as we can sync these during the main sync
*
* @param props - parameters used for initializing and enabling network syncing
*/
export function startNetworkSyncing(props: SetupNetworkSyncingProps) {
export function startNetworkSyncing(props: NetworkSyncingProps) {
const { messenger, getStorageConfig } = props;

try {
messenger.subscribe(
'NetworkController:networkAdded',
'NetworkController:networkRemoved',
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async (networkConfiguration) => {
try {
// As main sync is in progress, it will already local and remote networks
// So no need to re-process again.
if (isMainNetworkSyncInProgress) {
return;
}

const opts = await getStorageConfig();
await addNetwork(networkConfiguration, opts);
if (!opts) {
return;
}
await deleteNetwork(networkConfiguration, opts);
} catch {
// Silently fail sync
}
Expand All @@ -32,38 +54,75 @@ export function startNetworkSyncing(props: SetupNetworkSyncingProps) {
} catch (e) {
log.warn('NetworkSyncing, event subscription failed', e);
}
}

/**
* Action to perform the main network sync.
* It will fetch local networks and remote networks, then determines which networks (local and remote) to add/update
* @param props - parameters used for this main sync
*/
export async function performMainNetworkSync(props: NetworkSyncingProps) {
const { messenger, getStorageConfig } = props;
isMainNetworkSyncInProgress = true;
try {
messenger.subscribe(
'NetworkController:networkDeleted',
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async (networkConfiguration) => {
const opts = await getStorageConfig();
if (!opts) {
return;
}

const localNetworks = Object.values(
messenger.call('NetworkController:getState')
.networkConfigurationsByChainId ?? {},
);

const remoteNetworks = await getAllRemoteNetworks(opts);

const networksToUpdate = findNetworksToUpdate({
localNetworks,
remoteNetworks,
});

// Update Remote
if (networksToUpdate?.remoteNetworksToUpdate) {
await batchUpdateNetworks(networksToUpdate?.remoteNetworksToUpdate, opts);
}

// Add missing local networks
if (networksToUpdate?.missingLocalNetworks) {
networksToUpdate.missingLocalNetworks.forEach((n) => {
try {
const opts = await getStorageConfig();
await deleteNetwork(networkConfiguration, opts);
messenger.call('NetworkController:addNetwork', n);
} catch {
// Silently fail sync
// Silently fail, we can try this again on next main sync
}
},
);
} catch (e) {
log.warn('NetworkSyncing, event subscription failed', e);
}
});
}

try {
messenger.subscribe(
'NetworkController:networkChanged',
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async (networkConfiguration) => {
// Update local networks
if (networksToUpdate?.localNetworksToUpdate) {
const promises = networksToUpdate.localNetworksToUpdate.map(async (n) => {
try {
const opts = await getStorageConfig();
await updateNetwork(networkConfiguration, opts);
await messenger.call('NetworkController:updateNetwork', n.chainId, n);
} catch {
// Silently fail sync
// Silently fail, we can try this again on next main sync
}
},
);
} catch (e) {
log.warn('NetworkSyncing, event subscription failed', e);
});
await Promise.all(promises);
}

// Remove local networks
if (networksToUpdate?.localNetworksToRemove) {
networksToUpdate.localNetworksToRemove.forEach((n) => {
try {
messenger.call('NetworkController:removeNetwork', n.chainId);
} catch {
// Silently fail, we can try this again on next main sync
}
});
}
} catch {
// Silently fail sync
} finally {
isMainNetworkSyncInProgress = false;
}
}
Loading

0 comments on commit eef26fd

Please sign in to comment.