区块链钱包开发(十四)—— 构建NonceTracker
本节介绍钱包开发中管理交易 nonce 的关键工具 NonceTracker。在以太坊钱包开发中,正确分配和追踪 nonce 对于防止交易冲突、确保交易顺序和链上状态一致性至关重要。NonceTracker 正是为此而生,帮助开发者安全、高效地管理账户的 nonce。源码:https://github.com/MetaMask/nonce-tracker/blob/main/src/NonceTr
·
概述
本节介绍钱包开发中管理交易 nonce 的关键工具 NonceTracker。在以太坊钱包开发中,正确分配和追踪 nonce 对于防止交易冲突、确保交易顺序和链上状态一致性至关重要。NonceTracker 正是为此而生,帮助开发者安全、高效地管理账户的 nonce。
源码:https://github.com/MetaMask/nonce-tracker/blob/main/src/NonceTracker.ts
NonceTracker 是什么
NonceTracker 是一个用于管理和分配以太坊账户 nonce 的工具,主要负责:
-
获取最新网络 nonce:通过区块链节点获取当前账户的最新 nonce
-
结合本地状态:结合本地已确认和待处理交易,推算下一个可用 nonce
-
并发控制:通过锁机制防止多笔交易同时分配到相同 nonce
实现机制
源码解读
async getNonceLock(address: string): Promise<NonceDetails> {
// 等待全局锁释放,保证全局排他性,防止全局并发冲突
await this._globalMutexFree();
// 等待该地址的锁释放,并加锁,防止同一地址并发分配 nonce
const releaseLock: VoidFunction = await this._takeMutex(address);
try {
// 计算多种 nextNonce 策略
// 1. 从网络获取下一个 nonce
const networkNonceResult: NetworkNextNonce = await this._getNetworkNextNonce(address);
// 2. 获取本地已确认的最大 nonce(+1 得到下一个可用)
const highestLocallyConfirmed: number = this._getHighestLocallyConfirmed(address);
// 3. 网络建议的下一个 nonce
const nextNetworkNonce: number = networkNonceResult.nonce;
// 4. 取网络和本地已确认的最大值,作为建议起点
const highestSuggested: number = Math.max(
nextNetworkNonce,
highestLocallyConfirmed,
);
// 5. 获取本地 pending(待处理)交易
const pendingTxs: Transaction[] = this.#getPendingTransactions(address);
// 6. 检查 pending 交易占用的 nonce,找到连续未被占用的最小 nonce
const localNonceResult: HighestContinuousFrom = this._getHighestContinuousFrom(pendingTxs, highestSuggested);
// 7. 组装详细的 nonce 信息
const nonceDetails: NonceDetails = {
params: {
highestLocallyConfirmed,
nextNetworkNonce,
highestSuggested,
},
local: localNonceResult,
network: networkNonceResult,
};
// 8. 取网络和本地可用 nonce 的最大值,作为最终分配的 nextNonce
const nextNonce: number = Math.max(
networkNonceResult.nonce,
localNonceResult.nonce,
);
// 返回 nextNonce、详细信息和释放锁的方法
return { nextNonce, nonceDetails, releaseLock };
} catch (err) {
// 出错时及时释放锁,防止死锁
releaseLock();
throw err;
}
}
// 等待全局锁释放(只获取一次立即释放),用于全局排他控制
async _globalMutexFree(): Promise<void> {
const globalMutex: Mutex = this._lookupMutex('global');
const releaseLock: VoidFunction = await globalMutex.acquire();
releaseLock();
}
// 获取指定 lockId 的互斥锁,并加锁,返回释放锁的方法
async _takeMutex(lockId: string): Promise<VoidFunction> {
const mutex: Mutex = this._lookupMutex(lockId);
const releaseLock: VoidFunction = await mutex.acquire();
return releaseLock;
}
// 查找或创建指定 lockId 的互斥锁
_lookupMutex(lockId: string): Mutex {
let mutex: Mutex = this.#lockMap[lockId];
if (!mutex) {
mutex = new Mutex();
this.#lockMap[lockId] = mutex;
}
return mutex;
}
// 获取网络中的下一个 nonce
async _getNetworkNextNonce(address: string): Promise<NetworkNextNonce> {
// 获取最新区块号(保证数据新鲜且一致),需要确保 base count 和 pending count 来自同一个区块
const blockNumber = await this.#blockTracker.getLatestBlock();
// 用 ethers.js 的 Web3Provider 查询该地址在该区块的交易计数(即 nonce)
const baseCount: number = await new Web3Provider(
this.#provider,
).getTransactionCount(address, blockNumber);
// 返回结构体,包含来源、nonce值、区块号和原始计数
return {
name: 'network',
nonce: baseCount,
details: { blockNumber, baseCount },
};
}
// 获取本地已确认的最大nonce值
_getHighestLocallyConfirmed(address: string): number {
const confirmedTransactions: Transaction[] =
this.#getConfirmedTransactions(address);
const highest: number = this._getHighestNonce(confirmedTransactions);
// +1表示返回下一个可用的nonce值
return Number.isInteger(highest) ? highest + 1 : 0;
}
// 获取交易列表中的最大 nonce
_getHighestNonce(txList: Transaction[]): number {
const nonces: number[] = txList.map((txMeta) => {
const { nonce } = txMeta.txParams;
return parseInt(nonce, 16);
});
const highestNonce: number = Math.max.apply(null, nonces);
return highestNonce;
}
// 检查 pending 状态的交易占用的nonce值
_getHighestContinuousFrom(
txList: Transaction[],
startPoint: number,
): HighestContinuousFrom {
const nonces: number[] = txList.map((txMeta) => {
const { nonce } = txMeta.txParams;
return parseInt(nonce, 16);
});
let highest: number = startPoint;
// 如果pending状态的交易已经占用了之前计算的最大的nonce值,就把最大的nonce值再+1
while (nonces.includes(highest)) {
highest += 1;
}
return { name: 'local', nonce: highest, details: { startPoint, highest } };
}
安全机制
-
多重来源保障:结合链上状态、本地已确认、本地 pending,确保 nonce 唯一且连续。
-
锁机制防并发:全局锁+地址锁,防止多线程/多请求环境下的 nonce 冲突。
-
自动跳过冲突:pending 交易占用的 nonce 会被自动跳过,保证新交易不会和本地未上链交易冲突。
流程图
更多推荐
所有评论(0)