Skip to content
This repository was archived by the owner on May 7, 2025. It is now read-only.
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions adr-11-centralized-sequencer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# ADR 11: Centralized Sequencer

## Changelog

- 2024-10-01: Initial draft

## Context

Rollkit supports modular sequencer implementations and a centralized sequencer is a simple and efficient solution that can serve as a starting point for rollup developers who don't need the complexity of a decentralized sequencing solution.

The centralized sequencer needs to implement the Generic Sequencer interface defined in the `go-sequencing` package, provide transaction batching capabilities, and reliably submit these batches to a DA layer. It should also maintain state to track submitted batches and provide verification capabilities.

## Alternative Approaches

### Decentralized Sequencer

A decentralized sequencer would distribute the sequencing responsibility across multiple nodes, providing better censorship resistance and fault tolerance. However, this approach introduces significant complexity in terms of consensus, leader election, and coordination between nodes. It would also require more resources to operate and maintain.

This approach was not chosen for the initial implementation because:
1. It adds unnecessary complexity for many use cases
2. It requires more development time and resources
3. Many rollup projects start with a centralized sequencer and gradually move towards decentralization

### Embedded Sequencer in Rollup Nodes

Another approach would be to embed sequencing functionality directly into rollup nodes. This would simplify the architecture by eliminating a separate sequencer component.

This approach was not chosen because:
1. It couples sequencing logic with rollup node logic, reducing modularity
2. It makes it harder to upgrade or replace the sequencing component independently
3. It doesn't allow for a dedicated sequencing service that can be optimized separately

## Decision

We implement a standalone centralized sequencer that:

1. Implements the Generic Sequencer interface from the `go-sequencing` package
2. Batches transactions and submits them to a DA layer at regular intervals
3. Provides metrics for monitoring and observability

The centralized sequencer is a separate repository and can be deployed as a standalone service or as a Docker container.

## Detailed Design

### User Requirements

- Rollup developers need a simple, reliable sequencer that can order transactions and submit them to a DA layer
- The sequencer should be easy to deploy and configure
- The sequencer should provide metrics for monitoring
- The sequencer should be able to recover from crashes and maintain state

### Systems Affected

- Rollup nodes that interact with the sequencer
- DA layer where batches are submitted

### Data Structures

The centralized sequencer uses the following key data structures:

1. **BatchQueue**: A queue to store batches of transactions waiting to be processed
```go
type BatchQueue struct {
queue []sequencing.Batch // In-memory queue of batches waiting to be processed
mu sync.Mutex // Mutex to ensure thread-safe access to the queue
}
```

2. **Sequencer**: The main sequencer structure that implements the Generic Sequencer interface
```go
type Sequencer struct {
dalc *da.DAClient // Client for interacting with the Data Availability layer
batchTime time.Duration // Time interval between batch submissions
ctx context.Context // Context for controlling the sequencer's lifecycle
maxSize uint64 // Maximum size of a batch in bytes

rollupId sequencing.RollupId // Identifier for the rollup this sequencer serves

tq *TransactionQueue // Queue for storing pending transactions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this if we fire off as soon as we get the data? also should rollkit construct batches instead of the sequencer, well they are the same thing or will be with the changes

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a strong reason to have this either. Thinking that with the new changes, for a centralized sequencer setting, we should just default to the pre-separation code.

cc: @gupadhyaya

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rollkit relays transactions as they gets into mempool. centralized sequencer needs a place to collect transactions and prepare batch at the batch interval (could be 1s or 2s, etc). hence the transaction queue. while the centralized sequencer and rollkit node are same but different processes, you don't want the sequencer to directly access mempool right? if you don't store it, will you create batch for every transaction? (i know with api change of submitting []Tx instead of single Tx via SubmitRollupTxs api, still you could fit in multiple sets of txs (relayed via multiple SubmitRollupTxs calls) into a single batch. queue is convenient, but open to any other ways to handle this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not work that ordering is done by the aggregator execution env and we just getTxs() and submit that? what extra ordering is there?

lastBatchHash []byte // Hash of the last processed batch
lastBatchHashMutex sync.RWMutex // Mutex for thread-safe access to lastBatchHash
Comment on lines +80 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there multiple callers updating this? what is the reason for the mutex? seems expensive if there is only one updater

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can get rid of mutex. only one caller (header producer of rollkit)


seenBatches map[string]struct{} // Map to track batches that have been processed
seenBatchesMutex sync.Mutex // Mutex for thread-safe access to seenBatches
Comment on lines +83 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto on the mutex

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the batch producer the verifybatch is basically return true, hence you can get rid of this alltogether.

bq *BatchQueue // Queue for storing batches ready for processing

db *badger.DB // BadgerDB instance for persistent storage
dbMux sync.Mutex // Mutex for safe concurrent DB access

metrics *Metrics // Structure to hold metrics for monitoring
}
```

3. **Metrics**: Structure to hold metrics for monitoring
```go
type Metrics struct {
GasPrice metrics.Gauge // Tracks the gas price used for DA submissions
LastBlobSize metrics.Gauge // Tracks the size of the last submitted blob
TransactionStatus metrics.Counter // Counts transaction status outcomes
NumPendingBlocks metrics.Gauge // Tracks the number of blocks waiting to be submitted
IncludedBlockHeight metrics.Gauge // Tracks the height of the last included block in the DA layer
}
```

