“每一个区块,都是一次全网共识的快照。
它不仅记录了交易,更记录了时间的流逝。”
一、开场:区块,不只是数据
在上一讲中,我们成功连上了以太坊节点,并读取了最新区块号。
那时,我们看到的只是一个数字——
例如:
最新的区块号是: 5689012
但如果说,这个数字代表了以太坊心脏跳动的节奏,你是否会想更进一步?
——这些区块,到底“长什么样”?
——它们存着什么?
——交易又是如何被打包进去的?
在这一讲,我们就来一起“拆开”一个区块,看看里面的世界。
二、区块的本质:世界状态的“快照”
如果你来自传统后端背景,可以这样理解:
- 在数据库中,一次事务提交 会改变数据状态;
- 在区块链中,一次区块确认 会改变全网状态。
每一个区块(Block)都像一张“快照”,
记录了自上一个区块以来,发生的所有状态变化。
| 概念 | 类比 | 说明 |
|---|---|---|
区块号 (Block Number) | 数据版本号 | 记录世界状态的顺序 |
区块哈希 (Block Hash) | 快照 ID | 唯一标识这个区块内容 |
父区块哈希 (Parent Hash) | 上一个版本指针 | 像 Git 的 parent commit |
时间戳 (Timestamp) | 提交时间 | 区块打包的时间 |
交易列表 (Transactions) | 操作日志 | 区块内包含的所有操作 |
以太坊区块链,就是由这些“快照”首尾相连形成的时间长河。
你可以随时读取过去任何一个区块的状态,就像翻阅时间机器中的档案。
三、Go-Ethereum 实战:读取一个完整区块
接下来,我们动手验证这一切。
目标:
使用 Go 语言获取最新区块的详细信息,
并打印其中的核心字段。
1. 新建文件 block_inspect.go
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
// 1. 连接到节点
client, err := ethclient.Dial("https://sepolia.infura.io/v3/<YOUR_PROJECT_ID>")
if err != nil {
log.Fatalf("连接节点失败: %v", err)
}
// 2. 获取最新区块头,拿到区块号
header, err := client.HeaderByNumber(context.Background(), nil)
if err != nil {
log.Fatalf("无法获取区块头: %v", err)
}
blockNumber := header.Number
fmt.Printf("🔹 当前最新区块号: %v\n", blockNumber)
// 3. 获取完整区块对象
block, err := client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
log.Fatalf("无法获取区块信息: %v", err)
}
// 4. 打印关键字段
fmt.Println("———————— 区块信息 ————————")
fmt.Printf("区块号: %v\n", block.Number())
fmt.Printf("区块哈希: %v\n", block.Hash().Hex())
fmt.Printf("父区块哈希: %v\n", block.ParentHash().Hex())
fmt.Printf("时间戳: %v\n", block.Time())
fmt.Printf("交易数量: %d\n", len(block.Transactions()))
// 5. 读取区块内的第一笔交易(若存在)
if len(block.Transactions()) > 0 {
tx := block.Transactions()[0]
fmt.Println("———————— 第一笔交易 ————————")
fmt.Printf("交易哈希: %v\n", tx.Hash().Hex())
fmt.Printf("发起地址: %v\n", tx.From()) // 注意:tx.From() 需要额外签名信息,此处略
fmt.Printf("接收地址: %v\n", tx.To())
fmt.Printf("Gas 消耗上限: %v\n", tx.Gas())
}
}
运行它:
go run block_inspect.go
你会看到类似输出:
🔹 当前最新区块号: 5689012
———————— 区块信息 ————————
区块号: 5689012
区块哈希: 0x7f3a1d...
父区块哈希: 0x9b7f20...
时间戳: 1731098890
交易数量: 142
———————— 第一笔交易 ————————
交易哈希: 0xa1b9f...
接收地址: 0x32F0...D5a
Gas 消耗上限: 21000
恭喜!你已经成功“读懂”了一个区块的骨架。
四、交易:区块的灵魂
区块是容器,交易是灵魂。
在以太坊中,所有状态变化(账户余额、合约执行、事件日志)都来源于交易。
没有交易,就没有区块。
没有区块,就没有状态。
一笔交易包含什么?
| 字段 | 含义 |
|---|---|
nonce | 发送者账户的交易计数器,防止重放 |
to | 接收方地址(若为空,则是部署合约) |
value | 转账的 ETH 数量 |
gas | 允许消耗的最大 Gas |
gasPrice / maxFeePerGas | 每单位 Gas 的费用 |
data | 合约调用参数或部署代码 |
v, r, s | 签名字段,验证交易的合法性 |
当节点验证一笔交易时,它实际上在EVM 沙箱中执行了这段代码,
然后将执行结果(包括状态变化、事件日志)记录入区块。
五、区块链中的时间与不可逆性
每个区块都有一个时间戳 timestamp。
它并不是毫秒级精度,而是区块被“打包”进链时的粗略时间。
区块之间间隔大约为 12 秒(PoS 共识下),
这意味着以太坊的“时间”是由共识推动的,而非系统时钟。
区块链世界的时间有一个重要特征:
过去的状态永远不会被修改,只能被追加。
这就是所谓的不可篡改性(Immutability)。
而这,也正是它区别于一切传统数据库的根本。
六、思考与扩展
- 能否通过代码获取指定区块号的区块?
提示:client.BlockByNumber(ctx, big.NewInt(1234567))
- 能否将区块数据以 JSON 格式输出?
试着用 json.MarshalIndent(block, "", ” ”)
- 区块中有 100 多笔交易,你能否循环遍历并打印它们的哈希?
下一讲,我们就要这么做。
七、过渡:从区块到交易日志
我们已经看到了区块和交易的结构。
但在智能合约的世界中,真正有价值的数据往往隐藏在**事件日志(Logs)**中。
它们是应用层的“信号”,
代表某个合约函数被执行、某笔业务逻辑已触发。
八、总结
| 我们学到了什么 | 关键点 |
|---|---|
| 区块是以太坊的时间片段 | 每个区块都是全网状态的快照 |
| 交易是状态变化的源头 | 每笔交易都会改变链上状态 |
| 区块哈希形成了链式结构 | 确保数据不可篡改 |
| Go 语言可轻松读取区块内容 | BlockByNumber 是关键方法 |
