Skip to content
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

feat(node_framework): Implement FromContext and IntoContext derive macro #2330

Merged
merged 15 commits into from
Jul 1, 2024
Merged
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ members = [
"core/lib/mempool",
"core/lib/merkle_tree",
"core/lib/mini_merkle_tree",
"core/lib/node_framework_derive",
"core/lib/object_store",
"core/lib/prover_interface",
"core/lib/queued_job_processor",
Expand Down Expand Up @@ -172,6 +173,12 @@ tracing-opentelemetry = "0.21.0"
url = "2"
web3 = "0.19.0"

# Proc-macro
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"
trybuild = "1.0"

# "Internal" dependencies
circuit_sequencer_api_1_3_3 = { package = "circuit_sequencer_api", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.3.3" }
circuit_sequencer_api_1_4_0 = { package = "circuit_sequencer_api", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.4.0" }
Expand Down Expand Up @@ -239,6 +246,7 @@ zksync_crypto_primitives = { path = "core/lib/crypto_primitives" }

# Framework and components
zksync_node_framework = { path = "core/node/node_framework" }
zksync_node_framework_derive = { path = "core/lib/node_framework_derive" }
zksync_eth_watch = { path = "core/node/eth_watch" }
zksync_shared_metrics = { path = "core/node/shared_metrics" }
zksync_proof_data_handler = { path = "core/node/proof_data_handler" }
Expand Down
18 changes: 18 additions & 0 deletions core/lib/node_framework_derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "zksync_node_framework_derive"
version.workspace = true
edition.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
keywords.workspace = true
categories.workspace = true

[lib]
proc-macro = true

[dependencies]
syn = { workspace = true, features = ["full"] }
quote.workspace = true
proc-macro2.workspace = true
44 changes: 44 additions & 0 deletions core/lib/node_framework_derive/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::fmt;

use syn::{GenericArgument, PathArguments, Type};

use crate::labels::CtxLabel;

/// Representation of a single structure field.
pub(crate) struct Field {
/// Name of the field.
pub(crate) ident: syn::Ident,
/// Type of the field.
pub(crate) ty: syn::Type,
/// Parsed label.
pub(crate) label: CtxLabel,
}

impl fmt::Debug for Field {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Field")
.field("ident", &self.ident)
.field("label", &self.label)
.finish()
}
}

// Helper function to check if a field is of type Option<T> and extract T
pub(crate) fn extract_option_inner_type(ty: &Type) -> Option<&Type> {
if let Type::Path(type_path) = ty {
// Check if the path is `Option`
if type_path.path.segments.len() == 1 {
let segment = &type_path.path.segments[0];
if segment.ident == "Option" {
if let PathArguments::AngleBracketed(angle_bracketed_args) = &segment.arguments {
if angle_bracketed_args.args.len() == 1 {
if let GenericArgument::Type(inner_type) = &angle_bracketed_args.args[0] {
return Some(inner_type);
}
}
}
}
}
}
None
}
98 changes: 98 additions & 0 deletions core/lib/node_framework_derive/src/labels.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use std::fmt;

use syn::{spanned::Spanned as _, Attribute, Result};

/// Context label, e.g. `ctx(crate = "crate")`.
#[derive(Default)]
pub(crate) struct CtxLabel {
/// Special attribute that marks the derive as internal.
/// Alters the path to the trait to be implemented.
pub(crate) krate: Option<syn::Path>, // `crate` is a reserved keyword and cannot be a raw identifier.
pub(crate) span: Option<proc_macro2::Span>,
pub(crate) task: bool,
pub(crate) default: bool,
}

impl fmt::Debug for CtxLabel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// For some weird reason, doc tests fail with the derived impl, stating that
// `syn::Path` does not implement `Debug`.
popzxc marked this conversation as resolved.
Show resolved Hide resolved
f.debug_struct("CtxLabel")
.field("krate", &self.krate.as_ref().and_then(|p| p.get_ident()))
.field("span", &self.span)
.field("task", &self.task)
.field("default", &self.default)
.finish()
}
}

impl CtxLabel {
const ATTR_NAME: &'static str = "context";
const CRATE_LABEL: &'static str = "crate";
const TASK_LABEL: &'static str = "task";
const DEFAULT_LABEL: &'static str = "default";
const LABELS: &'static [&'static str] =
&[Self::CRATE_LABEL, Self::TASK_LABEL, Self::DEFAULT_LABEL];

pub(crate) fn parse(attrs: &[Attribute]) -> Result<Option<Self>> {
let mut self_ = Self::default();

let mut found = false;
for attr in attrs {
if attr.path().is_ident(Self::ATTR_NAME) {
found = true;
self_.span = Some(attr.span());
match attr.meta {
syn::Meta::Path(_) => {
// No values to parse.
break;
}
syn::Meta::NameValue(_) => {
return Err(syn::Error::new_spanned(
attr,
"Unexpected value, expected a list of labels",
));
}
syn::Meta::List(_) => {
// Do nothing, parsing happens below.
}
}
attr.parse_nested_meta(|meta| {
let mut added = false;
for &label in Self::LABELS {
if meta.path.is_ident(label) {
match label {
Self::CRATE_LABEL => {
let value = meta.value()?;
let lit: syn::LitStr = value.parse()?; // Check that the value is a path.
self_.krate = Some(syn::parse_str(&lit.value())?);
slowli marked this conversation as resolved.
Show resolved Hide resolved
}
Self::TASK_LABEL => {
self_.task = true;
}
Self::DEFAULT_LABEL => {
self_.default = true;
}
_ => unreachable!(),
}
added = true;
break;
}
}

if !added {
let err_msg =
format!("Unexpected token, supported labels: `{:?}`", Self::LABELS);
let err = syn::Error::new_spanned(attr, err_msg);
return Err(err);
}
Ok(())
})?;
}
}
if !found {
return Ok(None);
}
Ok(Some(self_))
}
}
39 changes: 39 additions & 0 deletions core/lib/node_framework_derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
extern crate proc_macro;

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

use crate::macro_impl::{MacroImpl, MacroKind};

pub(crate) mod helpers;
mod labels;
mod macro_impl;

/// Derive macro for the `FromContext` trait.
/// Allows to automatically fetch all the resources and tasks from the context.
///
/// See the trait documentation for more details.
#[proc_macro_derive(FromContext, attributes(context))]
pub fn from_context_derive(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);
MacroImpl::parse(MacroKind::FromContext, input)
.and_then(|from_context| from_context.render())
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}

/// Derive macro for the `IntoContext` trait.
/// Allows to automatically insert all the resources in tasks created by the wiring layer
/// into the context.
///
/// See the trait documentation for more details.
#[proc_macro_derive(IntoContext, attributes(context))]
pub fn into_context_derive(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);
MacroImpl::parse(MacroKind::IntoContext, input)
.and_then(|from_context| from_context.render())
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
Loading
Loading