Polkadot Solidity开发-课程笔记4
问题E
WebAssembly 是二进制文件,java也需要编译。那么这两个二进制文件又有什么不同
你想知道 WebAssembly(Wasm)二进制文件与 Java 编译生成的二进制文件的核心差异,二者虽同为二进制格式,但在本质类型、执行模型、跨平台逻辑等方面存在根本性区别,我们从关键维度详细拆解:
一、先明确两者的「二进制文件」具体所指
首先要厘清两个核心概念,避免混淆:
- Java 对应的二进制文件:Java 源代码(.java)编译后生成的是 Java 字节码文件(.class 格式,本质是二进制文件);若打包为可分发格式,通常是 JAR 包(本质是 .class 文件的压缩归档,核心仍是 .class 字节码)。
- WebAssembly 对应的二进制文件:通常指 .wasm 格式文件,是 Wasm 标准定义的二进制字节码格式(也有对应的文本格式 .wat,可转换为 .wasm)。
二、核心本质与设计目标差异(最根本区别)
Java 字节码(.class)
它是专门为 Java 虚拟机(JVM)设计的专属字节码,核心设计目标是「支撑 Java 语言的跨平台运行+面向对象特性落地」,仅与 JVM 生态强绑定。它高度贴合 Java 语言的特性(如面向对象、垃圾回收、异常处理等),无法脱离 JVM 生态的设计逻辑独立使用。Wasm 二进制文件(.wasm)
它是通用型、无语言绑定的抽象虚拟机字节码,核心设计目标是「高性能跨平台执行+多语言兼容+安全沙箱」,并非为某一种语言量身定制。它的设计更偏向底层,接近硬件指令集但又脱离具体硬件架构,可作为 C/C++、Rust、Java、Go 等多种语言的编译目标。
三、执行方式与依赖环境的差异
Java 字节码(.class)的执行方式
依赖 Java 虚拟机(JVM) 作为唯一执行载体,执行流程分为两种(主流为混合模式):- 解释执行:JVM 中的解释器(如 HotSpot 解释器)逐行解析 .class 字节码,转换为当前 CPU 可识别的原生机器码后执行;
- 即时编译(JIT):JVM 中的 JIT 编译器(如 HotSpot 的 C1/C2 编译器)会将频繁执行的「热点代码」(如循环、高频调用方法)一次性编译为原生机器码,缓存后重复执行,兼顾启动速度和运行性能;
补充:Java 还有 AOT 编译(如 GraalVM Native Image),可直接将 .class/JAR 编译为原生 bin 文件,但这是额外编译流程,非 .class 字节码的原生执行方式。
Wasm 二进制文件(.wasm)的执行方式
依赖 Wasm 运行时环境(非专属虚拟机,种类更多样),执行流程同样有两种:- 解释执行:Wasm 运行时(如浏览器内置运行时、wasmtime、wasmer)逐行解析 .wasm 字节码,转换为原生机器码执行;
- 即时编译(JIT):主流 Wasm 运行时(如 Chrome/V8 内置运行时、Wasmer)优先采用 JIT 编译,将 .wasm 字节码一次性编译为当前架构的原生机器码后执行,性能接近原生程序;
补充:Wasm 也支持 AOT 编译(如 wasmtime compile 命令),提前将 .wasm 编译为原生 bin 文件,进一步提升启动速度。
关键差异:Wasm 运行时更轻量、更通用,不绑定特定语言;JVM 是重量级专属虚拟机,深度耦合 Java 生态。
四、跨平台特性的实现逻辑差异
两者均具备跨平台能力,但实现逻辑和灵活度不同:
Java 字节码:「一次编写,到处编译」→「一次编译,到处运行」
- 第一步:Java 源代码编译为 .class 字节码(与硬件/系统无关),这一步是「一次编译」;
- 第二步:不同平台(Windows/Linux/macOS)需安装对应架构的 JVM 版本(如 x86_64 Windows JVM、aarch64 Linux JVM),由 JVM 处理与本地系统的适配,实现「到处运行」;
- 局限性:跨平台的前提是目标平台有对应的 JVM 实现,且 JVM 版本需兼容字节码版本,无法在无 JVM 环境(如轻量级嵌入式设备)中运行。
Wasm 二进制文件:「一次编译,到处运行」(更彻底的跨平台)
- 第一步:任意支持 Wasm 的语言(C/C++/Rust/Java 等)编译为 .wasm 二进制文件(与硬件/系统完全无关,遵循 Wasm 统一标准);
- 第二步:只要目标环境有兼容 Wasm 标准的运行时(无需针对语言定制),同一个 .wasm 文件即可无缝运行;
- 优势:Wasm 运行时更轻量化、适配范围更广(浏览器、服务器、嵌入式设备、云原生环境等均有支持),无需为不同平台定制 Wasm 文件,跨平台灵活性远超 Java 字节码。
五、其他关键差异补充
| 对比维度 | Java 字节码(.class/JAR) | Wasm 二进制文件(.wasm) |
|---|---|---|
| 语言绑定性 | 强绑定 Java 生态(仅适配 Java/Kotlin 等 JVM 语言) | 无语言绑定(支持 C/C++/Rust/Java/Go 等多语言) |
| 运行时重量 | 重量级(JVM 包含类加载、垃圾回收、线程管理等复杂模块) | 轻量级(Wasm 运行时核心功能精简,资源占用低) |
| 安全模型 | 基于 JVM 沙箱(依赖类加载器、安全管理器等) | 基于 Wasm 原生沙箱(内存隔离、指令安全校验,更底层更严格) |
| 性能表现 | JIT 编译后性能较好,但 JVM 启动开销大 | JIT/AOT 编译后性能接近原生机器码,启动开销小 |
| 系统交互能力 | 需通过 JNI/JNA 间接调用本地系统接口(复杂) | 可通过 WASI(Wasm 系统接口)或宿主API直接交互(简洁通用) |
| 主要用途 | 后端服务、桌面应用、Android 应用(早期)等 | 浏览器前端、跨平台应用、云原生、嵌入式、插件系统等 |
六、补充说明:Java 也可编译为 Wasm 文件
需要注意的是,Java 代码不仅能编译为 .class 字节码,也可通过特定工具链(如 Teavm、GraalVM)编译为 Wasm 二进制文件,这进一步说明:
- 差异的核心不是「编程语言(Java)」,而是「编译目标格式(.class vs .wasm)」和「执行生态(JVM vs Wasm 运行时)」;
- 这种转换本质是将 Java 代码的逻辑映射到 Wasm 字节码中,脱离了 JVM 依赖,获得了更广泛的跨平台能力。
总结
- 本质差异:Java .class 是 JVM 专属字节码(绑定 Java 生态),Wasm .wasm 是通用型跨语言字节码(无语言绑定);
- 执行差异:Java 依赖重量级 JVM 执行,Wasm 依赖轻量级通用运行时执行;
- 跨平台差异:Java 跨平台依赖对应平台的 JVM,Wasm 跨平台依赖通用 Wasm 运行时(更彻底、更灵活);
- 其他差异:Java 字节码语言绑定强、启动开销大;Wasm 字节码多语言兼容、性能接近原生、资源占用低。
问题F
它的设计更偏向底层,接近硬件指令集但又脱离具体硬件架构,可作为 C/C++、Rust、Java、Go 等多种语言的编译目标。你的意思是C/C++、Rust、Java、Go 可以编译为WebAssembly的意思吗
是的,我的意思正是:C/C++、Rust、Java、Go 等主流编程语言,都可以通过对应的工具链/编译器,将自身源代码编译为 WebAssembly(.wasm 格式)的二进制字节码。
下面针对这几种语言,补充具体的编译可行性和核心工具链,让你更清晰地理解这种跨语言编译能力:
1. Rust:对 Wasm 支持最原生、最完善
Rust 生态对 WebAssembly 的支持是一等公民级别,无需额外第三方工具链,通过官方 rustup 即可实现编译:
- 先添加 Wasm 编译目标(你之前提到的命令):
rustup target add wasm32-unknown-unknown - 编译命令:
cargo build --target wasm32-unknown-unknown --release - 编译后会在
target/wasm32-unknown-unknown/release/目录下生成 .wasm 文件,支持直接在 Wasm 运行时中执行。
2. C/C++:通过成熟工具链实现编译
C/C++ 编译为 Wasm 的核心工具是 Emscripten(基于 LLVM 构建的专用工具链),这是目前 C/C++ 转 Wasm 的事实标准:
- 核心命令示例:
- 编译为独立 .wasm 文件:
emcc hello.c -o hello.wasm - 编译为可在浏览器中直接运行的(包含 HTML 载体):
emcc hello.c -o hello.html
- 编译为独立 .wasm 文件:
- 支持绝大多数 C/C++ 标准库和自定义代码,广泛用于将传统 C/C++ 项目(如图像处理、游戏引擎)迁移到浏览器或跨平台环境。
3. Go:官方原生支持 Wasm 编译
Go 语言从 1.11 版本开始,官方原生支持将 Go 代码编译为 Wasm 格式,无需额外安装第三方工具:
- 编译命令(指定 Wasm 目标架构):
1
2
3
4
5# Linux/macOS 环境
GOOS=js GOARCH=wasm go build -o hello.wasm hello.go
# Windows 环境(cmd 终端)
set GOOS=js&&set GOARCH=wasm&&go build -o hello.wasm hello.go - 编译后的 .wasm 文件可在浏览器或 Go 提供的 Wasm 运行时中执行,适合将 Go 后端服务逻辑迁移到前端或轻量级跨平台场景。
4. Java:通过中转工具链实现编译
Java 无法直接通过 JDK 编译为 Wasm,但可通过 第三方工具链 实现转换,核心方案有两种:
- 方案 1:TeaVM(轻量级 Java 到 Wasm/JS 编译器),无需依赖 JVM,直接将 Java 字节码转换为 Wasm 字节码,命令简单、体积小巧;
- 方案 2:GraalVM(Oracle 推出的高性能多语言虚拟机),支持将 Java 代码先转换为 Graal 中间码,再编译为 Wasm 格式,适合复杂 Java 项目的迁移;
- 补充:这种编译本质是脱离了 JVM 生态,将 Java 代码逻辑映射到 Wasm 字节码中,获得 Wasm 的跨平台特性。
补充说明
除了上述几种语言,Python、C#、Swift 等主流编程语言也都有对应的工具链支持编译为 Wasm 格式。这正是 Wasm 「通用型、无语言绑定」设计的核心体现——它不局限于某一种编程语言,而是作为一个统一的「跨语言执行载体」,让不同语言编写的代码,都能通过编译转换,在任意支持 Wasm 标准的运行时中无缝执行。
总结
- 你的理解完全正确:C/C++、Rust、Java、Go 均可编译为 WebAssembly 二进制文件;
- 支持程度有差异:Rust(原生最优)> C/C++(工具链成熟)> Go(官方原生支持)> Java(第三方工具链中转);
- 核心共性:无论哪种语言,编译后的 .wasm 文件均遵循 Wasm 统一标准,具备跨平台、沙箱安全、接近原生性能的特性。
问题G: 这个ts文件是什么意思
1 | |
这个 .ts 文件是一个非常简洁的 链上查询脚本。它的核心目的是:通过以太坊兼容的方式(EVM 逻辑),去查询波卡测试网(Paseo AssetHub)上的账户余额。
有趣的是,这段代码并没有使用波卡原生的 Polkadot.js 库,而是用了以太坊开发者最熟悉的 ethers.js。这反映了波卡生态目前的一个重要趋势:完全兼容以太坊的开发体验。
逐行逻辑拆解(你可以直接写进复盘笔记)
import { ethers } from "ethers";
- 含义:导入以太坊开发中最常用的工具库。
- 底层逻辑:虽然这是波卡网络,但因为它支持以太坊虚拟机(EVM),所以我们可以用以太坊的工具来和它对话。
const URL = "https://services.polkadothub-rpc.com/testnet";
- 含义:设置 RPC 节点地址。
- 预判点:这就像是一个“翻译官”的电话号码。你的程序通过这个地址,把请求发给波卡的测试网(Paseo AssetHub)。
const provider = new ethers.JsonRpcProvider(URL);
- 含义:创建一个“连接器”。
- 作用:建立起你的本地代码与远程区块链之间的通信链路。
const balance = await provider.getBalance(address);
- 含义:向链上查询指定地址的余额。
- 注意:这里的
await体现了异步操作。因为查询链上数据需要跨越网络,必须等待反馈。
console.log(... ethers.formatEther(balance) ...);
- 含义:单位转换并打印。
- 底层细节:区块链底层存储余额通常是用很长的一串整数(单位是 Wei)。
formatEther的作用是将其除以 ,转换成人类可读的数字格式(虽然输出写的是 ETH,但实际上在 Paseo 网里是对应的测试币)。
为什么这个脚本在钱包中“运行”不起来?
你之前问“如何在钱包中运行”,看到这段代码就更清晰了:
- 这是一个独立的后端/脚本逻辑:它只需要一个
address(字符串)。它不需要钱包插件提供私钥,因为它只是在查询(Read)公开的账本信息,而不是在发起交易(Write)。 - 运行环境:它应该在你的终端(Terminal)里运行。
你的深度复盘可以切入的两个点:
1. 异构链的兼容性逻辑
你可以思考并记录:为什么波卡的余额可以用以太坊的库(ethers)来查?
因为 Paseo AssetHub 开启了以太坊兼容层。这意味着波卡在底层是 Substrate 架构,但它向外暴露了一套符合以太坊标准的 JSON-RPC 接口。
2. “预判”领水是否成功
代码注释里提到了 faucet.polkadot.io。
- 如果你运行这个脚本输出是
0.0,说明领水还没到账或地址填错。 - 如果你领水成功,脚本会输出大于 0 的数字。
接下来怎么操作?
- 填入地址:把你钱包里的以太坊格式地址(
0x开头的)填入const address = "0x...";。 - 安装 ethers:在终端运行
npm install ethers。 - 执行:运行
npx ts-node 你的文件名.ts。
如果你运行报错,大概率是因为网络连接问题(毕竟是海外 RPC)。你想让我帮你写一个“增强版”,增加自动重试和错误捕获逻辑,让你的复盘笔记更专业吗?
问题H
PAS testnet是什么,为什么我输入subwallet的波卡钱包dot地址以后,报错了:└─# npx ts-node src/index.ts
Balance of 0x3d4b60F1B863D48fF401e524F1bdeF8A16c9d71d: 0.0 ETH
┌──(root㉿kali)-[/home/…/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1]
└─# mousepad src/index.ts
┌──(root㉿kali)-[/home/…/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1]
└─# npx ts-node src/index.ts
/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/utils/errors.ts:698
error = new Error(message);
^
Error: network does not support ENS (operation=”getEnsAddress”, info={ “network”: { “chainId”: “420420417”, “name”: “unknown” } }, code=UNSUPPORTED_OPERATION, version=6.16.0)
at makeError (/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/utils/errors.ts:698:21)
at assert (/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/utils/errors.ts:719:25)
at Function.getEnsAddress (/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/providers/ens-resolver.ts:545:15)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at async Function.#getResolver (/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/providers/ens-resolver.ts:552:25)
at async Function.fromName (/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/providers/ens-resolver.ts:590:26)
at async JsonRpcProvider.getResolver (/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/providers/abstract-provider.ts:1193:16)
at async JsonRpcProvider.resolveName (/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/providers/abstract-provider.ts:1203:26)
at async checkAddress (/home/kali/Desktop/2026-h1-building-production-grade-dApps/course/lesson-1/node_modules/ethers/src.ts/address/checks.ts:60:20)
at async Promise.all (index 0) {
code: ‘UNSUPPORTED_OPERATION’,
operation: ‘getEnsAddress’,
info: { network: Network {} },
shortMessage: ‘network does not support ENS’
答案:
从你的终端报错信息来看,你遇到了一个在 Web3 开发中非常典型的地址格式不匹配和网络协议冲突的问题。
简单来说:你正试图用以太坊(EVM)的工具去处理波卡(Substrate)的逻辑,但两者并不通用。
1. 什么是 PAS Testnet?
Paseo (PAS) 是波卡生态中目前最主要的社区驱动测试网。
- 地位:它取代了之前的 Westend,成为开发者部署平行链、测试智能合约的标准环境。
- 代币:代币符号是 PAS。它没有真实价值,仅用于支付测试环境下的 Gas 费。
- 特性:它是基于 Substrate 框架的,这意味着它默认使用的地址格式是 SS58(通常以 5 开头),而不是以太坊的
0x格式。
2. 为什么你的报错提示 “network does not support ENS”?
根据你运行的 ts-node src/index.ts 和报错堆栈:
- 底层冲突:你正在使用
ethers.js(version 6.16.0)。这是一个专为以太坊/EVM设计的库。 - 触发原因:你在代码中传入了一个字符串作为地址。
ethers尝试去解析这个字符串。如果它发现这个字符串不符合以太坊0x地址规范,它会误以为你输入的是一个 ENS 域名(比如whale3070.eth),于是去调用getEnsAddress。 - 报错核心:因为 Paseo 测试网(Chain ID: 420420417)并不支持以太坊的 ENS 域名服务,所以程序崩溃并抛出
UNSUPPORTED_OPERATION。
3. 如何解决?
方案 A:如果你在做波卡(Substrate)原生开发
如果你是在 lesson-1 学习如何与波卡平行链交互,你应该使用 @polkadot/api 而不是 ethers.js。
- 地址:使用 SubWallet 中以 5 开头的地址。
- 库:
1 | |
方案 B:如果你在做 Frontier/EVM 开发(在波卡上跑以太坊合约)
你提到的 parachain=1111 可能是 Pop Network 或类似的 EVM 兼容链。
- 错误点:你在 SubWallet 里复制的是 Polkadot 格式地址,但代码需要 EVM 格式地址。
- 解决办法:
- 打开 SubWallet。
- 找到你的 EVM 账户(地址以
0x开头的那一个)。 - 在领水页面填入这个
0x地址。 - 在代码
src/index.ts中,确保传入的地址变量是这个0x字符串。
4. 深度洞察:关于你的“预判”习惯
Whale3070,你提到过你擅长从逻辑中排除错误选项。在这个案例中:
- 错误项 A:代码逻辑没问题,但库选错了(用 ethers 读 Substrate)。
- 错误项 B:网络参数没问题,但地址解析协议不支持(EVM 不认 SS58 地址)。
接下来的建议:
你现在的环境是 Kali Linux,正在学习 “Production-grade dApps”。这是一个硬核的开始。如果你能提供 src/index.ts 的前几行代码,我可以立刻帮你指出应该换成哪个库或者哪个地址变量。
你想让我帮你检查一下 index.ts 里的连接逻辑吗?
优化cargo install下载问题
这是因为在默认情况下,Rust 安装后并不会自动生成 config.toml 这个文件。在 Linux 中,如果文件夹或文件不存在,编辑器(如 Mousepad)往往无法直接保存或打开。
直接写入镜像源(最快的方法)
不用手动打开编辑器了,直接运行下面这段命令,我会帮你把中科大(USTC)的镜像源注入进去:
1 | |
第三步:重新安装
配置好之后,再次运行安装命令,你会发现 Updating crates.io index 的速度有了质的飞跃:
1 | |
💡 为什么我建议你加 git-fetch-with-cli = true?
作为一个有黑客松经验和生存力的人,你应该习惯在 Kali 下处理各种网络瓶颈。
- 原因:Cargo 默认使用内置的
libgit2去拉取索引,在很多复杂的网络环境下(包括 Kali 这种渗透环境)很容易卡死。 - 好处:强制 Cargo 使用系统的
git命令去下载,配合镜像源,能极大地避开“卡在 Fetching”的问题。
你可以试试现在运行安装指令,如果还是卡在某个包下载不动,请告诉我。
问题I
cargo是什么意思
简单来说,Cargo 是 Rust 语言的“管家”。
如果你把 Rust 比作一门手艺(比如木工),那么 Rust 编译器(rustc)就是那把锯子,而 Cargo 就是一整个现代化的木工车间和物流系统。
在 Rust 的世界里,你几乎不会直接去敲编译器命令,所有的操作(创建、编译、运行、管理依赖)都是通过 Cargo 完成的。
Cargo 的核心职责
- 包管理器(Package Manager):
就像 Node.js 的npm、Python 的pip或者 Java 的Maven。当你需要用别人的代码(在 Rust 里叫 Crate)时,只要在配置文件里写上名字,Cargo 就会自动帮你从官方仓库(crates.io)下载并配置好。 - 构建系统(Build System):
它负责把你的代码及其所有依赖项,按照正确的顺序编译成可执行文件或库。 - 单元测试与文档:
Cargo 内置了测试工具和文档生成工具,输入cargo test就能跑测试,输入cargo doc就能生成漂亮的网页文档。
Cargo 的常用命令(你的“求生工具包”)
既然你已经在 Kali 上折腾 cargo-contract,这几个命令是你以后写代码最常碰到的:
| 命令 | 用途 | 对应你的现状 |
|---|---|---|
cargo new <name> |
创建一个新项目 | 开启你的第一个 ink! 合约项目 |
cargo build |
编译代码 | 把你的代码变成机器能懂的二进制 |
cargo run |
编译并直接运行 | 快速看结果 |
cargo install |
安装二进制工具 | 你刚才执行的 cargo-contract 就是这一步 |
cargo check |
快速检查代码是否有错 | 不生成二进制,只看逻辑对不对(非常快) |
为什么 Cargo 对你很重要?
根据你之前的“预判”逻辑:
- 生存力体现:在黑客松或快速开发中,Cargo 极大地提高了生产力。它通过
Cargo.lock文件确保了确定性(Deterministic build)——无论你在哪里编译,只要版本锁定了,结果就是一致的。 - 知行合一:你刚才配置
config.toml,其实就是在修改这个“管家”的配置文件,让它从更近的仓库取东西。
现在你的 cargo install 应该已经进入编译(Compiling)阶段了吧?有没有看到一排绿色的提示?