1_Layers之间的通信

Messaging Between Layers · Offchain Labs Dev Center

标准Arbitrum交易:来自客户端的调用

Arbitrum上标准的客户端调用的交易是通过EthBridge中Inbox.sendL2Message来实现的: *function* sendL2Message(address chain, bytes calldata messageData) external;

正如在Tx call生命周期中描述的那样,通常大部分调用都会走聚合器并被批量处理。

不过,Arbitrum协议也支持在L1和L2之间传递信息。

最常见的跨链通信的目的是充值和提现;不过这只是Arbitrum支持的通用型跨链合约调用中的特定的一种。本章描述了这些通用协议;深入解释请见洞悉Arbitrum: 桥接

以太坊到Arbitrum:Retryable Tickets

解释

Arbitrum为以太坊到Arbitrum通信提供了几种方式;不过对L1到L2的通信我们一般只推荐retryable tickets,可重试票据。

该方式工作机理如下:一条L1交易提交到了收件箱中,其内容为向L2进行一笔转账(包含了calldata,callvalue,以及gas info)。如果该交易第一次没有执行成功,在L2上会进入一个『retry buffer重试缓存』。这意味着在一段时间内(与该链的挑战期有关,大约为一周),任何人都可以通过再次执行来尝试赎回该L2交易票据。

注意,如果有提供任意数量的gas,L2上的交易会自动执行。在乐观的/正常的情况下,L2上的交易会立即成功。因此,用户一般只需要签名并发布单笔交易。

而Retryable Tickets的合理性在于:如果我们想向L2充值一些代币,代币会进入L1合约中,然后再在L2上铸造等量的代币。假设L1交易成功了L2交易却因为燃气费突然飙升而失败了,在不成熟的实现中,将会带来严重后果——用户转移了代币但在L2上什么也没有;这些代币永远卡在了L1的合约里。不过在使用Retryable Tickets的情况下,用户(或任何人)都可有一周的时间窗口来再次执行该L2信息。

另外,Retryable Tickets系统也周密地考虑到了用户为L2上ArbGas多支付的情况,也避免了用户在某个合约地址的执行中笨拙地支付燃气的情况。如果用户多付了ArbGas,多余的ETH会返回给用户指定的特定地址;如果用户需要在L2上再次执行该交易,可以通过任何外部所有账户EOA来执行,EOA支付本次执行的费用。但合约地址自身仍要支付retryable交易的ArbGas。

Retryable Tickets API

Inbox.createRetryableTicket中有一个便捷的方法创建retryable ticket:

  1. /**
  2. @notice Put an message in the L2 inbox that can be re-executed for some fixed amount of time if it reverts
  3. * @dev all msg.value will deposited to callValueRefundAddress on L2
  4. * @param destAddr destination L2 contract address
  5. * @param l2CallValue call value for retryable L2 message
  6. * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
  7. * @param excessFeeRefundAddress maxGas x gasprice - execution cost gets credited here on L2 balance
  8. * @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
  9. * @param maxGas Max gas deducted from user's L2 balance to cover L2 execution
  10. * @param gasPriceBid price bid for L2 execution
  11. * @param data ABI encoded data of L2 message
  12. * @return unique id for retryable transaction (keccak256(requestID, uint(0) )
  13. */
  14. function createRetryableTicket(
  15. address destAddr,
  16. uint256 l2CallValue,
  17. uint256 maxSubmissionCost,
  18. address excessFeeRefundAddress,
  19. address callValueRefundAddress,
  20. uint256 maxGas,
  21. uint256 gasPriceBid,
  22. bytes calldata data
  23. ) external payable override returns (uint256)

