目前的 DApp 主要有两种构建方式,一种是基于智能合约构建的 DApp,也是目前最主流的方式;第二种则是以应用链(AppChain)的方式而构建的,还没形成大范围的应用。所以我们还是以智能合约形式的 DApp 为主。另外,智能合约平台以 EVM 为最大主流,而 EVM 平台的智能合约以 Solidity 为主流,因此,下面的内容也都是以 Solidity 体系为主。
从整体上来看,整个 DApp 全栈的技术架构可划分为三层:表现层、逻辑层和持久层。具体架构图大致如下:
表现层
表现层主要包括 UI 客户端、SDK、钱包和 Provider。
UI 客户端主要有 Web、iOS App 和 Android App,其主要使用的技术栈和传统的客户端技术栈是通用的。在 Web2 领域里,前端框架主流的有 React 和 Vue 两大阵营,国内开发者用 Vue 的比较多,但在 Web3 领域里,还是以 React 为主,因为很多 Web3 相关的工具库都是基于 React 的,比如 ConnectKit 和 wagmi。ConnectKit 是一个连接钱包的 React 组件库,wagmi 则封装了一系列 React Hooks 用于与区块链交互。iOS 和 Android,不管是 Web2 还是 Web3,使用 Flutter 开发的已经越来越多,Flutter 已经成为了新的主流开发框架。
不过,目前的 Web3 行业里,大部分 DApp 会优先选择推出 Web 端,兼顾到 App 端,除了移动钱包应用之外,其他的相对还不多,这主要是因为 App 的应用商店对 Web3 的应用依然有不少限制,这阻碍了大部分 DApp 上线 App 应用商店的计划。尤其是应用占比最多的 DeFi 应用,更是难以上架。就说已经成为基础设施的 Uniswap、Curve、Compound、Aave 等长期以来都只有 Web 端,近一年来,Uniswap 才终于推出了 App 版本的 Uniswap Wallet。
SDK 主要是将一些与 Web3 交互相关的操作统一封装成单独的库,提供给 UI 客户端接入,包括连接钱包、与智能合约交互、查询 Subgraph 数据等操作。主要用到的 Web3 框架就是 web3.js 或 ethers.js,主要用于连接钱包并与智能合约交互。查询 Subgraph 数据的则主要用 GraphQL 的客户端工具,有 Graph client、Apollo client、URQL 等。SDK 大部分是以 JS/TS 语言为主编写的,少部分 DApp 也会封装其他 SDK 适用于不同开发语言,比如 Dart 语言的 SDK 就适用于以 Flutter 为主的 App 开发。
SDK 本来是可以直接集成在客户端里的,但大部分客户端开发人员并不熟悉 Web3 的技术栈,所以才将 SDK 单独分离了出来,由熟悉 Web3 技术栈的开发人员进行封装。另外,对于一些成熟的 DApp,有了 SDK 也方便第三方应用集成协议。比如,Uniswap 的 SDK 里封装了计算最优兑换路径的算法,第三方应用接入 SDK 就很方便了,如果没有这个 SDK 的话,那第三方应用需要自己去实现计算最优兑换路径将会非常麻烦。
UI 客户端和 SDK 连接钱包时,也需要去适配支持不同的钱包,因为不同的钱包可能提供了不同的接入方式,有些钱包之间甚至可能会产生冲突,使用同一套代码进行适配时可能无法同时支持。一般来说,至少接入 MetaMask 和 WalletConnect 已经成为了所有 DApp 的标配。
最后,Provider 是前端 SDK 与智能合约交互的桥梁,确切地说,是与底层区块链交互的桥梁。Provider 其实是一个符合 EIP-1193 规范的 JavaScript 对象,负责实现以太坊 RPC 的方法调用。ethers.js 和 web3.js 都封装了不同的 Provider 实现,且提供了多种不同的 Provider。比如,web3.js 有分 HttpProvider、WebsocketProvider、IpcProvider,ethers.js 还有根据不同节点运营商分为了 AlchemyProvider、InfuraProvider、EtherscanProvider 等。
逻辑层
逻辑层是整个 DApp 最核心的一个层级,主要包括链上的智能合约,提供数据索引服务的 Subgraph & Graph Node,以及链下的 Keeper 服务和预言机等。
智能合约是整个 DApp 的重中之重,承载了应用协议最核心的链上业务逻辑,且因为智能合约的代码是需要在链上开源的,所以对安全性的要求非常高。如果上线后的智能合约代码存在安全漏洞,尤其是危害到资产安全的漏洞,一旦被黑客发现,甚至有可能会造成致命的损失。因此,智能合约代码的质量和安全尤其重要,对智能合约工程师的要求也比其他工程师高很多。不过,再有经验的智能合约工程师,也无法保证写出来的代码百分百没有 Bug,所以代码上线之前还需要经过多方的安全审计,包括团队内部的代码审计,公司内部和其他项目团队的互审,以及找外部专业的安全审计公司进行审计。安全审计在这个行业里是绝对的刚需。
另外,设计智能合约的时候,除了考虑安全性之外,还要兼顾功能性和可扩展性,不能一味只为了绝对安全而过度设计。通常情况下,这几个特性并不是全正相关的,比如,进一步提高安全性,就可能会减低功能性和可扩展性,因此,要追求的并不是三种特性全都越高越好,而是要根据情况有所取舍,达到一种平衡状态即可。而要达成目标,所需要遵循的设计原则和背后本质的设计思想,其实还是架构师们所熟知的那些,比如,单一职责原则、开闭原则、依赖倒置原则、接口隔离原则等,以及关注点分离、低耦合高内聚、适度设计等架构思想。而且,最核心的就是要将复杂问题简单化,这应用到开源的智能合约中,显得更加重要。
智能合约存储的链上数据和传统 Web2 的数据库完全不同,没法像 MySQL、MongoDB 这些数据库一样对数据进行索引查询,也没法做到对数据进行分组、排序或组合。而这种需求也属于刚需,为了满足这种需求,就出现了 The Graph,区块链数据索引协议。Subgraph 就是该协议的核心组成,Graph Node 则是其运行环境。
智能合约本身还缺乏自动执行的机制。Web2 应用因为有定时器,所以很多任务都可以用定时器自动执行。但智能合约却没有定时器,因此有些任务就没法自动执行,就需要外部程序来触发执行这些任务。这样的外部程序就被称为 Keeper,比如执行 Compound 的清算任务的 liquidator 就是一种 Keeper。大部分 Keeper 都是链下中心化的程序,而近两年来,也陆续出现了一些去中心化的 Keeper 网络,比较知名的有 keep3r.network、Chainlink Keepers、KeeperDAO。
也同样是因为智能合约本身的限制,无法像 Web2 应用一样主动向外部程序发起网络请求获取数据。否则,如果智能合约可以向 Coinbase、Binance 等中心化交易所发送网络请求获取价格数据的话,那不同节点因为请求的时间不同,获取到的价格也不同,就无法达成一致了。但智能合约又的确需要获取价格信息,所以就有了预言机。预言机是链上与链下数据打通的桥梁。
持久层
持久层,顾名思义,就是负责数据持久化的一个层级,主要就是底层的各种 EVM 区块链以及去中心化存储系统。EVM 链就比较多了,现在大部分 DApp 都会选择多链部署,主流的区块链包括 Ethereum、BNB Chain、Polygon、Arbitrum、Optimism 等。
通常情况下,一套智能合约代码,并不是可以毫无任何改动就直接部署到多链的,有时候是需要做一点小修改才能适配兼容到不同的链。比如,在 Ethereum 上获取当前区块高度,合约代码就是用 block.number;而在 Arbitrum 上,使用 block.number 获取的是 Layer1(即 Ethereum)的区块高度,要获取 Arbitrum 网络自身的区块高度,则需要使用 IArbSys(100).arbBlockNumber() 来获取。
再比如,Layer2 里的区块时间并不会和 Layer1 的时间完全同步,即是说,当在 Layer2 里读取 block.timestamp 时,并不会和 Layer1 的 block.timestamp 保持同步,Layer2 的 block.timestamp 更新时间通常会比 Layer1 的慢很多,Arbitrum 的 block.timestamp 更新时间基本就为 1 分钟,就是说,在 Arbitrum 里读取 block.timestamp,差不多每隔 1 分钟才会有更新,因此,对于使用一些跟区块时间强相关的算法时,其实是会有影响的,比如在 Arbitrum 上使用 Uniswap 的 TWAP 价格预言机,就不太合适直接用。Optimism 也存在同样问题。
还有,不同链的出块时间不同,做一些跟出块时间有关联的计算时也需要调整。比如,Compound V2 的代码里,硬编码了一年内的区块总数,这是根据以太坊的平均出块时间提前计算的值。有很多复制了 Compound 代码的借贷项目,如果要部署到其他链的话,比如部署到 BNB Chain,那就需要根据 BNB Chain 的出块时间调整这个区块总数的值。
去中心化存储系统也是很重要的基础设施,很多 NFT 的元数据就是存在去中心化存储系统里,内容发布平台 Mirror 里的文章也是存储在去中心化存储系统里。目前主流的去中心化存储系统就是 IPFS 和 Arweave,不过也有不少项目把元数据和媒体文件直接存储在中心化服务器里。
在以太坊上,IPFS 是最受欢迎的 NFT 元数据存储系统,大概有一半左右的 NFT 元数据是存储在 IPFS 上的。不过,IPFS 节点其实有一个”收集垃圾“的过程,所以存储在 IPFS 节点上的文件可能会被删除,除非它们被“钉住(pinning)”了。所谓钉住一个文件,就是要求“垃圾收集器”不要删除这个文件。很多项目常用 Pinata 这样的 pinning 服务来钉住一个文件,但免费服务提供的存储容量很有限,收费服务则需要不停续费才能维持文件的长时间存储。另外,如果 pinning 服务提供商有一天倒闭了,那对应的数据链接也将会失效。Filecoin 会好些,不会有倒闭风险,但提供的存储服务依然是有限定时间的,一旦到期,文件就会从 Filecoin 里被删除。
Arweave 采用了不一样的解决方案,用户只需支付一次性费用,来支付 200 年的存储成本。即是说文件可以存储 200 年,这就大大提高了文件存储的持久性。也因此,越多越多新项目选择了 Arweave 作为存储方案,包括 Mirror,也是将文章存储在了 Arweave。Solana、Polygon 上的很多项目也是选择了 Arweave 作为存储方案。
最后,还有一个新型的去中心化存储系统叫 EthStorage,这是一个基于以太坊的 Layer2 存储链,其最主要的特征是支持动态数据的存储,对于在去中心化的社交、电商、IM 等应用场景将可能发挥重大作用。