Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Temp/network syncing all changes #4849

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d2c8267
feat: add main network sync controller integration
Prithpal-Sooriya Sep 13, 2024
c7e9673
feat: add main network sync controller integration
Prithpal-Sooriya Sep 13, 2024
ea7b132
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1094/netw…
Prithpal-Sooriya Sep 13, 2024
3284236
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1094/netw…
Prithpal-Sooriya Sep 19, 2024
a22a9c2
Merge branch 'NOTIFY-1094/network-main-sync-integration' of github.co…
Prithpal-Sooriya Sep 19, 2024
6f824c1
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1094/netw…
Prithpal-Sooriya Sep 19, 2024
d581cc3
Merge branch 'main' into NOTIFY-1094/network-main-sync-integration
Prithpal-Sooriya Oct 23, 2024
49ef491
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1094/netw…
Prithpal-Sooriya Oct 24, 2024
b18119c
feat: add network sync callbacks
Prithpal-Sooriya Oct 24, 2024
94a67ba
test: add controller-integration - performMainSync() tests
Prithpal-Sooriya Oct 24, 2024
9df8d49
test: add test coverage
Prithpal-Sooriya Oct 24, 2024
3fcbacf
Merge branch 'main' of github.com:MetaMask/core into NOTIFY-1094/netw…
Prithpal-Sooriya Oct 24, 2024
336f992
feat: add NetworkController actions and events
Prithpal-Sooriya Sep 13, 2024
e7f13ce
feat: add lastUpdatedAt for NetworkConfiguration
Prithpal-Sooriya Sep 3, 2024
ec72890
Merge branch 'main' of github.com:MetaMask/core into temp/network-syn…
Prithpal-Sooriya Nov 4, 2024
de05940
Merge branch 'main' of github.com:MetaMask/core into temp/network-syn…
Prithpal-Sooriya Nov 7, 2024
6f71786
fix: handle integration edge-cases
Prithpal-Sooriya Nov 11, 2024
c10c811
feat: add new logic and fix syncing issues
Prithpal-Sooriya Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 194 additions & 4 deletions packages/network-controller/src/NetworkController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
ProxyWithAccessibleTarget,
} from './create-auto-managed-network-client';
import { createAutoManagedNetworkClient } from './create-auto-managed-network-client';
import { lastUpdatedNetworkConfiguration } from './last-updated-at-network-configuration';
import { projectLogger, createModuleLogger } from './logger';
import { NetworkClientType } from './types';
import type {
Expand Down Expand Up @@ -203,6 +204,11 @@
* interact with the chain.
*/
rpcEndpoints: RpcEndpoint[];
/**
* Profile Sync - Network Sync field.
* Allows comparison of local network state with state to sync.
*/
lastUpdatedAt?: number;
};

/**
Expand All @@ -212,8 +218,11 @@
* Custom RPC endpoints do not need a `networkClientId` property because it is
* assumed that they have not already been added and therefore network clients
* do not exist for them yet (and hence IDs need to be generated).
*
* However Custom RPC endpoints, that are synchronized between,
* can use the same `networkClientId` set on both devices.
*/
export type AddNetworkCustomRpcEndpointFields = Omit<
export type AddNetworkCustomRpcEndpointFields = Partialize<
CustomRpcEndpoint,
'networkClientId'
>;
Expand Down Expand Up @@ -409,13 +418,33 @@
payload: [networkConfiguration: NetworkConfiguration];
};

/**
* `networkUpdated` is published after a network configuration is updated in the
* network configuration registry and network clients are created as needed.
*/
export type NetworkControllerNetworkUpdatedEvent = {
type: 'NetworkController:networkUpdated';
payload: [networkConfiguration: NetworkConfiguration];
};

/**
* `networkRemoved` is published after a network configuration is removed from the
* network configuration registry and once the network clients have been removed.
*/
export type NetworkControllerNetworkRemovedEvent = {
type: 'NetworkController:networkRemoved';
payload: [networkConfiguration: NetworkConfiguration];
};

