查看原文
其他

引介 | 无状态客户端概念

Vitalik 以太坊爱好者 2019-01-23


我们首先来看一种数学概念上的协议转换,理论上它可以适用于很多种协议。假设我们使用状态迁移术语,STF(S, B) ->S’,其中 S 和 S’ 是状态,B 是一个区块(或者是一笔交易 T),STF是状态迁移函数。然后我们就可以开始转换:

S -> S 的状态树根(也就是包含了 S 的梅克尔.帕特里夏树的 32 字节根哈希)

B -> (B, W),其中 W 是“证据”——一组梅克尔树的分支,可以证明某个程序执行 B 所访问过的所有数据的有效性

STF -> STF’,它接受一个状态树根以及一个区块加上证据作为输入,当正在执行的区块需要读取账户、存储键值或其他状态数据时,将证据作为这些数据的“数据库”(如果证据数据中不包含所请求数据的一些片段,则产生一个错误),最后输出新的状态树根。

这就是说,全节点只存储状态树根,打包区块与梅克尔分支(“证据”)则是矿工责任,全节点应该下载并验证这些扩展区块。无状态全节点以及常规全节点在网络中共存是完全有可能的;可以让获得区块 B 的转换节点,附上验证该区块所需的证据,在无状态节点所在的网络协议上广播 (B, W) 消息;如果有矿工在无状态网络上挖到一个区块,那么其附带的证据将被直接去除,然后将该区块在常规网络上重新广播。

在现有协议上实现证据的最简单的方法就是将其视为一个由 RLP 编码的对象列表,该列表可以被客户端解析为形如 {sha3(x): x} 的键值对;这种映射可以被简单的当做“数据库”插入到现在的以太坊实现中。

将上述想法应用到现在的以太坊存在的一个局限是,系统仍然需要矿工是状态存储全节点。我们可以想象这样一个系统——交易发起者需要存储完整的状态树(即使只需要存储与他们相关的部分),矿工也是无状态的,但问题是对以太坊状态存储访问是动态的。例如,我们可以假设一个形如 getcodesize(sha3(sha3(...sha3(x)...)) % 2**160) 的智能合约代码,其中有数千个 sha3 运算。这需要消耗数百万 gas 的计算来访问一个未知的账户上的代码。因此,交易发起者可以发起一笔仅包含少数账户证据的交易,随后进行大量的计算,而最终只是尝试访问没有提供相关证据的账户。这也就等同于 DAO soft fork 漏洞

一种解决方法是要求交易包含它可以访问的账户的静态列表,就像 EIP648 但更加严格,因为它需要的是精确枚举而不是一个范围。但这会产生另一个问题:当交易通过网络传播时,交易涉及的账户状态以及作为证据提供的梅克尔树分支可能与交易创建时的数据不一致。为了解决这个问题,我们将证据放在交易的签名数据之外,并允许打包交易的矿工在打包交易之前根据需要调整这些证据。如果矿工们保存了过去 24 小时内产生的所有新状态树节点的数据,那么它们就具备更新过去 24 小时内发布的任意交易的梅克尔树分支所需要的全部信息。

该设计有如下优点:

  1. 一般来说,矿工和全节点不需要存储任何状态信息。这使得“快速同步”会快很多(可能只需要几秒钟)。

  2. 所有关于状态存储经济学的棘手问题都会消失,比如像租金这样的设计需求(例如:EIP35,EIP87,EIP88 )乃至现有的复杂的 SSTORE 消耗/返还方案,而区块链经济学则可以完全专注于带宽定价以及计算,这就简单多了。

  3. 磁盘输入输出不再是矿工和全节点的问题了。磁盘输入输出一直是以太坊 DoS 弱点的主要来源,即使到现在,它还是最简单的 DoS 可用向量。

  4. 要求交易指定账户列表的需求偶然地增加了高度的可并行性;这在很多方面都是 EIP 648 的强化版。

  5. 即使对于存储状态的客户端,有了账户列表也允许客户端从磁盘预提取存储数据,这可以是并行的,从而大大减少了 DoS 攻击的风险。

  6. 在分片化的区块链中,通过频繁地在分片之间进行混洗(reshuffle),安全性也将得以提高;客户端之间混洗越快,在 BFT 模型中就越安全。不过,在保存状态的客户端模式下,混洗需要客户端下载它们正在混洗的新分片的全状态。而在无状态客户端中,这个成本将降为零,允许客户端在它们产生的每个区块之间进行混洗。

