区块链钱包开发(十六)—— 构建网络控制器(NetworkController)

1. 概述

NetworkController 是 MetaMask 中负责管理多链网络连接的核心控制器,它提供了完整的网络管理功能,包括网络切换、RPC 端点管理、网络状态监控等。

源码位置 : github.com/MetaMask/co...

2. 核心架构

2.1 主要组件

NetworkController 继承自 BaseController,主要包含以下核心组件:

typescript 复制代码
class NetworkController extends BaseController {
  // 网络客户端注册表 - 管理所有网络客户端实例
  #networkClientRegistry: AutoManagedNetworkClientRegistry;
  
  // 当前选中的网络客户端ID
  #selectedNetworkClientId: NetworkClientId;
  
  // 网络配置映射 - 按网络客户端ID索引配置
  #networkConfigurationsByNetworkClientId: Map<NetworkClientId, NetworkConfiguration>;
  
  // RPC故障转移开关
  #isRpcFailoverEnabled: boolean;
}

2.2 状态结构

网络控制器的状态包含三个主要部分:

typescript 复制代码
type NetworkState = {
  // 当前选中的网络客户端ID
  selectedNetworkClientId: NetworkClientId;
  
  // 按链ID索引的网络配置
  networkConfigurationsByChainId: Record<Hex, NetworkConfiguration>;
  
  // 网络元数据信息(EIP支持、网络状态等)
  networksMetadata: NetworksMetadata;
};

3. 网络类型和配置

3.1 网络类型

NetworkController 支持两种主要的网络类型:

typescript 复制代码
// RPC端点类型
enum RpcEndpointType {
  Custom = 'custom',    // 自定义RPC
  Infura = 'infura',    // Infura RPC
}

// 网络客户端类型
enum NetworkClientType {
  Custom = 'custom',    // 自定义网络
  Infura = 'infura',    // Infura网络
}

3.2 网络配置结构

每个网络配置包含以下信息:

typescript 复制代码
type NetworkConfiguration = {
  chainId: Hex;                    // 链ID
  name: string;                    // 网络名称
  nativeCurrency: string;          // 原生代币
  blockExplorerUrls: string[];     // 区块浏览器URL
  rpcEndpoints: RpcEndpoint[];     // RPC端点列表
  defaultRpcEndpointIndex: number; // 默认RPC端点索引
};

3.3 RPC端点类型

Infura RPC端点

typescript 复制代码
type InfuraRpcEndpoint = {
  type: RpcEndpointType.Infura;
  networkClientId: BuiltInNetworkClientId;
  url: `https://${InfuraNetworkType}.infura.io/v3/{infuraProjectId}`;
  failoverUrls?: string[];  // 备用URL
  name?: string;            // 端点名称
};

自定义RPC端点

typescript 复制代码
type CustomRpcEndpoint = {
  type: RpcEndpointType.Custom;
  networkClientId: CustomNetworkClientId;
  url: string;              // 自定义URL
  failoverUrls?: string[];  // 备用URL
  name?: string;            // 端点名称
};

4. 核心功能实现

4.1 网络切换机制

设置网络类型

当用户选择切换到 Infura 网络时:

typescript 复制代码
async setProviderType(type: InfuraNetworkType) {
  // 验证网络类型有效性
  if (type === NetworkType.rpc) {
    throw new Error('不能使用setProviderType设置自定义网络');
  }
  
  // 验证是否为有效的Infura网络类型
  if (!isInfuraNetworkType(type)) {
    throw new Error(`未知的Infura网络类型: ${type}`);
  }
  
  // 调用setActiveNetwork进行实际切换
  await this.setActiveNetwork(type);
}

设置活跃网络

网络切换的核心逻辑:

typescript 复制代码
async setActiveNetwork(networkClientId: string, options = {}) {
  // 保存之前选中的网络ID
  this.#previouslySelectedNetworkClientId = this.state.selectedNetworkClientId;
  
  // 刷新网络连接
  await this.#refreshNetwork(networkClientId, options);
}

4.2 网络客户端管理

获取网络客户端

typescript 复制代码
getNetworkClientById(networkClientId: NetworkClientId) {
  // 确定网络客户端类型
  const networkClientType = this.#getNetworkClientType(networkClientId);
  
  // 从对应注册表获取客户端
  const registry = this.#networkClientRegistry[networkClientType];
  const networkClient = registry[networkClientId];
  
  // 验证客户端是否存在
  if (!networkClient) {
    throw new Error(`网络客户端 ${networkClientId} 不存在`);
  }
  
  return networkClient;
}

