导语 | 是什么让 Web 3.0 与 Web 2.0 如此不同?本文使用以太坊生态作为例子,从 Web 2.0 开始,由浅入深,一步步介绍 Web 3.0 的应用架构。
(一)Web 3.0 应用(即DApps)的架构与 Web 2.0 完全不同
以一个简单的博客网站Medium为例,用户可以在这里发布他们自己的内容并与其他用户的内容互动。
作为一个 Web 2.0 应用,可能听起来会很简单,但仍有以下这么多特性构成了Medium的架构,才使得一切成为可能:
- 首先,必须有一个地方用于存储重要的数据,比如用户信息、帖子、标签、评论、点赞等等,这需要一个不断更新的数据库。
- 其次,后端代码(使用Node.js、Java或Python等语言编写的)必须定义Medium的业务逻辑。比如当一个新用户注册时、发布一条新博客时 或 在其他人的博客上评论时,分别会发生什么。
- 第三,前端代码(通常用JavaScript、HTML和CSS编写)必须定义Medium的UI逻辑。例如,网页长什么样,用户跟页面上的每个元素交互时会发生什么。
把这些串联在一起,当你在Medium上写博客文章时,你跟它的前端交互,前端跟后端通信,后端跟数据库打交道。所有的这些代码都托管在中心化的服务器上并通过一个网络浏览器发送给用户(译者注:前端代码会通过网络发送到前端并在浏览器渲染,后端以接口形式对前端提供服务)。这是对现在大多数的 Web 2.0 应用如何运作的一个比较好的顶层抽象总结。
但所有这些都在变化之中。
区块链技术解锁了一个激动人心的Web 3.0应用新方向。在本文中,我们将聚焦于以太坊区块链给我们带来了什么。
(二)是什么让 Web 3.0 如此不同?
不像Medium这样的Web 2.0应用,Web 3.0消除了中间人,没有中心化的数据库存储应用状态,也没有中心化的Web服务器承载后端逻辑。
取而代之的是,你可以利用区块链,在一个由互联网上匿名节点维护的去中心化的状态机上构建应用。
这里的“状态机”,指的是维护某个状态的机器,包括某个给定的程序状态和该机器上未来允许的状态。区块链就是由某个“创世”状态实例化,并有着非常严格的状态转移规则(即共识机制)的状态机。
更好的是,没有一个单一实体可以控制这个去中心化状态机,因为他是由网络中的每个人共同维护的。
那后端服务器呢?不像Medium的后端控制方式,在Web 3.0中,你可以编写定义了你的应用逻辑的智能合约,并将它们部署在去中心的状态机中。这意味着,每个想要构建区块链应用的人都在这个共享的状态机上部署他们的代码。
还有前端呢?几乎保持不变,除了一些我们后面将会介绍的特例之外。
现在架构大概长这个样子:
现在,我们一起更深入地探究是什么让这一切成为可能。
(一) 区块链
以太坊区块链被誉为“世界计算机”。这是因为他是由点对点节点网络维护的全球可访问、确定性的状态机。该状态机的状态改变受网络中的节点间遵循的共识规则所约束。所以,换句话说,跟字面意思一样,它确实是被设计为世界上任何人都可以访问和写入的状态机。这就使得该机器不由任何一个单一实体独有,而是由网络中的每个人共同拥有。
我们还要知道一件事:数据只能写入以太坊区块链——你永远无法更新现有数据。
(二) 智能合约
智能合约是以太坊区块链上运行的一个程序,它定义了区块链上发生的状态改变背后的逻辑。智能合约使用高级语言编写,比如Solidity或Vyper。
因为智能合约的代码在以太坊区块链上存储,所以每个人都可以检查网络上所有智能合约的应用逻辑。
(三) 以太坊虚拟机(Ethereum Virtual Machine, EVM)
再往下,就是以太坊虚拟机,用于执行智能合约中定义的逻辑,并处理在这个全球可访问的状态机上发生的状态改变。
EVM不理解像Solidity和Vyper这些用来编写智能合约的高级语言。取而代之的是,你必须把高级语言编译为EVM可以执行的字节码。
(四) 前端
最后,是前端。如前所述,前端定义了UI逻辑,但是它也跟智能合约中定义的应用逻辑进行通信。前端和智能合约之间的通信比上图中显示的要复杂一些,接下来我们仔细看看这部分。
我们希望前端可以与智能合约通信,这样才能调用函数,但是请回想一下,以太坊是一个去中心化的网络。以太坊网络中的每个节点都保存了以太坊状态机上所有状态的副本,包括与每个智能合约相关的代码和数据。当我们想要与区块链上的数据和代码进行交互时,我们需要与其中一个节点交互。这是因为任何节点都可以广播会在EVM中执行的交易请求,然后,矿工会执行该交易,并将状态改变传播到网络中的其他节点。
有两个方法广播一个新交易:
- 建立你自己的节点,该节点运行以太坊区块链软件
- 使用第三方服务提供的节点,如Infura,Alchemy和Quicknode
如果你使用第三方服务,则不必头痛于自己运行一个全节点。毕竟,在你自己的服务器上构建一个新的以太坊节点可能需要几天的时间。(有很多数据需要同步——这甚至会占有比典型笔记本电脑能正常处理的更多带宽和存储空间)
此外,随着Dapp的规模逐渐扩大,存储完整以太坊区块链的成本也会增加,并且你需要增加更多节点来扩展你的基础设施。这就是为什么,随着基础设施愈加复杂,你需要全职的DevOps,他们会帮你维护基础设施,以确保可靠的正常运行时间和快速响应时间。
可以说,避免这些头痛是许多DApp选择使用Infura或Alchemy之类的服务来管理他们的节点基础设施的原因。当然,这是一个权衡,因为这会产生一个中心化的卡点,但是我们暂时先不讨论这个难以摆脱的难题。;)
继续往前,我们来谈谈Providers(提供商)。当你需要与区块链交互时,你连接的节点(无论是你自己建立的还是使用第三方服务提供的现成节点)通常被称为“providers”。
每个以太坊客户端(即provider)实现了一个JSON-RPC规范。这确保了当前端应用想要与区块链交互时,可以有一套统一的方法。如果你需要JSON-RPC的入门介绍,简单来说,它是一个无状态的、轻量级的远程过程调用(Remote Procedure Call, RPC)协议,定义了一些数据结构及其处理规则,它跟传输协议无关,所以这个概念可以在进程内通信、socket通信、HTTP通信或各种消息传输环境中使用。它使用了JSON(RFC 4627)作为数据格式。
一旦你通过一个provider连接上了区块链,你就可以读取存储在链上的状态。但是如果你想要写入状态,在提交交易到区块链之前,你还需要做一件事——使用你的私钥对交易进行签名。
例如,想象一下我们有一个DApp,用户可以在上面阅读博客或发布博客到区块链上。前端可能会有一个按钮,允许任何人查询特定用户写的博客。(回想一下,从区块链读取数据不需要用户对交易签名。)但是,当用户想要发布一个新帖子到链上时,DApp会要求用户使用他们的私钥对交易进行“签名”——只有这样,Dapp才会把交易转发到区块链上。否则,节点不会接受这个交易。
说到签名,就是 Metamask 大展身手的时候了。
Metamask是一个工具,可让应用程序轻松处理密钥管理和交易签名。它非常简单:Metamask将用户的私钥存储在浏览器中,每当前端需要用户对交易签名时,它会调起Metamask。
Metamask也提供了与区块链的连接(作为一个provider),因为它已经与Infura提供的节点有连接(因为对交易签名时需要Infura(译者注:实际上签名的操作并不依赖Infura,只是Matamask在签名之前可能需要从Infura读取预估Gas等信息,另外,如果是通过Metamask来发送交易,Metamask自然也需要连接到Infura提供的节点))。这样,Metamask既是provider又是signer(签名者)。
当然,如果你构建一个应用,这个应用的所有智能合约和数据完全承载在以太坊区块链上,也是行得通的。但是,任何在以太坊上构建过应用的人都知道,把所有内容存储在区块链上真的很昂贵,(即便)也很敏捷。
请记住,使用以太坊,用户每次往区块链增加新数据时都要付费。这是因为在去中心化状态机上添加状态会增加维护该状态机的节点的成本。使用你的Dapp,每次用户的交易需要增加一个新状态时,都要求用户额外付费,这不是最佳的用户体验。一个减轻这种情况的方法是使用一个去中心化的链下存储解决方案,例如 IPFS 或 Swarm。
IPFS是一个用于存储和访问数据的分布式文件系统。因此,IPFS系统不是将数据存储在一个中心化的数据库,而是将数据分布并存储在一个点对点的网站中,这使得你可以在需要时轻松地获取数据。
IPFS还有一个称为“Filecoin”的激励层。激励层激励世界各地的节点存储和检索这些数据。你可以使用Infura(为你提供IPFS节点)或Pinata(为你提供易于使用的服务,你可以把你的文件“pin(固定)”到IPFS,获取IPFS哈希并将其存储在区块链上)之类的提供商。
Swarm的相似之处在于它是一个去中心化的存储网络,但有一个显著的区别。Filecoin是一个独立的系统,而Swarm的激励系统是在以太坊区块链中内置并通过智能合约强制执行的,用来存储和检索数据。
所以到现在为止,加上IPFS或Swarm,我们的应用架构如下所示:
敏锐的读者可能也在下面的图中注意到了,前端代码并没有存储在区块链上。我们可以像在Web 2.0中那样在AWS上托管此代码,但那样就给你的DApp制造了一个中心化的卡点。如果AWS宕机了怎么办?如果它审查你的应用怎么办?
这就是为什么,如果你想要构建一个真正去中心化的应用,你应该选择把你的前端托管在一个去中心化的存储方案,比如IPFS或Swarm。
所以现在应用架构看起来更像是这样:
到目前为止,我们讨论了如何通过签名并向区块链发送交易,从而向区块链写入数据。但是怎么从区块链智能合约读取数据呢?有两种主要的方法:
(一) 智能合约事件
你可以使用Web.js库来查询并监听智能合约事件。你可以监听特定的事件并指定一个每次触发事件时的回调。例如,如果你有一个智能合约,该合约发送一个从A到B的连续的支付流(在每个区块),那么你可以在每产生一笔向B的新支付时发布一个事件。你的前端代码可以监听该智能合约触发的事件,并基于此执行特定的行为。
(二) The Graph
上面的方法是有效的,但它有一些局限性。例如,如果你在部署一个智能合约之后才意识到你需要发布一个原先没包含的事件,该怎么办?不幸的是,你不得不重新部署一个新的包含该事件和数据的智能合约。此外,使用回调来处理各种UI逻辑很快会变得相当复杂。
这就要“The Graph”出场了。
The Graph是一个链下的索引解决方案,它使得在以太坊区块链上查询数据变得更加容易。The Graph允许你定义要索引哪些智能合约,要监听哪些事件和方法调用,以及如何将传入的事件转换为前端逻辑(或任何使用该API的对象)可以消费的实体。它使用许多前端工程师都喜欢的GraphQL作为查询语言,因为它比传统的REST APIs更具表达性。
通过索引区块链数据,The Graph让我们可以在应用逻辑中低延迟地查询链上数据。
现在,应用架构如下所示:
我们差不多快结束了,但是还剩一个主要的话题:扩容。
你可能已经有所耳闻,以太坊不具备扩展性——至少目前还没有。
以太坊的平均gas价格
平均交易手续费
平均区块大小
很明显,这会有一个问题。在有着高gas费和全量区块的以太坊上构建一个DApp导致了非常糟糕的用户体验。值得庆幸的是,有一些解决方案正在开发中。
一个流行的扩容解决方案是 Polygon,一个L2(layer 2,二层链)扩容方案。Polygon没有在主链上执行交易,而是使用“侧链”来处理并执行交易。侧链是与主链链接的二级区块链。每过一段时间,侧链将其最近区块的聚合提交给主链。
另外一些L2解决方案的例子有 Optimistic Rollups 和 zkRollups。这里的思路是类似的,在链下使用一个“rollup”智能合约把交易分批打包,然后定期地把这些交易提交到主链上。
值得我们吸收的思想是:L2解决方案在链下执行交易(即比较缓慢的部分),链上仅保存交易数据。这使得我们可以拓展区块链,因为我们不必在链上执行每笔交易。这也使得交易更快,更便宜——并且在必要时交易仍然可以与以太坊主链进行通信。
如果这一切让你感到头昏脑胀,那你不是孤身一人。把所有这些工具拼凑在一起非常复杂,会带来非常痛苦的开发体验。但是请放心——我们开始看到新的开发者框架,这些框架确实地改善了开发者的体验。
例如,Hardhat 就是一个开发者框架,使以太坊开发者更容易构建、部署和测试他们的智能合约。Hardhat 提供了“Hardhat网络”,开发者可以利用它把智能合约部署在一个本地网络上——而无需处理真实的网络环境。更好的是,它还提供了一个非常不错的 插件生态,使开发者更加轻松。Hardhat也提供了类似 javascript 的 console.log() 方法,用于调试。
当然,这只是一个开始。希望在将来我们能继续看到更好的开发者工具。
大多数人花了几个月的时间来弄清工具链的是如何工作的,所以如果你是一名新的DApp开发者,希望本文为你节省了一些时间。
Web 3.0 的核心是去中心化,这也是与 Web 2.0 最本质的差别。而去中心化,则要求由分布式网络来维护系统状态,并且分布式网络需要由不同利益方维护的节点构成。
另外,一个可以执行自定义逻辑的通用虚拟机也是非常重要的,这使得各式各样的应用在 Web 3.0 生态中百花齐放成为可能。我们把可以在这个虚拟机上执行的代码叫智能合约。
在应用层面,前端在调用智能合约时,需要与网络中的其中一个节点进行通信,最符合去中心化的做法是应用自己部署一个区块链节点,成为去中心化网络中的一员,但这无疑加重了开发人员的工作量。另一个更轻量的方式是选择一个第三方供应商提供的节点,如Infura等。但后者有中心化的风险,所以需要根据实际情况做权衡。无论是以上哪种情况,被连接的节点,都称为Provider,每一个Provider都需要实现一套标准接口(Ethereum JSON-RPC Specification),用于读写区块链数据。
对于应用的用户管理,稍不同于 Web 2.0 的中心化存储用户信息和密钥,区块链上的每个用户使用一个地址唯一标示,每个地址对应一个私钥。想使用某个地址作为交易的发送方时,都需要使用对应的私钥进行签名。所以只要拥有某个地址的私钥,就可以在不同的应用上,使用该地址发送交易。但假如我们有10个应用,是不是需要在使用每个应用时,都输入我们的私钥呢?暂且不讨论安全性,用户使用的便捷性已经成为了一个问题,毕竟,可能每个应用都是使用同一个私钥,重复输入只会消耗用户的耐心。那有没有一个统一的方法来管理私钥呢?这就是类似Metamask这样的钱包做的事情,用户只需要在钱包输入一次私钥(实际操作可能是导入助记符),每个应用在需要使用私钥签名时,都会调起钱包。另外,钱包本身也有一些简单的功能,比如显示用户余额、转账等。
在数据存储方面,由于区块链上存储数据需要全网共同维护,非常昂贵,所以可以选择把部分数据存储在IPFS/SWARM等链下去中心化存储中,这些存储使用激励等方式来保证数据的安全性。
在数据查询方面,可以直接调用Provider的读接口直接读取区块、交易、合约数据和事件等,但这相比传统的中心化数据库有非常大的弊端,就是缺少索引,无法快速地读取到想要的数据。所以The Graph这样的链下索引服务也就应运而生,应用可以使用它更方便快捷地索引到想要的数据。
区块链经常被诟病的就是性能问题。已经有一些 Layer 2 方案陆续被提出并部分已付诸实践,通过交易在二层链执行,主链仅记录压缩的交易数据,可以在避开主链昂贵手续费和较大延迟的同时,又能享受主链带来的安全性保障。另外,以太坊计划在2022年将共识算法从POW升级到POS,这也会是性能优化的一个非常重要的里程碑。
最后,对于开发人员来说,可以使用Hardhat、Truffle、Remix等工具,来提高DApp应用开发的效率。
如果有任何疑问或发现文中的任何错误,欢迎评论交流!
记录下原文中几个有意思的短语:
- rabbit hole: 用于表示一个离奇的、令人迷惑的或者荒诞的情况或环境,通常难以从中解脱。
- take-home: take sth. home 可以表示吸收/记住会议上的想法、观点或概念。文中使用“-”连接两个单词使其成为形容词,可理解为“值得吸收的”。
本文译自:The Architecture of a Web 3.0 application
推荐阅读