-
Notifications
You must be signed in to change notification settings - Fork 15
feat: atomic combinator step #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| use {super::*, std::sync::Arc}; | ||
|
|
||
| pub struct AtomicMode; | ||
|
|
||
| impl<P: Platform> CompositeStepMode<P> for AtomicMode { | ||
| async fn steps( | ||
| &self, | ||
| steps: &[Arc<StepInstance<P>>], | ||
| payload: Checkpoint<P>, | ||
| ctx: StepContext<P>, | ||
| ) -> ControlFlow<P> { | ||
| let initial = payload.clone(); | ||
| let mut current = payload; | ||
|
|
||
| for step in steps { | ||
| if ctx.deadline_reached() { | ||
| return ControlFlow::Break(initial); | ||
| } | ||
|
|
||
| match step.step(current, ctx.clone()).await { | ||
| ControlFlow::Ok(next) => current = next, | ||
| ControlFlow::Break(_) => return ControlFlow::Break(initial), | ||
| ControlFlow::Fail(error) => return ControlFlow::Fail(error), | ||
|
Comment on lines
+22
to
+23
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My intuition is that those two variants should return |
||
| } | ||
| } | ||
|
|
||
| if ctx.deadline_reached() { | ||
| ControlFlow::Break(initial) | ||
| } else { | ||
| ControlFlow::Ok(current) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[macro_export] | ||
| macro_rules! atomic { | ||
| ($($step:expr),+ $(,)?) => {{ | ||
| let mut composite = | ||
| $crate::prelude::composite::CompositeStep::new( | ||
| $crate::prelude::composite::atomic::AtomicMode, | ||
| ); | ||
| $( | ||
| composite.append_step($step); | ||
| )+ | ||
| composite | ||
| }}; | ||
| } | ||
julio4 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use {super::*, crate::test_utils::*}; | ||
|
|
||
| // TODO: improve tests here | ||
|
|
||
| #[rblib_test(Ethereum)] | ||
| async fn atomic_mode_break_reverts<P: TestablePlatform>() -> eyre::Result<()> | ||
| { | ||
| let mut composite = CompositeStep::<P, _>::new(AtomicMode); | ||
| composite.append_step(AlwaysOkStep); | ||
| composite.append_step(AlwaysBreakStep); // break here | ||
| composite.append_step(AlwaysOkStep); | ||
|
|
||
| let result = OneStep::<P>::new(composite).run().await?; | ||
| assert!(matches!(result, ControlFlow::Break(_))); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[rblib_test(Ethereum)] | ||
| async fn atomic_mode_fail_propagates<P: TestablePlatform>() -> eyre::Result<()> | ||
| { | ||
| let mut composite = CompositeStep::<P, _>::new(AtomicMode); | ||
| composite.append_step(AlwaysOkStep); | ||
| composite.append_step(AlwaysFailStep); | ||
| composite.append_step(AlwaysOkStep); | ||
|
|
||
| let result = OneStep::<P>::new(composite).run().await?; | ||
| assert!(matches!(result, ControlFlow::Fail(_))); | ||
|
Comment on lines
+77
to
+78
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think we should My thoughts are that if As a user of this API I would imagine
What are your thoughts?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes this makes sense, we should go with what you described. I didn't think through the whole logic of Atomic too much at first |
||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[rblib_test(Ethereum)] | ||
julio4 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| async fn atomic_macro_basic<P: TestablePlatform>() -> eyre::Result<()> { | ||
| let composite = atomic!(AlwaysOkStep, AlwaysOkStep); | ||
|
|
||
| let result = OneStep::<P>::new(composite).run().await?; | ||
| assert!(matches!(result, ControlFlow::Ok(_))); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[rblib_test(Ethereum)] | ||
| async fn atomic_in_pipeline<P: TestablePlatform>() -> eyre::Result<()> { | ||
| let pipeline = | ||
| Pipeline::<P>::default().with_step(atomic!(AlwaysOkStep, AlwaysOkStep)); | ||
julio4 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| P::create_test_node(pipeline).await?.next_block().await?; | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| //! This module defines composite steps that can be used to compose multiple | ||
| //! steps into a single step. | ||
|
|
||
| use { | ||
| crate::{ | ||
| pipelines::step::StepInstance, | ||
| platform::types::BuiltPayload, | ||
| prelude::*, | ||
| }, | ||
| std::sync::Arc, | ||
| }; | ||
|
|
||
| pub mod atomic; | ||
|
|
||
| /// A composite step with a list of steps. | ||
| /// The associated mode defines the specific behavior of executing steps | ||
| pub struct CompositeStep<P: Platform, M> { | ||
| steps: Vec<Arc<StepInstance<P>>>, | ||
| mode: M, | ||
| } | ||
|
|
||
| impl<P: Platform, M> CompositeStep<P, M> { | ||
| pub fn new(mode: M) -> Self { | ||
| Self { | ||
| steps: Vec::new(), | ||
| mode, | ||
| } | ||
| } | ||
|
|
||
| pub fn append_step(&mut self, step: impl Step<P>) { | ||
| self.steps.push(Arc::new(StepInstance::new(step))); | ||
| } | ||
| } | ||
|
|
||
| /// A composite step mode defines the specific behavior of executing steps | ||
| /// It takes the list of steps, initial payload and context | ||
| trait CompositeStepMode<P: Platform>: Send + Sync { | ||
| fn steps( | ||
| &self, | ||
| steps: &[Arc<StepInstance<P>>], | ||
| payload: Checkpoint<P>, | ||
| ctx: StepContext<P>, | ||
| ) -> impl Future<Output = ControlFlow<P>> + Send; | ||
| } | ||
|
|
||
| /// `CompositeStep` will execute all before/after/setup functions of each step | ||
| /// in order The step implementation is delegated to the composite mode | ||
| impl<P, M> Step<P> for CompositeStep<P, M> | ||
| where | ||
| P: Platform, | ||
| M: CompositeStepMode<P> + Send + 'static, | ||
| { | ||
| async fn step( | ||
| self: Arc<Self>, | ||
| payload: Checkpoint<P>, | ||
| ctx: StepContext<P>, | ||
| ) -> ControlFlow<P> { | ||
| self.mode.steps(&self.steps, payload, ctx).await | ||
| } | ||
|
|
||
| async fn before_job( | ||
| self: Arc<Self>, | ||
| ctx: StepContext<P>, | ||
| ) -> Result<(), PayloadBuilderError> { | ||
| for step in &self.steps { | ||
| step.before_job(ctx.clone()).await?; | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| async fn after_job( | ||
| self: Arc<Self>, | ||
| ctx: StepContext<P>, | ||
| result: Arc<Result<BuiltPayload<P>, PayloadBuilderError>>, | ||
| ) -> Result<(), PayloadBuilderError> { | ||
| for step in &self.steps { | ||
| step.after_job(ctx.clone(), result.clone()).await?; | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| async fn setup( | ||
| &mut self, | ||
| init: InitContext<P>, | ||
| ) -> Result<(), PayloadBuilderError> { | ||
| for step in &self.steps { | ||
| step.setup(init.clone()).await?; | ||
| } | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use {super::*, crate::test_utils::*}; | ||
|
|
||
| // Composite step mode that tries to execute all steps and ignore all failures | ||
| // and breaks Will return the latest successful result | ||
| struct TryAllMode; | ||
| impl<P: Platform> CompositeStepMode<P> for TryAllMode { | ||
| async fn steps( | ||
| &self, | ||
| steps: &[Arc<StepInstance<P>>], | ||
| payload: Checkpoint<P>, | ||
| ctx: StepContext<P>, | ||
| ) -> ControlFlow<P> { | ||
| let mut current = payload.clone(); | ||
| for step in steps { | ||
| if let ControlFlow::Ok(res) = | ||
| step.step(current.clone(), ctx.clone()).await | ||
| { | ||
| current = res; | ||
| } | ||
| } | ||
| ControlFlow::Ok(current) | ||
| } | ||
| } | ||
|
|
||
| #[rblib_test(Ethereum)] | ||
| async fn composite_empty_steps<P: TestablePlatform>() -> eyre::Result<()> { | ||
| let composite = CompositeStep::<P, _>::new(TryAllMode); | ||
| let result = OneStep::<P>::new(composite).run().await?; | ||
| assert!(matches!(result, ControlFlow::Ok(_))); | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[rblib_test(Ethereum)] | ||
| async fn composite_single_step_ok<P: TestablePlatform>() -> eyre::Result<()> { | ||
| let mut composite = CompositeStep::<P, _>::new(TryAllMode); | ||
| composite.append_step(AlwaysOkStep); | ||
|
|
||
| let result = OneStep::<P>::new(composite).run().await?; | ||
| assert!(matches!(result, ControlFlow::Ok(_))); | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[rblib_test(Ethereum)] | ||
| async fn composite_multiple_steps_ok<P: TestablePlatform>() -> eyre::Result<()> | ||
| { | ||
| let mut composite = CompositeStep::<P, _>::new(TryAllMode); | ||
| composite.append_step(AlwaysOkStep); | ||
| composite.append_step(AlwaysOkStep); | ||
| composite.append_step(AlwaysOkStep); | ||
|
|
||
| let result = OneStep::<P>::new(composite).run().await?; | ||
| assert!(matches!(result, ControlFlow::Ok(_))); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.