export type NetworkControllerEvents =
| NetworkControllerStateChangeEvent
| NetworkControllerNetworkWillChangeEvent
| NetworkControllerNetworkDidChangeEvent
| NetworkControllerInfuraIsBlockedEvent
| NetworkControllerInfuraIsUnblockedEvent
| NetworkControllerNetworkAddedEvent;
| NetworkControllerNetworkAddedEvent
| NetworkControllerNetworkUpdatedEvent
| NetworkControllerNetworkRemovedEvent;

export type NetworkControllerGetStateAction = ControllerGetStateAction<
typeof controllerName,
Expand Down Expand Up @@ -473,6 +502,26 @@
handler: NetworkController['getNetworkConfigurationByNetworkClientId'];
};

export type NetworkControllerAddNetworkAction = {
type: 'NetworkController:addNetwork';
handler: NetworkController['addNetwork'];
};

export type NetworkControllerUpdateNetworkAction = {
type: 'NetworkController:updateNetwork';
handler: NetworkController['updateNetwork'];
};

export type NetworkControllerRemoveNetworkAction = {
type: 'NetworkController:removeNetwork';
handler: NetworkController['removeNetwork'];
};

export type NetworkControllerDangerouslySetNetworkConfigurationAction = {
type: 'NetworkController:dangerouslySetNetworkConfiguration';
handler: NetworkController['dangerouslySetNetworkConfiguration'];
};

export type NetworkControllerActions =
| NetworkControllerGetStateAction
| NetworkControllerGetEthQueryAction
Expand All @@ -483,7 +532,11 @@
| NetworkControllerSetActiveNetworkAction
| NetworkControllerSetProviderTypeAction
| NetworkControllerGetNetworkConfigurationByChainId
| NetworkControllerGetNetworkConfigurationByNetworkClientId;
| NetworkControllerGetNetworkConfigurationByNetworkClientId
| NetworkControllerAddNetworkAction
| NetworkControllerUpdateNetworkAction
| NetworkControllerRemoveNetworkAction
| NetworkControllerDangerouslySetNetworkConfigurationAction;

export type NetworkControllerMessenger = RestrictedControllerMessenger<
typeof controllerName,
Expand Down Expand Up @@ -954,6 +1007,32 @@
`${this.name}:getSelectedNetworkClient`,
this.getSelectedNetworkClient.bind(this),
);

this.messagingSystem.registerActionHandler(
// ESLint is mistaken here; `name` is a string.
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${this.name}:addNetwork`,
this.addNetwork.bind(this),
);

this.messagingSystem.registerActionHandler(
// ESLint is mistaken here; `name` is a string.
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${this.name}:updateNetwork`,
this.updateNetwork.bind(this),
);

