关键代码 在ethers中
const tx2 = {
to: addressWETH,
data: param2,
value: ethers.parseEther("0.001"),
// to: WETH合约地址
// data: calldata
// value: 发送的ETH数量
// gasLimit: 交易的gas上限
// gasPrice: 交易的gas单价
// nonce: 交易的nonce值
};
const wallet = new ethers.Wallet(privateKey, provider);
// 发起交易,写入操作需要 wallet.sendTransaction(tx)
const receipt1 = await wallet.sendTransaction(tx2);
// 等待交易上链
await receipt1.wait();
一、交易的整个流程
1. 交易创建(用户端)
- 用户操作:用户通过钱包(如 MetaMask 或代码中的 wallet.ts)输入接收地址、转账金额(ETH 数量)以及可选的附加数据(例如智能合约调用数据)。
- Gas 设置:用户设置 Gas 限制(Gas Limit,表示愿意支付的最大计算单位)和 Gas 价格(Gas Price 或 EIP-1559 的 Max Fee 和 Priority Fee,表示每单位 Gas 愿意支付的费用)。
- Nonce 获取:钱包会查询用户账户当前的 nonce 值(交易计数),确保这笔交易的 nonce 是正确的(通常是当前账户的交易计数)。
- 交易对象构建:钱包构建一个未签名的交易对象,包含字段如 to(接收地址)、value(转账金额)、gasLimit、gasPrice、nonce、data(附加数据)等。
- 签名交易:用户使用私钥对交易进行签名,生成签名数据(r、s、v),证明这笔交易是由账户持有者发起的。
2. 交易广播(网络层)
- 提交到节点:签名后的交易被发送到以太坊网络的一个节点(通常通过钱包连接的 RPC 端点)。
- 交易池加入:节点验证交易的基本格式和签名后,将其加入本地交易池(Mempool),等待矿工处理。同时,节点会将交易广播给其他节点,扩散到整个网络。
- 等待确认:交易在交易池中等待矿工挑选,矿工会优先选择 Gas 价格较高的交易。
3. 交易打包(矿工端)
- 交易选择:矿工从交易池中选择一组交易,通常按 Gas 价格从高到低排序,确保总 Gas 消耗不超过区块 Gas 限制。
- 区块构建:矿工将选定的交易打包进一个候选区块,同时解决工作量证明(PoW,在以太坊合并前)或通过权益证明(PoS,合并后)达成共识。
- 交易执行:在构建区块时,矿工会在本地 EVM 上模拟执行每笔交易,计算状态变化和 Gas 消耗。
4. 交易在 EVM 中的执行流程
以太坊虚拟机(EVM)是以太坊的核心执行环境,负责处理交易和智能合约逻辑。以下是交易在 EVM 中的执行步骤:
- 初始化:
- EVM 加载交易数据,验证签名和 nonce 是否正确。
- 检查发送账户是否有足够的 ETH 支付 Gas 费用(Gas Limit × Gas Price)。
- 扣除预估的 Gas 费用(暂时从账户余额中锁定)。
- 转账执行(如果是简单转账):
- 如果是普通 ETH 转账,EVM 直接更新发送者和接收者的余额状态。
- 扣除发送者的 ETH 金额,增加接收者的 ETH 金额。
- 智能合约执行(如果有 data 字段):
- 如果交易包含 data,EVM 会调用目标地址的智能合约代码。
- 加载合约字节码,逐条执行指令(类似计算机执行程序)。
- 每执行一步操作(如加法、存储数据)都会消耗一定的 Gas。
- 如果调用过程中触发其他合约调用,EVM 会创建新的执行上下文,递归处理。
- 状态更新:
- 执行完成后,EVM 更新账户余额、存储数据等状态(写入区块链状态树)。
- 计算实际使用的 Gas,退还未使用的 Gas 费用到发送者账户。
- 日志和事件:
- 如果合约执行过程中生成了事件(Event),EVM 会记录日志,供外部应用查询。
- 结果返回:
- EVM 返回执行结果(成功或失败),包括返回值(如果有)和使用的 Gas 量。
5. 区块确认和最终性
- 区块广播:矿工完成区块构建后,将区块广播到网络,其他节点验证区块的合法性。
- 交易确认:如果区块被网络接受,交易被认为是“已确认”。通常需要等待多个区块确认(例如 12 个区块)以确保交易不可逆。
- 最终性:在以太坊合并后(PoS),交易在几个 epoch(周期)后达到最终性,几乎不可能被回滚。
6. 用户反馈
- 钱包会监控区块链状态,显示交易状态(待处理、已确认、失败等)。
- 用户可以通过区块链浏览器查看交易详情,如交易哈希、Gas 使用情况等。
执行失败的触发条件
交易执行可能失败,以下是常见的触发条件和处理方式:
- Gas 不足:
- 触发:如果交易执行过程中 Gas 消耗超过 Gas Limit,EVM 会停止执行并抛出“Out of Gas”错误。
- 结果:交易失败,状态回滚(不影响区块链状态),但已消耗的 Gas 费用不会退还,作为矿工的补偿。
- 用户应对:增加 Gas Limit 重试交易。
- 余额不足:
- 触发:发送账户没有足够的 ETH 支付转账金额或 Gas 费用。
- 结果:交易被拒绝或失败,状态不更新。
- 用户应对:确保账户有足够余额。
- Nonce 错误:
- 触发:交易的 nonce 值与账户当前 nonce 不匹配(重复或跳跃)。
- 结果:交易被拒绝或挂起。
- 用户应对:检查并设置正确的 nonce 值。
- 智能合约逻辑错误:
- 触发:合约代码中抛出异常(例如 require 或 assert 条件不满足)。
- 结果:交易失败,状态回滚,但已消耗的 Gas 费用不退还。
- 用户应对:检查合约逻辑或输入参数。
- 签名无效:
- 触发:交易签名不匹配发送地址。
- 结果:交易被节点拒绝,不进入交易池。
- 用户应对:确保使用正确的私钥签名。
- 网络问题或矿工未打包:
- 触发:Gas 价格过低,矿工未选择打包;或网络拥堵,交易长时间未确认。
- 结果:交易长时间挂在交易池中,可能最终被丢弃。
- 用户应对:提高 Gas 价格或使用“取消交易”(发送 nonce 相同的空交易覆盖)。
- 目标地址无效或合约不存在:
- 触发:接收地址不存在或不是有效合约地址。
- 结果:交易可能失败,具体取决于调用逻辑。
- 用户应对:检查目标地址是否正确。
总结来说,发起一笔 ETH 交易涉及用户端创建和签名、网络广播、矿工打包、EVM 执行、区块确认等多个步骤。EVM 是核心执行环境,负责处理交易逻辑和状态更新。交易失败可能由 Gas 不足、余额不足、逻辑错误等多种原因触发,用户需要根据具体情况调整参数或逻辑。
二、Nonce 在以太坊交易流程中的作用
在以太坊中,nonce(即“number used once”的缩写)是一个非常重要的概念,用于确保交易的安全性和顺序。它的作用主要体现在以下几个方面:
- 防止重放攻击:
- 每笔交易都有一个唯一的 nonce 值,这个值是由发送账户(地址)生成的递增数字。
- 网络会检查交易的 nonce 是否与账户当前的 nonce 值匹配。如果不匹配,交易会被拒绝。
- 这样可以防止恶意用户重复发送同一笔交易(即重放攻击),因为相同的 nonce 只能使用一次。
- 确保交易顺序:
- nonce 是一个连续的整数,通常从 0 开始,每发送一笔交易就增加 1。
- 以太坊网络按照 nonce 的顺序处理交易。如果一个账户发送了多笔交易,网络会等待 nonce 较小的交易先被处理,然后再处理 nonce 较大的交易。
- 如果发送了一笔 nonce 较高的交易,而之前的 nonce 交易尚未完成,后面的交易会被挂起,直到前面的交易被确认。
- 账户状态管理:
- nonce 实际上是账户状态的一部分,存储在区块链上。
- 每当账户发送一笔交易,账户的 nonce 值就会增加 1,这确保了交易的唯一性和顺序性。
- 离线签名中的作用:
- 在离线签名场景中,正确管理 nonce 尤为重要。因为离线环境无法实时查询区块链状态,所以需要提前知道或估算正确的 nonce 值。
- 如果 nonce 值设置错误(例如重复使用旧的 nonce 或跳过某个值),交易可能会被拒绝或卡住。
nonce 的获取和管理是通过 getNonce 方法实现的
private async getNonce(address: string, network: Network): Promise<number> {
try {
const provider = new ethers.JsonRpcProvider(network.rpcUrl);
return await provider.getTransactionCount(address, "pending");
} catch (error) {
console.error("获取nonce失败:", error);
throw new Error("无法获取交易nonce,请检查网络连接");
}
}
- 作用:getNonce 方法通过连接到指定网络的 RPC 端点(network.rpcUrl),查询给定地址(address)的当前交易计数(即 nonce 值)。参数 "pending" 表示获取包括待处理交易在内的最新 nonce 值。
- 使用场景:在创建未签名交易时(createUnsignedTransaction 方法中),会调用此方法来获取最新的 nonce 值,并将其赋值给交易对象(transaction.nonce)。
- 错误处理:如果获取 nonce 失败(例如网络连接问题),方法会抛出错误,提示用户检查网络连接。
离线签名中 nonce 管理的注意事项
在离线签名中,nonce 管理尤为重要,因为无法实时查询区块链状态。以下是一些关键注意事项:
- 提前获取并保存 nonce 值:
- 在离线签名之前,建议在有网络连接时获取最新的 nonce 值,并将其存储在安全的地方。
- 如果有多笔交易需要签名,应记录每一笔交易的 nonce 值,确保它们按顺序递增。
- 避免 nonce 重复或跳跃:
- 重复使用相同的 nonce 会导致交易被拒绝,因为以太坊网络会认为这是重放攻击。
- 如果 nonce 值跳跃(例如跳过某个值),后续交易可能会被挂起,直到缺失的 nonce 交易被处理。
- 离线环境下的 nonce 估算:
- 如果完全离线,可以基于之前记录的 nonce 值进行递增估算。但需要注意,如果之前有未完成的交易,这种估算可能导致 nonce 错误。
- 一种策略是预留一定范围的 nonce 值,并在后续联网时验证这些值是否正确。
- 会话管理中的 nonce 跟踪:
- 在代码中,OfflineSigningSession 对象管理着一组未签名和已签名的交易。建议在会话中跟踪 nonce 值,确保会话内的交易按顺序分配 nonce。
- 如果会话中包含多笔交易,应确保它们的 nonce 值连续递增。
- 错误处理和用户提示:
- 如果因 nonce 错误导致交易失败,应向用户提供清晰的错误信息,说明可能的原因(例如 nonce 重复或跳跃)。
- 建议提供手动设置 nonce 的选项,允许用户在必要时更正值。
- 与在线环境的同步:
- 在离线签名完成后,回到在线环境时,应验证所有签名交易的 nonce 值是否与区块链状态一致。
- 如果发现 nonce 冲突,需重新生成交易并重新签名。
总结来说,nonce 是以太坊交易流程中的一个关键字段,用于防止重放攻击、确保交易顺序以及维护账户状态的完整性。在离线签名中,需要特别注意 nonce 的管理,以确保生成的交易能够被网络接受。