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

Create and expose collection of network clients #1439

Merged
merged 36 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1e2ba98
Create and expose collection of network clients
mcmire Jun 14, 2023
577f21c
Merge branch 'main' into expose-network-clients
mcmire Jun 27, 2023
df1f5f8
No need to pass provider into request methods anymore
mcmire Jun 27, 2023
7991c79
Merge branch 'main' into expose-network-clients
mcmire Jun 28, 2023
fa07c1f
Remove unnecessary tests
mcmire Jun 28, 2023
d561088
Fix type parameter for knownKeysOf
mcmire Jun 28, 2023
680d9eb
Refactor code to build network clients
mcmire Jun 28, 2023
8256cb0
Revert "Refactor code to build network clients"
mcmire Jun 28, 2023
c1dfb6b
Refactor some things
mcmire Jun 28, 2023
45ba9cb
Reword JSDocs for BlockTrackerProxy and ProviderProxy
mcmire Jun 28, 2023
85c7e9c
Rename 'nc' to 'networkConfiguration'
mcmire Jun 28, 2023
f01a57d
Fix formatting
mcmire Jun 28, 2023
e19f87b
Use networkConfig rather than networkConfiguration
mcmire Jun 28, 2023
154b375
Bring back the concept of built-in networks
mcmire Jun 29, 2023
11e52f5
Rename and split some initializeProvider tests
mcmire Jun 29, 2023
3c7879f
Make the signature of buildCustomNetworkClientId more obvious
mcmire Jun 29, 2023
96c4229
Remove getNetworkClients, add getNetworkClientsById
mcmire Jun 29, 2023
5f646b4
Clean up use of __target__
mcmire Jun 29, 2023
f2e5549
Make JSDocs for createAutoManagedNetworkClient more honest
mcmire Jun 29, 2023
03eee76
Fix lint violation
mcmire Jun 29, 2023
ec75958
Make functions to build IDs more straightforward
mcmire Jun 29, 2023
577b0ff
Fix TypeScript errors
mcmire Jun 29, 2023
1cdc7c8
Fix formatting
mcmire Jun 29, 2023
24aa829
Prioritize network config over provider config in network clients lis…
mcmire Jun 29, 2023
513edbd
Fix tests
mcmire Jun 29, 2023
90f44c7
Simplify reduce in upsertNetworkConfiguration
mcmire Jun 29, 2023
e15525e
Extract pick to a typecast-free version
mcmire Jun 29, 2023
ad08e04
Test .configuration and .destroy for auto-managed network client
mcmire Jun 29, 2023
0fa287e
Merge branch 'main' into expose-network-clients
mcmire Jun 29, 2023
9e67774
Simplify network client IDs
mcmire Jun 30, 2023
6977c44
Use 'registry' rather than 'obj'
mcmire Jun 30, 2023
163e8e7
Update description for 'pick'
mcmire Jun 30, 2023
84a9f12
Undo description change to 'pick'
mcmire Jun 30, 2023
72d6d6e
Update 'assertOfType' to rename 'test' to 'validate' and pass the giv…
mcmire Jun 30, 2023
d6d1dbd
No need to construct an Infura network client config for a provider c…
mcmire Jun 30, 2023
1a827fc
Fix lint violation
mcmire Jun 30, 2023
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
806 changes: 653 additions & 153 deletions packages/network-controller/src/NetworkController.ts

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { createNetworkClient, NetworkClient } from './create-network-client';
import { BlockTracker, NetworkClientConfiguration, Provider } from './types';

/**
* The name of the method on both the provider and block tracker proxy which can
* be used to get the underlying provider or block tracker from the network
* client, when it is initialized.
*/
const REFLECTIVE_PROPERTY_NAME = '__target__';

/**
* Represents a proxy object which wraps a target object. As a proxy, it allows
* for accessing and setting all of the properties that the target object
* supports, but also supports an extra propertyName (`__target__`) to access
* the target itself.
*
* @template Type - The type of the target object. It is assumed that this type
* will be constant even when the target is swapped.
*/
export type ProxyWithAccessibleTarget<TargetType> = TargetType & {
[REFLECTIVE_PROPERTY_NAME]: TargetType;
};