this.messagingSystem.registerActionHandler(
// ESLint is mistaken here; `name` is a string.
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${this.name}:removeNetwork`,
this.removeNetwork.bind(this),
);

this.messagingSystem.registerActionHandler(
`${this.name}:dangerouslySetNetworkConfiguration`,

Check failure on line 1033 in packages/network-controller/src/NetworkController.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (20.x)

Invalid type "any" of template literal expression
this.dangerouslySetNetworkConfiguration.bind(this),
);
}

/**
Expand Down Expand Up @@ -1557,7 +1636,8 @@
defaultOrCustomRpcEndpointFields.type === RpcEndpointType.Custom
? {
...defaultOrCustomRpcEndpointFields,
networkClientId: uuidV4(),
networkClientId:
defaultOrCustomRpcEndpointFields.networkClientId ?? uuidV4(),
}
: defaultOrCustomRpcEndpointFields;
return {
Expand Down Expand Up @@ -1881,6 +1961,11 @@
autoManagedNetworkClientRegistry,
});

this.messagingSystem.publish(
'NetworkController:networkUpdated',
updatedNetworkConfiguration,
);

return updatedNetworkConfiguration;
}

Expand Down Expand Up @@ -1939,6 +2024,110 @@
buildNetworkConfigurationsByNetworkClientId(
this.state.networkConfigurationsByChainId,
);

this.messagingSystem.publish(
'NetworkController:networkRemoved',
existingNetworkConfiguration,
);
}

/**
* This is used to override an existing network configuration.
* This is only meant for internal use only and not to be exposed via the UI.
* It is used as part of "Network Syncing", to sync networks, RPCs and block explorers cross devices.
*
* This will subsequently update the network client registry; state.networksMetadata, and state.selectedNetworkClientId
* @param networkConfiguration - the network configuration to override
*/
async dangerouslySetNetworkConfiguration(
networkConfiguration: NetworkConfiguration,
) {
const prevNetworkConfig: NetworkConfiguration | undefined =
networkConfiguration.chainId in this.state.networkConfigurationsByChainId
? this.state.networkConfigurationsByChainId[
networkConfiguration.chainId
]
: undefined;

// Update Registry (remove old and add new)
const autoManagedNetworkClientRegistry =
this.#ensureAutoManagedNetworkClientRegistryPopulated();
if (prevNetworkConfig) {
const networkClientOperations = prevNetworkConfig.rpcEndpoints.map(
(rpcEndpoint) => {
return {
type: 'remove' as const,
rpcEndpoint,
};
},
);
this.#unregisterNetworkClientsAsNeeded({
networkClientOperations,
autoManagedNetworkClientRegistry,
});
}
const networkClientOperations = networkConfiguration.rpcEndpoints.map(
(rpcEndpoint) => {
return {
type: 'add' as const,
rpcEndpoint,
};
},
);
this.#registerNetworkClientsAsNeeded({
networkFields: networkConfiguration,
networkClientOperations,
autoManagedNetworkClientRegistry,
});

// update new `networkConfigurationsByChainId` (full replace)
this.update((state) => {
state.networkConfigurationsByChainId[networkConfiguration.chainId] =
networkConfiguration;
});
this.#networkConfigurationsByNetworkClientId =
buildNetworkConfigurationsByNetworkClientId(
this.state.networkConfigurationsByChainId,
);

// update `networksMetadata` remove old
if (prevNetworkConfig) {
this.update((state) => {
const newNetworksMetadata: NetworksMetadata = {
...state.networksMetadata,
};
prevNetworkConfig.rpcEndpoints.forEach((rpcEndpoint) => {
delete newNetworksMetadata[rpcEndpoint.networkClientId];
});
});
}

// update `networksMetadata` update new
for (const rpcEndpoint of networkConfiguration.rpcEndpoints) {
await this.lookupNetwork(rpcEndpoint.networkClientId);
}

// update selectedNetworkId
const selectedClientId = this.state.selectedNetworkClientId;
const wasReplacedClientId = prevNetworkConfig?.rpcEndpoints.some(
(r) => r.networkClientId === selectedClientId,
);
const isValidClientIdInNewNetworks = networkConfiguration.rpcEndpoints.some(
(r) => r.networkClientId === selectedClientId,
);
if (wasReplacedClientId) {
if (!isValidClientIdInNewNetworks) {
// Update clientId to something that exists
const newSelectedNetworkMetadata =
networkConfiguration.rpcEndpoints.find(
(r) => r.networkClientId in this.state.networksMetadata,
)?.networkClientId;
const anyClientId = Object.keys(this.state.networksMetadata)[0];
const newlySelectedNetwork =
newSelectedNetworkMetadata ?? anyClientId ?? selectedClientId;
await this.#refreshNetwork(newlySelectedNetwork);
}
}
}

/**
Expand Down Expand Up @@ -2438,6 +2627,7 @@
}

if (mode === 'add' || mode === 'update') {
lastUpdatedNetworkConfiguration(args.networkConfigurationToPersist);
state.networkConfigurationsByChainId[args.networkFields.chainId] =
args.networkConfigurationToPersist;
}
Expand Down
7 changes: 7 additions & 0 deletions packages/network-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export type {
NetworkControllerNetworkDidChangeEvent,
NetworkControllerInfuraIsBlockedEvent,
NetworkControllerInfuraIsUnblockedEvent,
NetworkControllerNetworkAddedEvent,
NetworkControllerNetworkUpdatedEvent,
NetworkControllerNetworkRemovedEvent,
NetworkControllerEvents,
NetworkControllerGetStateAction,
NetworkControllerGetEthQueryAction,
Expand All @@ -26,6 +29,10 @@ export type {
NetworkControllerFindNetworkClientIdByChainIdAction,
NetworkControllerSetProviderTypeAction,
NetworkControllerSetActiveNetworkAction,
NetworkControllerAddNetworkAction,
NetworkControllerUpdateNetworkAction,
NetworkControllerRemoveNetworkAction,
NetworkControllerDangerouslySetNetworkConfigurationAction,
NetworkControllerGetNetworkConfigurationByNetworkClientId,
NetworkControllerActions,
NetworkControllerMessenger,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NetworkConfiguration } from './NetworkController';

/**
* Adds or updates the NetworkConfiguration `lastUpdatedAt` property.
* Keeping this property updated on network changes allows us to compare remote vs local NetworkConfiguration
* for network syncing.
*
* @param configuration - NetworkConfiguration that is being updated
* @returns the NetworkConfiguration with the lastUpdatedAt property updated.
*/
export function lastUpdatedNetworkConfiguration(
configuration: NetworkConfiguration,
) {
configuration.lastUpdatedAt = Date.now();
return configuration;
}
8 changes: 8 additions & 0 deletions packages/network-controller/tests/NetworkController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { NetworkStatus } from '../src/constants';
import * as createAutoManagedNetworkClientModule from '../src/create-auto-managed-network-client';
import type { NetworkClient } from '../src/create-network-client';
import { createNetworkClient } from '../src/create-network-client';
import * as LastUpdatedAtNetworkConfigurationModule from '../src/last-updated-at-network-configuration';
import type {
AutoManagedBuiltInNetworkClientRegistry,
AutoManagedCustomNetworkClientRegistry,
Expand Down Expand Up @@ -139,6 +140,13 @@ describe('NetworkController', () => {
uuidCounter += 1;
return uuid;
});

jest
.spyOn(
LastUpdatedAtNetworkConfigurationModule,
'lastUpdatedNetworkConfiguration',
)
.mockImplementation((n) => n);
});

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { lastUpdatedNetworkConfiguration } from '../src/last-updated-at-network-configuration';
import { buildCustomNetworkConfiguration } from './helpers';

describe('lastUpdatedNetworkConfiguration() tests', () => {
it('adds a timestamp (ms) to the network configuration', () => {
const configuration = buildCustomNetworkConfiguration();

expect(configuration.lastUpdatedAt).toBeUndefined();
lastUpdatedNetworkConfiguration(configuration);
expect(configuration.lastUpdatedAt).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ import type {
} from './UserStorageController';
import UserStorageController from './UserStorageController';

// Creates the correct typed call params for mocks
type CallParams = {
[K in AllowedActions['type']]: [
K,
...Parameters<Extract<AllowedActions, { type: K }>['handler']>,
];
}[AllowedActions['type']];

const typedMockFn = <Func extends (...args: unknown[]) => unknown>() =>
jest.fn<ReturnType<Func>, Parameters<Func>>();

Expand Down Expand Up @@ -1730,14 +1738,6 @@ function mockUserStorageMessenger(options?: {
const mockAccountsUpdateAccountMetadata = jest.fn().mockResolvedValue(true);

jest.spyOn(messenger, 'call').mockImplementation((...args) => {
// Creates the correct typed call params for mocks
type CallParams = {
[K in AllowedActions['type']]: [
K,
...Parameters<Extract<AllowedActions, { type: K }>['handler']>,
];
}[AllowedActions['type']];

const [actionType, params] = args as unknown as CallParams;

if (actionType === 'SnapController:handleRequest') {
Expand Down
Loading
Loading