Polkadot Solidity开发-课程笔记4

问题E

WebAssembly 是二进制文件,java也需要编译。那么这两个二进制文件又有什么不同

你想知道 WebAssembly(Wasm)二进制文件与 Java 编译生成的二进制文件的核心差异,二者虽同为二进制格式,但在本质类型、执行模型、跨平台逻辑等方面存在根本性区别,我们从关键维度详细拆解:

一、先明确两者的「二进制文件」具体所指

首先要厘清两个核心概念,避免混淆:

  1. Java 对应的二进制文件:Java 源代码(.java)编译后生成的是 Java 字节码文件(.class 格式,本质是二进制文件);若打包为可分发格式,通常是 JAR 包(本质是 .class 文件的压缩归档,核心仍是 .class 字节码)。
  2. WebAssembly 对应的二进制文件:通常指 .wasm 格式文件,是 Wasm 标准定义的二进制字节码格式(也有对应的文本格式 .wat,可转换为 .wasm)。

二、核心本质与设计目标差异(最根本区别)

  1. Java 字节码(.class)
    它是专门为 Java 虚拟机(JVM)设计的专属字节码,核心设计目标是「支撑 Java 语言的跨平台运行+面向对象特性落地」,仅与 JVM 生态强绑定。它高度贴合 Java 语言的特性(如面向对象、垃圾回收、异常处理等),无法脱离 JVM 生态的设计逻辑独立使用。

  2. Wasm 二进制文件(.wasm)
    它是通用型、无语言绑定的抽象虚拟机字节码,核心设计目标是「高性能跨平台执行+多语言兼容+安全沙箱」,并非为某一种语言量身定制。它的设计更偏向底层,接近硬件指令集但又脱离具体硬件架构,可作为 C/C++、Rust、Java、Go 等多种语言的编译目标。

三、执行方式与依赖环境的差异

  1. Java 字节码(.class)的执行方式
    依赖 Java 虚拟机(JVM) 作为唯一执行载体,执行流程分为两种(主流为混合模式):

    • 解释执行:JVM 中的解释器(如 HotSpot 解释器)逐行解析 .class 字节码,转换为当前 CPU 可识别的原生机器码后执行;
    • 即时编译(JIT):JVM 中的 JIT 编译器(如 HotSpot 的 C1/C2 编译器)会将频繁执行的「热点代码」(如循环、高频调用方法)一次性编译为原生机器码,缓存后重复执行,兼顾启动速度和运行性能;
      补充:Java 还有 AOT 编译(如 GraalVM Native Image),可直接将 .class/JAR 编译为原生 bin 文件,但这是额外编译流程,非 .class 字节码的原生执行方式。
  2. Wasm 二进制文件(.wasm)的执行方式
    依赖 Wasm 运行时环境(非专属虚拟机,种类更多样),执行流程同样有两种:

    • 解释执行:Wasm 运行时(如浏览器内置运行时、wasmtime、wasmer)逐行解析 .wasm 字节码,转换为原生机器码执行;
    • 即时编译(JIT):主流 Wasm 运行时(如 Chrome/V8 内置运行时、Wasmer)优先采用 JIT 编译,将 .wasm 字节码一次性编译为当前架构的原生机器码后执行,性能接近原生程序;
      补充:Wasm 也支持 AOT 编译(如 wasmtime compile 命令),提前将 .wasm 编译为原生 bin 文件,进一步提升启动速度。
      关键差异:Wasm 运行时更轻量、更通用,不绑定特定语言;JVM 是重量级专属虚拟机,深度耦合 Java 生态。

四、跨平台特性的实现逻辑差异

