在以太坊开发中,SendTransaction
和 CallContract
是两种核心的链交互方式,但它们的适用场景和错误处理机制截然不同。本文将深入解析它们的区别并提供错误处理最佳实践。
🚀 核心区别:状态改变 vs 状态查询
特性 | SendTransaction |
CallContract |
---|---|---|
链上状态改变 | ✅ 修改链上数据 | ❌ 只读操作 |
Gas 消耗 | ✅ 消耗真实 Gas | ❌ 模拟执行,不消耗 Gas |
执行位置 | 全网矿工执行 | 本地节点执行 |
返回值 | 交易哈希(txHash) | 合约调用的原始字节结果 |
典型场景 | 转账、合约写操作 | 数据查询、合约读操作 |
⚠️ 错误处理机制对比
1. SendTransaction
错误处理(写操作)
// 发送交易
txHash, err := vj.client.SendTransaction(ctx, signedTx)
if err != nil {// 立即错误:网络问题/参数错误log.Printf("发送失败: %v", err)return
}// 等待链上确认(关键!)
receipt, err := bind.WaitMined(ctx, vj.client, txHash)
if err != nil {// 等待错误:节点超时等log.Printf("确认失败: %v", err)
}if receipt.Status == 0 { // 检查最终状态// 链上执行失败:Gas耗尽/合约revertlog.Printf("交易执行失败!")// 解析失败原因(需要debug)_, err := vj.client.TransactionReceipt(ctx, txHash)// 或使用 debug_traceTransaction
}
错误类型:
-
立即错误:交易广播前失败(签名无效、Nonce错误)
-
链上错误:交易打包后执行失败(需检查
receipt.Status
)
2. CallContract
错误处理(读操作)
result, err := vj.client.CallContract(ctx, msg, block.Number())
if err != nil {// 立即错误:包含合约revert!if strings.Contains(err.Error(), "execution reverted") {// 提取revert原因revertData := extractRevertData(err)log.Printf("合约revert: %x", revertData)} else {log.Printf("调用失败: %v", err) // 网络/参数错误}return
}// 解析成功结果
var output SomeType
err = contractABI.Unpack(&output, "methodName", result)
错误类型:
-
单一立即错误:包含所有失败原因(网络问题、参数错误、合约
revert
)
🔧 获取错误码的最佳实践
-
写操作(修改状态)
// 检查交易回执的Status字段 if receipt.Status != 1 { // 1=成功, 0=失败fmt.Println("错误码:", receipt.Status) }
-
需要结合交易回执分析
-
使用
debug_traceTransaction
获取详细revert原因
-
-
读操作(查询状态)
if err != nil {// 直接获取错误对象fmt.Println("错误详情:", err.Error()) }
-
错误信息直接包含revert数据
-
可通过ABI解析revert消息:
unpacked, _ := abi.UnpackRevert(err.Data()) fmt.Println("Revert reason:", string(unpacked))
-
🎯 何时使用哪种方法?
场景 | 推荐方法 | 原因 |
---|---|---|
转账/修改合约状态 | SendTransaction |
需等待链上确认,错误分阶段处理 |
查询余额/调用view函数 | CallContract |
立即返回结果,错误一次性处理 |
估算Gas成本 | CallContract |
不消耗真实Gas,可模拟执行结果 |
需要交易回执 | SendTransaction |
唯一获取矿工打包确认信息的方式 |
💡 关键总结
-
修改状态 →
SendTransaction
-
错误处理分两步:发送错误 + 链上执行错误
-
必须检查
receipt.Status
-
-
读取状态 →
CallContract
-
错误立即可知,包含完整revert数据
-
适合快速失败场景
-
-
高级调试:
-
使用
debug_traceTransaction
分析失败交易 -
解析
revert
消息:abi.UnpackRevert(err.Data())
-
📌 黄金法则:
当你的操作需要消耗Gas时用SendTransaction
,否则用CallContract
。
永远不要假设交易一定成功,检查receipt.Status
是必须步骤!