获取选中的网络客户端

typescript 复制代码
getSelectedNetworkClient() {
  // 获取当前选中的网络客户端
  const networkClient = this.#networkClientRegistry[
    this.#getNetworkClientType(this.state.selectedNetworkClientId)
  ][this.state.selectedNetworkClientId];
  
  if (!networkClient) {
    return undefined;
  }
  
  // 返回Provider和BlockTracker代理
  return {
    provider: networkClient.provider,
    blockTracker: networkClient.blockTracker,
  };
}

4.3 网络配置管理

添加网络

添加新网络的完整流程:

typescript 复制代码
addNetwork(fields: AddNetworkFields) {
  const { chainId, rpcEndpoints, ...otherFields } = fields;
  
  // 验证链ID安全性
  if (!isSafeChainId(chainId)) {
    throw new Error(`无效的链ID: ${chainId}`);
  }
  
  // 检查网络是否已存在
  if (this.state.networkConfigurationsByChainId[chainId]) {
    throw new Error(`链ID ${chainId} 的网络已存在`);
  }
  
  // 处理RPC端点,为自定义端点生成ID
  const processedRpcEndpoints = rpcEndpoints.map((endpoint, index) => {
    if (endpoint.type === RpcEndpointType.Infura) {
      return endpoint; // Infura端点保持原样
    }
    
    // 为自定义端点生成唯一ID
    const networkClientId = uuidV4();
    return { ...endpoint, networkClientId };
  });
  
  // 创建网络配置
  const networkConfiguration = {
    chainId,
    rpcEndpoints: processedRpcEndpoints,
    ...otherFields,
  };
  
  // 更新状态
  this.update((state) => {
    state.networkConfigurationsByChainId[chainId] = networkConfiguration;
  });
  
  // 发布网络添加事件
  this.messagingSystem.publish('NetworkController:networkAdded', networkConfiguration);
  
  return networkConfiguration;
}

更新网络

更新网络配置的处理逻辑:

typescript 复制代码
async updateNetwork(chainId: Hex, fields: UpdateNetworkFields) {
  // 获取现有配置
  const existingConfiguration = this.state.networkConfigurationsByChainId[chainId];
  if (!existingConfiguration) {
    throw new Error(`链ID ${chainId} 的网络不存在`);
  }
  
  // 确定网络客户端操作(添加、删除、替换)
  const operations = this.#determineNetworkClientOperations(
    existingConfiguration.rpcEndpoints,
    fields.rpcEndpoints,
  );
  
  // 执行网络客户端操作
  await this.#executeNetworkClientOperations(operations);
  
  // 处理RPC端点更新
  const updatedRpcEndpoints = fields.rpcEndpoints.map((endpoint, index) => {
    if (endpoint.type === RpcEndpointType.Infura) {
      return endpoint;
    }
    
    // 保持现有ID或生成新ID
    const existingEndpoint = existingConfiguration.rpcEndpoints[index];
    const networkClientId = existingEndpoint?.networkClientId || uuidV4();
    
    return { ...endpoint, networkClientId };
  });
  
  // 更新配置
  const updatedConfiguration = {
    ...existingConfiguration,
    ...fields,
    rpcEndpoints: updatedRpcEndpoints,
  };
  
  // 更新状态
  this.update((state) => {
    state.networkConfigurationsByChainId[chainId] = updatedConfiguration;
  });
  
  return updatedConfiguration;
}

删除网络

删除网络的安全检查:

typescript 复制代码
removeNetwork(chainId: Hex) {
  // 获取网络配置
  const networkConfiguration = this.state.networkConfigurationsByChainId[chainId];
  if (!networkConfiguration) {
    throw new Error(`链ID ${chainId} 的网络不存在`);
  }
  
  // 检查是否为当前选中的网络
  const networkClientId = this.findNetworkClientIdByChainId(chainId);
  if (networkClientId === this.state.selectedNetworkClientId) {
    throw new Error(`不能删除当前选中的网络 ${chainId}`);
  }
  
  // 清理网络客户端资源
  networkConfiguration.rpcEndpoints.forEach((rpcEndpoint) => {
    if (rpcEndpoint.type === RpcEndpointType.Custom) {
      const networkClient = this.#networkClientRegistry[NetworkClientType.Custom][rpcEndpoint.networkClientId];
      if (networkClient) {
        networkClient.destroy(); // 销毁客户端
        delete this.#networkClientRegistry[NetworkClientType.Custom][rpcEndpoint.networkClientId];
      }
    }
  });
  
  // 更新状态
  this.update((state) => {
    delete state.networkConfigurationsByChainId[chainId];
  });
  
  // 发布删除事件
  this.messagingSystem.publish('NetworkController:networkRemoved', networkConfiguration);
}

