diff --git a/src/payload/checkpoint.rs b/src/payload/checkpoint.rs index db3b496..49a14da 100644 --- a/src/payload/checkpoint.rs +++ b/src/payload/checkpoint.rs @@ -116,7 +116,7 @@ impl Checkpoint

{ /// - Multiple transactions if this checkpoint represents a bundle. pub fn transactions(&self) -> &[Recovered>] { match &self.inner.mutation { - Mutation::Barrier | Mutation::NamedBarrier(_) => &[], + Mutation::Barrier => &[], Mutation::Executable(result) => result.transactions(), } } @@ -125,7 +125,7 @@ impl Checkpoint

{ /// checkpoint. pub fn result(&self) -> Option<&ExecutionResult

> { match &self.inner.mutation { - Mutation::Barrier | Mutation::NamedBarrier(_) => None, + Mutation::Barrier => None, Mutation::Executable(result) => Some(result), } } @@ -134,7 +134,7 @@ impl Checkpoint

{ /// transaction(s) that created this checkpoint. pub fn state(&self) -> Option<&BundleState> { match self.inner.mutation { - Mutation::Barrier | Mutation::NamedBarrier(_) => None, + Mutation::Barrier => None, Mutation::Executable(ref result) => Some(result.state()), } } @@ -144,9 +144,14 @@ impl Checkpoint

{ matches!(self.inner.mutation, Mutation::Barrier) } - /// Returns true if this checkpoint is a named barrier checkpoint. - pub fn is_named_barrier(&self, name: &str) -> bool { - matches!(self.inner.mutation, Mutation::NamedBarrier(ref barrier_name) if barrier_name == name) + /// Returns true if this checkpoint has the given tag. + pub fn is_tagged(&self, tag: &str) -> bool { + self.tag().is_some_and(|t| t == tag) + } + + /// Returns the tag of this checkpoint, if any. + pub fn tag(&self) -> Option<&str> { + self.inner.tag.as_deref() } /// If this checkpoint is created from a single transaction, returns a @@ -187,7 +192,23 @@ impl Checkpoint

{ .try_into_executable()? .execute(self.block(), self)?, ); - Ok(self.apply_with(mutation)) + Ok(self.apply_with(mutation, None)) + } + + /// Creates a new checkpoint on top of the current checkpoint and tags it. + /// The execution will use the cumulative state of all checkpoints in the + /// current checkpoint history as its state. + pub fn apply_with_tag( + &self, + executable: impl IntoExecutable, + tag: impl Into>, + ) -> Result> { + let mutation = Mutation::Executable( + executable + .try_into_executable()? + .execute(self.block(), self)?, + ); + Ok(self.apply_with(mutation, Some(tag.into()))) } /// Creates a new checkpoint on top of the current checkpoint that introduces @@ -195,7 +216,13 @@ impl Checkpoint

{ /// staging history. #[must_use] pub fn barrier(&self) -> Self { - Self::apply_with(self, Mutation::Barrier) + Self::apply_with(self, Mutation::Barrier, None) + } + + /// Creates a new tagged barrier checkpoint on top of the current checkpoint. + #[must_use] + pub fn barrier_with_tag(&self, tag: impl Into>) -> Self { + Self::apply_with(self, Mutation::Barrier, Some(tag.into())) } } @@ -204,7 +231,7 @@ impl Checkpoint

{ // Create a new checkpoint on top of the current one with the given mutation. // See public builder API. #[must_use] - fn apply_with(&self, mutation: Mutation

) -> Self { + fn apply_with(&self, mutation: Mutation

, tag: Option>) -> Self { Self { inner: Arc::new(CheckpointInner { block: self.inner.block.clone(), @@ -212,22 +239,7 @@ impl Checkpoint

{ depth: self.inner.depth + 1, mutation, created_at: Instant::now(), - }), - } - } - - /// Creates a new checkpoint on top of the current checkpoint that introduces - /// a named barrier. This new checkpoint will be now considered the new - /// beginning of staging history. - #[must_use] - pub fn named_barrier(&self, name: impl Into) -> Self { - Self { - inner: Arc::new(CheckpointInner { - block: self.inner.block.clone(), - prev: Some(Arc::clone(&self.inner)), - depth: self.inner.depth + 1, - mutation: Mutation::NamedBarrier(name.into()), - created_at: Instant::now(), + tag, }), } } @@ -244,6 +256,7 @@ impl Checkpoint

{ depth: 0, mutation: Mutation::Barrier, created_at: Instant::now(), + tag: None, }), } } @@ -292,11 +305,6 @@ enum Mutation { /// the previous checkpoint. The executable item can be a single transaction /// or a bundle of transactions. Executable(ExecutionResult

), - - /// A checkpoint that was created by applying a named barrier on top of the - /// previous checkpoint. The named barrier is used to indicate that any prior - /// checkpoints are immutable and should not be discarded or reordered. - NamedBarrier(String), } struct CheckpointInner { @@ -318,6 +326,11 @@ struct CheckpointInner { /// The timestamp when this checkpoint was created. created_at: Instant, + + /// Optional tag for this checkpoint. Tags are metadata used to mark + /// checkpoints for later reference in history queries and display/debug + /// output. + tag: Option>, } /// Converts a checkpoint into a vector of transactions that were applied to @@ -460,6 +473,7 @@ impl Debug for Checkpoint

{ f.debug_struct("Checkpoint") .field("depth", &self.depth()) .field("block", &format!("{} + 1", self.block().parent().hash())) + .field("tag", &self.tag()) .field( "txs", &self @@ -475,6 +489,11 @@ impl Debug for Checkpoint

{ impl Display for Checkpoint

{ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let tag_suffix = self + .tag() + .map(|tag| format!(" '{tag}'")) + .unwrap_or_default(); + let Mutation::Executable(exec_result) = &self.inner.mutation else { if self.depth() == 0 { // this is the initial checkpoint @@ -484,9 +503,8 @@ impl Display for Checkpoint

{ // this is a barrier checkpoint, which has no transactions // applied to it. return match &self.inner.mutation { - Mutation::Barrier => write!(f, "[{}] barrier", self.depth()), - Mutation::NamedBarrier(name) => { - write!(f, "[{}] barrier '{}'", self.depth(), name) + Mutation::Barrier => { + write!(f, "[{}] barrier{}", self.depth(), tag_suffix) } Mutation::Executable(_) => { unreachable!("Executable variant handled above") @@ -497,7 +515,7 @@ impl Display for Checkpoint

{ match exec_result.source() { Executable::Transaction(tx) => write!( f, - "[{}] tx {} ({}, {} gas)", + "[{}] tx {} ({}, {} gas){}", self.depth(), tx.tx_hash(), match exec_result.results()[0] { @@ -506,13 +524,15 @@ impl Display for Checkpoint

{ types::TransactionExecutionResult::

::Halt { .. } => "halt", }, self.gas_used(), + tag_suffix, ), Executable::Bundle(bundle) => write!( f, - "[{}] (bundle {} txs, {} gas)", + "[{}] (bundle {} txs, {} gas){}", self.depth(), bundle.transactions().len(), self.gas_used(), + tag_suffix, ), } } diff --git a/src/payload/ext/checkpoint.rs b/src/payload/ext/checkpoint.rs index 882e69a..a40657e 100644 --- a/src/payload/ext/checkpoint.rs +++ b/src/payload/ext/checkpoint.rs @@ -135,6 +135,22 @@ pub trait CheckpointExt: super::sealed::Sealed { /// first checkpoint in the history of this checkpoint that is created /// by `Checkpoint::new_at_block`. fn building_since(&self) -> Instant; + + /// Returns a span starting at the last checkpoint tagged with `tag`. + /// Returns `None` if no such tag exists in history. + fn history_since_last_tag(&self, tag: &str) -> Option> { + let history = self.history(); + let start = history.iter().rposition(|cp| cp.is_tagged(tag))?; + Some(history.skip(start)) + } + + /// Returns a span starting at the first checkpoint tagged with `tag`. + /// Returns `None` if no such tag exists in history. + fn history_since_first_tag(&self, tag: &str) -> Option> { + let history = self.history(); + let start = history.iter().position(|cp| cp.is_tagged(tag))?; + Some(history.skip(start)) + } } impl CheckpointExt

for Checkpoint

{ diff --git a/src/test_utils/step.rs b/src/test_utils/step.rs index 0376d82..efb567f 100644 --- a/src/test_utils/step.rs +++ b/src/test_utils/step.rs @@ -191,13 +191,16 @@ impl> OneStep

{ self } - /// Adds a named barrier to the input payload of the step at the current + /// Adds a tagged barrier to the input payload of the step at the current /// position. #[must_use] - pub fn with_payload_named_barrier(mut self, name: impl Into) -> Self { + pub fn with_payload_tagged_barrier( + mut self, + name: impl Into, + ) -> Self { self .payload_input - .push(InputPayloadItemFn::NamedBarrier(name.into())); + .push(InputPayloadItemFn::TaggedBarrier(name.into())); self } @@ -237,8 +240,8 @@ impl> OneStep

{ .map(|input| -> eyre::Result> { Ok(match input { InputPayloadItemFn::Barrier => InputPayloadItem::Barrier, - InputPayloadItemFn::NamedBarrier(name) => { - InputPayloadItem::NamedBarrier(name) + InputPayloadItemFn::TaggedBarrier(name) => { + InputPayloadItem::TaggedBarrier(name) } InputPayloadItemFn::Tx(mut builder) => InputPayloadItem::Tx( builder( @@ -315,7 +318,7 @@ impl Step

for PopulatePayload

{ while let Ok(input) = self.receiver.lock().await.try_recv() { payload = match input { InputPayloadItem::Barrier => payload.barrier(), - InputPayloadItem::NamedBarrier(name) => payload.named_barrier(name), + InputPayloadItem::TaggedBarrier(name) => payload.barrier_with_tag(name), InputPayloadItem::Tx(tx) => { payload.apply(tx).expect("Failed to apply transaction") } @@ -399,14 +402,14 @@ impl Step

for RecordBreakAndFail

{ enum InputPayloadItemFn { Barrier, - NamedBarrier(String), + TaggedBarrier(String), Tx(BoxedTxBuilderFn

), Bundle(types::Bundle

), } enum InputPayloadItem { Barrier, - NamedBarrier(String), + TaggedBarrier(String), Tx(types::TxEnvelope

), Bundle(types::Bundle

), }