2

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

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

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在上面的示例中会出现恐慌)。

4

1 回答 1

2

捕获异常的唯一方法是对生成异常的 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。对于这些场景,我们正在讨论添加一个特定构造的可能性,该构造是一个原子并且一旦它被删除就具有解析回调。我们称之为Safehttps ://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) 尝试run()从质押池 ( ) 中提取资金 ( contractB),但由于最近取消质押,资金可能仍被锁定,因此提取失败 (contractB.run()失败)。回调被称为 ( contractA.callback()) 并检查承诺 (of contractB.run) 的成功。由于提款失败,回调将状态恢复到原始状态(恢复状态)。

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

于 2020-07-20T16:20:32.243 回答