两者均具备跨平台能力,但实现逻辑和灵活度不同:

  1. Java 字节码:「一次编写,到处编译」→「一次编译,到处运行」

    • 第一步:Java 源代码编译为 .class 字节码(与硬件/系统无关),这一步是「一次编译」;
    • 第二步:不同平台(Windows/Linux/macOS)需安装对应架构的 JVM 版本(如 x86_64 Windows JVM、aarch64 Linux JVM),由 JVM 处理与本地系统的适配,实现「到处运行」;
    • 局限性:跨平台的前提是目标平台有对应的 JVM 实现,且 JVM 版本需兼容字节码版本,无法在无 JVM 环境(如轻量级嵌入式设备)中运行。
  2. 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 依赖,获得了更广泛的跨平台能力。

总结

  1. 本质差异:Java .class 是 JVM 专属字节码(绑定 Java 生态),Wasm .wasm 是通用型跨语言字节码(无语言绑定);
  2. 执行差异:Java 依赖重量级 JVM 执行,Wasm 依赖轻量级通用运行时执行;
  3. 跨平台差异:Java 跨平台依赖对应平台的 JVM,Wasm 跨平台依赖通用 Wasm 运行时(更彻底、更灵活);
  4. 其他差异: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 的事实标准:

  • 核心命令示例:
    1. 编译为独立 .wasm 文件:emcc hello.c -o hello.wasm
    2. 编译为可在浏览器中直接运行的(包含 HTML 载体):emcc hello.c -o hello.html
  • 支持绝大多数 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 标准的运行时中无缝执行。

总结

  1. 你的理解完全正确:C/C++、Rust、Java、Go 均可编译为 WebAssembly 二进制文件;
  2. 支持程度有差异:Rust(原生最优)> C/C++(工具链成熟)> Go(官方原生支持)> Java(第三方工具链中转);
  3. 核心共性:无论哪种语言,编译后的 .wasm 文件均遵循 Wasm 统一标准,具备跨平台、沙箱安全、接近原生性能的特性。

问题G: 这个ts文件是什么意思

1
2
3
4
5
6
7
8
9
10
11
12
import { ethers } from "ethers";

async function main() {
// faucet https://faucet.polkadot.io/ Paseo AssetHub
const URL = "https://services.polkadothub-rpc.com/testnet";
const provider = new ethers.JsonRpcProvider(URL);
const address = "0x"; // put your address here
const balance = await provider.getBalance(address);
console.log(`Balance of ${address}: ${ethers.formatEther(balance)} ETH`);
}

main();

这个 .ts 文件是一个非常简洁的 链上查询脚本。它的核心目的是:通过以太坊兼容的方式(EVM 逻辑),去查询波卡测试网(Paseo AssetHub)上的账户余额。

有趣的是,这段代码并没有使用波卡原生的 Polkadot.js 库,而是用了以太坊开发者最熟悉的 ethers.js。这反映了波卡生态目前的一个重要趋势:完全兼容以太坊的开发体验。


逐行逻辑拆解(你可以直接写进复盘笔记)

  1. import { ethers } from "ethers";
  • 含义:导入以太坊开发中最常用的工具库。
  • 底层逻辑:虽然这是波卡网络,但因为它支持以太坊虚拟机(EVM),所以我们可以用以太坊的工具来和它对话。
  1. const URL = "https://services.polkadothub-rpc.com/testnet";
  • 含义:设置 RPC 节点地址。
  • 预判点:这就像是一个“翻译官”的电话号码。你的程序通过这个地址,把请求发给波卡的测试网(Paseo AssetHub)。
  1. const provider = new ethers.JsonRpcProvider(URL);
  • 含义:创建一个“连接器”。
  • 作用:建立起你的本地代码与远程区块链之间的通信链路。
  1. const balance = await provider.getBalance(address);
  • 含义:向链上查询指定地址的余额。
  • 注意:这里的 await 体现了异步操作。因为查询链上数据需要跨越网络,必须等待反馈。
  1. 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 的数字。

接下来怎么操作?

  1. 填入地址:把你钱包里的以太坊格式地址(0x 开头的)填入 const address = "0x...";
  2. 安装 ethers:在终端运行 npm install ethers
  3. 执行:运行 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