4.4 EIP-1559 兼容性检测

控制器通过检查最新区块的 baseFeePerGas 属性来判断网络是否支持 EIP-1559:

typescript 复制代码
async getEIP1559Compatibility(networkClientId?: NetworkClientId) {
  // 如果指定了网络客户端ID,直接检测
  if (networkClientId) {
    return this.get1559CompatibilityWithNetworkClientId(networkClientId);
  }
  
  // 检查缓存
  const { EIPS } = this.state.networksMetadata[this.state.selectedNetworkClientId];
  if (EIPS[1559] !== undefined) {
    return EIPS[1559]; // 返回缓存结果
  }
  
  // 动态检测EIP-1559兼容性
  const isEIP1559Compatible = await this.#determineEIP1559Compatibility(
    this.state.selectedNetworkClientId,
  );
  
  // 更新缓存
  this.update((state) => {
    if (isEIP1559Compatible !== undefined) {
      state.networksMetadata[state.selectedNetworkClientId].EIPS[1559] = isEIP1559Compatible;
    }
  });
  
  return isEIP1559Compatible;
}

// 检测EIP-1559兼容性的具体实现
async #determineEIP1559Compatibility(networkClientId: NetworkClientId) {
  try {
    // 获取最新区块
    const block = await this.#getLatestBlock(networkClientId);
    
    // 检查是否有baseFeePerGas属性
    return block.baseFeePerGas !== undefined;
  } catch (error) {
    debugLog('检测EIP-1559兼容性时出错', error);
    return undefined;
  }
}
flowchart TD Start[开始检测] --> CheckCache{检查缓存} CheckCache -->|有缓存| ReturnCache[返回缓存结果] CheckCache -->|无缓存| GetBlock[获取最新区块] GetBlock --> CheckBaseFee{检查baseFeePerGas} CheckBaseFee -->|存在| Compatible[支持EIP-1559] CheckBaseFee -->|不存在| NotCompatible[不支持EIP-1559] CheckBaseFee -->|错误| Unknown[未知] Compatible --> UpdateCache[更新缓存] NotCompatible --> UpdateCache Unknown --> UpdateCache UpdateCache --> ReturnResult[返回结果] ReturnCache --> End[结束] ReturnResult --> End

5. 网络状态管理

5.1 网络状态枚举

typescript 复制代码
enum NetworkStatus {
  Unknown = 'unknown',      // 未知状态
  Available = 'available',  // 可用
  Unavailable = 'unavailable', // 不可用
  Blocked = 'blocked',      // 被阻止(仅Infura)
}

5.2 网络元数据

typescript 复制代码
type NetworkMetadata = {
  // 支持的EIP列表
  EIPS: {
    [eipNumber: number]: boolean;
  };
  // 网络状态
  status: NetworkStatus;
};

6. 事件系统

6.1 网络事件类型

typescript 复制代码
type NetworkControllerEvents =
  | NetworkControllerStateChangeEvent           // 状态变更
  | NetworkControllerNetworkWillChangeEvent     // 网络即将切换
  | NetworkControllerNetworkDidChangeEvent      // 网络已切换
  | NetworkControllerInfuraIsBlockedEvent       // Infura被阻止
  | NetworkControllerInfuraIsUnblockedEvent     // Infura解除阻止
  | NetworkControllerNetworkAddedEvent          // 网络已添加
  | NetworkControllerNetworkRemovedEvent        // 网络已删除
  | NetworkControllerRpcEndpointUnavailableEvent // RPC端点不可用
  | NetworkControllerRpcEndpointDegradedEvent   // RPC端点降级
  | NetworkControllerRpcEndpointRequestRetriedEvent; // RPC请求重试

6.2 事件发布示例

