Search
Optimistic rollup protocols move all execution off the layer 1 (L1) Ethereum chain, complete execution on a layer 2 (L2) chain, and return the results of the L2 execution back to the L1. These protocols have a sequencer that executes and rolls up the L2 transactions by batching multiple transactions into a single transaction.
If a sequencer becomes unavailable, it is impossible to access read/write APIs that consumers are using and applications on the L2 network will be down for most users without interacting directly through the L1 optimistic rollup contracts. The L2 has not stopped, but it would be unfair to continue providing service on your applications when only a few users can use them.
To help your applications identify when the sequencer is unavailable, you can use a data feed that tracks the last known status of the sequencer at a given point in time. This is to allow customers to prevent mass liquidations by providing a grace period to allow customers to react to such an event.
Topics
You can find proxy addresses for the L2 sequencer feeds at the following addresses:
The diagram below shows how these feeds update and how a consumer retrieves the status of the Arbitrum sequencer.
validate
function in the ArbitrumValidator
contract by calling it through the ValidatorProxy
contract.ArbitrumValidator
checks to see if the latest update is different from the previous update. If it detects a difference, it places a message in the Arbitrum inbox contract.ArbitrumSequencerUptimeFeed
contract. The message calls the updateStatus
function in the ArbitrumSequencerUptimeFeed
contract and updates the latest sequencer status to 0 if the sequencer is up and 1 if it is down. It also records the block timestamp to indicate when the message was sent from the L1 network.ArbitrumUptimeFeedProxy
contract, which reads values from the ArbitrumSequencerUptimeFeed
contract.If the Arbitrum network becomes unavailable, the ArbitrumValidator
contract continues to send messages to the L2 network through the delayed inbox on L1. This message stays there until the sequencer is back up again. When the sequencer comes back online after downtime, it processes all transactions from the delayed inbox before it accepts new transactions. The message that signals when the sequencer is down will be processed before any new messages with transactions that require the sequencer to be operational.
On Optimism and Metis, the sequencer’s status is relayed from L1 to L2 where the consumer can retrieve it.
On the L1 network:
A network of node operators runs the external adapter to post the latest sequencer status to the AggregatorProxy
contract and relays the status to the Aggregator
contract. The Aggregator
contract calls the validate
function in the OptimismValidator
contract.
The OptimismValidator
contract calls the sendMessage
function in the L1CrossDomainMessenger
contract. This message contains instructions to call the updateStatus(bool status, uint64 timestamp)
function in the sequencer uptime feed deployed on the L2 network.
The L1CrossDomainMessenger
contract calls the enqueue
function to enqueue a new message to the CanonicalTransactionChain
.
The Sequencer
processes the transaction enqueued in the CanonicalTransactionChain
contract to send it to the L2 contract.
On the L2 network:
The Sequencer
posts the message to the L2CrossDomainMessenger
contract.
The L2CrossDomainMessenger
contract relays the message to the OptimismSequencerUptimeFeed
contract.
The message relayed by the L2CrossDomainMessenger
contains instructions to call updateStatus
in the OptimismSequencerUptimeFeed
contract.
Consumers can then read from the AggregatorProxy
contract, which fetches the latest round data from the OptimismSequencerUptimeFeed
contract.
If the sequencer is down, messages cannot be transmitted from L1 to L2 and no L2 transactions are executed. Instead, messages are enqueued in the CanonicalTransactionChain
on L1 and only processed in the order they arrived later when the sequencer comes back up. As long as the message from the validator on L1 is already enqueued in the CTC
, the flag on the sequencer uptime feed on L2 will be guaranteed to be flipped prior to any subsequent transactions. The transaction that flips the flag on the uptime feed will be executed before transactions that were enqueued after it. This is further explained in the diagrams below.
When the Sequencer is down, all L2 transactions sent from the L1 network wait in the pending queue.
After the sequencer comes back up, it moves moves all transactions in the pending queue to the processed queue.
Create the consumer contract for sequencer uptime feeds similarly to contracts you use for Chainlink Data Feeds. Configure the constructor using the following variables:
sequencerUptimeFeed
object with the sequencer uptime feed proxy address for your L2 network.priceFeed
object with one of the Data Feed proxy addresses that are available for your network.// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract PriceConsumerWithSequencerCheck {
AggregatorV2V3Interface internal priceFeed;
AggregatorV2V3Interface internal sequencerUptimeFeed;
uint256 private constant GRACE_PERIOD_TIME = 3600;
error SequencerDown();
error GracePeriodNotOver();
/**
* Network: Optimism
* Data Feed: BTC/USD
* Data Feed Proxy Address: 0xD702DD976Fb76Fffc2D3963D037dfDae5b04E593
* Sequencer Uptime Proxy Address: 0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389
* For a list of available sequencer proxy addresses, see:
* https://docs.chain.link/docs/l2-sequencer-flag/#available-networks
*/
constructor() {
priceFeed = AggregatorV2V3Interface(0xD702DD976Fb76Fffc2D3963D037dfDae5b04E593);
sequencerUptimeFeed = AggregatorV2V3Interface(0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389);
}
// Check the sequencer status and return the latest price
function getLatestPrice() public view returns (int) {
(
/*uint80 roundId*/,
int256 answer,
uint256 startedAt,
/*uint256 updatedAt*/,
/*uint80 answeredInRound*/
) = sequencerUptimeFeed.latestRoundData();
// Answer == 0: Sequencer is up
// Answer == 1: Sequencer is down
bool isSequencerUp = answer == 0;
if (!isSequencerUp) {
revert SequencerDown();
}
// Make sure the grace period has passed after the sequencer is back up.
uint256 timeSinceUp = block.timestamp - startedAt;
if (timeSinceUp <= GRACE_PERIOD_TIME) {
revert GracePeriodNotOver();
}
(
/*uint80 roundID*/,
int price,
/*uint startedAt*/,
/*uint timeStamp*/,
/*uint80 answeredInRound*/
) = priceFeed.latestRoundData();
return price;
}
}
The sequencerUptimeFeed
object returns the following values:
answer
: A variable with a value of either 1
or 0
startedAt
: This timestamp indicates when the sequencer changed status. This timestamp returns 0
if a round is invalid. When the sequencer comes back up after an outage, wait for the GRACE_PERIOD_TIME
to pass before accepting answers from the price data feed. Subtract startedAt
from block.timestamp
and revert the request if the result is less than the GRACE_PERIOD_TIME
.If the sequencer is up and the GRACE_PERIOD_TIME
has passed, the function retrieves the latest price from the data feed using the priceFeed
object.