Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rollback update #405

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
63 changes: 42 additions & 21 deletions specs/interop/predeploys.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,8 @@ as well as domain binding, ie the executing transaction can only be valid on a s

### `relayExpire` Invariants

- Only callable by the `CrossL2Inbox`
- The message source MUST be `block.chainid`
- The `Identifier.origin` MUST be `address(L2ToL2CrossDomainMessenger)`
- The `Identifier.origin` and `sender` MUST be `address(L2ToL2CrossDomainMessenger)`
- The `expiredMessages` mapping MUST only contain messages that originated in this chain and failed to be relayed on destination.
- Already expired messages MUST NOT be relayed.

Expand Down Expand Up @@ -415,6 +414,9 @@ function relayMessage(ICrossL2Inbox.Identifier calldata _id, bytes calldata _sen
successfulMessages[messageHash] = true;
emit RelayedMessage(_source, _nonce, messageHash);
} else {
if (failedMessages[messageHash].timestamp == 0) {
failedMessages[messageHash] = FailedMessage({timestamp: block.timestamp, sourceChain: _source});
}
emit FailedRelayedMessage(_source, _nonce, messageHash);
}
}
Expand All @@ -437,54 +439,73 @@ failed messages and to prevent malicious actors from performing a griefing attac
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`
as expired in the `successfulMessages` mapping to ensure non-replayability and deleted
from `failedMessages`. An initiating message is then emitted to `relayExpire`.

`sendExpire` sets the sender of the message as the `L2ToL2CrossDomainMessenger` to add a path
for `relayExpire` to ensure that the message originated in `sendExpire`. There's should be no
other path where a message can have the `L2ToL2CrossDomainMessenger` as `origin` and `sender`.

```solidity
function sendExpire(bytes32 _expiredHash) external nonReentrant {
if (successfulMessages[_expiredHash]) revert MessageAlreadyRelayed();
require(!successfulMessages[_expiredHash]);
require(failedMessages[_expiredHash].timestamp != 0);

(uint256 messageTimestamp, uint256 messageSource) = failedMessages[_expiredHash];

if (block.timestamp < messageTimestamp + EXPIRY_WINDOW) revert ExpiryWindowHasNotEnsued();
require(block.timestamp >= messageTimestamp + EXPIRY_WINDOW);

delete failedMessages[_expiredHash];
successfulMessages[_expiredHash] = true;

bytes memory data = abi.encodeCall(
L2ToL2CrossDomainMessenger.expired,
(_expiredHash, messageSource)
);
emit SentMessage(data);
uint256 destination = messageSource;
address target;
uint256 nonce = messageNonce();
address sender = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

bytes memory message = abi.encode(_expiredHash);

emit SentMessage(destination, target, nonce, sender, message);

msgNonce++;
}
```

#### Relaying Expired Message Hashes

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.
of the log, and sender is the `L2_TO_L2_CROSS_DOMAIN_MESSENGER`.

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
If all checks have been successful, the message hash 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.

An expiry message is relayed by providing the [identifier](./messaging.md#message-identifier)
to a `SentMessage` event and its corresponding [message payload](./messaging.md#message-payload).

```solidity
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();
function relayExpire(ICrossL2Inbox.Identifier calldata _id, bytes calldata _sentMessage) external {
require(_id.origin == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);

if (CrossL2Inbox(Predeploys.CROSS_L2_INBOX).origin() != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
revert CrossL2InboxOriginNotL2ToL2CrossDomainMessenger();
}
CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_sentMessage));

(uint256 destination, , , address sender, bytes memory message) =
_decodeSentMessagePayload(_sentMessage);

require(destination == block.chainId);
require(sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);

bytes32 expiredHash = abi.decode(_sentMessage);

require(expiredMessages[expiredHash] == 0);

expiredMessages[_expiredHash] = block.timestamp;

emit MessageHashExpired(_expiredHash);
emit MessageHashExpired(_expiredHash, block.timestamp);
}
```

Expand Down