惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Register - Security
The Register - Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security @ Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
H
Hacker News: Front Page
L
Lohrmann on Cybersecurity
T
Troy Hunt's Blog
T
Threat Research - Cisco Blogs
A
About on SuperTechFans
T
Threatpost
AWS News Blog
AWS News Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Tor Project blog
Google Online Security Blog
Google Online Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
W
WeLiveSecurity
博客园 - 叶小钗
K
Kaspersky official blog
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
Hugging Face - Blog
Hugging Face - Blog
M
MIT News - Artificial intelligence
Hacker News - Newest:
Hacker News - Newest: "LLM"
Engineering at Meta
Engineering at Meta
有赞技术团队
有赞技术团队
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
小众软件
小众软件
D
Docker
爱范儿
爱范儿
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net

smallyu的博客

为什么买的加密货币一定要去中心化 RISC-V 虚拟机无法代替 EVM 虚拟机 Polkadot 的 Existential Deposit 机制错在哪里 利息,套利,交易策略,金融市场 LayerZero 从来不挑战比特币的地位 默认参数引起的以太坊节点运行错误 硬件钱包与资产安全 预测市场比币圈更赌场 为什么去中心化的跨链桥不可能实现 我对于 AI 时代的答案 不要投资任何隐私币 区块链技术世界的三大真理 以太坊 AA 钱包的致命问题是什么 基于 AI 语义执行的 MCP 区块链的设计 一个集成 Geth 和 CometBFT 的兼容层 我的加密货币定投策略(二) DeFi 基础: 理解 AMM 定价机制 Go 语言 GMP 调度器的原理是什么 Web3 项目分析计划 对 Psyche Network 项目的分析 continuation 教程: 理解 CPS 如何开发一个比特币符文(Runes)协议 比特币脚本开发教程 我从王垠的计算机科学视频班学到了什么 区块链技术面试题(2025年版) Rust 语言容易让新手困惑的一个“过度优化” Solana 智能合约开发教程 (1) 尝试开发一个最小 EVM 虚拟机 基于 ZK 的链上身份系统设计 一个 Web3 打赏系统的设计 鼓吹 Cursor 的人技术能力都差 关于 Code Review 的礼节 假如启动一个新的以太坊 PoS 网络 发行加密货币的最好方式 所有 BFT 共识的区块链都是中心化的 对 2025 年区块链行业的预测 Restaking 项目的经济难题 如何看懂任意区块链项目的技术架构 为什么不要做以太坊的二次开发 为什么不要做智能合约开发和 DeFi 开发 我的加密货币定投策略(一) PoS 类型的区块链如何处理分叉 Ethereum Casper 为什么需要 EIP-7251 区块链中的 PBFT 不需要第二次投票 开发者的思维方式 发币的核心要点 炒币投资的小 tips 为什么说 PoW 比 PoS 更加去中心化 牛市对普通人来说意味着什么 如何健康地远程工作 为什么比特币不用概率加密函数 程序员的 “服从权威” 心理 区块链技术面试题 如何区分公有链和联盟链 对 Layer 2 项目创业想法的回复 对区块链共识机制的理解 Pebbling Game 鹅卵石游戏 PDP 文件证明的局限性 不要小瞧 ChatGPT 为什么炒币不是一个好主意 一种在区块链上生成随机数的机制 为什么以太坊的私钥计算不可逆 关于以太坊的私钥碰撞 “猜均值的2/3” dApp 游戏设计 Proof of Storage/Space/Replication 的区别 Proofs of Retrievability 文件证明的含义 对 S-PDP 文件证明的示例和解释 我的加密货币交易机器人 对区块链行业的见闻 随机确认块的共识机制 为什么要重视编程思想 对 Web 3.0 的理解 GitBook 好用吗? 一种区块链节点存储扩容的方式 一种基于“自我中心主义”的共识机制 链表常见算法题及解析 基于 Multi-Linked List 的区块链设想 理解哈希函数与序列化 联盟链比公有链差在哪儿 在 Dijkstra 算法中保存路径 为什么数字货币使用区块链是政治问题 区块链:下一代数字身份认证体系的基石 网页技术能实现 3D 建模吗? 给区块链一个定义 从 Erlang 开始了解 Actor 模型 一种侧边导航栏的交互方式 Rust 的 ownership 是什么? Haskell 中的 Monad 是什么? 浅析 Libra 背后的区块链技术 对区块链的理性认识 Rust 基础语法概述 基于 Java 的爬虫框架 WebCollector Kotlin:简化版的 Scala JavaScript 有关联数组吗? 主流编程语言的异常处理机制 Go 语言基本语法 Scala 语法基础 用 Scala 改写 Java 浅度实践 Python 获取海贼王更新信息 HTML5 音乐可视化
VRF + BFT 共识引起交易失败的问题
2022-09-02 · via smallyu的博客

