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: EVM compatible proof plan serialization #432

Merged
merged 3 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub const FILTER_EXEC_NUM: u8 = 0;

pub const COLUMN_EXPR_NUM: u8 = 0;
pub const EQUALS_EXPR_NUM: u8 = 1;
pub const LITERAL_EXPR_NUM: u8 = 2;

pub const BIGINT_TYPE_NUM: u8 = 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use snafu::Snafu;

/// Errors that can occur during proof plan serialization.
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub enum ProofPlanSerializationError {
/// Error indicating that the operation is not supported.
#[snafu(display("Not supported"))]
NotSupported,
/// Error indicating that there are more than 255 results in the filter.
#[snafu(display("More than 255 results in filter."))]
TooManyResults,
/// Error indicating that there are more than 255 tables referenced in the plan.
#[snafu(display("More than 255 tables referenced in the plan."))]
TooManyTables,
/// Error indicating that there are more than 255 columns referenced in the plan.
#[snafu(display("More than 255 columns referenced in the plan."))]
TooManyColumns,
/// Error indicating that the table was not found.
#[snafu(display("Table not found"))]
TableNotFound,
/// Error indicating that the column was not found.
#[snafu(display("Column not found"))]
ColumnNotFound,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// This module contains constants used in the proof serialization process.
pub(super) mod constants;

/// This module defines errors that can occur during proof plan serialization.
mod error;

/// This module handles the serialization of proof expressions.
mod serialize_proof_expr;

/// This module handles the serialization of proof plans.
mod serialize_proof_plan;

/// This module provides the main serializer for proof plans.
mod serializer;

pub use error::ProofPlanSerializationError;
pub use serializer::DynProofPlanSerializer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
use super::{
constants::{BIGINT_TYPE_NUM, COLUMN_EXPR_NUM, EQUALS_EXPR_NUM, LITERAL_EXPR_NUM},
error::{ColumnNotFoundSnafu, NotSupportedSnafu},
DynProofPlanSerializer, ProofPlanSerializationError,
};
use crate::{
base::{database::LiteralValue, scalar::Scalar},
evm_compatibility::primitive_serialize_ext::PrimitiveSerializeExt,
sql::proof_exprs::{ColumnExpr, DynProofExpr, EqualsExpr, LiteralExpr},
};
use snafu::OptionExt;

impl<S: Scalar> DynProofPlanSerializer<S> {
pub(super) fn serialize_dyn_proof_expr(
self,
expr: &DynProofExpr,
) -> Result<Self, ProofPlanSerializationError> {
match expr {
DynProofExpr::Column(column_expr) => self
.serialize_u8(COLUMN_EXPR_NUM)
.serialize_column_expr(column_expr),
DynProofExpr::Literal(literal_expr) => self
.serialize_u8(LITERAL_EXPR_NUM)
.serialize_literal_expr(literal_expr),
DynProofExpr::Equals(equals_expr) => self
.serialize_u8(EQUALS_EXPR_NUM)
.serialize_equals_expr(equals_expr),
_ => NotSupportedSnafu.fail(),
}
}

fn serialize_column_expr(
self,
column_expr: &ColumnExpr,
) -> Result<Self, ProofPlanSerializationError> {
let column_number = self
.column_refs
.get(&column_expr.column_ref)
.copied()
.context(ColumnNotFoundSnafu)?;
Ok(self.serialize_u8(column_number))
}

fn serialize_literal_expr(
self,
literal_expr: &LiteralExpr,
) -> Result<Self, ProofPlanSerializationError> {
match literal_expr.value {
LiteralValue::BigInt(value) => Ok(self
.serialize_u8(BIGINT_TYPE_NUM)
.serialize_scalar(value.into())),
_ => NotSupportedSnafu.fail(),
}
}

fn serialize_equals_expr(
self,
equals_expr: &EqualsExpr,
) -> Result<Self, ProofPlanSerializationError> {
self.serialize_dyn_proof_expr(equals_expr.lhs.as_ref())?
.serialize_dyn_proof_expr(equals_expr.rhs.as_ref())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{
base::{
database::{ColumnRef, ColumnType, TableRef},
map::indexset,
scalar::test_scalar::TestScalar,
},
sql::proof_exprs::AndExpr,
};
use core::iter;
use itertools::Itertools;

#[test]
fn we_can_serialize_a_column_expr() {
let table_ref: TableRef = "namespace.table".parse().unwrap();
let column_0_ref: ColumnRef =
ColumnRef::new(table_ref, "column_0".parse().unwrap(), ColumnType::BigInt);
let column_1_ref: ColumnRef =
ColumnRef::new(table_ref, "column_1".parse().unwrap(), ColumnType::BigInt);
let column_2_ref: ColumnRef =
ColumnRef::new(table_ref, "column_2".parse().unwrap(), ColumnType::BigInt);
let serializer = DynProofPlanSerializer::<TestScalar>::try_new(
indexset! {},
indexset! { column_0_ref, column_1_ref },
)
.unwrap();

// Serialization of column 0 should result in a single byte with value 0.
let column_0_expr = ColumnExpr::new(column_0_ref);
let bytes_0 = serializer
.clone()
.serialize_column_expr(&column_0_expr)
.unwrap()
.into_bytes();
assert_eq!(bytes_0, vec![0]);

// Serialization of column 1 should result in a single byte with value 1.
let column_1_expr = ColumnExpr::new(column_1_ref);
let bytes_1 = serializer
.clone()
.serialize_column_expr(&column_1_expr)
.unwrap()
.into_bytes();
assert_eq!(bytes_1, vec![1]);

// Wrapping the column expression in a `DynProofExpr` should result in the same serialization,
// but with the column expression number prepended.
let wrapped_column_1_expr = DynProofExpr::Column(column_1_expr);
let wrapped_bytes_1 = serializer
.clone()
.serialize_dyn_proof_expr(&wrapped_column_1_expr)
.unwrap()
.into_bytes();
assert_eq!(wrapped_bytes_1, vec![COLUMN_EXPR_NUM, 1]);

// Serialization of column 2 should result in an error because there are only two columns.
let column_2_expr = ColumnExpr::new(column_2_ref);
let result = serializer.clone().serialize_column_expr(&column_2_expr);
assert!(matches!(
result,
Err(ProofPlanSerializationError::ColumnNotFound)
));
}

#[test]
fn we_can_serialize_a_literal_expr() {
let serializer =
DynProofPlanSerializer::<TestScalar>::try_new(indexset! {}, indexset! {}).unwrap();

// Serialization of a big int literal should result in a byte with the big int type number,
// followed by the big int value in big-endian form, padded with leading zeros to 32 bytes.
let literal_bigint_expr = LiteralExpr::new(LiteralValue::BigInt(4200));
let bigint_bytes = serializer
.clone()
.serialize_literal_expr(&literal_bigint_expr)
.unwrap()
.into_bytes();
let expected_bigint_bytes = iter::empty::<u8>()
.chain([BIGINT_TYPE_NUM])
.chain([0; 30])
.chain([16, 104])
.collect_vec();
assert_eq!(bigint_bytes, expected_bigint_bytes);

// Wrapping the literal expression in a `DynProofExpr` should result in the same serialization,
// but with the literal expression number prepended.
let wrapped_literal_expr = DynProofExpr::Literal(literal_bigint_expr);
let wrapped_bytes = serializer
.clone()
.serialize_dyn_proof_expr(&wrapped_literal_expr)
.unwrap()
.into_bytes();
let expected_wrapped_bytes = iter::empty::<u8>()
.chain([LITERAL_EXPR_NUM])
.chain(expected_bigint_bytes)
.collect_vec();
assert_eq!(wrapped_bytes, expected_wrapped_bytes);

// Serialization of a small int literal should result in an error
// because only big int literals are supported so far
let literal_smallint_expr = LiteralExpr::new(LiteralValue::SmallInt(4200));
let result = serializer
.clone()
.serialize_literal_expr(&literal_smallint_expr);
assert!(matches!(
result,
Err(ProofPlanSerializationError::NotSupported)
));
}

#[test]
fn we_can_serialize_an_equals_expr() {
let table_ref: TableRef = "namespace.table".parse().unwrap();
let column_0_ref: ColumnRef =
ColumnRef::new(table_ref, "column_0".parse().unwrap(), ColumnType::BigInt);
let serializer =
DynProofPlanSerializer::<TestScalar>::try_new(indexset! {}, indexset! { column_0_ref })
.unwrap();

let lhs = DynProofExpr::Column(ColumnExpr::new(column_0_ref));
let rhs = DynProofExpr::Literal(LiteralExpr::new(LiteralValue::BigInt(4200)));
let lhs_bytes = serializer
.clone()
.serialize_dyn_proof_expr(&lhs)
.unwrap()
.into_bytes();
let rhs_bytes = serializer
.clone()
.serialize_dyn_proof_expr(&rhs)
.unwrap()
.into_bytes();

// Serialization of an equals expression should result in the serialization of the left-hand side,
// followed by the serialization of the right-hand side.
let equals_expr = EqualsExpr::new(Box::new(lhs.clone()), Box::new(rhs.clone()));
let bytes = serializer
.clone()
.serialize_equals_expr(&equals_expr)
.unwrap()
.into_bytes();
let expected_bytes = iter::empty::<u8>()
.chain(lhs_bytes.clone())
.chain(rhs_bytes.clone())
.collect_vec();
assert_eq!(bytes, expected_bytes);

// Wrapping the equals expression in a `DynProofExpr` should result in the same serialization,
// but with the equals expression number prepended.
let wrapped_equals_expr = DynProofExpr::Equals(equals_expr);
let wrapped_bytes = serializer
.clone()
.serialize_dyn_proof_expr(&wrapped_equals_expr)
.unwrap()
.into_bytes();
let expected_wrapped_bytes = iter::empty::<u8>()
.chain([EQUALS_EXPR_NUM])
.chain(expected_bytes)
.collect_vec();
assert_eq!(wrapped_bytes, expected_wrapped_bytes);
}

#[test]
fn we_cannot_serialize_an_unsupported_expr() {
let table_ref: TableRef = "namespace.table".parse().unwrap();
let column_0_ref: ColumnRef =
ColumnRef::new(table_ref, "column_0".parse().unwrap(), ColumnType::BigInt);
let serializer =
DynProofPlanSerializer::<TestScalar>::try_new(indexset! {}, indexset! { column_0_ref })
.unwrap();

let lhs = DynProofExpr::Column(ColumnExpr::new(column_0_ref));
let rhs = DynProofExpr::Literal(LiteralExpr::new(LiteralValue::BigInt(4200)));
let expr = DynProofExpr::And(AndExpr::new(Box::new(lhs.clone()), Box::new(rhs.clone())));
let result = serializer.clone().serialize_dyn_proof_expr(&expr);
assert!(matches!(
result,
Err(ProofPlanSerializationError::NotSupported)
));
}
}
Loading
Loading