区块链钱包开发(六)—— 构建各个控制器间的通信框架

80 阅读5分钟

MetaMask控制器介绍

Metamask的后台控制器实现被设计的高度模块化,充分体现了软件工程中的模块化、安全性和可扩展性原则。在Metamask中拥有非常多的controller(50个左右),每个controller负责实现某个特定的功能,例如:

TransactionController(交易控制器): 负责链上交易的全生命周期管理,包括创建、签名、广播、跟踪状态以及Gas费用估算。

NetworkController(网络控制器): 管理与不同区块链网络的连接与切换,处理RPC端点、网络状态监控以及链ID识别,确保钱包能与各种EVM兼容链正确通信。

KeyringController(密钥管理控制器): 安全管理用户私钥和账户,处理私钥的创建、导入、加密存储以及签名操作。

ApprovalController(批准控制器): 处理需要用户确认的请求(如交易签名、连接DApp),管理批准流程和界面交互。

AccountsController(账户控制器): 管理用户账户信息,包括账户元数据、余额、交易历史和账户切换。

架构详解

源码地址:github.com/MetaMask/co…

控制器框架的设计主要是为了让这些控制器能够:

  • 管理自己的状态

  • 与其他控制器安全通信

  • 实现权限控制

核心组件详解

1. Messenger (消息系统)

Messenger.ts是整个架构的核心通信系统,它提供了两种主要的通信机制:

  • Actions (动作): 控制器可以调用的函数
  • Events (事件): 控制器可以发布和订阅的消息
export class Messenger<
  Action extends ActionConstraint,
  Event extends EventConstraint,
> {
  // 动作处理器映射
  readonly #actions = new Map<Action['type'], unknown>();
  // 事件订阅映射
  readonly #events = new Map<Event['type'], EventSubscriptionMap<Event>>();
}

Messenger支持:

  • registerActionHandler: 注册可被其他控制器调用的函数
  • call: 调用其他控制器注册的函数
  • publish: 发布事件给所有订阅者
  • subscribe: 订阅特定类型的事件

2. RestrictedMessenger (受限消息系统)

RestrictedMessenger.ts提供了对Messenger的安全包装,通过命名空间和许可列表实现细粒度的访问控制:

export class RestrictedMessenger<
  Namespace extends string,
  Action extends ActionConstraint,
  Event extends EventConstraint,
  AllowedAction extends string,
  AllowedEvent extends string,
> {
  readonly #messenger: Messenger<ActionConstraint, EventConstraint>;
  readonly #namespace: Namespace;
  readonly #allowedActions: NotNamespacedBy<Namespace, AllowedAction>[];
  readonly #allowedEvents: NotNamespacedBy<Namespace, AllowedEvent>[];
}

每个控制器获得一个受限的Messenger实例,该实例:

  • 只允许调用指定的actions和订阅指定的events
  • 拥有自己命名空间内actions和events的完全控制权
  • 必须遵循命名规则:控制器只能注册/发布形如controllerName:actionName的actions/events
// 创建TransactionController的受限Messenger
const transactionControllerMessenger = rootMessenger.getRestricted({
  name: 'TransactionController',
  // 允许调用的外部actions
  allowedActions: [
    'NetworkController:getNetworkState',
    'GasFeeController:getGasFeeEstimates',
    'KeyringController:signTransaction'
  ],
  // 允许订阅的外部events
  allowedEvents: [
    'NetworkController:stateChange',
    'AccountsController:selectedAccountChange'
  ]
});

// 正常工作:这是允许的action
transactionControllerMessenger.call('NetworkController:getNetworkState');
// 错误:未授权action
transactionControllerMessenger.call('PreferencesController:getState'); 

// 正常工作:这是允许的event
transactionControllerMessenger.subscribe('NetworkController:stateChange')
// 错误:未授权event
transactionControllerMessenger.subscribe('PreferencesController:stateChange')

// 注册自己命名空间下的action - 允许
transactionControllerMessenger.registerActionHandler(
  'TransactionController:addTransaction',
  (txParams) => this.addTransaction(txParams)
);

// 发布自己命名空间下的事件 - 允许
transactionControllerMessenger.publish(
  'TransactionController:transactionAdded',
  newTransactionMeta
);

// 错误:尝试注册其他命名空间的action
transactionControllerMessenger.registerActionHandler(
  'NetworkController:switchNetwork',
  (networkId) => {}
);

// 正确的命名方式
this.messagingSystem.registerActionHandler(
  'TransactionController:getState', 
  () => this.state
);

