2. 区块链数据基础:区块、交易、地址、Token标准回顾
好,咱们正式开始第二讲。在动手抓链上数据之前,有些基础概念得先捋清楚。我见过不少新手,上来就写代码调 RPC,结果连区块里装的是啥都没搞明白——嗯,那后面肯定踩坑。
这一节,咱们把区块、交易、地址、还有那几个 Token 标准过一遍。说白了,就是给后续的巨鲸追踪打地基。
2.1 区块:链上数据的“集装箱”
区块是什么?你可以把它想象成一个集装箱。每个集装箱里装了一批交易,盖上盖子(也就是区块头),然后挂到链条上。
我个人习惯把区块理解成“时间切片”。每出一个新区块,就代表链上状态更新了一次。以太坊平均 12 秒出一个块,比特币慢一些,10 分钟。
区块结构其实不复杂,核心就两部分:
- 区块头:存元数据。比如父区块哈希、时间戳、难度、Nonce 值。嗯,这里有个关键字段——
stateRoot,它记录了整个链上状态的 Merkle 根。我在做状态校验时经常用到它。 - 区块体:就是交易列表。以太坊的区块体里还有叔块引用,不过咱们做数据分析时,主要关注交易列表就够了。
避坑指南:我曾经以为区块高度是连续的,后来发现以太坊偶尔会出“空块”——就是区块里没有交易。这种情况在 Gas 费低的时候很常见。你统计交易量时,记得过滤掉空块。
来看一个实际抓取区块数据的代码片段,我用的是 Web3.py:
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'))
# 获取最新区块
latest_block = w3.eth.get_block('latest')
print(f"区块高度: {latest_block['number']}")
print(f"交易数量: {len(latest_block['transactions'])}")
print(f"时间戳: {latest_block['timestamp']}")
# 获取指定区块的完整信息
block = w3.eth.get_block(18000000, full_transactions=True)
for tx in block['transactions'][:3]: # 只看前3笔
print(f"交易哈希: {tx['hash'].hex()}")
你想想看,每次调用 get_block 其实就是在拉取一个集装箱的全部内容。如果 full_transactions=True,连箱子里的每件货物(交易详情)都给你。
2.2 交易:链上活动的“原子单位”
交易是区块链上最小的操作单元。说白了,任何状态变化——转账、部署合约、调用合约——都得通过交易来触发。
以太坊的交易结构,我列个表给你看:
| 字段 | 说明 | 我常用的场景 |
|---|---|---|
from |
发送方地址 | 识别巨鲸地址 |
to |
接收方地址(合约或EOA) | 判断是转账还是合约调用 |
value |
转账金额(单位:Wei) | 计算大额转账阈值 |
input |
调用数据(合约交互时) | 解析具体调用了哪个函数 |
gasPrice |
Gas 单价 | 判断交易紧急程度 |
nonce |
发送方交易序号 | 检测交易是否被替换 |
这里有个细节——input 字段。很多新手以为交易只有转账,其实大部分链上活动都是合约调用。比如你调用 Uniswap 的 swap 函数,input 里就编码了函数签名和参数。
我的经验:抓巨鲸交易时,别只看 value 字段。很多大额操作是通过合约转账完成的,value 可能是 0,但 input 里藏着几百万 USDT 的转移。我吃过这个亏,后来养成了解析 input 的习惯。
解析交易 input 的代码示例:
# 解析 ERC-20 transfer 函数的 input
# transfer 函数签名: 0xa9059cbb
# 参数: to (地址), value (uint256)
tx_input = "0xa9059cbb000000000000000000000000abc...0000000000000000000000000000000000000000000000000000000000000001"
# 提取接收方地址(从第10个字符开始,取40个字符)
to_address = "0x" + tx_input[34:74]
# 提取金额(最后64个字符)
value_hex = tx_input[74:]
value = int(value_hex, 16)
print(f"接收方: {to_address}")
print(f"金额: {value}")
为什么会这样?因为以太坊虚拟机(EVM)就是这么设计的——所有数据都塞进 input 里,按 ABI 编码规则排列。你只要知道合约的 ABI,就能反解出具体操作。
2.3 地址:链上的“身份标识”
地址,说白了就是你在区块链上的账户。以太坊地址是 40 位十六进制数,前面加个 0x。
但地址分两种:
- EOA(外部账户):由私钥控制,可以发起交易。巨鲸的钱包基本都是 EOA。
- 合约地址:由代码控制,不能主动发起交易,只能被动响应。
我记得刚开始做链上分析时,经常分不清一个地址是 EOA 还是合约。后来学了一招——查它的 code 字段。如果 code 是空的,就是 EOA;否则是合约。
def is_contract(address):
code = w3.eth.get_code(address)
return code != b''
# 测试
addr = "0x...一些巨鲸地址"
if is_contract(addr):
print("这是合约地址")
else:
print("这是个人钱包")
注意:有些合约地址的 code 虽然不为空,但可能已经自毁了(selfdestruct)。这种情况下,地址虽然存在,但已经无法交互。我在追踪一个老牌 DeFi 项目时就遇到过,折腾了半天才发现合约已经废了。
2.4 Token 标准:ERC-20 / ERC-721 / ERC-1155
Token 标准,就是一套约定好的接口规范。说白了,大家按同一个规则写合约,这样钱包、交易所、分析工具都能统一识别。
ERC-20:同质化代币
这是最常见的标准。USDT、UNI、LINK 都是 ERC-20。每个代币都一样,可以互换。
核心接口就几个:totalSupply()、balanceOf()、transfer()、approve()、transferFrom()。
我做巨鲸分析时,最常查的就是 balanceOf。比如查某个地址持有多少 USDT:
usdt_contract = "0xdAC17F958D2ee523a2206206994597C13D831ec7"
usdt = w3.eth.contract(address=usdt_contract, abi=erc20_abi)
whale_address = "0x...某巨鲸"
balance = usdt.functions.balanceOf(whale_address).call()
print(f"持有 USDT: {balance / 10**6}") # USDT 是6位小数
ERC-721:非同质化代币(NFT)
每个 NFT 都是独一无二的。核心区别在于多了个 tokenId 字段。你查某个地址的 NFT 持仓,得遍历它的所有 tokenId。
我遇到过最坑的事:有些 NFT 合约的 totalSupply() 不准确,因为部分 NFT 被销毁了。后来我改用 ownerOf() 逐个 tokenId 查,虽然慢,但准确。
ERC-1155:多代币标准
这个标准比较新,一个合约可以同时管理同质化和非同质化代币。说白了,就是 ERC-20 和 ERC-721 的合体。
它的 balanceOfBatch() 函数可以一次查多个地址、多个 tokenId 的余额,效率很高。我在分析游戏项目时经常用到——一个合约里既有金币(同质化),又有武器(非同质化)。
总结一下我的经验:
- 抓 ERC-20 转账,监听
Transfer事件就行 - 抓 NFT 交易,得同时监听
Transfer和Approval事件 - ERC-1155 的
TransferSingle和TransferBatch事件要分开处理
2.5 知识体系总览
下面这张图,我把这一节的核心逻辑串起来了。你保存下来,以后做数据分析时对照着看:
这张图把区块、交易、地址、Token 标准的关系画清楚了。区块包含交易,交易由地址发起,交易可以调用 Token 合约。你顺着这个逻辑去抓数据,思路会清晰很多。
最后说一句:这些基础概念,我建议你动手跑一遍代码。光看文档记不牢。我自己当年就是一边抓区块数据一边学的,踩过的坑现在都变成了经验。