Skip to content

Commit

Permalink
feat(node_framework): Implement FromContext and IntoContext derive ma…
Browse files Browse the repository at this point in the history
…cro (#2330)

## What ❔

- Improves context interfaces: removes unneded `async` when fetching
resources, and made `add_task` accept `T` instead of `Box`.
- Adds previously proposed `FromContext` and `IntoContext` macro that
will allow to redefine the wiring layer interface so that it doesn't
have direct access to the context.

⚠️ I didn't port the whole framework to using macros, since there may be
changes in the macro itself during this review. Once we merge this, I
will simultaneously rework the `WiringLayer` interface and will port
layers. For now, I've used new macros in the `BatchStatusUpdater` layer
to see how it works. Seems that it works fine 😅

## Why ❔

Ergonomics.

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted via `zk fmt` and `zk lint`.
  • Loading branch information
popzxc authored Jul 1, 2024
1 parent 7dabdbf commit 34f2a45
Show file tree
Hide file tree
Showing 65 changed files with 1,089 additions and 221 deletions.
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`.
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 path: syn::Path = value.parse()?;
self_.krate = Some(path);
}
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

0 comments on commit 34f2a45

Please sign in to comment.