/**
* An object that provides the same interface as a network client but where the
* network client is not initialized until either the provider or block tracker
* is first accessed.
*/
export type AutoManagedNetworkClient<
Configuration extends NetworkClientConfiguration,
> = {
configuration: Configuration;
provider: ProxyWithAccessibleTarget<Provider>;
blockTracker: ProxyWithAccessibleTarget<BlockTracker>;
destroy: () => void;
};

/**
* By default, the provider and block provider proxies will point to nothing.
* This is impossible when using the Proxy API, as the target object has to be
* something, so this object represents that "something".
*/
const UNINITIALIZED_TARGET = { __UNINITIALIZED__: true };

/**
* This function creates two proxies, one that wraps a provider and another that
* wraps a block tracker. These proxies are unique in that both will be "empty"
* at first; that is, neither will point to a functional provider or block
* tracker. Instead, as soon as a method or event is accessed on either object
* that requires a network request to function, a network client is created on
* the fly and the method or event in question is then forwarded to whichever
* part of the network client is serving as the receiver. The network client is
* then cached for subsequent usages.
*
* @param networkClientConfiguration - The configuration object that will be
* used to instantiate the network client when it is needed.
* @returns The auto-managed network client.
*/
export function createAutoManagedNetworkClient<
mcmire marked this conversation as resolved.
Show resolved Hide resolved
Configuration extends NetworkClientConfiguration,
>(
networkClientConfiguration: Configuration,
): AutoManagedNetworkClient<Configuration> {
let networkClient: NetworkClient | undefined;

const providerProxy = new Proxy(UNINITIALIZED_TARGET, {
get(_target: any, propertyName: PropertyKey, receiver: unknown) {
if (propertyName === REFLECTIVE_PROPERTY_NAME) {
return networkClient?.provider;
}

networkClient ??= createNetworkClient(networkClientConfiguration);
if (networkClient === undefined) {
throw new Error(
"It looks like `createNetworkClient` didn't return anything. Perhaps it's being mocked?",
);
}
const { provider } = networkClient;

if (propertyName in provider) {
// Typecast: We know that `[propertyName]` is a propertyName on
// `provider`.
const value = provider[propertyName as keyof typeof provider];
if (typeof value === 'function') {
// Ensure that the method on the provider is called with `this` as
// the target, *not* the proxy (which happens by default) —
// this allows private properties to be accessed
return function (this: unknown, ...args: any[]) {
// @ts-expect-error We don't care that `this` may not be compatible
// with the signature of the method being called, as technically
// it can be anything.
return value.apply(this === receiver ? provider : this, args);
};
}
return value;
}

return undefined;
},

has(_target: any, propertyName: PropertyKey) {
if (propertyName === REFLECTIVE_PROPERTY_NAME) {
return true;
}
networkClient ??= createNetworkClient(networkClientConfiguration);
const { provider } = networkClient;
return propertyName in provider;
},
});

const blockTrackerProxy: ProxyWithAccessibleTarget<BlockTracker> = new Proxy(
UNINITIALIZED_TARGET,
{
get(_target: any, propertyName: PropertyKey, receiver: unknown) {
if (propertyName === REFLECTIVE_PROPERTY_NAME) {
return networkClient?.blockTracker;
}

networkClient ??= createNetworkClient(networkClientConfiguration);
if (networkClient === undefined) {
throw new Error(
"It looks like createNetworkClient returned undefined. Perhaps it's mocked?",
);
}
const { blockTracker } = networkClient;

if (propertyName in blockTracker) {
// Typecast: We know that `[propertyName]` is a propertyName on
// `provider`.
const value = blockTracker[propertyName as keyof typeof blockTracker];
if (typeof value === 'function') {
// Ensure that the method on the provider is called with `this` as
// the target, *not* the proxy (which happens by default) —
// this allows private properties to be accessed
return function (this: unknown, ...args: any[]) {
// @ts-expect-error We don't care that `this` may not be
// compatible with the signature of the method being called, as
// technically it can be anything.
return value.apply(this === receiver ? blockTracker : this, args);
};
}
return value;
}

return undefined;
},

has(_target: any, propertyName: PropertyKey) {
if (propertyName === REFLECTIVE_PROPERTY_NAME) {
return true;
}
networkClient ??= createNetworkClient(networkClientConfiguration);
const { blockTracker } = networkClient;
return propertyName in blockTracker;
},
},
);

const destroy = () => {
networkClient?.destroy();
};

return {
configuration: networkClientConfiguration,
provider: providerProxy,
blockTracker: blockTrackerProxy,
destroy,
};
}
48 changes: 20 additions & 28 deletions packages/network-controller/src/create-network-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,24 @@ import {
ChainId,
NetworkId,
} from '@metamask/controller-utils';
import type { BlockTracker, Provider } from './types';
import {
BlockTracker,
NetworkClientConfiguration,
NetworkClientType,
Provider,
} from './types';