2022-09-03

昨天遇到一个问题,是 Ontology 的节点不出块了。节点使用的是 VBFT 共识,网络互通没有隔离,也就没有分叉,报的错是提案(proposal)过来的块,和预期的块哈希(MerkleRoot)不一样。由于种种原因,昨天的问题没有深入去查,用比较暴力的方法先让网络恢复正常。不过提到 VBFT,我想到了以前公司的一些事情。

我是在之前的公司开始接触区块链的,当时的项目号称自研区块链,也是用 VBFT(VRF + BFT)的共识,不过在共识方面不那么说,叫成 UBFT 还是什么。

我猜测 VRF + BFT 的主意是 Ontology 提出来的,我之前的公司把这种创意抄了过来,模仿着实现了一下。说来讽刺。

主要想说的是之前公司由于对 VRF + BFT 不靠谱的实现,引起的一个隐藏的 bug。那件事情距今快 2 年了,不记得当时为什么没有写博客记录一下,可能是在忙着做 PPT?昨天遇到共识相关的问题,我想起有那么一回事,正好现在有时间有心情写一下。由于过去时间太长了,细节上可能有出入。

背景

先介绍一下那个项目的情况,主打的特点有几个。

一个是异构多链,含义是可以在同一个节点上包含多条异构的链。异构是指一条链可以使用不同的共识机制、基于不同数据库运行起来。多链是指多条链可以在同一个节点上运行,因为觉得一条链不够用,一条链就相当于传统业务里的一张数据表,多条链可以方便地进行数据拆分,在联盟链的场景下更好地支持业务。异构多链,可以理解为把以太坊不同 chain id 的链,用同一个二进制包启动了。现在的某条开源联盟链,还在拿从异构多链演变来的灵活装配作为一大特点呢,猜猜为什么。

再一个是多数据库的支持,同时支持很多关系型数据库和非关系型数据库,做法是针对各种数据库,写数据操作的中间层做适配。

还有就是共识机制方面,基于开源的 Tendermint 项目。Tendermint core 是一个对 BFT 类共识的实现,在那个基础上,做的改动是把轮询选择提案节点,用 VRF 函数,替换为随机选择提案节点。另外还增加了对分层共识的支持,也就是共识组的概念,每隔多少个块换一次共识组,换共识组的方式借鉴 BFT 的流程,保证换共识组过程的安全性。分层共识这个理念也不知道起源于哪儿,可能同时期的项目流行这个?

项目的这些特点都是在我接触之前就已经开发完成的,我也只是有所了解。

我当时遇到的问题是,如果向区块链发送一笔失败交易,节点会立即返回交易失败的结果,然后如果再次发送一笔失败交易,第二笔交易的结果将迟迟不返回,节点不出块了。接下来如果仍然是失败交易,第三笔、第四笔,都会是同样的现象。这个时候,如果发送一笔正确的交易,节点会立即返回结果,之后一切恢复正常。而且这种现象是概率性出现的,并不是每一次失败交易都会引起问题。

前提

首先是失败交易指合约返回执行结果为失败的交易。区块链系统的交易失败有两种,一种是交易不能被执行,另一种是交易能被执行,但是合约中返回了合约层面的失败。那个项目并没有严格区分这两种失败的类型,合约有权限返回交易层面的失败,这其实是有问题的设计。

不正确的交易,将会在提案的时候,被忽略掉,因为不正确交易没有必要记录在区块链上。加上项目对失败交易错误的处理,造成的现象就是,合约执行失败的交易,会被忽略掉。这是前提。

