深入解析 PolkaVM:了解 Polkadot 2.0 的绝佳路径
2025-01-15 18:11
从波卡发布的 2025 路线图来看,PolkaVM 无疑是最重要的技术更新之一,而最先部署和应用的一个就是对 Solidity 的支持,在 Westend 的 Asset Hub 上。大家首先需要了解的是这个实现不是之前的 Frontier,它被广泛的应用在 Moonbeam 和其他支持 EVM 的平行链上,而是在新的基于 RISC-V 的 PVM 上运行 Solidity 代码。对于很多 DApp 和智能合约的开发者,这将是进入和了解 Polkadot 2.0 的绝佳路径。由于 Solidity 的开发者基数大,也会给 Polkadot 2.0 带来很多的开发者,让大家可以看到 PVM 的强大和高效的执行效率。我们先看下它的基本原理和技术架构,我们的智能合约解决方案包含以下组件:
这是执行智能合约的区块链模块。它像任何其他 pallet 一样添加了一堆外部函数和运行时 API。但是,它还添加了允许区块链处理以太坊风格交易的逻辑。这些特殊交易不会直接提交到链中。尽管这在理论上是可能的。源代码可以在此目录下查看:🔗 https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive pallet相反,用户(钱包、Dapps 等)连接到与区块链节点一起部署的代理服务器。此代理模拟以太坊 Json RPC,这意味着它将以太坊 JSON RPC 接口公开为服务器并作为网络客户端连接到节点。它将以太坊交易重新打包成一个特殊的可调度文件,同时保持有效载荷不变。解码以太坊交易并将其转换为 pallet-revive 可以理解的内容取决于上述逻辑。通过将以太坊交易的有效载荷逐字提交给区块,我们可以轻松调整不需要处理不同交易格式的工具(例如区块浏览器)。选择使用独立代理是有意为之:向节点二进制文件添加新端点需要其他客户端来实现它们。这就是我们选择这种不需要对客户端进行任何更改的方法的原因。这是我们与竞争技术相比做出的最明显的改变。我们使用新的自定义虚拟机而不是使用 EVM 来执行合约。目前,我们在运行时本身中包含一个 PolkaVM 解释器。稍后的更新将提供在客户端内运行的完整 PolkaVM JIT。请注意,我们仍将保持解释器可用,以便我们可以为每个工作负载使用最合适的后端。例如,对于仅执行很少代码的合约调用,解释器仍然会更快,因为它可以立即开始执行代码(惰性解释)。需要了解细节的可以去查看项目的源代码:🔗 https://github.com/paritytech/polkavm
EVM 是一台堆栈机。这意味着函数的参数在无限堆栈上传递。PolkaVM 基于 RISC-V,这是一种寄存器机。这意味着它在一组有限的寄存器中传递参数。这样做的主要好处是,由于这些都是寄存器机,因此它使转换到底层硬件的步骤更加高效。我们仔细选择了寄存器的数量,使它们比臭名昭著的寄存器匮乏的 x86-64 指令集要小。让我们将 NP-hard 寄存器分配问题简化为简单的 1 对 1 映射是 PolkaVM 快速编译时间的秘诀。EVM 使用 256 位的字长。这意味着每个算术运算都必须对这些大数字执行。这使得任何有意义的数字运算都非常慢,因为它必须转换为许多本机指令。PolkaVM 使用 64 位的字长,这是底层硬件本机支持的。也就是说,当通过 YUL(#Revive)转换 Solidity 合约时,我们仍然会得到 256 位算术,因为 YUL 太低级,无法自动转换整数类型。但是,完全可以用不同的语言编写合约并从 Solidity 无缝调用。我们设想一个系统,其中业务逻辑用 Solidity 编写,但底层架构用更快的语言编写,类似于 Python,其中大部分繁重的工作由 C 模块完成。为了在 PolkaVM 上运行 Solidity,我们需要将其编译为 RISC-V。为此,我们需要一个编译器。它的工作原理是使用原始 solc 编译器,然后将其中间表示 (YUL) 输出重新编译为 RISC-V。与实现完整的 Solidity 编译器相比,这样做的好处是任务要小得多。通过选择这种方法,我们支持 Solidity 及其所有不同版本的所有怪癖和怪异之处。项目的地址是 https://github.com/paritytech/revive,需要了解细节的可以去查看项目的源代码。Remix 是开发 Solidity 最流行的工具,它是基于网页的,让我们随时都可以开发,调试和部署合约。Polkadot 也提供链一个自己的版本,https://remix.polkadot.io,可以通过这个网站来访问。我们维护 REMIX 的一个分支的原因是,与原始版本相比,主要的变化是我们需要更改编译器,因此我们将 REMIX 更改为使用后端进行编译,而不是浏览器内的编译器。这是必要的,因为我们基于 LLVM 的 revive 对于浏览器来说太重了。了解完整个原理,我们可以动手实践,看下如何来完成合约的开发、部署和测试。我们可以参考这个文档: 🔗 https://contracts.polkadot.io/首先在 westend asset hub 中已经集成了 revive pallet,可以直接在上面开发和测试。当然你也可以搭建自己的本地测试环境,这样更容易查看后端日志,了解错误信息。1. 下载 polkadot sdk 的代码并编译 Kitchensink node当以上服务都启动之后,普通的 substrate 的服务会在 9944 端口,Eth 会使用 8545 端口。首先我们可以尝试 Solidity 合约,你可以直接使用 Remix 来开发。在 Environment 中,默认已经有 westend 的配置了,如果使用本地环境来测试,可以增加 customize 的配置。这样就可以通过和我们之前启动的 Eth RPC 服务交互。当然如果是开发一个复杂 Dapp 应用,可以使用其他框架。这里推荐大家使用 Typescript 和 Viem 来实验,我们可以参考 revive pallet 自带的测试代码:🔗 https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive/rpc/examples/js它还有使用 Rust 语言的版本,在和它平行的目录下。一个部署合约的简化版代码如下:import { createWalletClient, defineChain, Hex, http, parseEther, publicActions } from 'viem'
import { readFileSync } from 'node:fs'
import { walletClient } from './utils'
function getByteCode(name: string): Hex {
const bytecode = readFileSync(`pvm/${name}.polkavm`)
return `0x${Buffer.from(bytecode).toString('hex')}`
}
async function main() {
const piggyBankAbi = [] as const;
const piggyBankByteCode = getByteCode("piggyBank")
const wallet = await walletClient;
const hash = await wallet.deployContract({
abi: piggyBankAbi,
bytecode: getByteCode('piggyBank'),
})
const deployReceipt = await wallet.waitForTransactionReceipt({ hash })
const contractAddress = deployReceipt.contractAddress
const balance = await wallet.readContract({
address: contractAddress,
abi: piggyBankAbi,
functionName: 'getDeposit',
})
{
const { request } = await wallet.simulateContract({
account: wallet.account,
address: contractAddress,
abi: piggyBankAbi,
functionName: 'deposit',
value: parseEther('10'),
})
const hash = await wallet.writeContract(request)
const receipt = await wallet.waitForTransactionReceipt({ hash })
console.log(`Deposit receipt: ${receipt.status}`)
const balance = await wallet.readContract({
address: contractAddress,
abi: piggyBankAbi,
functionName: 'getDeposit',
})
}
}
main()
1. 首先我们初始化一个 wallet client。2. 如何使用 abi 和 bytecode 来部署一个智能合约,这里我们选择 examples 里面的一个简单合约,piggyBank。需要注意的是这里的 bytecode 是编译成 polkavm 的代码。除了使用 Solidity,我们也可以直接使用其他语言来写合约,然后编译成 polkavm 的代码然后直接部署。这里我们参考 https://github.com/paritytech/rust-contract-template。直接部署 polkaVM 合约,我们就不需要使用 Eth RPC 了。当合约编译好了之后,我们可以使用 revive 的 upload 方法将合约上传到链上。在调用成功之后,我们可以在浏览器的 event 里面找到 code 的 hash,比如在我的例子里面,hash 值是 0x9bef6d3f29397e4994d96657375674096379ba850c31f8c7b950a6f9c13a238d下面一步就是对合约实例化,调用 revive 的 Instantiate 方法。这里会碰到一个 account unmapped 错误。需要先调用 account map 的方法,才能部署合约和调用。我们来记录一些合约地址,它是一个标准的 Eth 地址,在这次部署中,合约地址是 0xce58c0af740d49e573998ce92c9147565604d321。最后我们调用 revive 的 call 方法,填入之前实例化的合约地址。这样我们就完成了一个 PolkaVM 合约的开发,部署和测试。当完成来基本的环境搭建,开发流程,之后就可以完成更加复杂的业务逻辑。总的说来,polkaVM 还处于早期,大家碰到了问题,或者希望帮助完成一些功能,修复 bug 都可以积极向这些 repo 提交 PR。相信随着 2025 年 polkadot 2.0 的功能逐步 delivery,polkaVM 的使用场景和使用范围会越发广阔,初创项目也可以提前做好技术储备,准备好在 JAM 上开发出安全高效的产品。OneBlock+ 作为区块链的人才聚集地,是全球领先的 Substrate 开发者社区。我们将提供专业的技术文章和开发课程,并组织研讨会、黑客松创业大赛等交流实践活动,从而帮助开发者掌握 Substrate 技术、深入探索 Web3 领域。同时,OneBlock+ 还为 Web3 优质项目提供技术指导、人才资源等多重创业支持,促使更多开发团队使用 Substrate 技术框架构建未来开放网络。Twitter: https://twitter.com/OneBlock_Medium: https://medium.com/@OneBlockplusTelegram: https://t.me/oneblock_devDiscord: https://discord.gg/fE8deY4UbPBilibili: https://space.bilibili.com/1650224419YouTube: https://www.youtube.com/channel/UCWo2r3wA6brw3ztr-JmzyXA
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。