Findora 优化其在 EVM 层的 TPS
编者注:本文是根据 Findora 的多位工程师的意见撰写的,并不代表一个人的努力。这是 Findora 优化其在 EVM 层的初步成果,不久还会有更多的优化。
介绍
Findora 区块链是 UTXO 和 EVM 分类账的组合,通过称为 Prism Transfer 的原子桥连接在一起。Findora 的目标是通过扩展以太坊隐私来创建一个基于区块链的内置隐私的新金融互联网。并通过先进的 ZK 密码学和 SNAKRS 来保护公链上的交易数据。
因此,虽然隐私是此协议的主要关注点,但可扩展性也必不可少——如果没有足够的带宽,网络就无法为多个生态系统提供隐私保护。
然而,虽然一些项目追求每秒 10,000+ 笔的交易速度,但现实情况是许多项目并不需要如此高的 TPS。绝大多数 TVL 存在于 Ethereum 和 Bitcoin 上,它们的 TPS 分别约为每秒 15 和 6 左右。
因此,高 TPS 是重要的但不是必要的,尤其是在刚开始。例如,Avalanche 的理论 TPS 为 4,500,但实际很少超过 9 TPS。
获取 Avalanche TPS 更多信息:https://www.cryptopolitan.com/speed-and-scalability-comparing-ethereum-solana-avalanche-cardano-and-the-internet-computer/#:~:text=Avalanche's%20current%20average%20time%20to,chains%2C%20is%20approximately%201.6094%20seconds.
在过去的两个月中,Findora 开发团队成功地将其 EVM 层的 TPS 提高了近 4 倍,达到了 150 左右——这足以承载它在未来将面临任何负荷。
大部分优化来自:
并行化 Tendermint ABCI(以前只有单线程)
使用读写锁代替唯一锁(互斥锁)
增强交易检查逻辑
优化冗余序列化(将块的交易列表放在 RAM 中,而不是数据库缓存中)
本文将介绍实现这些优化的科学过程,并重点介绍一些突出的优化领域。希望通过分享我们在过去 3 个月进行的优化分析、执行和测试的技术细节,使其他使用 EVM 的团队可以充分利用我们所做的工作,并复制和扩展我们的成果。
方法论概述
我们相信,测试和提高性能的最佳方法是使用科学的方法:测试环境、分析结果、部署修复程序,然后重复。因此,我们将优化过程分为 5 个步骤:
测试
测试结果收集
分析
结果分析
更新代码并再次部署
通过简化和去除冗余,我们缩短了交易时间,提高了效率。隐私交易比透明交易需要更多的计算能力。尽管网络必须能够承载足够的负荷才能在现实世界的应用中使用,并且可扩展性是一个关键目标,但 Findora 仍认为它的重要性次于隐私。
大部分 TPS 的优化来自快速累积的小改进。例如,团队通过优化序列化和反序列化过程,减少数据库读取和组合功能,提高了大约 10 TPS。
其他改进,例如改进 check_tx 函数和删除冗余内存分配,再次提高了 TPS。
潜在的优化领域
总共有 10 点我们认为可以改进。不过,大多数优化来自以下 6 点:
改进了主界面中的 deliver_tx 和 check_tx
减少了数据存储结构中的持久化操作的次数
优化需要持久数据调用的功能,减少调用次数
为 deliver_tx 和 check_tx 频繁调用的接口优化 Vault 性能
优化了日志记录流程
优化读写锁功能
以下四种方法没有多大成果。前三种是 Tendermint 的功能,我们团队对此无所进展。最后一种并不如预期的那样富有成效:
检查新交易的过程
Web3 PRC 服务器的处理功能
ABCI check_tx 的处理功能
将交易传输到验证器以及区块链上的所有节点的传输速度。
ABCI 的函数调用功能
优化依赖库的性能(这仍然是一个长期目标)
使用高性能替代库或接口
调整了库的编译选项
优化交易速度的工具
在确定哪些地方可以提高性能之前,我们使用了两个工具来测试网络性能。大多数优化进来自消除冗余。这两个工具是:
CLI 工具用于模拟交易环境的
pprof-rs 用于分析 ABCI 和 Findora 节点的 CPU 使用率
获取 pprof-rs 更多信息:https://github.com/tikv/pprof-rs
我们将详细介绍如何部署以及使用它们。
CLI 工具
我们编写了 CLI 工具来模拟客户端并方便测试。它允许我们进行交易、制作钱包、编写脚本等,并且让我们可以在基本不破坏网络的情况下进行压力测试。
代码:https://github.com/simonjiao/findora-agentd
通过 Prism 将原生 FRA 转换为智能 FRA,并发送到 root 账户
Root 账户将 FRA 发送到多个地址(源),并保存到文件
并行执行以下操作:
随机生成一批地址(targets),并指定一个 endpoint
获取 nonce 源
生成一个 EVM 转账交易,并通过 send_raw_transaction 发送到端点。保存哈希
客户端增加 nonce ,生成并提交交易,直到所有目标都发送一个交易。
如果交易失败的原因不是 “mempool full” 引起的,则会获得一个新的 nonce 并重新提交
为了减少服务器没有响应的影响,在 nonce 的接口中加入了服务弹性的逻辑
为了减少服务器的 “mempool full” 错误和服务器压力,增加了并行等待同步。
Pprof-rs 和 CPU 分析
“CPU Profiling” 是指我们用来测试和调优 Findora 网络性能的工具。我们提出了一个迭代过程,允许我们在不破坏网络的情况下对网络进行压力测试,以查看哪些功能可以优化。
用于进一步研究和找出 Findora 瓶颈的主要工具是 pprof-rs。Pprof-rs 是一种流行的测试 Rust 程序 CPU 使用率的方法。
获取 FinforaNetwork 更多信息:https://github.com/FindoraNetwork/platform
如何使用 Pprof-rs
我们在 abciapp crate 中导入了 pprof-rs,并启用了火焰图功能。pprof-rs 被编译成 abcid。有了这个,分析器可以按指定的频率对 adcid 中的操作上下文进行采样。
在 abcid 过程中,我们主要使用以下接口进行分析。
使用 ProfilerGuardBuilder 启用分析器,并将频率设置为 100。
将分析结果保存为火焰图。
因为abcid是一个持久进程,所以分析器可以通过以下方法停止:
Pprof-rs 的工作原理
分析器将按给定频率暂停程序,并采样程序的堆栈跟踪。采样数据存储在哈希图中。在采样中,分析器扫描每个堆栈帧,并累积存储在哈希图中的计数。
然后,采样数据可用于生成火焰图或其他形式来表示网络性能。
当 ProfileGaurdBuilder 启动分析器时,它将向 SIGPROF 注册一个信号处理程序和一个用于暂停主程序的计时器。当触发 SIGPRO 时,将调用处理程序对堆栈跟踪进行采样。这个过程由 backtrace crate 来执行。
使用 Pprof-rs 分析 Findora 全节点
首先,了解我们的区块链和 Tendermint 共识引擎的结构是很重要的。
在 ETH 全节点中,Tendermint 进程通过 socket 与 ABCI 进程通信。ABCI 应用程序默认注册以下接口:
Begin_block
Check_tx
Deliver_tx
End_block
Commit
它还为 Web3 RPC 服务器提供了以下接口,Web3 客户端可以通过这些接口执行测试:
eth_sendRawTransaction
https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction
eth_getTransactionCount
https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactioncount
eth_getTransactionReceipt
https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
该图显示了 ETH 全节点的基本工作流程。全节点是收集新交易并重放新交易的节点,是所有交易的入口和检查点。通过对全节点的 ABCI 进行剖析,我们可以获得关于链性能的完整数据。
在分析 abcid 之前,我们将 Tendermint 的 ABCI 并行化为两个线程,而不是单个线程。它允许全节点同时为新交易调用 check_tx 函数并重播新生成的块(主要是 Deliver_tx 函数)。此次升级后,全节点的 CPU 平均分配。因此,每个区块的交易数量在 3000 左右,减少了出块时间。
我们还测试了只有一笔交易的区块的时间成本。主要测试函数是:
begin_block
deliver_tx
commit
end_block
除 deliver_tx 函数外,其他函数仅在一个块中被调用一次。当一个区块中的交易较少时,调用这四个函数的计数很小很小而且很接近。
对于包含很多交易的区块,除了deliver_tx,其他三个函数的时间成本都很小。
根据修改和测试结果,我们得出结论:check_tx 和 deliver_tx 函数占用全节点的大部分 CPU。
为了得出这个结论,我们在每个块的开头(begin_block)启动分析器。然后我们保存分析火焰图,并在下一个块之前停止分析器,以免影响全节点的性能。为此,我们使用了两个全局变量:
原子布尔变量用于确定是启动还是停止分析器
一个变量用于存储正在运行的 profileGuard
pprof-rs 不提供停止分析器的接口。我们通过转移 ProfileGuard 的所有权来停止分析器并释放数据。
在停止分析器之前,可以将采样数据作为火焰图文件存储在分类账目录中。
出于测试目的,我们添加了两个 RPC。一个用于启用/禁用分析器。
另一个用于检索火焰图文件中生成的分析数据存储。
第 1 步:开始测试
我们的测试是由使用 CLI 工具调用 feth 的脚本来执行的。在选择一个全节点进行测试后,我们通过子命令 fund 将 FRA 转移到 2000 个测试账户:
feth fund — network http://dev-qa01-us-west-2-full-001-open.dev.findora.org:8545 — amount 2000 — redeposit — load — count 2000
然后,我们开始向全节点发送交易。每个账户的并行度、超时和交易计数都是可配置的。例如
feth — network http://dev-qa01-us-west-2-full-001-open.dev.findora.org:8545 — max-parallelism 300 — timeout 100 — count 10
第 2 步:收集测试结果
有四种方法可以收集测试数据并分析它们。
我们可以使用 Blockscout 手动监控测试结果和块。这个过程让我们可视化块来评估性能。
我们从 Web3 RPC 中获取性能数据。首先,我们可以使用接口 eth_getBlockByNumber 来获取目标块。然后,我们可以通过交易数组的长度得到实际的交易数。只能从此接口检索有效交易。对于区块时间,我们可以通过相邻块的时间戳之间的差异来计算它。
Tendermint RPC(从 tendermint RPC 查询区块信息):与Web3 RPC 类似,我们使用 curl、jq 等工具来检索交易数量和区块时间。这个 RPC 为我们提供了打包在块中的所有交易(有效/无效)的数量。注意:Web3 RPC 和 Tendermint RPC 有时都会出现“无响应”问题。
Tendermint 日志(提取区块时间戳,有效/无效 txns):为了更方便地检索、保存和分析测试数据,我们在 feth 中使用了子命令 etl。有了这个,该命令可以解析 fullnode 的日志,并将其保存到 redis 数据库中。如下图所示,出块时间、总交易数、有效交易都可以通过全节点重放出块过程中产生的 terdermint 日志进行解析。
第 3 步:分析测试结果
一旦我们得到测试结果,我们就会对结果进行分析或可视化,以便我们可以迭代代码。分析是其中一个步骤,我们可以发现是什么占用了 CPU 和时间,并寻找优化的方法。
我们在 feth 中添加了一个启用分析器的子命令。例如:
在下一个块中启用分析器
feth profiler — network http://dev-qa01-us-west-2-full-001-open.dev.findora.org:8669 –enable
生成火焰图并停止分析器
feth profiler — network http://dev-qa01-us-west-2-full-001-open.dev.findora.org:8669
这个火焰图展示了一个 CPU 函数所花费的时长。时间越长,函数就越长,因此通过查看长条图形,我们可以知道哪些过程需要优化。
第 4 步:重新部署代码
代码更新后,我们使用 Jenkins 将其部署到测试环境中。
为优化 Findora 所做的更改
根据我们的测试,以下是我们已经实施或将要实施的一些更改,以提高 Findora 的 EVM 层 TPS。
将内存池设置为 8k
通过测试,我们发现为了提高全节点的稳定性并确保生成块不会花费太长时间,内存池的最佳大小是 8,000。我们希望尽快更新主网上的内存池。
并行 Tendermint ABCI
并行化 Tendermint ABCI 以便可以同时执行 check_yx 和 Deliver_tx,这是我们发现的另一个可改进之处。这也有助于防止堵塞时间过长。但这并不能显著提高 TPS,因为交易是均匀分布的。
减少序列化/反序列化
通过结合 SDK 中打包的账户中的一些函数来减少数据库读取和反序列化。
通过这种优化,TPS 提高了大约 10 txn/s。
删除不必要的检查
函数 check_tx 和 Deliver_tx 使用相同的逻辑来处理交易,唯一的区别在于上下文。但是对于 check_tx 函数,它不需要 PendingTransactions、emit、events 等逻辑。因此,我们可以通过上下文将这两个函数分开。TPS 已提高到 79.2 txn/s。
重构 SessionCache
在之前的执行中,cur 和 base 之间用了大量的内存 copy/allocation.deallocation 来执行一个交易。
通过减少这些操作的次数,TPS 达到了 149 txn/s。
避免打印冗余日志
我们删除了数百个不必要的日志(我们称之为详细日志)。这些日志可以重新打印用于调试,但不会在主网上自动打印。
未来优化点
根据火焰图,我们在未来还可以做两件事。
Recover_signer 函数
secp256k1_ecdsa_recover 函数在 recover_signer 函数中占用了大量时间。这个 secp256k1_ecdsa_recover 函数的核心部分是 libsecp256k1:recover,它的 crate 接口花费的时间最多。
我们可以考虑优化这个库,用另一个高性能库替换它,或者减少调用。
适用于 EVM 的 Findora 后端
这部分占据了整个交易过程的很大一部分,但是我们仍然找不到优化的地方。未来我们可能需要对这部分进行更多的测试和分析。
结论
提高 TPS 是一个迭代过程,我们一直在寻找新的方法来扩容。虽然我们想要保持竞争力,但我们更愿意相信在 Web3 中合作才能使整个行业强大。因此,我们很乐意分享我们的优化过程,以帮助其他团队,并获得建设性的反馈。希望此次分享可以帮助到 EVM 环境中的其他团队,以便他们提高自己的 TPS。
Findora相关链接:
Website:https://findora.org Mainnet:https://mainnet.findora.org Medium:https://medium.com/findorafoundation Twitter:https://twitter.com/Findora Github:https://github.com/findoranetwork Findora Academy:https://medium.com/findorafoundation/tagged/academy