最近线上出了个区块链应用的Bug,用户反馈充值不到账。

我从晚上九点开始排查,一直到第二天早上七点,整整一夜,终于找到了问题的根源。

本文分享这次排查的完整过程,包括问题现象、排查思路、定位过程、根本原因、解决方案,以及踩过的坑。

一、问题背景

先说说这个区块链应用的背景。

1. 应用介绍

我们做的是一个基于区块链的数字资产平台,主要功能:

  • 用户充值:用户把加密货币充值到平台地址
  • 用户提现:用户把资产从平台提走
  • 内部转账:用户之间的资产转移
  • 资产兑换:不同币种之间的兑换

技术栈:

  • 公链:以太坊(Ethereum)
  • 智能合约:Solidity编写的多签钱包合约
  • 后端:Java + Spring Boot
  • 数据库:MySQL
  • 缓存:Redis
  • 消息队列:Kafka

2. 问题现象

晚上九点,客服接到用户反馈:充值了ETH,但账户余额没有增加。

一开始以为是个别用户的问题,让客服查了一下,发现有十几个用户都反馈了同样的问题。而且都是最近一小时内的充值。

我意识到问题严重了,赶紧登录系统查看。

二、初步排查

1. 查看监控

首先看系统监控:

  • 服务状态:正常,没有宕机
  • 数据库:正常,连接数和CPU都正常
  • Redis:正常
  • Kafka:正常,没有消息堆积
  • 区块链节点:同步正常,区块高度正常

服务都正常,但用户充值不到账,问题出在哪里?

2. 查看日志

查看充值服务的日志,发现:

  • 充值请求正常接收
  • 区块链节点查询正常
  • 但事件监听服务有报错:"交易确认数不足,等待中"

哦,可能是交易确认的问题。

3. 查看链上交易

用用户提供的交易哈希,去区块链浏览器上查:

  • 交易存在,已经被打包
  • 交易确认数:12个确认(以太坊通常12个确认就足够了)
  • 交易状态:成功
  • 接收地址:是我们的充值地址

链上交易是成功的,但系统没有给用户入账。问题出在链下的处理逻辑。

三、深入排查

1. 事件监听机制

我们的充值监听机制是这样的:

  • 启动一个事件监听器,监听合约的Deposit事件
  • 监听到事件后,检查交易确认数
  • 确认数达到阈值(12个)后,给用户入账
  • 入账后,记录到数据库,发送通知

理论上,这个流程是没问题的。但为什么会漏单?

2. 检查事件监听日志

仔细看事件监听服务的日志,发现一个奇怪的现象:

  • 有些交易,监听到了事件,但确认数一直显示为0
  • 有些交易,确认数正常增长
  • 确认数为0的交易,一直卡在"等待确认"的状态

为什么确认数会是0?链上明明已经有12个确认了。

3. 检查区块同步

怀疑是区块链节点同步的问题。

查看节点的同步状态:

  • 当前区块高度:正常,和公链一致
  • 最新区块时间:正常
  • 节点同步状态:已同步

节点同步没问题。那为什么确认数是0?

4. 检查确认数计算逻辑

看确认数的计算代码:

long currentBlock = web3j.ethBlockNumber().send().getBlockNumber().longValue();
long txBlock = transactionReceipt.getBlockNumber().longValue();
long confirmations = currentBlock - txBlock;

逻辑很简单:当前区块高度 - 交易所在区块高度 = 确认数。

但问题是,transactionReceipt是从哪里来的?

看代码,交易回执是从缓存里取的:

TransactionReceipt receipt = redisTemplate.opsForValue().get("tx:receipt:" + txHash);

5. 发现问题

问题找到了!

交易回执是在交易刚被打包时获取的,然后缓存到Redis里。但缓存的交易回执里,blockNumber是交易刚被打包时的区块高度。

之后,虽然区块链在继续出块,但缓存的交易回执没有更新。所以计算确认数的时候,用的是缓存里的旧区块高度,导致确认数一直是0(或者很小)。

正常情况下,缓存会在一定时间后过期,然后重新获取最新的交易回执。但我们的缓存过期时间设得太长了(24小时),而且没有主动更新机制。

所以,交易被打包后,缓存了旧的回执,确认数一直不够,就一直卡在等待确认的状态。24小时后缓存过期,才会重新获取,这时候确认数够了,才会入账。但用户等不了24小时啊!

四、为什么之前没出问题

这个Bug其实一直存在,但为什么之前没爆发?

1. 之前的流量小

之前充值的用户不多,偶尔有几个漏单,客服手动处理了,没有引起重视。

2. 最近流量增大

最近做了活动,充值用户暴增,漏单的数量也多了,才暴露出来。

3. 以太坊升级

以太坊最近进行了合并(The Merge,2022年9月),出块时间从平均13秒变成了固定12秒。出块时间的变化,可能影响了确认数的计算逻辑。

虽然这不是根本原因,但可能加剧了问题。

五、解决方案

找到问题后,开始修复。

1. 紧急修复

首先,紧急处理已经漏单的交易:

  • 写了一个脚本,扫描所有"等待确认"状态超过1小时的交易
  • 重新从区块链获取最新的交易回执
  • 确认数够了的,手动入账
  • 一共处理了37笔漏单交易

