Skip to content

Commit 34235ee

Browse files
authored
fix: Checkpoint doc + readability improvements (#18)
1 parent 80920b5 commit 34235ee

File tree

1 file changed

+97
-84
lines changed

1 file changed

+97
-84
lines changed

src/payload/checkpoint.rs

Lines changed: 97 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use {
1717
state::{AccountInfo, Bytecode},
1818
},
1919
},
20-
std::{sync::Arc, time::Instant},
20+
std::{iter::Successors, sync::Arc, time::Instant},
2121
thiserror::Error,
2222
};
2323

@@ -35,11 +35,11 @@ pub enum Error<P: Platform> {
3535
///
3636
/// Notes:
3737
/// - There is no public API to create a checkpoint directly. Checkpoints are
38-
/// created by the [`BlockContext`] when it starts a new payload building
39-
/// process or by mutatations applied to an already existing checkpoint.
38+
/// created from the [`BlockContext`] when it starts a new payload building
39+
/// process or by mutations applied to an already existing checkpoint.
4040
///
4141
/// - Checkpoints contain all the information needed to assemble a full block
42-
/// payload, they however cannot be used directly to assemble a block. The
42+
/// payload. However, they cannot be used directly to assemble a block. The
4343
/// block assembly process is very node-specific and is part of the pipelines
4444
/// api, which has more info and access to the underlying node facilities.
4545
///
@@ -48,13 +48,13 @@ pub enum Error<P: Platform> {
4848
/// existing ones, forming a chain of checkpoints.
4949
///
5050
/// - Checkpoints may represent forks in the payload building process. Two
51-
/// checkpoints can share a common ancestor, without having linear history
51+
/// checkpoints can share a common ancestor without having a linear history
5252
/// between them. Each of the diverging checkpoints can be used to build
5353
/// alternative versions of the payload.
5454
///
55-
/// - Checkpoints are cheap to clone, discard and move around. They are
56-
/// expensive to create, as they require executing a transaction by the EVM
57-
/// and storing the resulting state changes.
55+
/// - Checkpoints are inexpensive to clone, discard and move around. However,
56+
/// they are expensive to create, as they require executing transactions
57+
/// through the EVM and storing the resulting state changes.
5858
///
5959
/// - Checkpoints are thread-safe, Send + Sync + 'static.
6060
///
@@ -75,11 +75,11 @@ pub struct Checkpoint<P: Platform> {
7575

7676
/// Public read API
7777
impl<P: Platform> Checkpoint<P> {
78-
/// Returns the number of checkpoints preceeding this checkpoint since the
79-
/// beginning of the block payload we're building.
78+
/// Returns the number of checkpoints preceding this checkpoint from the
79+
/// beginning of the block payload.
8080
///
8181
/// Depth zero is when [`BlockContext::start`] is called, and the first
82-
/// checkpoint is created and has no previous checkpoints.
82+
/// checkpoint is created with no previous checkpoints.
8383
pub fn depth(&self) -> usize {
8484
self.inner.depth
8585
}
@@ -89,7 +89,7 @@ impl<P: Platform> Checkpoint<P> {
8989
self.inner.created_at
9090
}
9191

92-
/// The returns the payload version before the current checkpoint.
92+
/// Returns the previous checkpoint before the current checkpoint.
9393
///
9494
/// Using the previous checkpoint is equivalent to discarding the
9595
/// state mutations made in the current checkpoint.
@@ -102,16 +102,19 @@ impl<P: Platform> Checkpoint<P> {
102102
})
103103
}
104104

105-
/// Returns the block context that is the root of theis checkpoint.
105+
/// Returns the block context at the root of the checkpoint.
106106
pub fn block(&self) -> &BlockContext<P> {
107107
&self.inner.block
108108
}
109109

110-
/// The transactions that created this checkpoint. This could be either an
111-
/// empty iterator if this checkpoint is a barrier or other non-transaction
112-
/// checkpoint, it can be one transaction if this checkpoint was created by
113-
/// applying a single transaction, or it can be multiple if this checkpoint
114-
/// represents a bundle.
110+
/// The transactions that created this checkpoint.
111+
/// The returned slice is a view into all applied transactions in this
112+
/// checkpoint:
113+
/// - Empty if this checkpoint is a barrier or other non-transaction
114+
/// checkpoint.
115+
/// - Single transaction if this checkpoint was created by applying a single
116+
/// transaction.
117+
/// - Multiple transactions if this checkpoint represents a bundle.
115118
pub fn transactions(&self) -> &[Recovered<types::Transaction<P>>] {
116119
match &self.inner.mutation {
117120
Mutation::Barrier | Mutation::NamedBarrier(_) => &[],
@@ -128,7 +131,7 @@ impl<P: Platform> Checkpoint<P> {
128131
}
129132
}
130133

131-
/// The state changes that occured as a result of executing the
134+
/// The state changes that occurred as a result of executing the
132135
/// transaction(s) that created this checkpoint.
133136
pub fn state(&self) -> Option<&BundleState> {
134137
match self.inner.mutation {
@@ -147,27 +150,25 @@ impl<P: Platform> Checkpoint<P> {
147150
matches!(self.inner.mutation, Mutation::NamedBarrier(ref barrier_name) if barrier_name == name)
148151
}
149152

150-
/// If this checkpoint is a single transaction, returns a reference to the
151-
/// transaction that created this checkpoint. otherwise returns `None`.
153+
/// If this checkpoint is created from a single transaction, returns a
154+
/// reference to this transaction. Otherwise, returns `None`.
152155
pub fn as_transaction(&self) -> Option<&Recovered<types::Transaction<P>>> {
153156
if let Mutation::Executable(result) = &self.inner.mutation {
154157
if let Executable::Transaction(tx) = result.source() {
155158
return Some(tx);
156159
}
157160
}
158-
159161
None
160162
}
161163

162-
/// If this checkpoint is a bundle, returns a reference to the bundle that
163-
/// created this checkpoint. otherwise returns `None`.
164+
/// If this checkpoint is created from a bundle, returns a reference to this
165+
/// bundle. Otherwise, returns `None`.
164166
pub fn as_bundle(&self) -> Option<&types::Bundle<P>> {
165167
if let Mutation::Executable(result) = &self.inner.mutation {
166168
if let Executable::Bundle(bundle) = result.source() {
167169
return Some(bundle);
168170
}
169171
}
170-
171172
None
172173
}
173174
}
@@ -182,32 +183,35 @@ impl<P: Platform> Checkpoint<P> {
182183
&self,
183184
executable: impl IntoExecutable<P, S>,
184185
) -> Result<Self, ExecutionError<P>> {
185-
Ok(Self {
186-
inner: Arc::new(CheckpointInner {
187-
block: self.inner.block.clone(),
188-
prev: Some(Arc::clone(&self.inner)),
189-
depth: self.inner.depth + 1,
190-
mutation: Mutation::Executable(
191-
executable
192-
.try_into_executable()?
193-
.execute(self.block(), self)?,
194-
),
195-
created_at: Instant::now(),
196-
}),
197-
})
186+
let mutation = Mutation::Executable(
187+
executable
188+
.try_into_executable()?
189+
.execute(self.block(), self)?,
190+
);
191+
Ok(self.apply_with(mutation))
198192
}
199193

200194
/// Creates a new checkpoint on top of the current checkpoint that introduces
201195
/// a barrier. This new checkpoint will be now considered the new beginning of
202196
/// mutable history.
203197
#[must_use]
204198
pub fn barrier(&self) -> Self {
199+
Self::apply_with(self, Mutation::Barrier)
200+
}
201+
}
202+
203+
/// Internal API
204+
impl<P: Platform> Checkpoint<P> {
205+
// Create a new checkpoint on top of the current one with the given mutation.
206+
// See public builder API.
207+
#[must_use]
208+
fn apply_with(&self, mutation: Mutation<P>) -> Self {
205209
Self {
206210
inner: Arc::new(CheckpointInner {
207211
block: self.inner.block.clone(),
208212
prev: Some(Arc::clone(&self.inner)),
209213
depth: self.inner.depth + 1,
210-
mutation: Mutation::Barrier,
214+
mutation,
211215
created_at: Instant::now(),
212216
}),
213217
}
@@ -228,13 +232,11 @@ impl<P: Platform> Checkpoint<P> {
228232
}),
229233
}
230234
}
231-
}
232235

233-
/// Internal API
234-
impl<P: Platform> Checkpoint<P> {
235236
/// Start a new checkpoint for an empty payload rooted at the
236237
/// state of the parent block of the block for which the payload is
237238
/// being built.
239+
#[must_use]
238240
pub(super) fn new_at_block(block: BlockContext<P>) -> Self {
239241
Self {
240242
inner: Arc::new(CheckpointInner {
@@ -246,14 +248,35 @@ impl<P: Platform> Checkpoint<P> {
246248
}),
247249
}
248250
}
251+
252+
/// Lazy iterator over historic checkpoints.
253+
/// Note that it is in reverse history order, starting from the latest applied
254+
/// checkpoint up to the first one.
255+
fn iter(&self) -> Successors<Self, fn(&Self) -> Option<Self>> {
256+
<&Self as IntoIterator>::into_iter(self)
257+
}
249258
}
250259

251-
/// Describes the type of state mutation that was applied to the
252-
/// previous checkpoint to create this checkpoint.
260+
impl<P: Platform> IntoIterator for &Checkpoint<P> {
261+
type IntoIter =
262+
Successors<Checkpoint<P>, fn(&Checkpoint<P>) -> Option<Checkpoint<P>>>;
263+
type Item = Checkpoint<P>;
264+
265+
fn into_iter(self) -> Self::IntoIter {
266+
std::iter::successors(Some(self.clone()), |cp| {
267+
cp.inner.prev.as_ref().map(|prev| Checkpoint {
268+
inner: Arc::clone(prev),
269+
})
270+
})
271+
}
272+
}
273+
274+
/// Describes the type of state mutation that was applied to the previous
275+
/// checkpoint to create this checkpoint.
253276
enum Mutation<P: Platform> {
254277
/// A checkpoint that indicates that any prior checkpoints are immutable and
255278
/// should not be discarded or reordered. An example of this would be placing
256-
/// a barrier after applying sequencer transactions, to ensure that they do
279+
/// a barrier after applying sequencer transactions to ensure that they do
257280
/// not get reordered by pipelines. Another example would be placing a barrier
258281
/// after every commited flashblock, to ensure that any steps in the pipeline
259282
/// do not modify the commited state of the payload in process.
@@ -284,13 +307,14 @@ struct CheckpointInner<P: Platform> {
284307
/// The previous checkpoint in this chain of checkpoints, if any.
285308
prev: Option<Arc<Self>>,
286309

287-
/// The number of checkpoints in the chain starting from the begining of the
310+
/// The number of checkpoints in the chain starting from the beginning of the
288311
/// block context.
289312
///
290-
/// Depth zero is when [`BlockContext::start`] is called, and the first
313+
/// Depth zero is when [`BlockContext::start`] is called, as the first
314+
/// checkpoint
291315
depth: usize,
292316

293-
/// The mutation
317+
/// The mutation kind for the checkpoint.
294318
mutation: Mutation<P>,
295319

296320
/// The timestamp when this checkpoint was created.
@@ -311,7 +335,7 @@ impl<P: Platform> From<Checkpoint<P>> for Vec<types::Transaction<P>> {
311335

312336
/// Any checkpoint can be used as a database reference for an EVM instance.
313337
/// The state at a checkpoint is the cumulative aggregate of all state mutations
314-
/// that occured in the current checkpoint and all its ancestors on top of the
338+
/// that occurred in the current checkpoint and all its ancestors on top of the
315339
/// base state of the parent block of the block for which the payload is being
316340
/// built.
317341
impl<P: Platform> DatabaseRef for Checkpoint<P> {
@@ -327,19 +351,15 @@ impl<P: Platform> DatabaseRef for Checkpoint<P> {
327351
// starting from the most recent one, to find the first checkpoint
328352
// that has touched the given address.
329353

330-
let mut current = Some(&self.inner);
331-
while let Some(checkpoint) = current {
332-
if let Mutation::Executable(result) = &checkpoint.mutation {
333-
if let Some(account) = result
334-
.state()
335-
.account(&address)
336-
.and_then(|account| account.info.as_ref())
337-
{
338-
return Ok(Some(account.clone()));
339-
}
340-
}
341-
342-
current = checkpoint.prev.as_ref();
354+
if let Some(account) = self.iter().find_map(|checkpoint| {
355+
checkpoint
356+
.result()?
357+
.state()
358+
.account(&address)
359+
.and_then(|account| account.info.as_ref())
360+
.cloned()
361+
}) {
362+
return Ok(Some(account));
343363
}
344364

345365
// none of the checkpoints priori to this have touched this address,
@@ -359,17 +379,14 @@ impl<P: Platform> DatabaseRef for Checkpoint<P> {
359379
// starting from the most recent one, to find the first checkpoint
360380
// that has created the code with the given hash.
361381

362-
let mut current = Some(&self.inner);
363-
while let Some(checkpoint) = current {
364-
if let Mutation::Executable(result) = &checkpoint.mutation {
365-
if let Some(code) = result.state().bytecode(&code_hash) {
366-
return Ok(code);
367-
}
368-
}
369-
370-
current = checkpoint.prev.as_ref();
382+
if let Some(code) = self
383+
.iter()
384+
.find_map(|checkpoint| checkpoint.result()?.state().bytecode(&code_hash))
385+
{
386+
return Ok(code);
371387
}
372388

389+
// check if the code exists in the base state of the block context.
373390
Ok(
374391
self
375392
.block()
@@ -389,19 +406,15 @@ impl<P: Platform> DatabaseRef for Checkpoint<P> {
389406
// traverse checkpoints history looking for the first checkpoint that
390407
// has touched the given address.
391408

392-
let mut current = Some(&self.inner);
393-
while let Some(checkpoint) = current {
394-
if let Mutation::Executable(result) = &checkpoint.mutation {
395-
if let Some(slot) = result
396-
.state()
397-
.account(&address)
398-
.and_then(|account| account.storage.get(&index))
399-
{
400-
return Ok(slot.present_value);
401-
}
402-
}
403-
404-
current = checkpoint.prev.as_ref();
409+
if let Some(value) = self.iter().find_map(|checkpoint| {
410+
checkpoint
411+
.result()?
412+
.state()
413+
.account(&address)
414+
.and_then(|account| account.storage.get(&index))
415+
.map(|slot| slot.present_value)
416+
}) {
417+
return Ok(value);
405418
}
406419

407420
// none of the checkpoints prior to this have touched this address,

0 commit comments

Comments
 (0)