@aztec/sequencer-client 0.0.1-commit.9b94fc1 → 0.0.1-commit.9badcec54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dest/client/sequencer-client.d.ts +26 -16
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +76 -36
  4. package/dest/config.d.ts +35 -9
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +109 -50
  7. package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
  8. package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
  9. package/dest/global_variable_builder/fee_predictor.js +128 -0
  10. package/dest/global_variable_builder/fee_provider.d.ts +21 -0
  11. package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
  12. package/dest/global_variable_builder/fee_provider.js +58 -0
  13. package/dest/global_variable_builder/global_builder.d.ts +25 -17
  14. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  15. package/dest/global_variable_builder/global_builder.js +44 -68
  16. package/dest/global_variable_builder/index.d.ts +4 -2
  17. package/dest/global_variable_builder/index.d.ts.map +1 -1
  18. package/dest/global_variable_builder/index.js +2 -0
  19. package/dest/index.d.ts +2 -3
  20. package/dest/index.d.ts.map +1 -1
  21. package/dest/index.js +1 -2
  22. package/dest/publisher/config.d.ts +53 -20
  23. package/dest/publisher/config.d.ts.map +1 -1
  24. package/dest/publisher/config.js +124 -39
  25. package/dest/publisher/index.d.ts +2 -1
  26. package/dest/publisher/index.d.ts.map +1 -1
  27. package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
  28. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
  29. package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
  30. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +58 -0
  31. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
  32. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
  33. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
  34. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
  35. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
  36. package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
  37. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
  38. package/dest/publisher/l1_tx_failed_store/index.js +2 -0
  39. package/dest/publisher/sequencer-publisher-factory.d.ts +15 -8
  40. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  41. package/dest/publisher/sequencer-publisher-factory.js +28 -4
  42. package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
  43. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  44. package/dest/publisher/sequencer-publisher-metrics.js +23 -86
  45. package/dest/publisher/sequencer-publisher.d.ts +114 -80
  46. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  47. package/dest/publisher/sequencer-publisher.js +1008 -242
  48. package/dest/sequencer/chain_state_overrides.d.ts +25 -0
  49. package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
  50. package/dest/sequencer/chain_state_overrides.js +39 -0
  51. package/dest/sequencer/checkpoint_proposal_job.d.ts +119 -0
  52. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
  53. package/dest/sequencer/checkpoint_proposal_job.js +1355 -0
  54. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
  55. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
  56. package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
  57. package/dest/sequencer/checkpoint_voter.d.ts +34 -0
  58. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
  59. package/dest/sequencer/checkpoint_voter.js +106 -0
  60. package/dest/sequencer/config.d.ts +3 -2
  61. package/dest/sequencer/config.d.ts.map +1 -1
  62. package/dest/sequencer/events.d.ts +47 -0
  63. package/dest/sequencer/events.d.ts.map +1 -0
  64. package/dest/sequencer/events.js +1 -0
  65. package/dest/sequencer/index.d.ts +4 -2
  66. package/dest/sequencer/index.d.ts.map +1 -1
  67. package/dest/sequencer/index.js +3 -1
  68. package/dest/sequencer/metrics.d.ts +38 -7
  69. package/dest/sequencer/metrics.d.ts.map +1 -1
  70. package/dest/sequencer/metrics.js +220 -73
  71. package/dest/sequencer/sequencer.d.ts +138 -134
  72. package/dest/sequencer/sequencer.d.ts.map +1 -1
  73. package/dest/sequencer/sequencer.js +812 -657
  74. package/dest/sequencer/timetable.d.ts +67 -16
  75. package/dest/sequencer/timetable.d.ts.map +1 -1
  76. package/dest/sequencer/timetable.js +158 -64
  77. package/dest/sequencer/types.d.ts +3 -0
  78. package/dest/sequencer/types.d.ts.map +1 -0
  79. package/dest/sequencer/types.js +1 -0
  80. package/dest/sequencer/utils.d.ts +14 -8
  81. package/dest/sequencer/utils.d.ts.map +1 -1
  82. package/dest/sequencer/utils.js +7 -4
  83. package/dest/test/index.d.ts +6 -7
  84. package/dest/test/index.d.ts.map +1 -1
  85. package/dest/test/mock_checkpoint_builder.d.ts +95 -0
  86. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
  87. package/dest/test/mock_checkpoint_builder.js +231 -0
  88. package/dest/test/utils.d.ts +53 -0
  89. package/dest/test/utils.d.ts.map +1 -0
  90. package/dest/test/utils.js +104 -0
  91. package/package.json +31 -30
  92. package/src/client/sequencer-client.ts +102 -62
  93. package/src/config.ts +128 -60
  94. package/src/global_variable_builder/README.md +44 -0
  95. package/src/global_variable_builder/fee_predictor.ts +172 -0
  96. package/src/global_variable_builder/fee_provider.ts +75 -0
  97. package/src/global_variable_builder/global_builder.ts +64 -96
  98. package/src/global_variable_builder/index.ts +3 -1
  99. package/src/index.ts +1 -7
  100. package/src/publisher/config.ts +166 -51
  101. package/src/publisher/index.ts +3 -0
  102. package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
  103. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +57 -0
  104. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
  105. package/src/publisher/l1_tx_failed_store/index.ts +3 -0
  106. package/src/publisher/sequencer-publisher-factory.ts +43 -13
  107. package/src/publisher/sequencer-publisher-metrics.ts +19 -71
  108. package/src/publisher/sequencer-publisher.ts +761 -321
  109. package/src/sequencer/README.md +601 -0
  110. package/src/sequencer/chain_state_overrides.ts +87 -0
  111. package/src/sequencer/checkpoint_proposal_job.ts +1137 -0
  112. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  113. package/src/sequencer/checkpoint_voter.ts +119 -0
  114. package/src/sequencer/config.ts +2 -1
  115. package/src/sequencer/events.ts +27 -0
  116. package/src/sequencer/index.ts +3 -1
  117. package/src/sequencer/metrics.ts +273 -83
  118. package/src/sequencer/sequencer.ts +607 -857
  119. package/src/sequencer/timetable.ts +188 -81
  120. package/src/sequencer/types.ts +6 -0
  121. package/src/sequencer/utils.ts +18 -9
  122. package/src/test/index.ts +5 -6
  123. package/src/test/mock_checkpoint_builder.ts +323 -0
  124. package/src/test/utils.ts +167 -0
  125. package/dest/sequencer/block_builder.d.ts +0 -27
  126. package/dest/sequencer/block_builder.d.ts.map +0 -1
  127. package/dest/sequencer/block_builder.js +0 -134
  128. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  129. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  130. package/dest/tx_validator/nullifier_cache.js +0 -24
  131. package/dest/tx_validator/tx_validator_factory.d.ts +0 -17
  132. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  133. package/dest/tx_validator/tx_validator_factory.js +0 -53
  134. package/src/sequencer/block_builder.ts +0 -222
  135. package/src/tx_validator/nullifier_cache.ts +0 -30
  136. package/src/tx_validator/tx_validator_factory.ts +0 -132