typescript 复制代码
// 网络切换前发布事件
this.messagingSystem.publish('NetworkController:networkWillChange', this.state);

// 网络切换后发布事件
this.messagingSystem.publish('NetworkController:networkDidChange', this.state);

// 网络添加事件
this.messagingSystem.publish('NetworkController:networkAdded', networkConfiguration);

// RPC端点不可用事件
this.messagingSystem.publish('NetworkController:rpcEndpointUnavailable', {
  chainId,
  endpointUrl,
  failoverEndpointUrl,
  error,
});

7. RPC故障转移机制

7.1 故障转移配置

typescript 复制代码
// 启用RPC故障转移
enableRpcFailover() {
  this.#isRpcFailoverEnabled = true;
}

// 禁用RPC故障转移
disableRpcFailover() {
  this.#isRpcFailoverEnabled = false;
}

7.2 故障转移事件

typescript 复制代码
// RPC端点不可用事件
type NetworkControllerRpcEndpointUnavailableEvent = {
  type: 'NetworkController:rpcEndpointUnavailable';
  payload: [{
    chainId: Hex;
    endpointUrl: string;
    failoverEndpointUrl?: string;
    error: unknown;
  }];
};

// RPC端点降级事件
type NetworkControllerRpcEndpointDegradedEvent = {
  type: 'NetworkController:rpcEndpointDegraded';
  payload: [{
    chainId: Hex;
    endpointUrl: string;
  }];
};

7.2 图示

graph TD subgraph "RPC端点配置" Primary[主RPC端点
https://mainnet.infura.io/v3/xxx] Failover1[备用端点1
https://backup1.example.com] Failover2[备用端点2
https://backup2.example.com] Failover3[备用端点3
https://backup3.example.com] end subgraph "故障转移状态" Healthy[健康状态
响应时间 < 1000ms] Degraded[降级状态
响应时间 1000-5000ms] Unavailable[不可用状态
响应时间 > 5000ms 或错误] Blocked[被阻止状态
地理限制或IP封禁] end subgraph "故障转移流程" Start[开始RPC请求] --> CheckPrimary{检查主端点} CheckPrimary -->|健康| Success[请求成功] CheckPrimary -->|降级| DegradedEvent[发布降级事件
NetworkController:rpcEndpointDegraded] CheckPrimary -->|不可用| UnavailableEvent[发布不可用事件
NetworkController:rpcEndpointUnavailable] CheckPrimary -->|被阻止| BlockedEvent[发布阻止事件
NetworkController:infuraIsBlocked] DegradedEvent --> ContinuePrimary[继续使用主端点
但标记为降级] UnavailableEvent --> TryFailover1{尝试备用端点1} BlockedEvent --> TryFailover1 TryFailover1 -->|可用| SwitchToFailover1[切换到备用端点1] TryFailover1 -->|不可用| TryFailover2{尝试备用端点2} TryFailover2 -->|可用| SwitchToFailover2[切换到备用端点2] TryFailover2 -->|不可用| TryFailover3{尝试备用端点3} TryFailover3 -->|可用| SwitchToFailover3[切换到备用端点3] TryFailover3 -->|不可用| AllFailed[所有端点不可用] subgraph "重试机制" Retry1[第1次重试
延迟: 1s] Retry2[第2次重试
延迟: 2s] Retry3[第3次重试
延迟: 4s] MaxRetries[最大重试次数: 3] end AllFailed --> Retry1 Retry1 --> RetryEvent1[发布重试事件
NetworkController:rpcEndpointRequestRetried] Retry1 -->|失败| Retry2 Retry2 --> RetryEvent2[发布重试事件
NetworkController:rpcEndpointRequestRetried] Retry2 -->|失败| Retry3 Retry3 --> RetryEvent3[发布重试事件
NetworkController:rpcEndpointRequestRetried] Retry3 -->|失败| MaxRetries MaxRetries --> FinalError[最终错误
所有端点都不可用] end subgraph "状态恢复监控" MonitorPrimary[监控主端点恢复] MonitorFailover1[监控备用端点1恢复] MonitorFailover2[监控备用端点2恢复] MonitorFailover3[监控备用端点3恢复] SwitchToFailover1 --> MonitorPrimary SwitchToFailover2 --> MonitorPrimary SwitchToFailover3 --> MonitorPrimary MonitorPrimary -->|恢复| SwitchBack[切换回主端点] MonitorFailover1 -->|恢复| UpdateStatus1[更新端点1状态] MonitorFailover2 -->|恢复| UpdateStatus2[更新端点2状态] MonitorFailover3 -->|恢复| UpdateStatus3[更新端点3状态] end subgraph "事件发布" Events[事件系统] DegradedEvent --> Events UnavailableEvent --> Events BlockedEvent --> Events RetryEvent1 --> Events RetryEvent2 --> Events RetryEvent3 --> Events SwitchBack --> Events end subgraph "配置选项" EnableFailover[启用故障转移
isRpcFailoverEnabled: true] DisableFailover[禁用故障转移
isRpcFailoverEnabled: false] TimeoutConfig[超时配置
timeout: 30000ms] RetryConfig[重试配置
maxRetries: 3] end EnableFailover --> Start DisableFailover --> Primary TimeoutConfig --> CheckPrimary RetryConfig --> Retry1

