From 9de574856cf9d948a1f67f48181bc735302043f8 Mon Sep 17 00:00:00 2001
From: tynes The Versioning is handled in the most significant bits of the nonce, similarly to how it is handled by
the Predeploys
@@ -304,6 +308,7 @@ relayMessage
InvariantssendExpire
InvariantsrelayExpire
InvariantsConstant Value Address 0x4200000000000000000000000000000000000023
+MESSAGE_VERSION
uint256(0)
EXPIRY_WINDOW
uint256(7200)
L2ToL2CrossDomainMessenger
is a higher level abstraction on top of the CrossL2Inbox
that
@@ -317,6 +322,21 @@
+sendExpire
Invariants
+
+EXPIRY_WINDOW
MUST have elapsed since the message first failed to be relayed
+relayExpire
Invariants
+
CrossL2Inbox
block.chainid
Identifier.origin
MUST be address(L2ToL2CrossDomainMessenger)
expiredMessages
mapping MUST only contain messages that originated in this chain and failed to be relayed on destination.Message Versioning
CrossDomainMessenger
.Relaying
chain. The hash of the message is used for replay protection.
It is important to ensure that the source chain is in the dependency set of the destination chain, otherwise it is possible to send a message that is not playable.
+When a message fails to be relayed, only the timestamp at which it
+first failed along with its source chain id are stored. This is
+needed for calculation of the failed message's expiry. The source chain id
+is also required to simplify the function signature of sendExpire
.
function relayMessage(uint256 _destination, uint256 _source, uint256 _nonce, address _sender, address _target, bytes memory _message) external payable {
require(msg.sender == address(CROSS_L2_INBOX));
require(_destination == block.chainid);
@@ -381,12 +405,75 @@ Relaying
_calldata: _message
});
- require(success);
-}
+ if (!success) {
+ emit FailedRelayedMessage(messageHash);
+
+ if (failedMessages[messageHash].timestamp == 0) {
+ failedMessages[messageHash] = FailedMessage({timestamp: block.timestamp, sourceChainId: _source});
+ }
+
+ return;
+ }
+
+ successfulMessages[messageHash] = true;
+ delete failedMessages[messageHash];
+ emit RelayedMessage(messageHash);
+};
Note that the relayMessage
function is payable
to enable relayers to earn in the gas paying asset.
To enable cross chain authorization patterns, both the _sender
and the _source
MUST be exposed via public
getters.
When expiring a message that failed to be relayed on the destination chain
+to the source chain, it's crucial to ensure the message can only be sent back
+to the L2ToL2CrossDomainMessenger
contract in its source chain.
This function has no auth, which allows anyone to expire a given message hash.
+The EXPIRY_WINDOW
variable is added to give the users enough time to replay their
+failed messages and to prevent malicious actors from performing a griefing attack
+by expiring messages upon arrival.
Once the expired message is sent to the source chain, the message on the local chain is set
+as successful in the successfulMessages
mapping to ensure non-replayability and deleted
+from failedMessages
. An initiating message is then emitted to relayExpire
function sendExpire(bytes32 _expiredHash) external nonReentrant {
+ if (successfulMessages[_expiredHash]) revert MessageAlreadyRelayed();
+
+ (uint256 messageTimestamp, uint256 messageSource) = failedMessages[_expiredHash];
+
+ if (block.timestamp < messageTimestamp + EXPIRY_WINDOW) revert ExpiryWindowHasNotEnsued();
+
+ delete failedMessages[_expiredHash];
+ successfulMessages[_expiredHash] = true;
+
+ bytes memory data = abi.encodeCall(
+ L2ToL2CrossDomainMessenger.expired,
+ (_expiredHash, messageSource)
+ );
+ emit SentMessage(data);
+}
+
+When relaying an expired message, only message hashes +of actual failed messages should be stored, for this we must ensure the origin +of the log, and caller are all expected contracts.
+It's also important to ensure only the hashes of messages that were initiated +in this chain are accepted.
+If all checks have been successful, the message has is stored in the
+expiredMessages
mapping. This enables smart contracts to read from it and
+check whether a message expired or not, and handle this case accordingly.
function relayExpire(bytes32 _expiredHash, uint256 _messageSource) external {
+ if (_messageSource != block.chainid) revert IncorrectMessageSource();
+ if (expiredMessages[_expiredHash] != 0) revert ExpiredMessageAlreadyRelayed();
+ if (msg.sender != Predeploys.CROSS_L2_INBOX) revert ExpiredMessageCallerNotCrossL2Inbox();
+
+ if (CrossL2Inbox(Predeploys.CROSS_L2_INBOX).origin() != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
+ revert CrossL2InboxOriginNotL2ToL2CrossDomainMessenger();
+ }
+
+ expiredMessages[_expiredHash] = block.timestamp;
+
+ emit MessageHashExpired(_expiredHash);
+}
+