如何处理 NEAR 跨合约调用中的异常?

Posted

技术标签:

【中文标题】如何处理 NEAR 跨合约调用中的异常?【英文标题】:How to handle exceptions in NEAR cross contract calls? 【发布时间】:2020-11-09 05:37:15 【问题描述】:

如何在合约之间的异步调用链中捕获和处理异常?

假设我的事务发起了以下调用:

contractA.run()
  -> do changes in contractA
  -> calls contractB.run()
     -> do changes in contractB
  -> then calls another method on contractA: contractA.callback()
     * callback() crashes

在 Promise 中出现异常后,NEAR 不会回滚过去 Promise 中发生的更改。我也没有在near-sdk 中看到任何处理异常的方法。

一个想法是返回错误而不是抛出异常并创建一堆私有函数来更新错误值后的状态并添加/释放互斥锁。然而,这并不能解决有时我们无法控制的问题,例如在外部智能合约中(例如,如果contractB.do 在上面的示例中会出现恐慌)。

【问题讨论】:

是的,我们目前遇到了这个问题,即来自 Promise 的错误不会从运行时暴露出来。同样正如您所提到的,我们缺乏对合约状态的这种“更新”抽象。 【参考方案1】:

捕获异常的唯一方法是对产生异常的 Promise 进行回调。

在解释的场景中,contractA.callback() 不应该崩溃。您需要足够仔细地构建合约以避免回调失败。大多数情况下是可以做到的,因为您控制回调的输入和附加的气体量。如果回调失败,则类似于异常处理代码中出现异常。

还请注意,您可以确保 callback 已正确安排,并在 contractA.run() 中附加了足够的气体。如果不是这种情况,例如您没有足够的 gas 附加到 run,回调和其他承诺的调度将失败,并且来自 run 更改的整个状态将回滚。 但是一旦run 完成,从run 的状态变化就被提交,callback 必须小心处理。

lockup 合约中有几个地方允许回调失败:https://github.com/near/core-contracts/blob/6fb13584d5c9eb1b372cfd80cd18f4a4ba8d15b6/lockup/src/owner_callbacks.rs#L7-L24

还有大部分回调没有失败的地方:https://github.com/near/core-contracts/blob/6fb13584d5c9eb1b372cfd80cd18f4a4ba8d15b6/lockup/src/owner_callbacks.rs#L28-L61


指出在某些情况下合约不想依赖其他合约的稳定性,例如当流量为A --> B --> A --> B。在这种情况下,B 无法将回调附加到提供给A 的资源。对于这些场景,我们正在讨论添加一个特定构造的可能性,该构造是一个原子并且一旦它被删除就具有解析回调。我们称之为Safe:https://github.com/nearprotocol/NEPs/pull/26


编辑

如果contractB.run 失败并且我想更新contractA 中的状态以从contractA.run 回滚更改怎么办?

在这种情况下,contractA.callback() 仍然被调用,但它的依赖关系为 PromiseResult::Failed contractB.run

所以callback() 可以修改contractA 的状态以恢复更改。

例如,锁仓合约实现的回调,用于处理从权益池合约中退出:https://github.com/near/core-contracts/blob/6fb13584d5c9eb1b372cfd80cd18f4a4ba8d15b6/lockup/src/foundation_callbacks.rs#L143-L185

如果我们调整名称以匹配示例:

锁仓合约(contractA)试图从质押池(contractB)中提取资金(run()),但由于最近取消质押,资金可能仍被锁定,因此提取失败(contractB.run()失败)。 回调被调用 (contractA.callback()) 并检查承诺 (contractB.run) 是否成功。由于提款失败,回调将状态恢复到原来的状态(恢复状态)。

其实稍微复杂一点,因为实际的序列是A.withdraw_all -> B.get_amount -> A.on_amount_for_withdraw -> B.withdraw(amount) -> A.on_withdraw

【讨论】:

感谢您的回答!使用contractA.callback 有意义。如果contractB.run 失败并且我想更新contractA 中的状态以从contractA.run 回滚更改怎么办? 添加到答案中

以上是关于如何处理 NEAR 跨合约调用中的异常?的主要内容,如果未能解决你的问题,请参考以下文章

智能合约如何处理多个用户和不同的存储?

JVM 是如何处理异常的?

如何处理 lambda 表达式中的异常 [重复]

SpringBoot.09.SpringBoot中如何处理Filter抛出的异常

如何处理 C++ 加载的 C# DLL 中的异常

这张关于 NEAR 平台上如何处理交易的图片有多准确?