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

120 阅读5分钟

1. 概述

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

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

2. 核心架构

2.1 主要组件

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

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

2.2 状态结构

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

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

3. 网络类型和配置

3.1 网络类型

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

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

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

3.2 网络配置结构

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

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

3.3 RPC端点类型

Infura RPC端点

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

自定义RPC端点

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

4. 核心功能实现

4.1 网络切换机制

设置网络类型

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

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);
}

设置活跃网络

网络切换的核心逻辑:

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

4.2 网络客户端管理

获取网络客户端

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;
}

获取选中的网络客户端

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 网络配置管理

添加网络

添加新网络的完整流程:

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;
}

更新网络

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

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;
}

删除网络

删除网络的安全检查:

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:

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 网络状态枚举

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

5.2 网络元数据

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

6. 事件系统

6.1 网络事件类型

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

6.2 事件发布示例

// 网络切换前发布事件
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 故障转移配置

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

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

7.2 故障转移事件

// 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端点<br/>https://mainnet.infura.io/v3/xxx]
        Failover1[备用端点1<br/>https://backup1.example.com]
        Failover2[备用端点2<br/>https://backup2.example.com]
        Failover3[备用端点3<br/>https://backup3.example.com]
    end
    
    subgraph "故障转移状态"
        Healthy[健康状态<br/>响应时间 < 1000ms]
        Degraded[降级状态<br/>响应时间 1000-5000ms]
        Unavailable[不可用状态<br/>响应时间 > 5000ms 或错误]
        Blocked[被阻止状态<br/>地理限制或IP封禁]
    end
    
    subgraph "故障转移流程"
        Start[开始RPC请求] --> CheckPrimary{检查主端点}
        
        CheckPrimary -->|健康| Success[请求成功]
        CheckPrimary -->|降级| DegradedEvent[发布降级事件<br/>NetworkController:rpcEndpointDegraded]
        CheckPrimary -->|不可用| UnavailableEvent[发布不可用事件<br/>NetworkController:rpcEndpointUnavailable]
        CheckPrimary -->|被阻止| BlockedEvent[发布阻止事件<br/>NetworkController:infuraIsBlocked]
        
        DegradedEvent --> ContinuePrimary[继续使用主端点<br/>但标记为降级]
        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次重试<br/>延迟: 1s]
            Retry2[第2次重试<br/>延迟: 2s]
            Retry3[第3次重试<br/>延迟: 4s]
            MaxRetries[最大重试次数: 3]
        end
        
        AllFailed --> Retry1
        Retry1 --> RetryEvent1[发布重试事件<br/>NetworkController:rpcEndpointRequestRetried]
        Retry1 -->|失败| Retry2
        Retry2 --> RetryEvent2[发布重试事件<br/>NetworkController:rpcEndpointRequestRetried]
        Retry2 -->|失败| Retry3
        Retry3 --> RetryEvent3[发布重试事件<br/>NetworkController:rpcEndpointRequestRetried]
        Retry3 -->|失败| MaxRetries
        MaxRetries --> FinalError[最终错误<br/>所有端点都不可用]
    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[启用故障转移<br/>isRpcFailoverEnabled: true]
        DisableFailover[禁用故障转移<br/>isRpcFailoverEnabled: false]
        TimeoutConfig[超时配置<br/>timeout: 30000ms]
        RetryConfig[重试配置<br/>maxRetries: 3]
    end
    
    EnableFailover --> Start
    DisableFailover --> Primary
    TimeoutConfig --> CheckPrimary
    RetryConfig --> Retry1

8. 使用示例

8.1 基本网络切换

// 切换到以太坊主网
await networkController.setProviderType('mainnet');

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

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

8.2 添加自定义网络

// 添加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 网络状态监控

// 获取网络客户端
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)