@@ -0,0 +1,601 @@
1
+ # Sequencer Timing Model
2
+
3
+ The Aztec sequencer divides each slot into **fixed-duration sub-slots**. Each sub-slot has a pre-defined start and end time based on an initialization offset (how much time we expect syncing the previous slot will take), the configured block duration, and whether checkpoint finalization is paid for in the current slot or deferred under proposer pipelining.
4
+
5
+ **Example: 72-second slot with 8-second sub-slots (non-pipelined)**
6
+
7
+ ```
8
+ 0s: Slot starts
9
+ 0-2s: Sync + proposer check (fixed 2s offset)
10
+
11
+ Sub-slot 1: 2s-10s → Build Block 1, deadline at 10s
12
+ Sub-slot 2: 10s-18s → Build Block 2, deadline at 18s
13
+ Sub-slot 3: 18s-26s → Build Block 3, deadline at 26s
14
+ Sub-slot 4: 26s-34s → Build Block 4, deadline at 34s
15
+ Sub-slot 5: 34s-42s → Build Block 5 (last block), deadline at 42s
16
+ Sub-slot 6: 42s-50s → Reserved for validators to re-execute Block 5
17
+
18
+ 42s: Broadcast checkpoint with Block 5
19
+ 44s: Validators receive proposal (2s propagation)
20
+ 44-52s: Validators re-execute Block 5 (8s)
21
+ 52s: Validators send attestations
22
+ 54s: Proposer receives attestations (2s propagation)
23
+ 54-55s: Finalize checkpoint (1s)
24
+ 55-67s: Publish to L1 (12s)
25
+ 72s: Slot ends
26
+ ```
27
+
28
+ Deadlines are fixed relative to slot start, not relative to when work actually completes. If you finish initialization at 1s instead of 2s, you get bonus time for Block 1. If you finish at 3s, you have less time. If you finish at 9s, you skip the first sub-slot altogether and start building for the second one.
29
+
30
+ ---
31
+
32
+ ## Overview
33
+
34
+ The Aztec sequencer operates in fixed-duration **slots** (typically 72 seconds). During each slot, a designated proposer builds multiple **blocks** containing transactions over multiple **sub-slots**. In the default mode, the same slot also reserves time to collect attestations for the resulting **checkpoint**, finalize it, and publish it to L1 Ethereum. When proposer pipelining is enabled, the slot budget for block building is larger because checkpoint finalization is deferred to the next target slot.
35
+
36
+ ## Key Concepts
37
+
38
+ ### Slot vs Block vs Checkpoint vs Sub-Slot
39
+
40
+ - **Slot**: A fixed time window (e.g., 72 seconds) during which a proposer can build blocks
41
+ - **Block**: A single batch of transactions, executed and validated
42
+ - **Checkpoint**: The collection of all blocks built in a slot, attested by validators and published to L1
43
+ - **Sub-slot**: A fixed-duration time window within a slot (e.g., 8 seconds) during which a block should be built
44
+
45
+ In a typical configuration without pipelining, a 72-second slot contains:
46
+ - 1 initialization period (2 seconds)
47
+ - 5 block-building sub-slots (8 seconds each = 40 seconds)
48
+ - 1 last validator re-execution sub-slot (8 seconds)
49
+ - 1 attestation and publishing period (17 seconds)
50
+
51
+ With proposer pipelining enabled, the last validator re-execution sub-slot is still reserved, but the checkpoint finalization and L1 publishing budget is no longer subtracted when deciding how many block-building sub-slots fit in the slot.
52
+
53
+ ### The Fixed Sub-Slot Model
54
+
55
+ Building multiple blocks per slot uses **fixed sub-slots** with predictable deadlines:
56
+
57
+ 1. **Equal-duration sub-slots**: All sub-slots have the same duration (`BLOCK_DURATION`)
58
+ 2. **Fixed deadlines**: Block N deadline = `initializationOffset + N * BLOCK_DURATION`
59
+ 3. **Last sub-slot reserved**: The final sub-slot is reserved for validators to re-execute the last block (no block is built during this sub-slot)
60
+ 4. **Skip if too late**: If we can't start a block with at least `MIN_EXECUTION_TIME` remaining before its deadline, we immediately start building in the next sub-slot
61
+
62
+ ## Timing Components
63
+
64
+ Understanding slot timing requires knowing these time constants:
65
+
66
+ | Component | Example Value | Purpose |
67
+ |-----------|---------------|---------|
68
+ | **Slot Duration** | 72s | Total time available for the entire checkpoint |
69
+ | **Block Duration** | 8s | Duration of each sub-slot (time budget for building one block) |
70
+ | **Initialization Offset** | 2s | Fixed estimate for sync + proposer check |
71
+ | **Propagation Time** | 2s | Time for messages to travel across the P2P network (one-way) |
72
+ | **Finalization Time** | 1s | Time to finalize checkpoint and prepare proposal message |
73
+ | **L1 Publishing Time** | 12s | Time reserved for L1 transaction to land in an Ethereum block |
74
+ | **Min Execution Time** | 3s | Minimum time needed to meaningfully build a block |
75
+
76
+ These values are configurable but must satisfy certain constraints (explained below). Example values may differ from the ones in the source code.
77
+
78
+ ## Calculating Sub-Slots and Blocks
79
+
80
+ Given a slot configuration, we calculate how many blocks fit using these formulas:
81
+
82
+ ```
83
+ checkpointFinalizationTime = propagationTime
84
+ + propagationTime
85
+ + finalizationTime
86
+ + l1PublishingTime
87
+
88
+ timeReservedAtEnd (normal mode) = blockDuration (last sub-slot for reexecution)
89
+ + checkpointFinalizationTime
90
+
91
+ timeReservedAtEnd (pipelining) = assembleTime
92
+ + propagationTime (proposal must reach validators before the slot flips)
93
+
94
+ timeAvailableForBlocks = slotDuration - initializationOffset - timeReservedAtEnd
95
+
96
+ numberOfBlocks = floor(timeAvailableForBlocks / blockDuration)
97
+ ```
98
+
99
+ **Example with typical values:**
100
+ ```
101
+ timeReservedAtEnd = 8s + 2s + 2s + 1s + 12s = 25s
102
+ timeAvailableForBlocks = 72s - 2s - 25s = 45s
103
+ numberOfBlocks = floor(45s / 8s) = 5 blocks
104
+ ```
105
+
106
+ This means:
107
+ - Sub-slots 1-5: Build blocks 1-5
108
+ - Sub-slot 6: Reserved for validator re-execution of block 5
109
+ - After sub-slot 6: Attestation collection, finalization, and L1 publishing
110
+
111
+ **The same slot with proposer pipelining enabled:**
112
+ ```
113
+ timeReservedAtEnd = 1s + 2s = 3s
114
+ timeAvailableForBlocks = 72s - 2s - 3s = 67s
115
+ numberOfBlocks = floor(67s / 8s) = 8 blocks
116
+ ```
117
+
118
+ The extra two block opportunities come from not charging the current slot for checkpoint finalization and L1 publishing.
119
+
120
+ ### Pipelining Mode
121
+
122
+ When proposer pipelining is enabled, the sequencer uses the current wall-clock slot to build the checkpoint for the **next target slot**.
123
+
124
+ It helps to think in terms of two different slots:
125
+
126
+ - **Wall-clock slot N-1**: The sequencer initializes checkpoint `N`, builds its blocks, and validators re-execute the last block
127
+ - **Target slot N**: Checkpoint `N` is proposed, attestations are gathered, and the L1 transaction is submitted
128
+
129
+ So the work is split like this:
130
+
131
+ - **During slot N-1**: Initialization, block building, and last-block re-execution
132
+ - **Near the end of slot N-1**: The checkpoint proposal is broadcast so validators can start the last re-execution as slot `N` begins.
133
+ - **During slot N**: Validators finish re-executing, send attestations, the proposer collects them, and the checkpoint is submitted to L1 before slot `N` reaches its publish cutoff
134
+
135
+ In other words, pipelining does not mean "do everything for slot N earlier". It specifically moves **block production and block re-execution** earlier, while **checkpoint proposal, attestation gathering, and L1 submission** remain aligned with slot `N`.
136
+
137
+ **Example: building checkpoint 12 while wall-clock time is in slot 11**
138
+ ```
139
+ Slot 11 (wall clock):
140
+ - Build blocks that will make up checkpoint 12
141
+ - Validators re-execute the last block of checkpoint 12
142
+ - Broadcast checkpoint 12 proposal
143
+ - Collect checkpoint 12 attestations
144
+
145
+ Slot 12 (target/submission slot):
146
+ - Collect attestations for checkpoint 12 until slot 12 reaches its L1 publish cutoff
147
+ - Submit checkpoint 12 to L1
148
+ ```
149
+
150
+ For timetable purposes, this changes two things:
151
+
152
+ - `maxNumberOfBlocks` is computed by reserving only the final validator re-execution sub-slot
153
+ - `initializeDeadline` no longer subtracts checkpoint finalization time; it only requires enough time for initialization, execution, and validator re-execution
154
+
155
+ In code, that means:
156
+
157
+ ```
158
+ initializeDeadline (normal mode) =
159
+ slotDuration - initializationOffset - 2 * minExecutionTime - checkpointFinalizationTime
160
+
161
+ initializeDeadline (pipelining) =
162
+ slotDuration - initializationOffset - 2 * minExecutionTime
163
+ ```
164
+
165
+ The fixed sub-slot deadlines themselves do not change. Pipelining only changes how much of the slot is considered available for block building.
166
+
167
+ ## The Sequencer's Work
168
+
169
+ When elected as proposer for a slot, the sequencer performs these tasks:
170
+
171
+ ### 1. Initialization Phase
172
+
173
+ Before building any blocks, the sequencer must:
174
+ - Verify it's the designated proposer
175
+ - Check all subsystems are synced
176
+ - Initialize checkpoint state
177
+ - Prepare global variables
178
+
179
+ Note that the initialization phase has a **fixed time budget** (`initializationOffset`, typically 2s). This is an *estimate*, not a deadline. The sequencer will take as long as it needs for initialization, but the sub-slot deadlines remain fixed regardless.
180
+
181
+ ### 2. Block Building Loop
182
+
183
+ The sequencer builds blocks in **fixed sub-slots** based on the configured block duration.
184
+
185
+ #### Sub-slot deadline calculation
186
+
187
+ Each sub-slot has a fixed start time and deadline:
188
+
189
+ ```
190
+ subSlotStart[N] = initializationOffset + (N - 1) * blockDuration
191
+ subSlotDeadline[N] = initializationOffset + N * blockDuration
192
+ ```
193
+
194
+ Where N is the sub-slot number (1-indexed).
195
+
196
+ **Example with 2s offset and 8s block duration:**
197
+ ```
198
+ Sub-slot 1: starts at 2s, deadline at 10s
199
+ Sub-slot 2: starts at 10s, deadline at 18s
200
+ Sub-slot 3: starts at 18s, deadline at 26s
201
+ Sub-slot 4: starts at 26s, deadline at 34s
202
+ Sub-slot 5: starts at 34s, deadline at 42s
203
+ ```
204
+
205
+ #### Building a block
206
+
207
+ For each sub-slot, the sequencer:
208
+
209
+ 1. **Checks if we can start**: If current time is past `deadline - MIN_EXECUTION_TIME`, skip this sub-slot, and start building the block as if it were on the next sub-slot
210
+ 2. **Waits for transactions** (if needed): Wait up to the deadline for minimum number of transactions
211
+ 3. **Builds block**: Execute transactions until the deadline of the sub-slot
212
+ 4. **Signs and broadcasts**: Finalize block and broadcast proposal to validators
213
+
214
+ **Key point:** The deadline is **fixed** based on the sub-slot number, not based on when the previous block finished.
215
+
216
+ Note that if a block finishes early, then the sequencer waits until the next sub-slot starts to maintain the regular interval. This prevents "rushing ahead" and keeps the timing predictable. Conversely, if the block finishes later than expected, this "eats into" the time budget for the next block.
217
+
218
+ #### Waiting for minimum transactions
219
+
220
+ Before building a block, the sequencer must ensure there are enough transactions in the mempool. This waiting phase has its own timing constraints:
221
+
222
+ **Configuration:**
223
+ - `minTxsPerBlock`: Minimum number of transactions required (configurable, e.g., 1-4)
224
+ - Polling interval: 500ms (checks mempool every half-second)
225
+ - Deadline: `blockDeadline - 1000ms` (must start building at least 1 second before the block deadline)
226
+
227
+ **Behavior:**
228
+ 1. Check current pending transaction count
229
+ 2. If count >= `minTxsPerBlock`, proceed to build immediately
230
+ 3. If count < `minTxsPerBlock`, poll every 500ms until either:
231
+ - Enough transactions arrive (proceed to build)
232
+ - Deadline is reached (skip building this block)
233
+
234
+ **Special cases:**
235
+ - **Last block with empty checkpoint allowed**: If `buildCheckpointIfEmpty` is true and this is the last block, skip waiting and force build with 0+ transactions
236
+ - **Non-enforced timetable**: If enforcement is disabled, exit immediately if not enough transactions (don't wait)
237
+
238
+ **Example:**
239
+ ```
240
+ Sub-slot 3 deadline: 26s
241
+ Transaction wait deadline: 25s (26s - 1s)
242
+ Current time: 20s
243
+
244
+ 20.0s: Check mempool → 2 txs (need 4)
245
+ 20.5s: Check mempool → 2 txs (need 4)
246
+ 21.0s: Check mempool → 4 txs (need 4) ✓ Start building!
247
+ 21-26s: Build block with those 4+ transactions
248
+ ```
249
+
250
+ If the deadline (25s) is reached with only 2 transactions, the block is skipped and the sequencer moves to the next sub-slot.
251
+
252
+ ### 3. Last Block and Validator Re-execution
253
+
254
+ The **last block** is built during the **penultimate sub-slot**. The final sub-slot is reserved for validators to re-execute the last block.
255
+
256
+ **Why the last sub-slot is reserved:**
257
+
258
+ Validators execute blocks **sequentially**. While the proposer builds Block N+1, validators are re-executing Block N (with a ~2s delay due to propagation). However, for the **last block**, there's no "Block N+1" to build while validators re-execute. We must wait for them to finish so they can attest.
259
+
260
+ **Timeline for the last block:**
261
+
262
+ ```
263
+ T: Last block finishes building, checkpoint proposal broadcast
264
+ Last sub-slot begins (duration: blockDuration)
265
+ T+2s: Validators receive proposal (propagation delay)
266
+ T+2s to T+2s+blockDuration: Validators re-execute last block
267
+ T+2s+blockDuration: Validators finish re-execution, send attestations
268
+ T+4s+blockDuration: Proposer receives attestations (propagation delay)
269
+ ```
270
+
271
+ **Example with 8s block duration:**
272
+ ```
273
+ 42s: Block 5 finishes, checkpoint broadcast, sub-slot 6 starts
274
+ 44s: Validators receive checkpoint (42s + 2s)
275
+ 44-52s: Validators re-execute Block 5 (8s)
276
+ 52s: Validators send attestations
277
+ 54s: Proposer receives attestations (52s + 2s)
278
+ ```
279
+
280
+ Note that validators finish at `52s`, which is `2s` after the last sub-slot ends at `50s`. This is expected and accounted for in the `timeReservedAtEnd` calculation.
281
+
282
+ ### 4. Attestation Collection and L1 Publishing
283
+
284
+ After the last block is built and validators have re-executed it:
285
+
286
+ 1. **Collect attestations**: Wait for validators to send their signatures (arrive at T+4s+blockDuration)
287
+ 2. **Finalize checkpoint**: Sign over attestations, assemble final checkpoint (1s)
288
+ 3. **Publish to L1**: Submit transaction to Ethereum (needs 12s to land)
289
+
290
+ **Time reserved:** `2*propagationTime + finalizationTime + l1PublishingTime = 2s + 2s + 1s + 12s = 17s`
291
+
292
+ In the non-pipelined path, this 17s comes after the last sub-slot, ensuring we have enough time to complete the checkpoint. If the sequencer receives the necessary attestations before the reserved time, the L1 tx is submitted earlier.
293
+
294
+ With proposer pipelining enabled, this finalization budget is not charged against the current slot when calculating how many blocks fit. The checkpoint is instead queued for submission at the start of the target slot, so proposal broadcast, attestation gathering, and L1 submission happen in slot `N` while block building and block re-execution already happened in slot `N-1`.
295
+
296
+ ## Handling Timing Variations
297
+
298
+ How does the sequencer timetable handle deviations from the expected times.
299
+
300
+ ### Fast Initialization
301
+
302
+ **Scenario:** Initialization completes at 1s instead of 2s
303
+
304
+ ```
305
+ 0-1s: SYNCHRONIZING, PROPOSER_CHECK (1s actual, vs 2s estimate)
306
+ 1s: Ready to build Block 1
307
+ 1-10s: Build Block 1 (9s available vs 8s budgeted)
308
+ 10s: Block 1 deadline
309
+ 10-18s: Build Block 2
310
+ ...
311
+ ```
312
+
313
+ **Result:** Block 1 gets a bonus 1s of execution time. The extra time allows for more transactions or more complex execution.
314
+
315
+ ### Slow Initialization
316
+
317
+ **Scenario:** Initialization completes at 3s instead of 2s
318
+
319
+ This may happen if the sequencer has a slow L1 RPC endpoint and syncing the previous checkpoint from L1 takes longer than expected.
320
+
321
+ ```
322
+ 0-3s: SYNCHRONIZING, PROPOSER_CHECK (3s actual, vs 2s estimate)
323
+ 3s: Ready to build Block 1
324
+ 3-10s: Build Block 1 (7s available vs 8s budgeted)
325
+ 10s: Block 1 deadline
326
+ 10-18s: Build Block 2
327
+ ...
328
+ ```
329
+
330
+ **Result:** Block 1 has 1s less time (7s instead of 8s). Still enough time to build a block, just with fewer transactions or simpler execution.
331
+
332
+ ### Very Slow Initialization
333
+
334
+ **Scenario:** Initialization completes at 9s instead of 2s
335
+
336
+ While extremely unlikely, we still account for this scenario. We'd expect it to be related to faults to syncing blob data.
337
+
338
+ ```
339
+ 0-9s: SYNCHRONIZING, PROPOSER_CHECK (9s actual, vs 2s estimate)
340
+ 9s: Ready to build Block 1
341
+ Check: Can we start Block 1 in sub-slot 1?
342
+ - Sub-slot 1 deadline: 10s
343
+ - Current time: 9s
344
+ - Time available: 1s
345
+ - MIN_EXECUTION_TIME: 3s
346
+ - 1s < 3s, so CANNOT use sub-slot 1
347
+
348
+ Use sub-slot 2 instead:
349
+ 9s: Start building Block 1 using sub-slot 2
350
+ 9-18s: Build Block 1 (9s available vs 8s budgeted)
351
+ 18s: Block 1 deadline (sub-slot 2)
352
+ 18-26s: Build Block 2 using sub-slot 3
353
+ ...
354
+ ```
355
+
356
+ **Result:** Sub-slot 1 is skipped entirely. We build 4 blocks instead of 5 (using sub-slots 2-5). Block 1 gets a bonus 1s of time.
357
+
358
+ ### Block Takes Longer Than Expected
359
+
360
+ **Scenario:** Block 2 takes 9s instead of 8s
361
+
362
+ This scenario should not happen since the sequencer forcefully stops the block builder at the given deadline, but we still consider it.
363
+
364
+ ```
365
+ 10s: Start building Block 2
366
+ 19s: Block 2 finishes (1s late, deadline was 18s)
367
+ 19s: Broadcast Block 2
368
+ Check: Can we start Block 3?
369
+ - Block 3 deadline: 26s
370
+ - Current time: 19s
371
+ - Time available: 7s
372
+ - MIN_EXECUTION_TIME: 3s
373
+ - 7s >= 3s, so CAN start Block 3
374
+
375
+ 19-26s: Build Block 3 (7s available vs 8s budgeted)
376
+ 26s: Block 3 deadline
377
+ ```
378
+
379
+ **Result:** Block 3 has less time (7s instead of 8s), but we still build it. The delay propagates but doesn't cascade uncontrollably.
380
+
381
+ **Extreme case:** If Block 2 finishes at 24s (6s late):
382
+ ```
383
+ 24s: Block 2 finishes (6s late)
384
+ Check: Can we start Block 3 in sub-slot 3?
385
+ - Sub-slot 3 deadline: 26s
386
+ - Current time: 24s
387
+ - Time available: 2s
388
+ - MIN_EXECUTION_TIME: 3s
389
+ - 2s < 3s, so CANNOT use sub-slot 3
390
+
391
+ Use sub-slot 4 instead:
392
+ 24-34s: Build Block 3 using sub-slot 4 (10s available vs 8s budgeted)
393
+ ```
394
+
395
+ **Result:** Sub-slot 3 is skipped, we build Block 3 using sub-slot 4 instead with bonus time.
396
+
397
+ ### Block Finishes Early
398
+
399
+ **Scenario:** Block 2 finishes at 15s instead of 18s
400
+
401
+ This can happen if the sequencer hits a block limit (number of txs, gas, size, etc) or runs out of available txs before the sub-slot deadline:
402
+
403
+ ```
404
+ 10-15s: Build Block 2 (5s used vs 8s budgeted)
405
+ 15s: Block 2 finished
406
+ 15s: Broadcast Block 2
407
+ Check: Should we start Block 3 now or wait?
408
+ - Next sub-slot starts at 18s
409
+ - Wait until 18s to maintain regular intervals
410
+
411
+ 15-18s: WAITING_UNTIL_NEXT_BLOCK
412
+ 18s: Start building Block 3
413
+ 18-26s: Build Block 3
414
+ ```
415
+
416
+ **Result:** We wait until the next sub-slot starts. This prevents "rushing ahead" and maintains consistent block intervals, which is better for validators who are re-executing blocks in parallel.
417
+
418
+ ## Parallel execution between Proposers and Validators
419
+
420
+ A key aspect of this design is **parallel execution** between proposer and validators.
421
+
422
+ ### Timeline Example (8-second sub-slots)
423
+
424
+ ```
425
+ Time | Proposer | Validators
426
+ -----|----------------------------|---------------------------
427
+ 2s | Start building Block 1 | (idle)
428
+ 10s | Finish Block 1, broadcast | (idle)
429
+ 10s | Start building Block 2 |
430
+ 12s | | Receive Block 1 (10s + 2s)
431
+ | | Start re-executing Block 1
432
+ 18s | Finish Block 2, broadcast |
433
+ 20s | | Finish re-executing Block 1 (12s + 8s)
434
+ | | Receive Block 2 (18s + 2s)
435
+ | | Start re-executing Block 2
436
+ 18s | Start building Block 3 |
437
+ 26s | Finish Block 3, broadcast |
438
+ 28s | | Finish re-executing Block 2 (20s + 8s)
439
+ | | Receive Block 3 (26s + 2s)
440
+ | | Start re-executing Block 3
441
+ ...
442
+ 42s | Finish Block 5, broadcast |
443
+ | checkpoint proposal |
444
+ 44s | | Finish re-executing Block 4 (36s + 8s)
445
+ | | Receive Block 5 + checkpoint (42s + 2s)
446
+ | | Start re-executing Block 5
447
+ 42-54s| COLLECTING_ATTESTATIONS |
448
+ 52s | | Finish re-executing Block 5 (44s + 8s)
449
+ | | Send attestations
450
+ 54s | Receive attestations | (done)
451
+ 54-55s| ASSEMBLING_CHECKPOINT |
452
+ 55s | PUBLISHING_CHECKPOINT |
453
+ ```
454
+
455
+ **Key observations:**
456
+ - Validators lag by ~2s (propagation delay)
457
+ - While proposer builds Block N+1, validators re-execute Block N (parallel work)
458
+ - For the last block, proposer waits while validators re-execute
459
+ - The last sub-slot provides the time budget for this waiting period
460
+
461
+ ## Configuration Guidelines
462
+
463
+ When configuring timing parameters, ensure these constraints are satisfied:
464
+
465
+ ### Minimum Slot Duration
466
+
467
+ For a valid multi-block configuration without pipelining:
468
+ ```
469
+ slotDuration >= initializationOffset
470
+ + blockDuration * 2 (at least 2 blocks)
471
+ + blockDuration (last sub-slot)
472
+ + 2 * propagationTime (round-trip)
473
+ + finalizationTime (checkpoint finalization)
474
+ + l1PublishingTime (L1 publishing)
475
+ ```
476
+
477
+ Simplified:
478
+ ```
479
+ slotDuration >= initializationOffset + 3*blockDuration + 2*propagationTime + finalizationTime + l1PublishingTime
480
+ ```
481
+
482
+ With proposer pipelining enabled, the same "at least 2 buildable blocks plus the final validator re-execution sub-slot" requirement becomes:
483
+ ```
484
+ slotDuration >= initializationOffset + 3*blockDuration
485
+ ```
486
+
487
+ **Example:**
488
+ ```
489
+ slotDuration >= 2s + 3*8s + 2*2s + 1s + 12s = 2s + 24s + 4s + 1s + 12s = 43s
490
+ ```
491
+
492
+ For a 72s slot, this leaves `72s - 43s = 29s` of slack, allowing for about 3-4 additional blocks (29s / 8s ≈ 3.6).
493
+
494
+ ### Block Duration Constraints
495
+
496
+ Block duration should be greater than the min execution time, and ideally a divisor of the time available for building.
497
+
498
+ ```
499
+ blockDuration >= MIN_EXECUTION_TIME (3s practical minimum for meaningful execution)
500
+ ```
501
+
502
+ ### Initialization Offset
503
+
504
+ The initialization offset should be set based on empirical measurements of how long initialization typically takes, with typical values being 1-3 seconds.
505
+
506
+ **Key point:** This is an *estimate*, not a hard deadline. The sequencer will take as long as needed for initialization. If it takes longer than the offset, the first block just has less time. If it takes less, the first block has bonus time.
507
+
508
+ ### Propagation Time
509
+
510
+ Should be measured empirically on the actual P2P network, accounting for:
511
+ - Network latency between geographically distributed validators
512
+ - Gossip network propagation (not direct communication)
513
+ - Block/checkpoint size (larger messages take longer)
514
+
515
+ Typical values: 1-3 seconds
516
+
517
+ ### L1 Publishing Time
518
+
519
+ Must account for Ethereum slot duration (12s) and blob propagation time:
520
+ - Bare minimum: 8s (Ethereum allows txs up to 4s into the slot)
521
+ - Recommended minimum: 12s (full Ethereum slot)
522
+ - With high blob congestion: 24s (two slots)
523
+
524
+ ## State Machine
525
+
526
+ The sequencer transitions through these states during a slot:
527
+
528
+ | State | Time Budget | Purpose |
529
+ |-------|-------------|---------|
530
+ | **SYNCHRONIZING** | No limit | Wait for all subsystems to sync |
531
+ | **PROPOSER_CHECK** | Part of init offset | Verify we're the proposer |
532
+ | **INITIALIZING_CHECKPOINT** | Part of init offset | Set up checkpoint state |
533
+ | **WAITING_FOR_TXS** | Until block deadline | Wait for enough transactions |
534
+ | **CREATING_BLOCK** | Until block deadline | Execute transactions and build block |
535
+ | **WAITING_UNTIL_NEXT_BLOCK** | Until next sub-slot start | Sleep between blocks to maintain intervals |
536
+ | **ASSEMBLING_CHECKPOINT** | assembleTime (1s) | Assemble final checkpoint |
537
+ | **COLLECTING_ATTESTATIONS** | Until L1 publish deadline | Wait for validator signatures |
538
+ | **PUBLISHING_CHECKPOINT** | Until L1 publish deadline | Submit to L1 |
539
+
540
+ ## Complete Example: 72-Second Slot with 8-Second Sub-Slots
541
+
542
+ Let's walk through a complete slot with the happy path:
543
+
544
+ ```
545
+ T=0s Slot begins for slot N
546
+
547
+ T=0-2s SYNCHRONIZING, PROPOSER_CHECK, INITIALIZING_CHECKPOINT
548
+ Actual time: 1.8s (slightly faster than 2s estimate)
549
+
550
+ T=2s Sub-slot 1 deadline calculation: 2s + 1*8s = 10s
551
+ T=1.8s Ready to build, start Block 1 immediately
552
+ Available time: 10s - 1.8s = 8.2s (bonus 0.2s!)
553
+ T=1.8-9.5s CREATING_BLOCK 1
554
+ T=9.5s Block 1 complete, broadcast
555
+ T=9.5-10s Wait for next sub-slot
556
+
557
+ T=10s Sub-slot 2 starts, deadline: 2s + 2*8s = 18s
558
+ T=10-17.5s CREATING_BLOCK 2
559
+ T=17.5s Block 2 complete, broadcast
560
+ T=17.5-18s Wait for next sub-slot
561
+
562
+ T=18s Sub-slot 3 starts, deadline: 2s + 3*8s = 26s
563
+ T=18-25s CREATING_BLOCK 3
564
+ T=25s Block 3 complete, broadcast
565
+ T=25-26s Wait for next sub-slot
566
+
567
+ T=26s Sub-slot 4 starts, deadline: 2s + 4*8s = 34s
568
+ T=26-33.5s CREATING_BLOCK 4
569
+ T=33.5s Block 4 complete, broadcast
570
+ T=33.5-34s Wait for next sub-slot
571
+
572
+ T=34s Sub-slot 5 starts, deadline: 2s + 5*8s = 42s
573
+ T=34-41s CREATING_BLOCK 5 (last block)
574
+ T=41s Block 5 complete
575
+
576
+ T=41s ASSEMBLING_CHECKPOINT (1s)
577
+ T=42s Checkpoint proposal broadcast
578
+ Sub-slot 6 starts (last sub-slot, reserved for validator reexec)
579
+
580
+ T=44s Validators receive checkpoint (42s + 2s propagation)
581
+ Validators start re-executing Block 5
582
+
583
+ T=52s Validators finish re-executing Block 5 (44s + 8s)
584
+ Validators send attestations
585
+
586
+ T=54s COLLECTING_ATTESTATIONS
587
+ Proposer receives attestations (52s + 2s propagation)
588
+
589
+ T=55s PUBLISHING_CHECKPOINT
590
+ Sign over attestations, submit L1 transaction
591
+
592
+ T=67s L1 transaction lands in Ethereum block (12s)
593
+
594
+ T=72s Slot ends (5s buffer remaining)
595
+ ```
596
+
597
+ **Summary:**
598
+ - Built 5 blocks (sub-slots 1-5)
599
+ - Last sub-slot (6) reserved for validator re-execution
600
+ - Total time: 72s
601
+ - Buffer: 5s (72s - 67s)
@@ -0,0 +1,87 @@
1
+ import { RollupContract, SimulationOverridesBuilder, type SimulationOverridesPlan } from '@aztec/ethereum/contracts';
2
+ import { CheckpointNumber } from '@aztec/foundation/branded-types';
3
+ import type { Fr } from '@aztec/foundation/curves/bn254';
4
+ import type { Logger } from '@aztec/foundation/log';
5
+ import type { ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
6
+
7
+ type PipelinedParentSimulationOverridesPlanInput = {
8
+ checkpointNumber: CheckpointNumber;
9
+ proposedCheckpointData?: ProposedCheckpointData;
10
+ rollup: RollupContract;
11
+ log: Logger;
12
+ };
13
+
14
+ type SubmissionSimulationOverridesPlanInput = {
15
+ pipelinedParentPlan?: SimulationOverridesPlan;
16
+ invalidateToPendingCheckpointNumber?: CheckpointNumber;
17
+ lastArchiveRoot: Fr;
18
+ pipeliningEnabled: boolean;
19
+ };
20
+
21
+ /** Builds the simulated parent checkpoint view used while constructing a pipelined proposal. */
22
+ export async function buildPipelinedParentSimulationOverridesPlan(
23
+ input: PipelinedParentSimulationOverridesPlanInput,
24
+ ): Promise<SimulationOverridesPlan | undefined> {
25
+ const parentCheckpointNumber = CheckpointNumber(input.checkpointNumber - 1);
26
+ const builder = new SimulationOverridesBuilder().forPendingCheckpoint(parentCheckpointNumber);
27
+
28
+ const pendingFeeHeader = await computePipelinedParentFeeHeader(input);
29
+ if (pendingFeeHeader) {
30
+ builder.withPendingFeeHeader(pendingFeeHeader);
31
+ }
32
+
33
+ return builder.build();
34
+ }
35
+
36
+ /** Builds the simulated chain view used when validating and enqueueing checkpoint submission. */
37
+ export function buildSubmissionSimulationOverridesPlan(
38
+ input: SubmissionSimulationOverridesPlanInput,
39
+ ): SimulationOverridesPlan | undefined {
40
+ const pendingCheckpointNumber =
41
+ input.invalidateToPendingCheckpointNumber ?? input.pipelinedParentPlan?.pendingCheckpointNumber;
42
+
43
+ const builder = SimulationOverridesBuilder.from(input.pipelinedParentPlan).forPendingCheckpoint(
44
+ pendingCheckpointNumber,
45
+ );
46
+
47
+ if (input.pipeliningEnabled && pendingCheckpointNumber !== undefined) {
48
+ builder.withPendingArchive(input.lastArchiveRoot);
49
+ }
50
+
51
+ return builder.build();
52
+ }
53
+
54
+ /** Derives the pending parent fee header used during pipelined proposal simulation. */
55
+ export async function computePipelinedParentFeeHeader(input: PipelinedParentSimulationOverridesPlanInput) {
56
+ if (!input.proposedCheckpointData || input.checkpointNumber < 2) {
57
+ return undefined;
58
+ }
59
+
60
+ const grandparentCheckpointNumber = CheckpointNumber(input.checkpointNumber - 2);
61
+
62
+ try {
63
+ const [grandparentCheckpoint, manaTarget] = await Promise.all([
64
+ input.rollup.getCheckpoint(grandparentCheckpointNumber),
65
+ input.rollup.getManaTarget(),
66
+ ]);
67
+
68
+ if (!grandparentCheckpoint?.feeHeader) {
69
+ input.log.error(
70
+ `Grandparent checkpoint or feeHeader missing for checkpoint ${grandparentCheckpointNumber.toString()}`,
71
+ );
72
+ return undefined;
73
+ }
74
+
75
+ return RollupContract.computeChildFeeHeader(
76
+ grandparentCheckpoint.feeHeader,
77
+ input.proposedCheckpointData.totalManaUsed,
78
+ input.proposedCheckpointData.feeAssetPriceModifier,
79
+ manaTarget,
80
+ );
81
+ } catch (err) {
82
+ input.log.error(
83
+ `Failed to derive pipelined parent fee header for checkpoint ${grandparentCheckpointNumber.toString()}: ${err}`,
84
+ );
85
+ return undefined;
86
+ }
87
+ }