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

前言

上一节我们介绍了钱包中主要的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

本教程配套视频教程:space.bilibili.com/382494787/l...

下期预告:构建json RPC框架

相关推荐
链上罗主任17 小时前
以太坊十年:智能合约与去中心化的崛起
web3·区块链·智能合约·以太坊
不可描述的两脚兽19 小时前
学习笔记《区块链技术与应用》第4天 比特币脚本语言
笔记·学习·区块链
余_弦21 小时前
区块链钱包开发(四.1)—— 搭建stream风格的通信框架
区块链
技术路上的探险家1 天前
Web3:在 VSCode 中使用 Vue 前端与已部署的 Solidity 智能合约进行交互
vscode·web3·区块链·交互·react·solidity·ethers.js
阿祥~1 天前
FISCO BCOS Gin调用WeBASE-Front接口发请求
区块链·gin·fisocbocs
技术路上的探险家1 天前
Web3:以太坊虚拟机
web3·区块链·智能合约·solidity·foundry
AWS官方合作商1 天前
AWS Blockchain Templates:快速部署企业级区块链网络的终极解决方案
区块链·aws
boyedu2 天前
哈希指针与数据结构:构建可信数字世界的基石
数据结构·算法·区块链·哈希算法·加密货币
技术路上的探险家2 天前
Web3:赛道划分与发展趋势解析
web3·区块链