另外,在位于地址0x000000000000000000000000000000000000006E上的预编译合约ArbRetryableTx中也有一些retryable交易的相关方法:

  1. pragma solidity >=0.4.21 <0.7.0;
  2. /**
  3. * @title precompiled contract in every Arbitrum chain for retryable transaction related data retrieval and interactions. Exists at 0x000000000000000000000000000000000000006E
  4. */
  5. interface ArbRetryableTx {
  6. /**
  7. * @notice Redeem a redeemable tx.
  8. * Revert if called by an L2 contract, or if txId does not exist, or if txId reverts.
  9. * If this returns, txId has been completed and is no longer available for redemption.
  10. * If this reverts, txId is still available for redemption (until it times out or is canceled).
  11. @param txId unique identifier of retryabale message: keccak256(requestID, uint(0) )
  12. */
  13. function redeem(bytes32 txId) external;
  14. /**
  15. * @notice Return the minimum lifetime of redeemable txn.
  16. * @return lifetime in seconds
  17. */
  18. function getLifetime() external view returns(uint);
  19. /**
  20. * @notice Return the timestamp when txId will age out, or zero if txId does not exist.
  21. * The timestamp could be in the past, because aged-out txs might not be discarded immediately.
  22. * @param txId unique identifier of retryabale message: keccak256(requestID, uint(0) )
  23. * @return timestamp for txn's deadline
  24. */
  25. function getTimeout(bytes32 txId) external view returns(uint);
  26. /**
  27. * @notice Return the price, in wei, of submitting a new retryable tx with a given calldata size.
  28. * @param calldataSize call data size to get price of (in wei)
  29. * @return (price, nextUpdateTimestamp). Price is guaranteed not to change until nextUpdateTimestamp.
  30. */
  31. function getSubmissionPrice(uint calldataSize) external view returns (uint, uint);
  32. /**
  33. * @notice Return the price, in wei, of extending the lifetime of txId by an additional lifetime period. Revert if txId doesn't exist.
  34. * @param txId unique identifier of retryabale message: keccak256(requestID, uint(0) )
  35. * @return (price, nextUpdateTimestamp). Price is guaranteed not to change until nextUpdateTimestamp.
  36. */
  37. function getKeepalivePrice(bytes32 txId) external view returns(uint, uint);
  38. /**
  39. @notice Deposits callvalue into the sender's L2 account, then adds one lifetime period to the life of txId.
  40. * If successful, emits LifetimeExtended event.
  41. * Revert if txId does not exist, or if the timeout of txId is already at least one lifetime in the future, or if the sender has insufficient funds (after the deposit).
  42. * @param txId unique identifier of retryabale message: keccak256(requestID, uint(0) )
  43. * @return New timeout of txId.
  44. */
  45. function keepalive(bytes32 txId) external payable returns(uint);
  46. /**
  47. * @notice Return the beneficiary of txId.
  48. * Revert if txId doesn't exist.
  49. * @param txId unique identifier of retryabale message: keccak256(requestID, uint(0) )
  50. * @return address of beneficiary for transaction
  51. */
  52. function getBeneficiary(bytes32 txId) external view returns (address);
  53. /**
  54. @notice Cancel txId and refund its callvalue to its beneficiary.
  55. * Revert if txId doesn't exist, or if called by anyone other than txId's beneficiary.
  56. @param txId unique identifier of retryabale message: keccak256(requestID, uint(0) )
  57. */
  58. function cancel(bytes32 txId) external;
  59. event LifetimeExtended(bytes32 indexed txId, uint newTimeout);
  60. event Redeemed(bytes32 indexed txId);
  61. event Canceled(bytes32 indexed txId);
  62. }

arb-ts 中,ArbRetryableTx接口由bridge类实例化并暴露出来:

  1. myBridge.ArbRetryableTx.redeem('mytxid')

Arbitrum到以太坊

解释

L2到L1消息和L1到L2消息工作机制类似,但是是反向的:L2交易与编码过的L1信息数据一通发布,稍后执行。

一个关键不同在于,从L2到L1方向,用户必须等挑战期结束后才能在L1上执行该消息;这是有乐观式rollup的特性决定的(见最终性)。另外,不像retryable ticket,L2到L1信息没有时间上限;一旦挑战期过后,可以在任意时间点执行,无需着急。

