-
Notifications
You must be signed in to change notification settings - Fork 221
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: Composable foreign call handlers (#6857)
- Loading branch information
Showing
7 changed files
with
284 additions
and
147 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
use std::marker::PhantomData; | ||
|
||
use acvm::{acir::brillig::ForeignCallResult, pwg::ForeignCallWaitInfo, AcirField}; | ||
use noirc_printable_type::ForeignCallError; | ||
|
||
use super::ForeignCallExecutor; | ||
|
||
/// Returns an empty result when called. | ||
/// | ||
/// If all executors have no handler for the given foreign call then we cannot | ||
/// return a correct response to the ACVM. The best we can do is to return an empty response, | ||
/// this allows us to ignore any foreign calls which exist solely to pass information from inside | ||
/// the circuit to the environment (e.g. custom logging) as the execution will still be able to progress. | ||
/// | ||
/// We optimistically return an empty response for all oracle calls as the ACVM will error | ||
/// should a response have been required. | ||
pub struct Empty; | ||
|
||
impl<F: AcirField> ForeignCallExecutor<F> for Empty { | ||
fn execute( | ||
&mut self, | ||
_foreign_call: &ForeignCallWaitInfo<F>, | ||
) -> Result<ForeignCallResult<F>, ForeignCallError> { | ||
Ok(ForeignCallResult::default()) | ||
} | ||
} | ||
|
||
/// Returns `NoHandler` for every call. | ||
pub struct Unhandled; | ||
|
||
impl<F: AcirField> ForeignCallExecutor<F> for Unhandled { | ||
fn execute( | ||
&mut self, | ||
foreign_call: &ForeignCallWaitInfo<F>, | ||
) -> Result<ForeignCallResult<F>, ForeignCallError> { | ||
Err(ForeignCallError::NoHandler(foreign_call.function.clone())) | ||
} | ||
} | ||
|
||
/// Forwards to the inner executor if its own handler doesn't handle the call. | ||
pub struct Layer<H, I, F> { | ||
pub handler: H, | ||
pub inner: I, | ||
_field: PhantomData<F>, | ||
} | ||
|
||
impl<H, I, F> ForeignCallExecutor<F> for Layer<H, I, F> | ||
where | ||
H: ForeignCallExecutor<F>, | ||
I: ForeignCallExecutor<F>, | ||
{ | ||
fn execute( | ||
&mut self, | ||
foreign_call: &ForeignCallWaitInfo<F>, | ||
) -> Result<ForeignCallResult<F>, ForeignCallError> { | ||
match self.handler.execute(foreign_call) { | ||
Err(ForeignCallError::NoHandler(_)) => self.inner.execute(foreign_call), | ||
handled => handled, | ||
} | ||
} | ||
} | ||
|
||
impl<H, I, F> Layer<H, I, F> { | ||
/// Create a layer from two handlers | ||
pub fn new(handler: H, inner: I) -> Self { | ||
Self { handler, inner, _field: PhantomData } | ||
} | ||
} | ||
|
||
impl<H, F> Layer<H, Empty, F> { | ||
/// Create a layer from a handler. | ||
/// If the handler doesn't handle a call, a default empty response is returned. | ||
pub fn or_empty(handler: H) -> Self { | ||
Self { handler, inner: Empty, _field: PhantomData } | ||
} | ||
} | ||
|
||
impl<H, F> Layer<H, Unhandled, F> { | ||
/// Create a layer from a handler. | ||
/// If the handler doesn't handle a call, `NoHandler` error is returned. | ||
pub fn or_unhandled(handler: H) -> Self { | ||
Self { handler, inner: Unhandled, _field: PhantomData } | ||
} | ||
} | ||
|
||
impl<F> Layer<Unhandled, Unhandled, F> { | ||
/// A base layer that doesn't handle anything. | ||
pub fn unhandled() -> Self { | ||
Self { handler: Unhandled, inner: Unhandled, _field: PhantomData } | ||
} | ||
} | ||
|
||
impl<H, I, F> Layer<H, I, F> { | ||
/// Add another layer on top of this one. | ||
pub fn add_layer<J>(self, handler: J) -> Layer<J, Self, F> { | ||
Layer::new(handler, self) | ||
} | ||
|
||
pub fn handler(&self) -> &H { | ||
&self.handler | ||
} | ||
|
||
pub fn inner(&self) -> &I { | ||
&self.inner | ||
} | ||
} | ||
|
||
/// Compose handlers. | ||
pub trait Layering { | ||
/// Layer an executor on top of this one. | ||
/// The `other` executor will be called first. | ||
fn add_layer<L, F>(self, other: L) -> Layer<L, Self, F> | ||
where | ||
Self: Sized + ForeignCallExecutor<F>, | ||
L: ForeignCallExecutor<F>; | ||
} | ||
|
||
impl<T> Layering for T { | ||
fn add_layer<L, F>(self, other: L) -> Layer<L, T, F> | ||
where | ||
T: Sized + ForeignCallExecutor<F>, | ||
L: ForeignCallExecutor<F>, | ||
{ | ||
Layer::new(other, self) | ||
} | ||
} | ||
|
||
/// Support disabling a layer by making it optional. | ||
/// This way we can still have a known static type for a composition, | ||
/// because layers are always added, potentially wrapped in an `Option`. | ||
impl<H, F> ForeignCallExecutor<F> for Option<H> | ||
where | ||
H: ForeignCallExecutor<F>, | ||
{ | ||
fn execute( | ||
&mut self, | ||
foreign_call: &ForeignCallWaitInfo<F>, | ||
) -> Result<ForeignCallResult<F>, ForeignCallError> { | ||
match self { | ||
Some(handler) => handler.execute(foreign_call), | ||
None => Err(ForeignCallError::NoHandler(foreign_call.function.clone())), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.