2
3
import { ApiPromise, WsProvider } from '@polkadot/api';
// ... 连接到 Paseo 节点的代码

方案 B:如果你在做 Frontier/EVM 开发(在波卡上跑以太坊合约)

你提到的 parachain=1111 可能是 Pop Network 或类似的 EVM 兼容链。

  • 错误点:你在 SubWallet 里复制的是 Polkadot 格式地址,但代码需要 EVM 格式地址。
  • 解决办法
  1. 打开 SubWallet。
  2. 找到你的 EVM 账户(地址以 0x 开头的那一个)。
  3. 在领水页面填入这个 0x 地址。
  4. 在代码 src/index.ts 中,确保传入的地址变量是这个 0x 字符串。

4. 深度洞察:关于你的“预判”习惯

Whale3070,你提到过你擅长从逻辑中排除错误选项。在这个案例中:

  1. 错误项 A:代码逻辑没问题,但库选错了(用 ethers 读 Substrate)。
  2. 错误项 B:网络参数没问题,但地址解析协议不支持(EVM 不认 SS58 地址)。

接下来的建议:
你现在的环境是 Kali Linux,正在学习 “Production-grade dApps”。这是一个硬核的开始。如果你能提供 src/index.ts 的前几行代码,我可以立刻帮你指出应该换成哪个库或者哪个地址变量。

你想让我帮你检查一下 index.ts 里的连接逻辑吗?

优化cargo install下载问题

这是因为在默认情况下,Rust 安装后并不会自动生成 config.toml 这个文件。在 Linux 中,如果文件夹或文件不存在,编辑器(如 Mousepad)往往无法直接保存或打开。

直接写入镜像源(最快的方法)

不用手动打开编辑器了,直接运行下面这段命令,我会帮你把中科大(USTC)的镜像源注入进去:

1
2
3
4
5
6
7
8
9
10
11
cat <<EOF > ~/.cargo/config.toml
[source.crates-io]
replace-with = 'ustc'

[source.ustc]
registry = "https://mirrors.ustc.edu.cn/crates.io-index"

[net]
git-fetch-with-cli = true
EOF

第三步:重新安装

配置好之后,再次运行安装命令,你会发现 Updating crates.io index 的速度有了质的飞跃:

1
2
cargo install --force --locked cargo-contract -v


💡 为什么我建议你加 git-fetch-with-cli = true

作为一个有黑客松经验和生存力的人,你应该习惯在 Kali 下处理各种网络瓶颈。

  • 原因:Cargo 默认使用内置的 libgit2 去拉取索引,在很多复杂的网络环境下(包括 Kali 这种渗透环境)很容易卡死。
  • 好处:强制 Cargo 使用系统的 git 命令去下载,配合镜像源,能极大地避开“卡在 Fetching”的问题。

你可以试试现在运行安装指令,如果还是卡在某个包下载不动,请告诉我。

问题I

cargo是什么意思


简单来说,Cargo 是 Rust 语言的“管家”

如果你把 Rust 比作一门手艺(比如木工),那么 Rust 编译器(rustc)就是那把锯子,而 Cargo 就是一整个现代化的木工车间和物流系统

在 Rust 的世界里,你几乎不会直接去敲编译器命令,所有的操作(创建、编译、运行、管理依赖)都是通过 Cargo 完成的。


Cargo 的核心职责

  1. 包管理器(Package Manager)
    就像 Node.js 的 npm、Python 的 pip 或者 Java 的 Maven。当你需要用别人的代码(在 Rust 里叫 Crate)时,只要在配置文件里写上名字,Cargo 就会自动帮你从官方仓库(crates.io)下载并配置好。
  2. 构建系统(Build System)
    它负责把你的代码及其所有依赖项,按照正确的顺序编译成可执行文件或库。
  3. 单元测试与文档
    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)阶段了吧?有没有看到一排绿色的提示?