BFT 共识的基础,是投两轮票,最终确定一个块。不管是什么 BFT,在前面加什么字母,不管通过多么复杂的流程决定出哪个节点提案、如何提案,不管对共识的效率做什么优化,是并行提案还是流水线共识什么的,只要是 BFT 类的共识,都是投票两轮。对两个阶段的命名可能不一样,不管是用 proposal 还是 prepare 来描述,都是那样一个过程。

BFT 的流程,是先有一个节点生成一个块,然后把这个块发送给其他节点,如果超过 2/3 节点同意,会进行下一轮投票。第二轮投票如果超过 2/3 节点同意,这个块就算确认下来了。两轮投票是理解 BFT 共识的关键。至于为什么投票两轮就可以达到 3f + 1 的容错效果、为什么至少要两轮,我也不知道。

忽略掉失败交易的操作,是在检查交易的过程中完成的。项目里有两次检查,共识前检查和共识后检查。有一些交易是没办法在共识前进行检查的,比如在合约里进行的写数据库操作,如果共识前就写库了,然后共识失败了,数据不就乱套了吗。所以只能共识后进行检查。这是第二个前提。

第一笔失败交易

我们根据 bug 的现象分析一下,第一笔失败交易的流程是正常的。一笔失败交易进来,在共识之前是不会检查出失败的,所以预提案的节点正常提出了一个块,分发给其他节点,进行第一轮投票。之后正常进行第二轮投票,在确认块的阶段,写入块之前,会进行共识之后的检查,检查过程中发现交易失败,并且这个块只包含这一笔交易,这个块就作废了,没出块。同时,其他节点也都会返回消息告诉提案节点,这个块没出来,这笔交易失败了。所以第一笔失败交易是正常返回结果的。

第二笔失败交易进来,按照同样的处理流程,一切都应该是正常的才对。因为即使是失败交易,即使是在确认块的阶段,如果检查失败了,也会广播处理结果给其他节点。整个协议中投票失败或者落块失败,都是用空消息表示。其他节点不会因为交易失败,就收不到消息苦苦等待超时。那么既然 BFT 协议的流程没有问题,为什么还是出现 bug?

这个时候要提到项目在 VRF 方面的改造。

在 BFT 的协议中,是需要一个节点去生成一个块,分发给其他节点开始进行第一轮投票的。那么由哪个节点来进行这个生成块的操作呢?总不能是同一个节点吧,那就太中心化了。Tendermint 的做法是依次进行,比如有 4 个节点,第一次节点 A,第二次节点 B,这样轮询。

VRF(Verfiable Random Function)做的事情,是改变依次选择节点的方式。因为如果按照顺序来,那很容易预测到下一轮要由哪个节点去生成块,顺序可以预测之后,就存在节点被贿赂、节点被攻击等安全隐患。VRF 的功能是参数相同结果一定相同,参数不同则结果随机。把块高度、投票的轮数作为参数,就可以很好地实现,每一个块都能由随机的节点来生成,无法预测。这个改动也是作为项目的一个亮点的。

不过 VRF 存在一个问题,既然是随机的,那就有一定可能,第一次随机到节点 A ,第二次也随机到节点 A,这样的概率还是不小的。如果节点 A 是恶意节点,然后由节点 A 连续两次生成块,会给网络带来一些负担,虽然不至于破坏网络,但也是一点小小的麻烦。所以项目为了解决这个问题,在 VRF 的基础上加了黑名单的机制。

如果上一轮是节点 A 生成块,就把节点 A 放到黑名单里。如果 VRF 的结果在黑名单里,就再 VRF 一次,避免重复选择相同的节点。

第二笔失败交易

不返回结果

结合 VRF 和黑名单,再来看看第一笔失败交易发生了什么。节点 A 收到交易,会先把这笔交易广播给其他节点,然后打包成块进行投票的流程。此时节点 A 在黑名单里。投票失败后,节点 A 返回失败,并且这笔交易已经不在节点 A 的交易池里了,因为已经处理过了。

那么节点 B 呢?块里面的交易验证失败了,但是交易池里收到的交易还在,因为这笔交易还没有处理啊,处理的只是广播过来的块里面的交易。这个时候是不是应该把交易池里面的交易删掉?对,但是没删。所以造成一个问题,节点 B 被选作生成块的节点,把这笔交易打包了一下,广播了出去。这个块当然也是提案失败的。此时节点 A、节点 B 都在黑名单里。