处理完后,用户的余额都到账了,客服反馈用户满意。

2. 根本修复

然后,修复代码逻辑:

方案一:缩短缓存时间

  • 把交易回执的缓存时间从24小时改成5分钟
  • 这样,5分钟后就会重新获取最新的回执
  • 缺点:频繁查询区块链节点,增加节点压力

方案二:不缓存交易回执

  • 每次计算确认数时,都重新获取交易回执
  • 缺点:性能差,节点压力大

方案三:缓存交易回执,但定期更新

  • 缓存交易回执,但后台有个定时任务,定期更新待确认交易的回执
  • 优点:平衡了性能和准确性
  • 我们选择了这个方案

具体实现:

// 监听到事件后,缓存交易回执,但标记为"待确认"
// 定时任务每分钟扫描"待确认"的交易
// 重新获取交易回执,更新确认数
// 确认数达到阈值后,入账并标记为"已确认"

3. 增加告警

增加监控和告警:

  • "待确认"状态超过30分钟的交易数量,超过阈值就告警
  • 事件监听服务的错误率,超过阈值就告警
  • 充值入账延迟,超过阈值就告警

这样,以后再出问题,能及时发现,不用等用户反馈。

六、排查过程中的其他坑

在排查过程中,还遇到了其他一些坑。

坑一:区块链节点的不同步

我们用了两个区块链节点做负载均衡。

有一次,查交易回执,一个节点返回正常,另一个节点返回"交易不存在"。

原因是,两个节点的同步状态不一致,一个同步到了最新区块,另一个还在同步中。

解决:

  • 检查所有节点的同步状态
  • 只向已同步的节点发送请求
  • 增加节点健康检查

坑二:智能合约事件的顺序

智能合约的事件,可能在同一个区块里触发多次。

我们的事件监听,没有处理同一个区块里多个事件的顺序问题,导致有时候入账顺序错乱。

解决:

  • 事件处理时,按交易索引和日志索引排序
  • 同一个区块的事件,按顺序处理
  • 增加幂等性处理,防止重复入账

坑三:数据库事务和链上状态的一致性

有一次,数据库入账成功了,但链上交易实际上失败了(因为gas不足)。

原因是,我们只检查了交易是否被打包,没有检查交易的状态(status字段)。

解决:

  • 入账前,检查交易的status字段,必须是成功(1)
  • 失败的交易,不入账,记录异常
  • 增加对账机制,定期核对链上和链下数据

坑四:重放攻击

有用户尝试用同一笔交易,在不同的账户上重复充值。

原因是,我们没有检查交易的from地址是否和用户绑定的地址一致。

解决:

  • 充值时,验证交易的from地址是否是用户绑定的地址
  • 交易哈希做唯一索引,防止重复入账
  • 增加风控规则,异常充值自动拦截

七、经验总结

这次排查,让我对区块链应用的开发有了更深的理解。

1. 区块链应用的特殊性

区块链应用和传统应用有很大不同:

  • 数据最终一致性:链上数据是最终一致的,不是实时的
  • 交易不可篡改:一旦上链,无法修改
  • 确认时间:交易需要确认,不能实时到账
  • 节点同步:节点可能不同步,数据可能不一致

开发区块链应用,要充分考虑这些特殊性。

2. 缓存的使用要谨慎

在区块链应用中,缓存的使用要特别谨慎:

  • 链上数据是不断变化的,缓存可能过期
  • 缓存时间不能太长,否则数据不一致
  • 关键数据(如交易回执),要定期更新
  • 缓存要有主动失效机制

3. 监控和告警很重要

区块链应用的问题,往往不是服务宕机,而是数据不一致。

传统的监控(CPU、内存、服务状态)发现不了这些问题。需要针对区块链应用的特点,增加专门的监控:

  • 交易确认延迟
  • 事件监听延迟
  • 链上链下数据一致性
  • 漏单率和重复率

4. 幂等性和对账

区块链应用,一定要做好幂等性和对账:

  • 所有操作都要幂等,防止重复处理
  • 定期对账,核对链上和链下数据
  • 发现不一致,及时告警和修复
  • 异常交易要有处理流程

5. 测试要充分

区块链应用的测试,要覆盖各种边界情况:

  • 交易确认数不足
  • 节点不同步
  • 交易失败
  • 事件重复
  • 区块重组(reorg)

这些情况在测试环境中不容易模拟,但在生产环境中可能出现。

八、写在最后

这次排查,从晚上九点到第二天早上七点,整整一夜。

虽然很累,但收获很大。我对区块链应用的开发、区块链的特性、问题排查的思路,都有了更深的理解。

区块链技术还在发展中,很多基础设施还不够成熟。开发区块链应用,需要比传统应用更加谨慎,更加注重数据一致性和异常处理。

2022年了,区块链从概念走向落地,越来越多的应用开始使用区块链技术。但落地的过程中,会遇到各种各样的问题。作为开发者,我们需要不断学习,不断踩坑,才能把区块链应用做好。

最后,用一句话总结:"区块链应用的坑,大部分来自链上和链下的数据不一致。做好缓存、监控、幂等、对账,就能避开大部分坑。"

愿你的区块链应用,没有Bug,不用熬夜排查。