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
21 changes: 21 additions & 0 deletions core/lib/node_framework_derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[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

[dev-dependencies]
trybuild.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::ResourceOrTask;

/// 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,
/// `resource`/`task` label.
pub(crate) label: Option<ResourceOrTask>,
}

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
}
147 changes: 147 additions & 0 deletions core/lib/node_framework_derive/src/labels.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use syn::{spanned::Spanned as _, Attribute, Result};

/// Trait allowing to iterate over the attributes and parse the labels.
/// Only supports simple path attributes, like `ctx(local)`.
pub(crate) trait ParseLabels: Sized + Default {
const ATTR_NAME: &'static str;
const LABELS: &'static [&'static str];

fn set_label(&mut self, label: &str);

fn set_span(&mut self, span: proc_macro2::Span);

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_.set_span(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) {
self_.set_label(label);
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_))
}
}

/// Context label, e.g. `ctx(local)`.
#[derive(Debug, Default)]
pub(crate) struct CtxLabel {
/// Special attribute that marks the derive as internal.
/// Alters the path to the trait to be implemented.
pub(crate) local: bool,
popzxc marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) span: Option<proc_macro2::Span>,
}

impl ParseLabels for CtxLabel {
const ATTR_NAME: &'static str = "ctx";
const LABELS: &'static [&'static str] = &["local"];

fn set_label(&mut self, label: &str) {
match label {
"local" => self.local = true,
_ => unreachable!(),
}
}

fn set_span(&mut self, span: proc_macro2::Span) {
self.span = Some(span);
}
}

/// Resource label, e.g. `resource` or `resource(default)`.
#[derive(Debug, Default)]
pub(crate) struct ResourceLabel {
/// Resource should be retrieved with `get_resource_or_default`.
pub(crate) default: bool,
pub(crate) span: Option<proc_macro2::Span>,
}

impl ParseLabels for ResourceLabel {
const ATTR_NAME: &'static str = "resource";
const LABELS: &'static [&'static str] = &["default"];

fn set_label(&mut self, label: &str) {
match label {
"default" => self.default = true,
_ => unreachable!(),
}
}

fn set_span(&mut self, span: proc_macro2::Span) {
self.span = Some(span);
}
}

/// Task label, e.g. `task`.
#[derive(Debug, Default)]
pub(crate) struct TaskLabel {
pub(crate) span: Option<proc_macro2::Span>,
}

impl ParseLabels for TaskLabel {
const ATTR_NAME: &'static str = "task";
const LABELS: &'static [&'static str] = &[];

fn set_label(&mut self, _label: &str) {
unreachable!("No labels are supported for `task`")
}

fn set_span(&mut self, span: proc_macro2::Span) {
self.span = Some(span);
}
}

#[derive(Debug)]
pub(crate) enum ResourceOrTask {
Resource(ResourceLabel),
Task(TaskLabel),
}

impl ResourceOrTask {
pub(crate) fn parse(attrs: &[Attribute]) -> Result<Option<Self>> {
if let Some(resource) = ResourceLabel::parse(attrs)? {
return Ok(Some(ResourceOrTask::Resource(resource)));
}
if let Some(task) = TaskLabel::parse(attrs)? {
return Ok(Some(ResourceOrTask::Task(task)));
}
Ok(None)
}
}
Loading
Loading