小议以太坊应用状态同步

简单说说以太坊应用状态同步的 why、what 和 how。

除非业务逻辑非常简单单一,典型的以太坊应用一般都绕不开状态同步的问题。这里所说的状态指的是“交易状态”,所谓状态同步即指:将链上交易状态同步到业务数据库中。

为什么要做状态同步?

这个问题的答案很直接,无外乎两点:

  • 以太坊的速度和查询无法支撑良好的用户体验,甚至连“普通的”用户体验都无法支撑。
  • 业务数据关联的需要。比如,某个用户操作除了导致(本地数据库中的)业务数据本身的变化之外,还会提交以太坊的交易。那么,此时通常需要将两者(链上和链下)关联在一起。

有时,你会看到有些文章和书籍上提到:indexing 或 caching,其本质都是交易状态同步。

另外请注意:本文所指的状态同步都是指后端而言,前端状态同步要简单很多,各位可参见我的。

同步哪些数据?

用一句话回答就是:it depends。但并非没有章法可循,通常的步骤:

  • 创建业务模型。
    • 这部分与技术无关,完全由需求分析人的能力决定。
  • 区分链上和链下数据。
    • 链下数据决定了数据库模型。
    • 链上数据决定了合约状态。
  • 设计将两者关联起来的合约事件。
    • 何时 emit
    • 哪些 indexed

按以上三步走完,哪些数据要同步自然呼之欲出。

这里列出一些参考,主要无外乎以下几类:

  • 地址类,如:owner。
  • 交易类,如:tx_hash 和 tx_timestamp。
  • Token 类,如:address、token_id。
  • 合约状态类,这依赖于合约设计本身。

在进入下一节之前,再啰嗦两句:

  • 善用 Token,不要望文生义认为就是“炒币”!
    • token 究其本身而言可视为用户的权益:谁拥有谁就可以行使。如此推而广之:权限、优惠劵、房卡、游戏道具等,皆可以 token 实现。借助以太坊本身的 ERC20 和 ERC721/ERC1155 的合约基础设施,可以简化设计。
  • 最简原则:能不同步的就不同。
    • 比如,balance,除非 balance 本身参与数据库查询,那么只在前端同步就够了。

状态同步模式

状态同步模式的掌握离不开必要的基础知识,如果你对于 ethers.js 还不熟悉,那就可以就此止步,先去看看我的这两篇文章:

有同学可能会奇怪:为何不借助 the graph 的力量?很简单:

  • 不一定有现成的 subgraph。
  • 除非打算做成类似 uniswap 这类公共服务,开发自己的 subgraph 属于小题大做。
  • 开发 subgraph 比直接用 ethers.js 复杂多了。
  • the graph 要花钱。当然,我不排斥付费服务,但对于付费决策需要谨慎。

模式 1:Event Listener

这个模式的特点是:前端和后端没有任何通信,完全依赖以太坊的事件机制。两方的职责:

  • 前端:
    1. 发起交易。
    2. 等待交易结束。
    3. 检查数据库是否更新,这一步显然需要调用后端暴露的 api 接口。但在整个交易过程中,它与后端没有任何通信。
  • 后端:
    1. 启动时注册事件监听器。
    2. 收到事件后,执行事件处理函数。

适用场合:

  • 无需要任何前端信息就能全面更新数据的场景,如:仅跟踪状态(如 owner)变化。
  • 反之,则使用下一节介绍的模式。

优点:

  • 简单,介入方少。

缺点:

  • 完全依赖事件,若错过事件则状态同步失败。而事件丢失在各个 provider 中并不少见。
  • 为了能拿到全部数据,需要依赖一些编程技巧(参见上文链接),将这些信息发到链上,然后在事件处理时取出。但,这些信息可能并不适合发到链上。如:联系方式。
  • 对于后端负责交易费的场景,不适合。

模式 2:Tx Checker

与之相反,此模式要求前后端精确配合。整个交互可复杂可简单,毕竟涉及到两阶段提交的问题。简单的说,这里双方职责:

  • 前端:
    1. 提交事务。
    2. 待事务提交时候,向后端发送请求,包含业务数据和 tx_hash。
    3. 等待交易结束。
    4. 检查数据库是否更新,同上,timeout 可设为 n 个检查周期。
  • 后端:
    1. 启动时创建 tx checker,设定检查周期。
      • 检查条件:tx_timestamp 为空。
    2. 在接收到前端请求时,更新数据库。
    3. 在 checker 内检查事务状态:
      • 若完成,则更新。
      • 若失败,则删除。

适用场合:

  • 需要前端信息更新数据的场景,如:仅联系方式。

优点:

  • 过程更可控。相比而言,前一个模式则可能由于事件丢失导致前端始终处于状态检查状态。

缺点:

  • 更复杂,典型的多事务处理场景。以上示例只是简单说明,若要更可靠,则整个交互过程更复杂。

模式 3:Log Watcher

此模式一般和前两个模式联合使用,通常作为一种补救措施。并且,它还有一个附加的好处:在适用于模式 1 的场景之下时,它客观上起到了数据库恢复器的作用。

它的参与方只有一个:后端,其职责就一个:查询日志,处理日志,修正数据库的不一致。

在实际使用时,它有两个阶段:启动阶段和运行阶段。这两个阶段请采用不同的查询日志的方式:

  • 启动阶段:从初始 block 开始查询。
  • 运行阶段:只看最近若干 block,至于这个个数如何界定,依赖于各个 EVM 兼容链的出块速度和时间。

对于数据库熟悉的同学应该敏感地发现这个策略跟数据库的备份策略很像:全量备份和增量备份的结合。

总结

以上虽然交互双方是以前端和后端进行的说明,但“前端”换作另一个词或许更合适:交易发起方。因为 tx 未必只能前端发起。如,对于后端承担交易费用的场景,这个交易发起方则为后端。

但这个小的的变动并不影响以上模式的描述,在此只是提醒各位注意。

基本上,采用本文介绍的三种模式,就可以很容易的构建自己的状态同步基础设施。并且,同样的思路亦可应用到其他情形。

最后,广告时间:构建以上模式,离不开对于 ethers 这类工具的熟练掌握。小弟拙作则以大量的实际示例可帮助初学者快速迈过学习门槛:

本文参与2022世界杯预选赛赛程直播社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 6天前
  • 阅读 ( 285 )
  • 学分 ( 10 )
  • 分类:以太坊

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
胡键
胡键

6 篇文章, 533 学分