// 错误的命名方式 - 不符合命名空间规则
this.messagingSystem.registerActionHandler(
  'getTransactions',
   () => this.getTransactions()
);

这确保了控制器之间的安全隔离,防止未授权访问。

3. BaseController (控制器基类)

BaseControllerV2.ts为所有控制器提供了统一的基础实现:

export class BaseController<
  ControllerName extends string,
  ControllerState extends StateConstraint,
  messenger extends RestrictedMessenger<...>
> {
  #internalState: ControllerState;
  protected messagingSystem: messenger;
  public readonly name: ControllerName;
  public readonly metadata: StateMetadata<ControllerState>;
}

BaseController提供了:

状态管理:通过Immer库提供不可变状态更新

Immer库在钱包开发中是非常重要的,因为钱包中的各种数据结构庞大而复杂,并且需要频繁更新,Immer可以大大提高性能和编码效率。

//BaseController的状态管理通过Immer实现真正的不可变状态更新。考虑以下场景:

// 不使用Immer的复杂嵌套状态更新
this.setState({
  ...state,
  transactions: {
    ...state.transactions,
    [txId]: {
      ...state.transactions[txId],
      status: 'confirmed'
    }
  }
});

// 使用Immer的简洁写法
this.update((state) => {
  state.transactions[txId].status = 'confirmed';
});

protected update(
  callback: (state: Draft<ControllerState>) => void | ControllerState,
): {
  nextState: ControllerState;
  patches: Patch[];
  inversePatches: Patch[];
} {
  const [nextState, patches, inversePatches] = (
    produceWithPatches as unknown as (
      state: ControllerState,
      cb: typeof callback,
    ) => [ControllerState, Patch[], Patch[]]
  )(this.#internalState, callback);

  if (patches.length > 0) {
    this.#internalState = nextState;
    this.messagingSystem.publish(
      `${this.name}:stateChange`,
      nextState,
      patches,
    );
  }

  return { nextState, patches, inversePatches };
}

元数据:指定哪些状态应该持久化、哪些应该匿名化

public readonly metadata: StateMetadata<ControllerState>;

事件通知:状态变更时自动发布事件

this.messagingSystem.publish(`${this.name}:stateChange`, nextState, patches);

流程图:

flowchart TD
    A[控制器方法调用] --> B[调用this.update]
    B --> C[Immer创建状态草稿]
    C --> D[修改草稿状态]
    D --> E[Immer生成新状态和补丁]
    E --> F[更新内部状态]
    F --> G[发布stateChange事件]
    
    G --> H1[其他控制器接收事件]
    G --> H2[UI更新视图]
    
    F --> J[检查元数据persist标记]
    J --> K[持久化标记为true的状态属性]
    K --> L[保存到本地存储]
    
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style G fill:#bbf,stroke:#333,stroke-width:2px
    style H2 fill:#bfb,stroke:#333,stroke-width:2px

实际控制器实例: TransactionController

TransactionController.ts中,我们可以看到一个实际的控制器如何利用这个框架:

export class TransactionController extends BaseController<
  typeof controllerName,
  TransactionControllerState,
  TransactionControllerMessenger
> {
  constructor(options: TransactionControllerOptions) {
    super({
      name: controllerName,
      metadata,
      state: { ...getDefaultTransactionControllerState(), ...options.state },
      messenger: options.messenger,
    });
    
    // 注册动作处理器
    this.messagingSystem.registerActionHandler(
      `${controllerName}:updateCustodialTransaction`,
      this.updateCustodialTransaction.bind(this),
    );
    // 调用其他控制器方法
    const findNetworkClientIdByChainId = (chainId: Hex) => {
      return this.messagingSystem.call(
        `NetworkController:findNetworkClientIdByChainId`,
        chainId,
      );
    };
    // 订阅事件
    this.messagingSystem.subscribe(
      'TransactionController:stateChange',
      this.#checkForPendingTransactionAndStartPolling,
    );

  }
  
  #onConfirmedTransaction(transactionMeta: TransactionMeta) {
    // 发布事件
    this.messagingSystem.publish(
      `${controllerName}:transactionConfirmed`,
      transactionMeta,
   );

}

控制器间通信流程

  1. 初始化过程:
// 1. 创建根Messenger实例
import { Messenger } from '@metamask/base-controller';
const rootMessenger = new Messenger<ActionType, EventType>();

// 2. 为每个控制器创建RestrictedMessenger实例
const networkControllerMessenger = rootMessenger.getRestricted({
  name: 'NetworkController',
  allowedActions: ['PreferencesController:getState'],
  allowedEvents: ['PreferencesController:stateChange']
});

const transactionControllerMessenger = rootMessenger.getRestricted({
  name: 'TransactionController',
  allowedActions: ['NetworkController:getNetworkState'],
  allowedEvents: ['NetworkController:stateChange']
});

// 3. 初始化各个控制器
const networkController = new NetworkController({
  messenger: networkControllerMessenger,
  // 其他配置...
});

const transactionController = new TransactionController({
  messenger: transactionControllerMessenger,
  // 其他配置...
});
  1. 控制器间调用动作:
class TransactionController extends BaseController<'TransactionController', State, Messenger> {
  async submitTransaction(txParams) {
    // 获取当前网络状态
    const networkState = this.messagingSystem.call(
      'NetworkController:getNetworkState'
    );

    // 使用网络状态信息
    const chainId = networkState.provider.chainId;
    
    // 继续交易提交逻辑...
    const txMeta = await this.addTransaction({
      ...txParams,
      chainId,
    });

    return txMeta;
  }
}
  1. 控制器间事件订阅:

    NetworkController发布 'NetworkController:stateChange' 事件 TransactionController (已订阅) 接收事件并处理

class NetworkController extends BaseController<'NetworkController', NetworkState, Messenger> {
  async setProviderType(type) {
    // 更新状态
    this.update((state) => {
      state.provider = { type, ...getNetwork(type) };
    });
    // 状态变更事件自动发布
    // BaseController会自动执行:
    // this.messagingSystem.publish('NetworkController:stateChange', this.state)
  }
}

class TransactionController extends BaseController<'TransactionController', State, Messenger> {
  constructor({ messenger, ...options }) {
    super({
      name: 'TransactionController',
      metadata,
      state: getDefaultState(),
      messenger,
    });

    // 订阅网络变更事件
    this.messagingSystem.subscribe(
      'NetworkController:stateChange',
      this.#onNetworkStateChange.bind(this)
    );
  }

  #onNetworkStateChange(networkState) {
    // 当网络变化时,更新所有挂起交易的网络ID
    const { chainId } = networkState.provider;
    
    this.update((state) => {
      state.transactions.forEach((tx) => {
        if (tx.status === 'unapproved') {
          tx.chainId = chainId;
        }
      });
    });
  }
}