8. 使用示例

8.1 基本网络切换

typescript 复制代码
// 切换到以太坊主网
await networkController.setProviderType('mainnet');

// 获取当前链ID
const chainId = networkController.getSelectedChainId();

// 检查EIP-1559兼容性
const isEIP1559Compatible = await networkController.getEIP1559Compatibility();

8.2 添加自定义网络

typescript 复制代码
// 添加Polygon网络
const polygonNetwork = networkController.addNetwork({
  chainId: '0x89',
  name: 'Polygon',
  nativeCurrency: 'MATIC',
  blockExplorerUrls: ['https://polygonscan.com'],
  rpcEndpoints: [{
    type: RpcEndpointType.Custom,
    url: 'https://polygon-rpc.com',
    name: 'Polygon RPC',
  }],
  defaultRpcEndpointIndex: 0,
});

// 切换到新添加的网络
await networkController.setActiveNetwork(polygonNetwork.rpcEndpoints[0].networkClientId);

8.3 网络状态监控

typescript 复制代码
// 获取网络客户端
const networkClient = networkController.getSelectedNetworkClient();
const provider = networkClient.provider;
const blockTracker = networkClient.blockTracker;

// 监听网络状态变化
networkController.messagingSystem.subscribe('NetworkController:networkDidChange', (state) => {
  console.log('网络已切换到:', state.selectedNetworkClientId);
});

9. 总结

NetworkController 是 MetaMask 多链支持的核心组件,它提供了:

  1. 多网络管理: 支持 Infura 和自定义 RPC 端点
  2. 网络切换: 无缝切换不同网络
  3. 状态监控: 实时监控网络状态和可用性
  4. 故障转移: 自动故障转移到备用 RPC 端点
  5. EIP 兼容性: 动态检测网络支持的 EIP
  6. 事件系统: 完整的事件通知机制

这个控制器为 MetaMask 提供了强大的多链网络管理能力,是构建现代多链钱包应用的基础。通过其灵活的配置和强大的功能,用户可以轻松管理多个区块链网络,享受无缝的多链体验。

学习交流请添加vx: gh313061

下期预告:构建密钥管理控制器(KeyringController)

相关推荐
YSGZJJ2 小时前
股指期货合约是个啥?怎么玩?
区块链
数据与人工智能律师2 小时前
刑法视野下的虚拟财产属性争议:法律风险与市场潜力解析
大数据·网络·人工智能·云计算·区块链
追梦人物3 小时前
Uniswap 手续费和协议费机制剖析
前端·后端·区块链
余_弦7 小时前
区块链钱包开发(十五)—— 构建交易控制器(TransactionController)
区块链·以太坊
一眼万年0414 小时前
Ethereum: 专为区块链定制了一个完善的数据存储系统
区块链·以太坊
Sui_Network2 天前
Walrus 与 Pipe Network 集成,提升多链带宽并降低延迟
人工智能·web3·区块链·智能合约·量子计算
idaretobe2 天前
宝龙地产债务化解解决方案二:基于资产代币化与轻资产转型的战略重构
人工智能·web3·去中心化·区块链·智能合约·信任链
小明的小名叫小明3 天前
区块链技术原理(1) -密码学
区块链·密码学·哈希算法
元宇宙时间3 天前
引领GameFi 2.0新范式:D.Plan携手顶级财经媒体启动“龙珠创意秀”
人工智能·web3·区块链