我们将要实现的另一个最佳实践称为防御性示例(Defensive Example),这是一种模式,它允许我们在不强制原始交易失败的情况下重新处理失败的消息。让我们解释一下它的工作原理。
接收和处理消息
当目标区块链接收到消息时,CCIP路由器会调用ccipReceive
函数。此函数作为合约处理传入CCIP消息的入口点,通过onlyRouter
和onlyAllowlisted
修饰符实施关键的安全检查。
以下是该过程的逐步分解:
通过ccipReceive
进入:
ccipReceive
函数被调用,传入一个包含待处理消息的Any2EVMMessage
结构体。
安全检查确保调用来自授权的路由器、已允许的源链和已允许的发送者。
处理消息:
ccipReceive
调用processMessage
函数,该函数是外部的,以利用Solidity的try/catch错误处理机制。注意:onlySelf
修饰符确保只有合约本身可以调用此函数。
在processMessage
内部,使用s_simRevert
状态变量检查模拟回滚条件。这个模拟由只有合约所有者可调用的setSimRevert
函数来切换。
如果s_simRevert
为假,processMessage
调用_ccipReceive
函数进行进一步的消息处理。
_ccipReceive
中的消息处理:
_ccipReceive
从消息中提取并存储各种信息,如messageId
、解码后的sender
地址、代币数量和数据。
然后发出一个MessageReceived
事件,表示消息处理成功。
错误处理:
如果在处理过程中发生错误(或触发了模拟回滚),ccipReceive
内的catch块将被执行。
失败消息的messageId
被添加到s_failedMessages
中,消息内容被存储在s_messageContents
中。
发出一个MessageFailed
事件,允许以后识别和重处理失败的消息。
失败消息的重新处理
retryFailedMessage
函数提供了一种机制,如果CCIP消息处理失败,可以用来恢复资产。它专门设计用来处理消息数据问题导致整个处理无法进行,但允许代币恢复的场景:
初始化:
只有合约所有者可以调用此函数,提供失败消息的messageId
和代币恢复用的tokenReceiver
地址。
验证:
使用s_failedMessages.get(messageId)
检查消息是否已失败。如果没有,就回滚交易。
状态更新:
将消息的错误代码更新为RESOLVED
,以防止重复进入和多次重试。
代币恢复:
使用s_messageContents[messageId]
检索失败的消息内容。
将与失败消息关联的锁定代币转移到指定的tokenReceiver
,作为一个逃生舱口,而无需再次处理整个消息。
事件发出:
发出一个MessageRecovered
事件,以信号代币已成功恢复。
这个函数展示了一个优雅的资产恢复解决方案,即使在消息处理遇到问题时也能保护用户的价值。