以此类推,就这一笔交易,一轮下来,4 个节点全在 VRF 的黑名单里。但是对这笔交易结果的返回是没有影响的,因为交易结果在节点 A 的时候就已经返回了。

第二笔失败交易过来了,所有节点全在黑名单里,会发生什么?当然不能选不出节点,节点全在黑名单里,黑名单就失效了。VRF 的结果是哪个节点,就是哪个节点。

分析一下第二笔失败交易。同样是节点 A 收到交易,假如这一次是节点 B 负责生成块,然后这个块验证失败了,节点 B 就会删掉这笔交易,对吧,这个没问题。

注意,删掉交易的同时,通知客户端,交易失败了,返回交易结果。节点 B 被选中,节点 B 生成块,节点 B 返回通知。但提交这笔交易的客户端,连的是节点 A 啊!

第一笔失败交易为什么会收到响应?因为黑名单还没有失效,所有节点都处理了一遍交易,所有节点都返回了一遍交易结果。现在黑名单失效了,只有节点 B 会返回结果,所以节点 A 的客户端收不到交易结果。

那为什么黑名单失效,就不能像第一笔交易一样,所有节点都处理一遍?

阻塞后续交易

接着分析一下第二笔失败交易,节点 C 还有交易啊,节点 C 上面的这笔失败交易还没处理呢,节点 C 就开始用 VRF 选节点了。

刚才提到,此时黑名单失效,VRF 选出哪个就是哪个。刚才选出了节点 B,这一轮有没有可能再选一次节点 B?黑名单失效,就变得可能了。这个时候如果又是节点 B 负责生成块,会发生什么?

节点 B 生成不了块,因为节点 B 已经没有交易了,它已经把唯一的失败交易,在上一轮就删掉了。也就是说,在新一轮的共识过程中,4 个节点全部在等节点 B 生成块,节点 B 自己也知道该自己了,但是节点 B 拿不出块,节点 B 直接放弃这一轮共识,进入下一轮,并且节点 B 没有发出任何消息。

在分布式系统中,没有消息是一件可怕的事情。其他节点都在等节点 B 呢,节点 B 自己玩了。这个时候,节点 B 的轮数要比其他节点快一轮。

在 BFT 共识中,有两个索引值,一个是块高度,一个是共识的轮数。同一个块高度,有可能因为块没确认,就经过很多轮共识。由于节点 B 自己没生成块,轮数增加了,其他节点还不知道。

现在,所有节点都在 VRF 的黑名单里,节点 B 的共识轮数高于其他节点,

如果节点 A 再收到失败交易,有两种情况。一种情况是 VRF 又选中节点 B 了,节点 B 提出的块会被拒绝,因为其他节点还在等节点 B 上上轮的块,它拿出了高轮数的块,是对不上的。另一种情况是,VRF 选中了其他节点,那其他节点首先要等节点 B 上上轮的出块超时。超时之后,其他节点把轮数最高的数值同步一下,共识就算恢复正常了,然后 VRF 再选。

但是注意,这可是一笔失败交易,此时黑名单仍然失效,即使共识恢复正常,也还是有概率重蹈整个覆辙,节点 A 仍然收不到交易结果。至于具体的概率是多少,就懒得算了。

总结

可以看到,这个 bug 是由很多系统性的不合理设计共同造成的,直接原因在于 VRF 黑名单的失效,因为所有节点都在黑名单里了。或者说,黑名单没有及时清空,原先的错误之处在于,只有在块高度变化的时候才清空黑名单,可能是认为每个块的产生都应该由不同的节点来处理。这种想法的失误在于忽略了相同块高度的时候,共识的轮数也会发生变化,每一轮都会产生一个新的块。所以只要在共识轮数发生变化的时候,也清一下黑名单就好了。实际的代码改动只有两行。

上面写的东西,可能我自己也不想仔细去看,不好理解、抽象,而且文字的表达能力也弱,看起来费劲。这种类似状态机状态转换的文字描述,看起来是很痛苦的事情。尤其是内容和当时项目的耦合很深。总的来说,对于这个问题的分析和解决,我认为在逻辑上是自治的,能很好的解释成因和现象,以及用最简单的方式在表面上修复它。

时隔近 2 年的时间,我竟然还能记起来这些,感觉也是很奇怪。