本文深入介绍了Tendermint轻客户端的实现,包括其工作原理、算法和在Go中的实现。文章分为三个部分:轻客户端的定义及其必要性,核心原则与算法,及如何使用轻客户端。通过对算法的详尽探讨,文章提供了有关如何保证轻客户端的安全性及如何在网络中同步的深刻见解。
Tendermint 的下一个版本引入了 Tendermint light client 的新实现,在这篇三部分的文章中,我们将告诉你关于它的一切。
在第一部分中,我们将讨论 light client 的定义及其必要性。第二部分将引导我们通过使用的核心原则和算法。在最后的第三部分中,我们将涉及 Go 的实现。
light client 是全节点的轻量替代品(因此得名)。全节点由于执行交易和验证结果(以及进行许多其他操作)通常消耗大量资源。相反,light client 的资源需求低,因为它们只验证结果(而不执行交易)。全节点通常存储大量数据(区块、交易结果等)。light client 只存储最新的几个头部信息。
light client 是连接到全节点的客户端,请求新的区块头,并验证这些头部信息是否可信。
light client 的概念在比特币白皮书中首次提出。在第 8 章,Satoshi 描述了一种“简化支付验证”方法。SPV(“轻量”)客户端连接到全节点并下载新的头部信息。只要这些头部属于最长链,则可以信任这些头部。
如果你想更深入地了解这个术语和用例,可以查看 这篇 Parity Technologies 的优秀文章。
light client 的存在为新网络节点的安全和高效状态同步以及跨链通信(或简称 IBC;即一个链实例的 light client 运行在另一个链的状态机中)奠定了基础。要了解更多有关 IBC 的信息,请访问 https://cosmos.network/ibc。
那么我们如何确保从全节点获得的第一个头部信息是可信的呢?记住,我们除了共识算法和创世信息(genesis.json 文件和创世区块 #1)没有任何先前知识。
我们可以获取创世头信息并尝试从中同步到最新状态,但这将是:
X
数量的代币放入一个特殊账户(进行质押/绑定)。如果它进行了不当行为,其质押将被削减。当它决定取出其代币时,它向网络发出了解除绑定的信号。在一段时间后(解除绑定窗口),它会取回代币。相反,我们从一个可信来源获取不早于一个解除绑定窗口之前的头部并声明我们信任它。这被称为“弱主观性”。
\\ — 减去可配置的证据提交同步边界
我们从全节点获得的第一个头部信息必须在解除绑定窗口内,因为我们希望能够惩罚一旦给我们恶意构造的头部的验证者。否则,如果没有解除绑定期,验证者(或一组验证者)可以构造一个坏的头部并立即解除绑定。
可信来源 可以是验证者、朋友或安全的网站。一种更用户友好的解决方案是建立一个基于 HTTPS 的协议,使用默认端点来填充这些信息。此外,未来在链上的信任根注册(例如,在 Cosmos Hub 上)似乎很可能出现。(如果你有想法来加强这里的安全模型,请在 GH#4422 上发表评论。)
另外,如果你想深入研究,可以阅读 Vitalik 的文章 Proof of Stake: How I Learned to Love Weak Subjectivity。
当新的 light client 连接到网络时,或者当一个离线超过解除绑定期的 light client 连接到网络时,它必须提供以下选项:
trust_options.go – Medium
typeTrustOptionsstruct { | |
// tp: 可信期。 | |
// | |
// 应该显著少于解除绑定期(例如,解除绑定 | |
// 期 = 3 周,可信期 = 2 周)。 | |
// | |
// 更具体地说,可信期 + 检查头部所需的时间 + | |
// 报告和惩罚不当行为所需的时间应小于解除绑定 | |
// 期。 | |
Period time.Duration | |
// 头部的高度和哈希必须提供以强制信任特定头部。 | |
Height int64 | |
Hash []byte | |
} |
view raw trust_options.go hosted with ❤ by GitHub
没有两种方法可以从可信头部同步 light client 到最新状态:顺序验证和跳过验证。让我们详细看一下这两种方法。
正如你可能已经猜到的,顺序验证依次验证头部。从 trusted header(height: H
)开始,下载下一个头部(height: H+1
)并应用Tendermint 验证规则。一直进行到最新的头部(height: H+N
)。
图片 1
假设我们有四个验证者:A、B、C 和 D,所有验证者的质押相等 — 总供应量的 25%。如果头部 #2 的签名来自总供应量的 2/3 以上(约 66%),则可以信任它。A、B 和 C 占 75% 的总供应量,因此头部 #2 是可以的。
请注意,验证者集合在两个区块(#2 和 #3 之间)间可以 100% 更改。
只要 header2.NextValidatorSetHash == header3.ValidatorSetHash
并且新验证者集合中的 2/3 以上签署了头部 #3,它就可以被信任。
尽管其简单性,顺序验证头部的速度较慢(由于签名验证)且需要下载所有中间头部。
跳过验证是一种带宽和计算密集度较低的机制,在最乐观的情况下,light client 只需下载两个区块头信息即可同步。这是 Go light client 的默认方法。
算法按以下方式进行:light client 获取最新的头部。如果来自可信头部的验证者在最新头部的质押中占据了超过 1/3,则将头部标记为已验证 ***。
\\*\ — 总是使用顺序验证相邻头部
图片 2
如果失败,则执行以下二分算法:
light client 尝试下载最新高度与可信高度之间的中点区块,并尝试上述相同算法。在失败的情况下,递归执行中点验证直到成功,然后以更新的验证者集合和可信头部重新开始。
图片 3
Tendermint 失败模型假设在任何时候恶意验证者不超过 1/3。通过要求 1/3 以上的质押来自可信验证者,light client 确保新的头部中至少存在一个正确的验证者。
如果 Tendermint 失败模型不成立,并且在任何时间恶意验证者拥有 1/3 以上的总质押,他们可以试图欺骗使用跳过验证的 light clients。这就是为什么light client 还要将每个新头部与见证者进行交叉检查。当有人试图欺骗 light client 时,若存在至少一个正确的见证者,则会检测到攻击并报告给所有连接的全节点。
当从见证者接收到的头部与新头部不匹配,并且它们都有 1/3 以上的可信验证者时,有两种可能的选项:要么有人试图攻击这个 light client 要么确实在主链上发生了分叉。
然后,light client 将两个冲突的头部打包,发送到所有连接的全节点,并返回错误。
请参见 分叉问责规范 获取有关 light client 上攻击的列表。
当有人请求一个比最早可信头部更旧的头部时,light client 会执行反向验证。从最新的可信头部(height: H
)开始,下载前一个头部(height: H-1
)并检查 trustedHeader.LastBlockID.Hash == prevHeader.Hash
。
图片 4
假设我们从头部 #2 开始,但现在需要头部 #1 来验证某个过去的交易。light client 将通过 RPC 获取头部 #1,检查它的哈希与头部 #2 的 LastBlockID
匹配,并将其保存到可信存储中。
在发现可信存储中存在更旧的头部的情况下(例如,请求的头部 — #5,可信存储中的头部 — #1,最新的头部 — #10),light client 从存储中离请求头部最近的头部执行跳过验证。
虽然上述算法听起来复杂,但所有复杂性都隐藏并由实现处理。
要创建一个新的 light client,你需要五样东西:
client.go – Medium
// NewClient 返回一个新的 light client。如果获取主节点的头部和验证者失败或它们无效(例如,信任 | |
// 哈希与头部中的哈希不匹配),则返回错误。 | |
// | |
// Witnesses 是提供者,将用于交叉检查主节点的 | |
// 提供者。如果使用跳过验证,必须提供至少一个见证者(默认)。见证者可以在当前主节点 | |
// 不可用的情况下成为主节点。 | |
// | |
// 请参见所有选项以获取更多配置。 | |
func NewClient( | |
chainID string, | |
trustOptions TrustOptions, | |
primary provider.Provider, | |
witnesses []provider.Provider, | |
trustedStore store.Store, | |
options... Option) (*Client, error) { |
view raw client.go hosted with ❤ by GitHub
db, err := dbm.NewGoLevelDB("light-client-db", dbDir) | |
if err != nil { | |
// 处理错误 | |
} | |
c, err := NewHTTPClient( | |
chainID, | |
TrustOptions{ | |
Period: 504 * time.Hour, // 21 天 | |
Height: 273, | |
Hash: []byte("188F4F36CBCD2C91B57509BBF231C777E79B52EE3E0D90D06B1A25EB16E6E23D"), | |
}, | |
"http://primary:26657", | |
[]string{"http://witness1:26657"}, | |
dbs.New(db, ""), | |
) | |
if err != nil { | |
// 处理错误 | |
} |
view raw example.go hosted with ❤ by GitHub
还可以查看 NewClientFromTrustedStore, NewHTTPClient 和 NewHTTPClientFromTrustedStore 方法。
options 参数允许你调整 light client。例如,你可以通过提供 SequentialVerification()
切换到顺序验证。或者你可以设置日志记录器(Logger(l)
)。或者设置要在可信存储中存储多少个头部(PruningSize(10)
)。有关更多信息,请查看 Option 文档。
NewClient
将在高度 trustOptions.Height
获取头部,检查并将其保存到可信存储中。
如果你想将 light client 更新到最新状态,可以调用 Update(time.Now())
。
如果你想将 light client 更新到特定高度,可以调用 VerifyHeaderAtHeight(98, time.Now())
。
如果你已经有一个新的头部并想验证它,可以调用 VerifyHeader(newHeader, newVals, time.Now())
。
如果见证者或主节点没有响应,则在尝试次数达到一定数量后将被移除(请参见 MaxRetryAttempts 选项)。
Tendermint 配备了内置的 tendermint light
命令,可以用来运行 light client 代理服务器,验证 Tendermint RPC。所有可以通过证明追溯到区块头的调用将在返回给调用者之前进行验证。除此之外,它将呈现与完整 Tendermint 节点相同的接口。
$ tendermint light supernova -p tcp://233.123.0.140:26657 -w tcp://179.63.29.15:26657,tcp://144.165.223.135:26657 --height=10 --hash=37E9A6DD3FA25E83B22C18835401E8E56088D0D7ABC6FD99FCDC920DD76C1C57
有关其他选项的信息,请运行 tendermint light --help
。
—
如果你想与我们分享你到目前为止的体验,请在 聊天中 替我们留言!是否有我们应该关注的问题或潜在的改进?如果我们漏掉了任何内容,请留言。谢谢!
light client 的第一个版本是由 Ethan Frey 在 2018 年开发的。它有一个二分算法,尝试通过二分搜索找到验证者集投票权的变化小于 1/3 的最少区块头数量。尽管这为我们的用户提供了某种工作方案,但理论上是不安全的。
因此,第二个版本的工作于 2019 年开始,首先从 规范 开始。该项目主要由 Informal Systems 的研究团队主导。Go 实现后来由 Anton Kaliaev 和 Callum Waters 开发。
在比特币白皮书中,Satoshi 写道:
因此,只要诚实的节点控制了网络,验证就是可靠的,但如果网络被攻击者压制,便更容易受到攻击。虽然网络节点可以为自己验证事务,但攻击者的伪造事务在攻击者能够持续压制网络的期间内,可以欺骗简化的方法。防止这类攻击的一种策略是,当网络节点发现一个无效的区块时,接受来自网络节点的警报,促使用户的软件下载完整的区块和被警报的事务以确认这些不一致性。
在 2018 年 9 月发布的论文 “Fraud and Data Availability Proofs: Maximising Light Client Security and Scaling Blockchains with Dishonest Majorities” 中,描述了如何实现“警报”(欺诈证明)来削弱诚实多数的假设并改善 light client 的安全性。
目前 Tendermint Light Client 没有欺诈证明(或者数据可用性证明)。它仅依靠至少有一个诚实见证者,这将告知它主节点是否作弊。为了监控欺诈证明的网络,已经有一个提案 (GH#4873)。
我要感谢 Zaki Manian、Ethan Buchman、Zarko Milosevic、Josef Widder、Anca Zamfir 的贡献!大家的工作真是太棒了!🙌
感谢 Erik Grinaker、Aleksandr Bezobchuk 和 Tess Rinearson 对本文的审阅!
- 原文链接: medium.com/tendermint/ev...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!