不过该方案也引入了一个问题,那就是:谁来存储状态?以太坊的主要优势之一就是平台的易用性,且用户无需关注如存储私有状态这样的细节。因此,要使该方案运作良好,我们必须复制类似的用户体验。以下是关于如何做到这一点的一个混合方案:

  1. 所有被创建或者使用的新的状态树对象都默认由全节点存储 3 个月。这些数据大约有 2.5 GB,这就像是网络自愿提供的“福利存储”。我们知道该级别的服务绝对能自愿实现,因为目前的轻客户端就是依赖于利他主义的。三个月后,客户端将随机丢弃数据,因此最后使用时间在 12 个月之前的状态树对象将仍被 25% 的节点存储着,最后使用时间在 60 个月之前的状态树对象将仍被 5% 的节点存储着。客户端可以尝试使用当前的轻客户端协议请求这些对象。

  2. 希望确保特定数据片段的更长可用性的用户通过在状态通道中支付使用费来达到目的。客户端可以与收费的存档节点建立通道,并在通道中进行有条件的支付,比如以这种形式:“我放弃这 0.0001 美元,在默认情况下,这笔支付将永远消失。然而,如果你能提供附带哈希值 H 的对象,我对其进行签名后,这 0.0001 美元将属于你”。这是一个可信的承诺,保证未来有可能为那个对象解锁资金,而存档节点则可以加入到数百万的此类协议中,并等待数据请求变为收入来源。

  3. 我们希望 dApp 开发者们能让他们的用户在浏览器本地存储中随机存储部分与他们的 dApp 相关的存储键值。这甚至可以在 web3 API 中轻松实现。

事实上,我们预计直到引入分片、总状态数据大小超过 1-10 TB 时,那些一直存储所有内容的“存档节点”的数量都足以为整个网络提供服务;因此,以上设计甚至可能并不需要。

相关内容讨论链接:

  • https://github.com/ethereum/sharding/blob/develop/docs/account_redesign_eip.md

  • https://github.com/ethereum/EIPs/issues/726




下面是两条回复

jannikluhn

美妙的想法。但是存在一个明显的问题,那就是交易大小的显著增加。

实际上,所有交易的集合将存储全部的状态,但是对于以梅克尔树分枝的形式保存的每个可访问的值而言,这种存储是非常低效的。尽管我是不是可以认为只有全节点的带宽会受到影响(因此交易大小并不重要)?

我们希望 dapp 开发者人员能让他们的用户在浏览器本地存储中随机存储部分与他们的 dapp 相关的存储键值。

它们不是必须是全节点吗?为了给甚至是单个状态创建梅克尔树分支,它们需要跟踪所有状态更新,因为每个更新都会改变状态树根。

如果矿工们保存了过去 24 小时内产生的所有新状态树节点的数据。

难道这不能减少到几分钟吗?因为:只有在创建交易以及将交易打包进区块这段时间内发生的状态变化是相关的。因此,用跟踪状态更新的平均确认时间加上一段安全时间范围应该就足够了。

vbuterin

尽管我是不是可以认为只有全节点的带宽会受到影响(因此交易大小并不重要)?

如果你所说的“只有全节点”是指“不是轻节点”,那就是对的。轻节点当然还是需要一直下载梅克尔树分支以获取他们所要访问的所有内容,但这对于他们来说是现状。还需要注意的是,在无状态客户端范式中,一个节点可以在“全节点”模式和“轻节点”模式间任意切换。

它们不是必须是全节点吗?为了给甚至是单个状态创建梅克尔树分支,它们需要跟踪所有状态更新,因为每个更新都会改变状态根。

并不是必须的。如果一个无状态节点下载了一个区块,其中修改了账户 C 的状态,那么这个区块的证据将包含 C 的梅克尔树分支,因此该节点可以存储这个分支而没有该状态的其他部分。同样,节点也可以充当轻客户端,并从网络中下载它们需要的任何状态分支。

难道这不能减少到几分钟吗?因为:只有在创建交易以及将交易打包进区块这段时间内发生的状态变化是相关的。因此,用跟踪状态更新的平均确认时间加上一段安全时间范围应该就足够了。

是的。但是由于一些低费用的交易会延迟很多小时,例如在 ICO 期间,所以我认为 24 小时是一个安全的时间窗口。




原文链接: https://ethresear.ch/t/the-stateless-client-concept/172 

作者: Vitalik 

翻译&校对: Aisling & 风静縠纹平




你可能还会喜欢:

引介 | 以太坊区块链的轻客户端入门
引介 | Cosmos 如何与 Ethereum 相连?
干货 | STARKs, Part I: 多项式证明


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存