### APIs

The centralized sequencer implements the Generic Sequencer interface from the `go-sequencing` package:

```go
type Sequencer interface {
SubmitRollupBatchTxs(ctx context.Context, req SubmitRollupBatchTxsRequest) (*SubmitRollupBatchTxsResponse, error)
GetNextBatch(ctx context.Context, req GetNextBatchRequest) (*GetNextBatchResponse, error)
VerifyBatch(ctx context.Context, req VerifyBatchRequest) (*VerifyBatchResponse, error)
}
```

1. **SubmitRollupBatchTxs**:
- This method is responsible for accepting a batch of transactions from a rollup client. It takes a context and a request containing the rollup ID and the batch of transactions to be submitted.
- The method first validates the rollup ID to ensure it matches the expected ID for the sequencer. If the ID is invalid, it returns an error.
- Upon successful validation, the method adds the transactions to the internal transaction queue (`TransactionQueue`) for processing.
- It then triggers the batch submission process, which involves retrieving the next batch of transactions and submitting them to the designated Data Availability (DA) layer.
- Finally, it returns a response indicating the success or failure of the submission.

2. **GetNextBatch**:
- This method retrieves the next batch of transactions that are ready to be processed by the rollup. It takes a context and a request containing the rollup ID and the last batch hash.
- The method first checks if the rollup ID is valid. If not, it returns an error.
- It then verifies the last batch hash to ensure that the rollup client is requesting the correct next batch.
- If a valid batch is found, it prepares the batch response, which includes the batch of transactions and a timestamp.
- If no transactions are available, it returns an empty batch response.

Note that this method is used by the rollup node to get a sequencer soft-confirmed batch that the sequencer promises to publish to the DA layer.

3. **VerifyBatch**:
- This method is used to verify the that a batch received (soft-confirmed) from the sequencer was actually published on the DA layer. It takes a context and a request containing the rollup ID and the batch hash.
- Similar to the other methods, it first validates the rollup ID.
- It then checks if the provided batch hash exists in the internal data structure that tracks seen batches.
- If the batch hash is found, it returns a response indicating that the batch is valid. If not, it returns a response indicating that the batch is invalid.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a TODO here? Currently VerifyBatch only does a quick verification of the batch hash exist in the seenBatches, however in the future, it is expected to perform the full DA verification where the batch submitted to DA as referenced using a DA height and VerifyBatch would query the DA at the referenced height to verify that the batch exists in DA.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this be what the pending header system would work off of.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pending headers in rollkit is different. the verifybatch is for transaction batches.
verifyBatch for the batch producer (which is rollkit's header producer) is basically return true
for fullnodes, it needs to provide a stateless verification, meaning the verification is done using the DA

Once this method returns true for batch, a rollup node can mark the rollup block associated to this batch as `DA included` and mark it as fully confirmed from its view.

These methods work together to ensure that the centralized sequencer can effectively manage transaction submissions, retrievals, and verifications, providing a reliable interface for rollup clients to interact with the sequencer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add something about the batch submission loop?
The centralized sequencer runs a batch submission loop at the batch timer interval which prepares a batch and submits it to DA. The batch timer is configurable.


### Efficiency Considerations

- The sequencer uses a configurable batch time to balance between latency and efficiency
- Transactions are batched to reduce the number of DA submissions
- The sequencer maintains an in-memory queue for fast access and a persistent database for durability
- Exponential backoff is used for DA submission retries to handle temporary failures

### Access Patterns

- Rollup clients will submit transactions to the sequencer at varying rates
- The sequencer will batch transactions and submit them to the DA layer at regular intervals
- Rollup nodes will request the next batch from the sequencer to process transactions

### Logging, Monitoring, and Observability

The sequencer provides the following metrics:

- Gas price of DA submissions
- Size of the last submitted blob
- Transaction status counts
- Number of pending blocks
- Last included block height

These metrics can be exposed via Prometheus for monitoring.

### Security Considerations

- The centralized sequencer is a single point of failure and control
- Access control is not implemented in the initial version, but can be added in future versions
- The sequencer validates rollup IDs to ensure transactions are submitted to the correct rollup

### Privacy Considerations

- The sequencer has access to all transactions before they are submitted to the DA layer
- Transactions are not encrypted, so sensitive data should not be included in transactions

### Testing

The centralized sequencer includes:

- Unit tests for core functionality
- Integration tests with a mock DA layer
- Test coverage reporting via Codecov

### Breaking Changes

This is a new component and does not introduce breaking changes to existing systems.

## Status

Proposed

## Consequences

### Positive

- Provides a simple, production-ready sequencer for rollup developers
- Implements the Generic Sequencer interface, making it compatible with existing Rollkit components
- Includes metrics for monitoring and observability
- Maintains state to track submitted batches and provide verification
- Can be deployed as a standalone service or as a Docker container

### Negative

- Centralized design introduces a single point of failure
- No built-in access control or authentication in the initial version
- Limited scalability compared to a distributed sequencer

### Neutral

- Requires a separate deployment and management of the sequencer service
- Developers need to configure the sequencer to connect to their chosen DA layer

## References

- [Generic Sequencer Interface](https://github.com/rollkit/go-sequencing)
- [Rollkit Repository](https://github.com/rollkit/rollkit)
- [Centralized Sequencer Repository](https://github.com/rollkit/centralized-sequencer)
Loading