区块链钱包开发(四.2)—— stream通信框架的使用

154 阅读4分钟

前言

上一节我们介绍了钱包中主要的stream通信组件,这一节我们看他们具体是如何在钱包通信模块大显身手的。 本章涉及到的源码文件地址:

github.com/MetaMask/me… github.com/MetaMask/me… github.com/MetaMask/me… github.com/MetaMask/me… github.com/MetaMask/me…

与钱包通信的几种方式

DApp通过window.ethereum接口通信:

  • 网页应用通过注入的Provider API与钱包交互
  • 使用EIP-1193标准API请求签名、发送交易等

通过MetaMask UI界面通信:

  • 用户通过弹出窗口(popup)、全屏界面或通知直接与钱包交互
  • 内部使用trusted communication通道,权限最高

外部连接通信:

  • 其他扩展或网页通过chrome.runtime.connect直接连接MetaMask

本章主要讲解DApp通过window.ethereum接口通信,也是最复杂的一种方式,其他通信方式可以大致看成它的子集。

步骤梳理与分析(DAPP到background.js)

这部分是从DAPP通过window.ethereum.request发起请求开始到请求进入background.js,请求在Stream中的流向。注意这不是完整的请求响应过程,把这部分单独拿出来为了让大家参考流程然后对照源码先熟悉一下,后面会给出完整的请求响应流程:

发起请求后的消息流向

1. DApp 发送请求

  • DApp 调用 window.ethereum.request({ method: 'eth_requestAccounts' })
  • 实际调用的是 MetaMaskInpageProvider(继承自 StreamProvider)的 request 方法
  • 该方法最终调用 _rpcEngine.handle(payload, callback)

2. 进入 JSON-RPC Engine

  • _rpcEngine.handle 触发中间件链
  • 其中一个中间件是 StreamProvider 构造时注入的 _jsonRpcConnection.middleware(由 createStreamMiddleware 创建)

3. 通过中间件写入流

  • _jsonRpcConnection.middleware 通过 stream.push(req) 将请求写入 _jsonRpcConnection.stream(一个 Duplex 流)