L2到L1信息的生命周期

L2到L1信息的生命周期大致可以分为四步,只有两部(最多!)需要用户发布交易。

  1. 发布L2到L1交易(Arbitrum交易) 客户端通过调用L2上的ArbSys.sendTxToL1来发布信息
  2. 创建发件箱 在Arbitrum链状态前进一段时间后,ArbOS会搜集所有的外出信息,将其梅克尔化,然后将梅克尔树根发布在收件箱的OutboxEntry中。请注意,该过程是自动的,不需要用户做什么。
  3. 用户获取外出信息的梅克尔证明 在Outbox Entry发布在L1上后,用户(任何人都行)可以通过NodeInterface.lookupMessageBatchProof计算其信息的梅克尔证明。 ```

/** @title Interface for providing Outbox proof data

  • @notice This contract doesn’t exist on-chain. Instead it is a virtual interface accessible at 0x00000000000000000000000000000000000000C8
  • This is a cute trick to allow an Arbitrum node to provide data without us having to implement an additional RPC ) */

interface NodeInterface { /**

  1. * @notice Returns the proof necessary to redeem a message
  2. * @param batchNum index of outbox entry (i.e., outgoing messages Merkle root) in array of outbox entries
  3. * @param index index of outgoing message in outbox entry
  4. * @return (
  5. * proof: Merkle proof of message inclusion in outbox entry
  6. * path: Index of message in outbox entry
  7. * l2Sender: sender if original message (i.e., caller of ArbSys.sendTxToL1)
  8. * l1Dest: destination address for L1 contract call
  9. * l2Block l2 block number at which sendTxToL1 call was made
  10. * l1Block l1 block number at which sendTxToL1 call was made
  11. * timestamp l2 Timestamp at which sendTxToL1 call was made
  12. * amouunt value in L1 message in wei
  13. * calldataForL1 abi-encoded L1 message data
  14. *
  15. */
  16. function lookupMessageBatchProof(uint256 batchNum, uint64 index)
  17. external
  18. view
  19. returns (
  20. bytes32[] memory proof,
  21. uint256 path,
  22. address l2Sender,
  23. address l1Dest,
  24. uint256 l2Block,
  25. uint256 l1Block,
  26. uint256 timestamp,
  27. uint256 amount,
  28. bytes memory calldataForL1
  29. );

}

  1. 4. **用户执行L1信息(以太坊交易)**
  2. 挑战期过后的任何时间,任何用户都可以通过`Outbox.executeTransaction`L1上执行信息;如果被失败,可以尝试无限次,也没有任何时间上限:

/**

  1. * @notice Executes a messages in an Outbox entry. Reverts if dispute period hasn't expired and
  2. * @param outboxIndex Index of OutboxEntry in outboxes array
  3. * @param proof Merkle proof of message inclusion in outbox entry
  4. * @param index Index of message in outbox entry
  5. * @param l2Sender sender if original message (i.e., caller of ArbSys.sendTxToL1)
  6. * @param destAddr destination address for L1 contract call
  7. * @param l2Block l2 block number at which sendTxToL1 call was made
  8. * @param l1Block l1 block number at which sendTxToL1 call was made
  9. * @param l2Timestamp l2 Timestamp at which sendTxToL1 call was made
  10. * @param amount value in L1 message in wei
  11. * @param calldataForL1 abi-encoded L1 message data
  12. */
  13. function executeTransaction(
  14. uint256 outboxIndex,
  15. bytes32[] calldata proof,
  16. uint256 index,
  17. address l2Sender,
  18. address destAddr,
  19. uint256 l2Block,
  20. uint256 l1Block,
  21. uint256 l2Timestamp,
  22. uint256 amount,
  23. bytes calldata calldataForL1
  24. )

``` 请注意,上述步骤我们在arb-ts中提供了一些简便的方法。

相关用例请见integration testsToken Bridge UI

3_ArbSys预编译合约2_代币桥接