Web3 硬核入门(上篇):基石篇 —— 连接、协议、你的第一行链上代码
三篇连载的第一篇。这篇不讲"元宇宙""财富自由"这种话,只讲工程事实:Web3 应用到底是怎么和链"说话"的,你的钱包点下"Connect"那一下背后发生了什么,以及为什么理解这些之后,你会发现 Web3 开发和你熟悉的分布式系统工程有大量共通的直觉。
面向读者:有多年 IT/后端/基础设施经验的工程师,以及想扎实入门而不是背概念的学生。默认你会一门后端语言、懂 HTTP/RPC、写过至少一个能跑的系统。如果你还没看过区块链底层原理(哈希、Merkle 树、PoW/PoS、UTXO vs Account),强烈建议先补一下 —— 本文默认你已经知道"一个区块长什么样""为什么链上数据难篡改",不会重复讲这些,而是直接站在这个地基上往上盖楼。
目录
- 1. 先破除误解:Web3 到底是什么
- 2. 全景地图:Web3 技术栈总览
- 3. 节点与 RPC:你的 dApp 从来没有"连接区块链"
- 4. 实战:不用任何 SDK,用 curl 直接跟链对话
- 5. 三大主流库选型:ethers.js vs web3.js vs viem vs web3.py
- 6. 实战:生成钱包、签名验证、连接本地链
- 7. 钱包连接协议:"Connect Wallet"按钮背后到底发生了什么
- 8. 本篇小结 + 中篇预告
1. 先破除误解:Web3 到底是什么
如果你搜"什么是 Web3",大概率会看到"下一代互联网""用户拥有数据主权"这类话——正确但没什么信息量。给你一个工程师视角的、可操作的定义:
Web3 = 把传统架构里"服务器持有并仲裁状态"这件事,换成"一组互相不信任的节点通过共识协议共同维护状态,任何客户端都能独立验证这个状态"。
拿你最熟悉的架构对比一下:
| 维度 | Web2(传统架构) | Web3 |
|---|---|---|
| 状态存在哪 | 公司的数据库(MySQL/Postgres/...) | 全体节点共同维护的链上状态 |
| 谁能改状态 | 后端服务(受公司控制) | 满足合约逻辑 + 共识规则的任何交易 |
| 你怎么信任结果 | 信任这家公司不作恶、不被黑、不倒闭 | 自己验证签名、自己验证共识规则,不需要信任任何单一方 |
| API 是谁定义的 | 公司自己定义的私有 REST/GraphQL API | 公开的 ABI + 标准化接口(ERC系列) |
| 服务下线了怎么办 | 数据可能永久丢失 | 只要有一个诚实全节点,历史数据就在 |
| 中间人/结算方 | Stripe、银行、平台抽成 | 智能合约自动结算,规则写死在代码里 |
关键认知:Web3 不是"更好的数据库",它是用密码学和博弈论换来的"去信任化"——代价是更差的性能、更高的存储成本、更复杂的用户体验。它不该被用在所有场景,只在"多方互不信任、但需要共同维护同一份状态"这个场景里有不可替代的价值(公开可验证的资产转移、无需许可的金融协议、抗审查的记录)。判断一个项目是不是"为了用而用区块链",就看它是否真的需要这条特性——这也是资深工程师面试/尽调项目时最该问的第一个问题。
2. 全景地图:Web3 技术栈总览
在扎进细节之前,先建立一张全局地图,后面每一篇的每一节都能在这张图上找到自己的位置:
这三篇文章的覆盖范围:上篇吃透"应用层如何跟节点对话"这一段(RPC、库选型、钱包连接);中篇深入"合约层"和"应用层"的开发实战(合约、ABI、前端集成、安全);下篇讲"扩展层"和更前沿的话题(L2、账户抽象、Restaking、跨链、职业路径)。
3. 节点与 RPC:你的 dApp 从来没有"连接区块链"
这是几乎所有新手(甚至一些做了一两年的从业者)理解模糊的一点:你的浏览器/服务器代码从来没有直接"连上区块链"。区块链是全球几千上万个节点各自维护的一份数据,你的代码实际做的是:向某一个具体的节点,发一个 JSON-RPC 请求。
这意味着几个很实际的工程后果,是经验丰富的工程师尤其需要建立的直觉:
- 这个节点可以骗你(尤其是免费公共 RPC),它理论上可以返回错误数据、审查你的交易(拒绝广播)、或者记录你的所有请求做画像。生产环境要么自己跑节点,要么用信誉良好、可审计的服务商,敏感场景要做多节点交叉验证。
- RPC 是有状态查询和无状态提交的混合:
eth_call(只读调用)不需要广播、不花 Gas、结果由这一个节点单方面计算返回;而eth_sendRawTransaction(提交交易)是把签名好的交易广播出去,最终会不会被打包、什么时候被打包,取决于全网矿工/验证者,不取决于你连的这个节点。 - "连接节点"和"连接钱包"是两回事——很多新手把这两者搞混。连接节点是为了读写链上数据;连接钱包(MetaMask 等)是为了获取用户的签名授权。你完全可以只连节点不连钱包(纯读场景),也可以先连钱包获取签名,再通过任意节点广播。
自建节点 vs 节点服务商:一张对比表
| 维度 | 自建全节点(geth/erigon/reth) | 节点服务商(Infura/Alchemy/QuickNode) | 公共免费RPC |
|---|---|---|---|
| 数据可信度 | 完全自己验证,最高 | 需要信任服务商 | 需要信任,且常有限流/审查风险 |
| 运维成本 | 高(存储/带宽/持续同步) | 几乎零 | 零 |
| 延迟/可用性 | 取决于自己的基础设施 | 一般有 SLA 保证 | 不稳定,常见限流 |
| 历史数据/归档查询 | 需要额外跑 Archive Node(存储成本更高) | 大部分提供归档节点 API(增值服务) | 通常不支持 |
| 适用场景 | 交易所、做市商、对可信度要求极高的基础设施 | 绝大多数生产级 dApp 的现实选择 | 学习、原型验证,不要用于生产 |
对有基础设施背景的工程师来说,这里的权衡跟"要不要自建 Kafka 集群还是用云托管服务"是完全一样的思维模型——一样是可用性、可信度、运维成本的三方博弈。
4. 实战:不用任何 SDK,用 curl 直接跟链对话
在学任何 ethers.js/web3.py 之前,先看清楚它们到底在帮你封装了什么——直接发原始 JSON-RPC 请求,你会发现这就是普通的 HTTP POST + JSON,没有任何魔法。这一步对建立底层直觉极其重要,强烈建议亲手跑一遍(把下面的 URL 换成你自己的 RPC 端点,或者本地跑的 Hardhat/Anvil 节点 http://127.0.0.1:8545)。
# 查询最新区块高度
curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://127.0.0.1:8545
# 返回类似: {"jsonrpc":"2.0","id":1,"result":"0x10"}
# 注意: 结果是十六进制字符串,0x10 = 十进制16,这是JSON-RPC以太坊接口的通用约定
# 查询某个地址的ETH余额(单位是wei, 1 ETH = 10^18 wei)
curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","latest"],"id":1}' \
http://127.0.0.1:8545
# 查询链ID(用于区分主网/测试网/L2,防止跨链重放攻击的关键字段之一)
curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \
http://127.0.0.1:8545
# 只读调用一个合约方法(以调用ERC20的balanceOf为例, data字段是ABI编码后的calldata)
curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0x合约地址","data":"0x70a08231000000000000000000000000你的地址补齐到32字节"},"latest"],"id":1}' \
http://127.0.0.1:8545看到最后一条你应该已经能感受到:所有那些 SDK(ethers/web3.py/viem)本质上都在做两件事——把人类可读的函数调用编码成这种十六进制 data 字段(ABI 编码),以及把返回的十六进制结果解码回可读类型。第 5 节我们就来对比这几个 SDK 具体是怎么做这件事的。
eth_getBalance、eth_chainId、eth_blockNumber、eth_call、eth_sendRawTransaction、eth_getTransactionReceipt 是你会用到最频繁的几个方法,建议把 Ethereum JSON-RPC 规范 收藏起来,遇到 SDK 报错看不懂的时候,回来对照原始协议排查——这个习惯能帮你解决 80% "SDK 黑盒报错看不懂"的问题。
5. 三大主流库选型:ethers.js vs web3.js vs viem vs web3.py
2026 年的现状,前端/Node.js 生态基本是这个格局:
| 维度 | ethers.js | web3.js | viem | web3.py |
|---|---|---|---|---|
| 现状 | 最主流,v6持续迭代,文档质量高 | 最早的库之一,历史包袱重,新项目已较少首选 | 增长最快的新一代库,TypeScript原生、模块化、体积小,常与 wagmi 搭配 | Python生态事实标准,适合后端/脚本/数据分析场景 |
| 类型安全 | 良好(TS支持完善) | 一般 | 极佳(类型即文档,IDE自动补全ABI参数) | Python类型提示逐步完善 |
| 包体积 | 中等 | 较大 | 很小(tree-shaking友好) | 不适用(后端场景) |
| 适合场景 | 通用首选,生态最成熟 | 遗留项目维护 | 追求性能与类型安全的新前端项目 | 链上数据分析、自动化脚本、后端服务、量化交易 |
| 学习曲线 | 平缓 | 平缓但文档较旧 | 需要适应函数式风格 | 对Python工程师几乎零门槛 |
选型建议其实很直接:新前端项目 → viem(配合 wagmi);需要兼顾生态成熟度和教程资源 → ethers.js;后端/脚本/数据分析 → web3.py;老项目维护 → 继续 web3.js,不必强行迁移。
用同一个任务——"用私钥生成钱包,并对一条消息签名"——对比三者的代码风格感受一下差异:
// ethers.js v6
import { ethers } from "ethers";
const wallet = new ethers.Wallet(privateKey);
const signature = await wallet.signMessage("hello web3");// viem
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(privateKey);
const signature = await account.signMessage({ message: "hello web3" });# web3.py
from eth_account import Account
account = Account.from_key(private_key)
signed = Account.sign_message(encode_defunct(text="hello web3"), private_key=private_key)三者最终做的事完全一样(EIP-191 消息签名),API 设计哲学不同而已——ethers 偏面向对象、viem 偏函数式+强类型、web3.py 贴合 Python 生态惯例。理解底层协议之后,换库只是换语法,这也是为什么第 3、4 节的底层内容值得花时间吃透。
6. 实战:生成钱包、签名验证、连接本地链
下面代码在本地 Hardhat 测试链上实际跑通验证过。先确保你本地起一条测试链(后面中篇会详细讲 Hardhat/Foundry 环境搭建,这里先用最简命令):
npm install --save-dev hardhat@2 ethers
npx hardhat node # 启动本地测试链, 默认监听 127.0.0.1:8545, 自动生成20个预置10000ETH的测试账户用 ethers.js 生成钱包 + 签名 + 验证 + ABI 编解码(全部实测通过):
import { ethers } from "ethers";
// 1. 生成一个全新的随机钱包(助记词 + 私钥 + 地址)
const wallet = ethers.Wallet.createRandom();
console.log("助记词:", wallet.mnemonic.phrase);
console.log("私钥: ", wallet.privateKey);
console.log("地址: ", wallet.address);
// 2. 用私钥对一条消息签名(EIP-191),再验证签名者地址
const message = "Sign in to MyDapp at " + new Date().toISOString().slice(0,10);
const signature = await wallet.signMessage(message);
const recovered = ethers.verifyMessage(message, signature);
console.log("恢复出的地址:", recovered, "匹配:", recovered === wallet.address);
// 3. ABI编码演示: 把一次函数调用编码成calldata(跟第4节curl示例里看到的16进制data是同一回事)
const iface = new ethers.Interface([
"function transfer(address to, uint256 amount) returns (bool)"
]);
const calldata = iface.encodeFunctionData("transfer", [
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
ethers.parseUnits("100", 18)
]);
console.log("编码后的calldata:", calldata);真实运行输出:
助记词: happy extend outdoor tuition ugly confirm slab effort matter bean timber broom
私钥: 0x354f121c08f4488e354e62dbe59c02bc303c807383df80fe6906206e2d1f0e93
地址: 0x2782e53aeB650B47681ba1a27407C77c8F566492
恢复出的地址: 0x2782e53aeB650B47681ba1a27407C77c8F566492 匹配: true
编码后的calldata: 0xa9059cbb00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000056bc75e2d63100000用 web3.py 连接同一条链、查余额、查链信息(实测通过):
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
print("是否连接成功:", w3.is_connected())
print("当前区块高度:", w3.eth.block_number)
print("链ID:", w3.eth.chain_id)
owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
print("余额:", w3.from_wei(w3.eth.get_balance(owner), "ether"), "ETH")真实输出:
是否连接成功: True
当前区块高度: 2
链ID: 31337
余额: 9999.999429702821627704 ETH31337 是 Hardhat 本地测试链的默认链 ID,9999.99... 而不是整数 10000 是因为该账户此前已经发起过交易、消耗了一部分 Gas——这是一个很好的验证,说明你确实在跟一条真实运作的链交互,而不是看静态数据。
7. 钱包连接协议:"Connect Wallet"按钮背后到底发生了什么
几乎每个 dApp 首页都有一个"Connect Wallet"按钮,点下去之后 MetaMask 弹窗要你确认——这套交互不是各家 dApp 自己发明的,而是遵循标准化协议:
背后的核心标准是 EIP-1193(以太坊 Provider JavaScript API):它规定了浏览器钱包必须在 window.ethereum 上暴露一套统一的 request() 接口,不管你装的是 MetaMask、Rabby 还是 Coinbase Wallet,dApp 代码调用方式完全一致——这正是"多钱包适配"能够成立的原因,是标准化协议而不是每个钱包单独接入。
移动端/跨设备场景(比如手机钱包扫码连接网页 dApp)用的是另一套标准 WalletConnect(目前主流版本是 v2),原理是通过一个中继服务器建立一条加密的会话通道,手机钱包和网页 dApp 之间交换签名请求/响应,私钥全程不离开手机端。
一个经验丰富的工程师应该建立的关键认知:私钥永远不会、也不应该发送给 dApp 前端。dApp 只能拿到"已连接的账户地址"和"用户对某个请求的签名结果",私钥的持有和签名操作永远在钱包(浏览器插件/硬件钱包/手机 App)内部完成。这个信任边界设计,类似于 OAuth 里"第三方应用永远拿不到你的密码,只能拿到授权后的 token"——如果你做过 OAuth 集成,这套心智模型你已经很熟悉了。
8. 本篇小结 + 中篇预告
这一篇建立的核心直觉:
- Web3 的本质是把状态的持有和仲裁权,从中心化服务器转移到去信任化的共识网络
- 你的代码从来不是"连接区块链",而是向某个具体节点发 JSON-RPC 请求——节点是否可信、是自建还是托管,是一个需要认真权衡的工程决策
- 所有 SDK(ethers/viem/web3.py)本质上都是把人类可读的函数调用编译成 ABI 编码的 calldata,再把返回结果解码——理解了这层,换库只是换语法
- "Connect Wallet"背后是标准化协议(EIP-1193/WalletConnect),私钥永远留在钱包内部,dApp 只拿到签名结果
中篇我们会正式进入合约开发:Hardhat/Foundry 环境实战搭建、ABI 与合约交互的完整闭环、ERC20/721 标准生态、Gas 优化技巧、前端集成(wagmi + viem + EIP-712 签名)、预言机与去中心化存储,以及一次真实的重入攻击代码复现和 2026 年一起价值 2.9 亿美元的真实攻击案例分析。
本文所有代码均在本地 Hardhat 测试链环境实际运行验证,非伪代码。