4. pipeline 触发下游流

  • _jsonRpcConnection.stream 通过 pipeline 连接到 connectionStream(即 Substream(METAMASK_EIP_1193_PROVIDER)
  • pipeline 机制会自动调用 Substream._write(),将请求写入 Substream

5. Substream 写入父 ObjectMultiplex

  • Substream._write() 实现为:this._parent.push({ name: this._name, data: chunk })
  • 这里的 parent 是 ObjectMultiplex(如 inpage.js 里的 mux
  • 数据被 push 到 ObjectMultiplex 的缓冲区

6. pipeline 触发 WindowPostMessageStream

  • ObjectMultiplex 通过 pipeline 连接到 WindowPostMessageStream(inpage.js 里的 metamaskStream
  • pipeline 机制会自动调用 WindowPostMessageStream._write(),将数据写入

7. WindowPostMessageStream 通过 postMessage 发送

  • WindowPostMessageStream._write() 调用 _postMessage(),实际通过 window.postMessage 发送数据到目标窗口(内容脚本)

8. contentscript 端 WindowPostMessageStream 接收

  • contentscript 端的 WindowPostMessageStream(如 pageStream)监听 message 事件,收到数据后调用 _onData,数据进入流

9. pipeline 触发 ObjectMultiplex

  • pageStream 通过 pipeline 连接到 ObjectMultiplexpageMux
  • pipeline 机制会自动调用 ObjectMultiplex._write(),将数据分发到对应的 Substream(pageChannel

10. pipeline 触发 extensionEip1193Channel

  • pageChannel 通过 pipeline 连接到 extensionEip1193Channel
  • 数据被写入 extensionEip1193Channel,再 push 到其父 extensionMux

11. pipeline 触发 PortStream

  • extensionMux 通过 pipeline 连接到 PortStreamextensionStream
  • PortStream.write() 通过 Port.postMessage(req) 发送数据到 background.js

12. background.js 端接收

  • background.js 端的 Port 监听 onMessage,收到请求后进入后台处理

流程图表示

flowchart TD
    subgraph 网页 [DApp]
        A1["window.ethereum.request({ method: 'eth_requestAccounts' })"]
        A2["MetaMaskInpageProvider.request()"]
        A3["_rpcEngine.handle()"]
        A4["_jsonRpcConnection.middleware(req)"]
        A5["stream.push(req)"]
    end

    subgraph inpage.js
        B1["Substream(METAMASK_EIP_1193_PROVIDER)"]
        B2["ObjectMultiplex (mux)"]
        B3["WindowPostMessageStream (metamaskStream)"]
    end

    subgraph contentscript.js
        C1["WindowPostMessageStream (pageStream)"]
        C2["ObjectMultiplex (pageMux)"]
        C3["Substream(pageChannel)"]
        C4["Substream(extensionEip1193Channel)"]
        C5["ObjectMultiplex (extensionMux)"]
        C6["PortStream (extensionStream)"]
    end

    subgraph background.js
        D1["Port (browser.runtime.onConnect)"]
        D2["MetaMask Controller"]
    end

    %% DApp 到 inpage.js
    A1 --> A2 --> A3 --> A4 --> A5
    A5 -->|pipeline| B1
    B1 -->|push到父| B2
    B2 -->|pipeline| B3
    B3 -- window.postMessage --> C1

    %% contentscript.js 内部流转
    C1 -->|pipeline| C2
    C2 -->|分发到| C3
    C3 -->|pipeline| C4
    C4 -->|push到父| C5
    C5 -->|pipeline| C6

    %% 到 background
    C6 -- port.postMessage --> D1
    D1 --> D2

    %% 说明
    classDef stream fill:#f9f,stroke:#333,stroke-width:2px;
    classDef logic fill:#bbf,stroke:#333,stroke-width:2px;
    class A1,A2,A3,A4,A5 logic;
    class B1,B2,B3,C1,C2,C3,C4,C5,C6,D1 stream;


步骤梳理与分析(DAPP请求响应全流程)

请求方向(DApp → background.js)

  1. DAPP 发送请求(如 eth_requestAccounts)
  2. _rpcEngine.handle(eth_requestAccounts)
  3. 交给 Duplex::_jsonRpcConnection.middleware 处理
  4. middleware 内部通过 stream.push(req) 写入 Duplex::_jsonRpcConnection.stream 缓冲区
  5. pipeline 触发 Substream(METAMASK_EIP_1193_PROVIDER)::connectionStream._write()
  6. connectionStream._write() 把请求 push 到父 ObjectMultiplex::mux 缓冲区
  7. pipeline 触发 WindowPostMessageStream::metamaskStream._write()
  8. metamaskStream._write() 通过 window.postMessage() 发送到 contentscript
  9. contentscript 的 WindowPostMessageStream::pageStream 接收
  10. pipeline 触发 ObjectMultiplex::pageMux._write(),分发到 Substream(METAMASK_EIP_1193_PROVIDER)::pageChannel
  11. pipeline 触发 Substream(METAMASK_EIP_1193_PROVIDER)::extensionEip1193Channel._write()
  12. extensionEip1193Channel.write() push 到父 ObjectMultiplex::extensionMux
  13. pipeline 触发 PortStream::extensionStream._write()
  14. extensionStream.write() 通过 Port.postMessage(req) 发送到 background
  15. background 端 PortStream::portStream._onMessage() 接收
  16. 请求传入 metamask-controller.js.setupUntrustedCommunicationEip1193()
  17. pipeline 触发 ObjectMultiplex::mux.write(),push 到 Substream(METAMASK_EIP_1193_PROVIDER)::outStream
  18. pipeline 触发 Duplex::providerStream.write()
  19. engine.handle 最终处理请求,生成响应

响应方向(background.js → DApp)

  1. 响应 push 到 providerStream 自身缓冲区
  2. pipeline 触发 Substream(METAMASK_EIP_1193_PROVIDER)::outStream.write(),写入父 ObjectMultiplex::mux
  3. pipeline 触发 PortStream::portStream._write(),响应发送回 contentscript
  4. contentscript 端 PortStream::extensionStream._onMessage() 接收
  5. pipeline 触发 ObjectMultiplex::extensionMux.write(),分发到 Substream(METAMASK_EIP_1193_PROVIDER)::extensionEip1193Channel
  6. pipeline 触发 Substream(METAMASK_EIP_1193_PROVIDER)::pageChannel.write(),写入父 ObjectMultiplex::pageMux
  7. pipeline 触发 WindowPostMessageStream::pageStream._write(),通过 window.postMessage() 发送到 inpage
  8. inpage 端 WindowPostMessageStream::metamaskStream 接收
  9. pipeline 触发 ObjectMultiplex::mux.write(),push 到 Substream(METAMASK_EIP_1193_PROVIDER)::connectionStream
  10. pipeline 触发 Duplex::_jsonRpcConnection.stream.write(),调用 processMessage()
  11. processMessage() 调用 processResponse(),DAPP 拿到响应结果

流程图表示

flowchart TD
    %% DApp -> background 请求链路
    subgraph 网页 [DApp]
        A1["window.ethereum.request(...)"]
        A2["_rpcEngine.handle()"]
        A3["_jsonRpcConnection.middleware(req)"]
        A4["stream.push(req)"]
    end

    subgraph inpage.js
        B1["Substream(METAMASK_EIP_1193_PROVIDER)::connectionStream._write()"]
        B2["ObjectMultiplex::mux"]
        B3["WindowPostMessageStream::metamaskStream._write()"]
    end

    subgraph contentscript.js
        C1["WindowPostMessageStream::pageStream"]
        C2["ObjectMultiplex::pageMux"]
        C3["Substream::pageChannel"]
        C4["Substream::extensionEip1193Channel"]
        C5["ObjectMultiplex::extensionMux"]
        C6["PortStream::extensionStream"]
    end

    subgraph background.js
        D1["PortStream::portStream._onMessage()"]
        D2["setupUntrustedCommunicationEip1193()"]
        D3["ObjectMultiplex::mux"]
        D4["Substream::outStream"]
        D5["Duplex::providerStream.write()"]
        D6["engine.handle()"]
    end

    %% background -> DApp 响应链路
    subgraph background.js
        E1["engine.handle() 生成响应"]
        E2["providerStream.push(res)"]
        E3["Substream::outStream.write()"]
        E4["ObjectMultiplex::mux"]
        E5["PortStream::portStream._write()"]
    end

    subgraph contentscript.js
        F1["PortStream::extensionStream._onMessage()"]
        F2["ObjectMultiplex::extensionMux"]
        F3["Substream::extensionEip1193Channel"]
        F4["Substream::pageChannel"]
        F5["ObjectMultiplex::pageMux"]
        F6["WindowPostMessageStream::pageStream._write()"]
    end

    subgraph inpage.js
        G1["WindowPostMessageStream::metamaskStream"]
        G2["ObjectMultiplex::mux"]
        G3["Substream::connectionStream"]
        G4["Duplex::_jsonRpcConnection.stream.write()"]
        G5["processMessage() -> processResponse()"]
        G6["DApp 拿到响应"]
    end

    %% 请求链路
    A1 --> A2 --> A3 --> A4
    A4 -->|pipeline| B1
    B1 -->|push到父| B2
    B2 -->|pipeline| B3
    B3 -- window.postMessage --> C1
    C1 -->|pipeline| C2
    C2 -->|分发到| C3
    C3 -->|pipeline| C4
    C4 -->|push到父| C5
    C5 -->|pipeline| C6
    C6 -- port.postMessage --> D1
    D1 --> D2 --> D3 --> D4 --> D5 --> D6

    %% 响应链路
    D6 --> E1 --> E2 --> E3 --> E4 --> E5
    E5 -- port.postMessage --> F1
    F1 --> F2 --> F3 --> F4 --> F5 --> F6
    F6 -- window.postMessage --> G1
    G1 --> G2 --> G3 --> G4 --> G5 --> G6

    %% 说明
    classDef stream fill:#f9f,stroke:#333,stroke-width:2px;
    classDef logic fill:#bbf,stroke:#333,stroke-width:2px;
    class A1,A2,A3,A4,G6 logic;
    class B1,B2,B3,C1,C2,C3,C4,C5,C6,D1,D2,D3,D4,D5,D6,E1,E2,E3,E4,E5,F1,F2,F3,F4,F5,F6,G1,G2,G3,G4,G5 stream;

时序图表示

如果不习惯看流程图,可以参考时序图:

sequenceDiagram
    participant DApp as DApp
    participant Inpage as inpage.js
    participant Content as contentscript.js
    participant BG as background.js

    %% 请求方向
    DApp->>DApp: window.ethereum.request(...)
    DApp->>DApp: _rpcEngine.handle()
    DApp->>Inpage: _jsonRpcConnection.middleware(req)
    Inpage->>Inpage: stream.push(req)
    Inpage->>Inpage: Substream(METAMASK_EIP_1193_PROVIDER)._write()
    Inpage->>Inpage: ObjectMultiplex::mux
    Inpage->>Inpage: WindowPostMessageStream::metamaskStream._write()
    Inpage->>Content: window.postMessage(req)
    Content->>Content: WindowPostMessageStream::pageStream
    Content->>Content: ObjectMultiplex::pageMux
    Content->>Content: Substream::pageChannel
    Content->>Content: Substream::extensionEip1193Channel
    Content->>Content: ObjectMultiplex::extensionMux
    Content->>Content: PortStream::extensionStream
    Content->>BG: Port.postMessage(req)
    BG->>BG: PortStream::portStream._onMessage()
    BG->>BG: setupUntrustedCommunicationEip1193()
    BG->>BG: ObjectMultiplex::mux
    BG->>BG: Substream::outStream
    BG->>BG: Duplex::providerStream.write()
    BG->>BG: engine.handle()

    %% 响应方向
    BG->>BG: engine.handle() 生成响应
    BG->>BG: providerStream.push(res)
    BG->>BG: Substream::outStream.write()
    BG->>BG: ObjectMultiplex::mux
    BG->>BG: PortStream::portStream._write()
    BG->>Content: Port.postMessage(res)
    Content->>Content: PortStream::extensionStream._onMessage()
    Content->>Content: ObjectMultiplex::extensionMux
    Content->>Content: Substream::extensionEip1193Channel
    Content->>Content: Substream::pageChannel
    Content->>Content: ObjectMultiplex::pageMux
    Content->>Content: WindowPostMessageStream::pageStream._write()
    Content->>Inpage: window.postMessage(res)
    Inpage->>Inpage: WindowPostMessageStream::metamaskStream
    Inpage->>Inpage: ObjectMultiplex::mux
    Inpage->>Inpage: Substream::connectionStream
    Inpage->>Inpage: _jsonRpcConnection.stream.write()
    Inpage->>Inpage: processMessage() -> processResponse()
    Inpage->>DApp: DApp 拿到响应结果

总结

本章有一定的复杂性,需要对照源码去理解,如果还是不理解可以参考对应的视频讲解。

这其实还只是请求响应流的传递过程,实际中间还包括很多RPC中间件的处理过程,控制器间的通信过程,元数据的存取过程,前后端的状态实时同步等等,可见一个好的钱包框架是非常复杂的,不过不用担心,我们后面会一一详细讲解。

学习交流请添加vx: gh313061

下期预告:构建json RPC框架