概述
我们这一节介绍下钱包开发中必不可少的工具 BlockTracker。在钱包开发中,实时感知区块链状态变化(如新块产生)对于账户余额、交易状态等功能至关重要,BlockTracker 正是为此而生。
BlockTracker 是什么
BlockTracker 是一个区块链区块追踪器,主要负责:
- 实时监控区块链状态:持续追踪最新的区块信息
- 提供区块数据:获取当前区块号和最新区块号
- 事件通知:当新区块产生时发出事件通知
- 状态管理:维护区块状态和轮询机制
如何使用
适合 provider 不支持订阅的环境
以太坊节点的 provider 有两种主流类型:
- 支持订阅(Subscription) :如 WebSocket provider,可以直接订阅
newHeads
,一有新区块就推送通知。 - 不支持订阅 :如 HTTP provider,只能通过轮询(polling)不断请求
eth_blockNumber
来获取新区块。
PollingBlockTracker 的设计初衷:
- 适合 HTTP provider,因为 HTTP 不能主动推送新区块,只能靠定时请求。
- 如果用 WebSocket provider,推荐用事件订阅(如
eth_subscribe
),这样更高效、实时。
举例说明:
- 你用 Infura 的 HTTP RPC 地址(如
https://mainnet.infura.io/v3/xxx
),只能用 PollingBlockTracker。 - 你用 WebSocket 地址(如
wss://mainnet.infura.io/ws/v3/xxx
),可以用 SubscriptionBlockTracker(订阅型)。
网络控制器中的使用
- 为每个网络客户端创建独立的区块追踪器
- 提供网络状态监控能力
- 支持多链环境下的区块追踪
交易控制器中的使用
- 监控交易状态变化
- 检测交易确认情况
- 触发交易状态更新
账户追踪器中的使用
- 监听新区块事件
- 更新账户余额和状态
- 同步账户信息
BlockTracker 实现机制
核心接口
js
export type BlockTracker = SafeEventEmitter & {
destroy(): Promise<void>; // 销毁追踪器
isRunning(): boolean; // 检查是否正在运行
getCurrentBlock(): string | null; // 获取当前区块号
getLatestBlock(): Promise<string>; // 获取最新区块号
checkForLatestBlock(): Promise<string>; // 检查最新区块
};
主要属性
_isRunning
:是否正在轮询_currentBlock
:当前已知的最新区块号_provider
:用于发起 JSON-RPC 请求的 provider_pollingInterval
:轮询间隔(默认20秒)_retryTimeout
:出错时的重试间隔_blockResetDuration
:区块号失效的超时时间_usePastBlocks
:是否允许回退到较旧区块#internalEventListeners
:内部事件监听器数组#pendingLatestBlock
/#pendingFetch
:用于处理并发请求的延迟 Promise
关键方法与流程
伪代码
js
class PollingBlockTracker:
初始化(opts):
provider = opts.provider
pollingInterval = opts.pollingInterval or 20秒
retryTimeout = opts.retryTimeout or pollingInterval/10
blockResetDuration = opts.blockResetDuration or 20秒
usePastBlocks = opts.usePastBlocks or false
currentBlock = null
isRunning = false
this.on('newListener', this.onNewListener);
this.on('removeListener', this.onRemoveListener);
onNewListener(eventName):
if eventName in ['sync', 'latest']:
maybeStart()
onRemoveListener():
if 没有任何监听器:
maybeEnd()
maybeStart():
if isRunning: return
isRunning = true
取消区块失效定时器
start()
触发 '_started' 事件
maybeEnd():
if not isRunning: return
isRunning = false
设置区块失效定时器
end()
触发 '_ended' 事件
start():
调用 updateAndQueue() // 启动轮询主循环
end():
清除轮询定时器
updateAndQueue():
interval = pollingInterval
try:
updateLatestBlock()
except error:
触发 'error' 事件
interval = retryTimeout
if not isRunning: return
清除轮询定时器
设置定时器 interval 后再次调用 updateAndQueue()
触发 '_waitingForNextIteration' 事件
updateLatestBlock():
latestBlock = fetchLatestBlock()
newPotentialLatest(latestBlock)
fetchLatestBlock():
如果有 pendingFetch:
return pendingFetch.promise
创建新的 deferredPromise
try:
result = provider.request('eth_blockNumber')
resolve(result)
return result
except error:
reject(error)
rejectPendingLatestBlock(error)
抛出 error
finally:
pendingFetch = undefined
newPotentialLatest(newBlock):
if shouldUseNewBlock(newBlock):
setCurrentBlock(newBlock)
shouldUseNewBlock(newBlock):
if currentBlock == null: return true
if usePastBlocks and newBlock < currentBlock: return true
if newBlock > currentBlock: return true
return false
setCurrentBlock(newBlock):
oldBlock = currentBlock
currentBlock = newBlock
触发 'latest' 事件(newBlock)
触发 'sync' 事件({oldBlock, newBlock})
setupBlockResetTimeout():
清除区块失效定时器
设置定时器 blockResetDuration 后调用 resetCurrentBlock()
resetCurrentBlock():
currentBlock = null
getLatestBlock():
if currentBlock: return currentBlock
if pendingLatestBlock: return pendingLatestBlock.promise
创建新的 deferredPromise
if not isRunning:
latestBlock = fetchLatestBlock()
newPotentialLatest(latestBlock)
resolve(latestBlock)
return latestBlock
else:
添加 internalListener
once('latest', internalListener)
return promise
destroy():
取消区块失效定时器
maybeEnd()
移除所有监听器
rejectPendingLatestBlock('Block tracker destroyed')
监听器驱动的自动启动/停止
_onNewListener
:当有监听器监听sync
或latest
事件时自动启动轮询_onRemoveListener
:当所有监听器移除后自动停止轮询
轮询主循环
flowchart TD
A[有监听器] --> B[启动 _maybeStart]
B --> C[设置 _isRunning = true]
C --> D[调用 _start]
D --> E[执行 _updateAndQueue]
E --> F[调用 _updateLatestBlock]
F --> G[调用 _fetchLatestBlock]
G --> H{获取成功?}
H -- 是 --> I[更新 _currentBlock, 触发 latest/sync 事件]
H -- 否 --> J[触发 error 事件, 设置重试间隔]
I & J --> K[setTimeout 下一轮 _updateAndQueue]
K --> L{_isRunning?}
L -- 是 --> F
L -- 否 --> M[停止]
区块获取与事件触发
_fetchLatestBlock
:通过 provider 发起eth_blockNumber
请求,支持跳过缓存_newPotentialLatest
:判断新获取的区块号是否比当前新,若是则更新并触发事件_setCurrentBlock
:设置当前区块号,并触发latest
和sync
事件
并发与缓存处理
#pendingLatestBlock
/#pendingFetch
:防止并发请求导致重复 RPC 调用getLatestBlock
:如果已有区块号直接返回,否则等待最新区块事件或主动拉取
区块失效与重置
_setupBlockResetTimeout
/cancelBlockResetTimeout
:在停止轮询后,定时清空当前区块号,防止数据过时
学习交流请添加 vx: gh313061
下期预告:构建 NonceTracker