画图表示

graph TB
    subgraph "权限控制系统"
        RM[RootMessenger]
        
        subgraph "TransactionController权限"
            TC[TransactionController]
            TC_A[允许的Actions:\nNetworkController:getNetworkState\nKeyringController:signTransaction]
            TC_E[允许的Events:\nNetworkController:stateChange\nApprovalController:approved]
            TC_O[拥有的命名空间:\nTransactionController:*]
        end
        
        subgraph "NetworkController权限"
            NC[NetworkController]
            NC_A[允许的Actions:\nPreferencesController:getState]
            NC_E[允许的Events:\nPreferencesController:stateChange]
            NC_O[拥有的命名空间:\nNetworkController:*]
        end
        
        TC --> |只能调用允许的Actions| NC
        NC --> |只能调用允许的Actions| TC
        
        TC -.-> |只能发布自己命名空间的Events| RM
        NC -.-> |只能发布自己命名空间的Events| RM
        
        RM -.-> |只能订阅允许的Events| TC
        RM -.-> |只能订阅允许的Events| NC
    end
    
    style TC fill:#faa,stroke:#333,stroke-width:2px
    style NC fill:#aaf,stroke:#333,stroke-width:2px
    style RM fill:#afa,stroke:#333,stroke-width:2px

安全与权限设计

MetaMask的这套架构设计了精细的权限控制:

  1. 命名空间隔离: 每个控制器只能在自己的命名空间内注册和发布

  2. 动作许可控制: 每个控制器只能调用被明确允许的动作

    allowedActions: ['AccountsController:getState', 'GasFeeController:getGasFeeEstimates']

  3. 事件许可控制: 每个控制器只能订阅被明确允许的事件

    allowedEvents: ['PreferencesController:stateChange']

总结

BaseControllerV2.ts、Messenger.ts和RestrictedMessenger.ts共同构建了一个:

  1. 模块化: 每个控制器专注于一个功能领域
  2. 可组合: 控制器可以灵活组合成更大的应用状态
  3. 类型安全: 利用TypeScript的高级类型系统确保通信安全
  4. 权限隔离: 精细控制哪些控制器可以访问哪些功能
  5. 状态管理: 提供不可变状态更新和状态变更通知

这种架构使MetaMask能够构建一个复杂但可维护的钱包应用,同时保持高度的安全性和可扩展性。

学习交流请添加vx: gh313061

下期预告:创建注入网页的Provider