const SECOND = 1000;

/**
* The type of network client that can be created.
* The pair of provider / block tracker that can be used to interface with the
* network and respond to new activity.
*/
export enum NetworkClientType {
Custom = 'custom',
Infura = 'infura',
}

/**
* A configuration object that can be used to create a provider engine for a
* custom network.
*/
type CustomNetworkConfiguration = {
chainId: Hex;
rpcUrl: string;
type: NetworkClientType.Custom;
};

/**
* A configuration object that can be used to create a provider engine for an
* Infura network.
*/
type InfuraNetworkConfiguration = {
network: InfuraNetworkType;
infuraProjectId: string;
type: NetworkClientType.Infura;
export type NetworkClient = {
configuration: NetworkClientConfiguration;
provider: Provider;
blockTracker: BlockTracker;
destroy: () => void;
};

/**
Expand All @@ -66,8 +54,8 @@ type InfuraNetworkConfiguration = {
* @returns The network client.
*/
export function createNetworkClient(
networkConfig: CustomNetworkConfiguration | InfuraNetworkConfiguration,
): { provider: Provider; blockTracker: BlockTracker } {
networkConfig: NetworkClientConfiguration,
): NetworkClient {
const rpcApiMiddleware =
networkConfig.type === NetworkClientType.Infura
? createInfuraMiddleware({
Expand Down Expand Up @@ -114,7 +102,11 @@ export function createNetworkClient(

const provider = providerFromEngine(engine);

return { provider, blockTracker };
const destroy = () => {
blockTracker.destroy();
};

return { configuration: networkConfig, provider, blockTracker, destroy };
Gudahtt marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
37 changes: 37 additions & 0 deletions packages/network-controller/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import { InfuraNetworkType } from '@metamask/controller-utils';
import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider';
import { Hex } from '@metamask/utils';
import type { PollingBlockTracker } from 'eth-block-tracker';

export type Provider = SafeEventEmitterProvider;

export type BlockTracker = PollingBlockTracker;

/**
* The type of network client that can be created.
*/
export enum NetworkClientType {
Custom = 'custom',
Infura = 'infura',
}

/**
* A configuration object that can be used to create a client for a custom
* network.
*/
export type CustomNetworkClientConfiguration = {
chainId: Hex;
rpcUrl: string;
type: NetworkClientType.Custom;
};

/**
* A configuration object that can be used to create a client for an Infura
* network.
*/
export type InfuraNetworkClientConfiguration = {
network: InfuraNetworkType;
infuraProjectId: string;
type: NetworkClientType.Infura;
};

/**
* A configuration object that can be used to create a client for a network.
*/
export type NetworkClientConfiguration =
| CustomNetworkClientConfiguration
| InfuraNetworkClientConfiguration;
Loading