From 43d751865f43b3234a2ad1972e0c27ba5b3abc96 Mon Sep 17 00:00:00 2001 From: rudy Date: Tue, 28 Jan 2025 13:07:07 +0100 Subject: [PATCH] feat: progagate tfhe events do database --- fhevm-engine/Cargo.lock | 3 + fhevm-engine/coprocessor/Cargo.toml | 1 + fhevm-engine/coprocessor/src/lib.rs | 2 +- fhevm-engine/coprocessor/src/tests/mod.rs | 1 + .../coprocessor/src/tests/operators.rs | 34 +- .../src/tests/operators_from_events.rs | 490 ++++++++++++++++++ ...5e614a9d71b97e5bdd8118f4d7d986a48aeb5.json | 22 + ...dab453de438a41d388eadfe2628d2ba4e1aed.json | 18 + ...133a04d9177e1c02db388b3d0fe1308b2b917.json | 12 + fhevm-engine/fhevm-listener/Cargo.toml | 8 +- fhevm-engine/fhevm-listener/README.md | 23 +- fhevm-engine/fhevm-listener/src/bin/main.rs | 25 +- fhevm-engine/fhevm-listener/src/build.rs | 12 + .../fhevm-listener/src/database/mod.rs | 1 + .../src/database/tfhe_event_propagate.rs | 354 +++++++++++++ fhevm-engine/fhevm-listener/src/lib.rs | 1 + 16 files changed, 984 insertions(+), 23 deletions(-) create mode 100644 fhevm-engine/coprocessor/src/tests/operators_from_events.rs create mode 100644 fhevm-engine/fhevm-listener/.sqlx/query-69e5a39a57d25b91b21c5fa00f45e614a9d71b97e5bdd8118f4d7d986a48aeb5.json create mode 100644 fhevm-engine/fhevm-listener/.sqlx/query-b88e9d81c24c47b7df985acf83fdab453de438a41d388eadfe2628d2ba4e1aed.json create mode 100644 fhevm-engine/fhevm-listener/.sqlx/query-f7111b94c0efa152dcf24928a13133a04d9177e1c02db388b3d0fe1308b2b917.json create mode 100644 fhevm-engine/fhevm-listener/src/build.rs create mode 100644 fhevm-engine/fhevm-listener/src/database/mod.rs create mode 100644 fhevm-engine/fhevm-listener/src/database/tfhe_event_propagate.rs diff --git a/fhevm-engine/Cargo.lock b/fhevm-engine/Cargo.lock index 847456db..6cb0580a 100644 --- a/fhevm-engine/Cargo.lock +++ b/fhevm-engine/Cargo.lock @@ -1813,6 +1813,7 @@ dependencies = [ "bincode", "clap", "fhevm-engine-common", + "fhevm-listener", "hex", "itertools 0.13.0", "lazy_static", @@ -2433,8 +2434,10 @@ dependencies = [ "alloy-rpc-types", "alloy-sol-types", "clap", + "fhevm-engine-common", "futures-util", "serde", + "sqlx", "tokio", ] diff --git a/fhevm-engine/coprocessor/Cargo.toml b/fhevm-engine/coprocessor/Cargo.toml index d3e792c2..9feb3090 100644 --- a/fhevm-engine/coprocessor/Cargo.toml +++ b/fhevm-engine/coprocessor/Cargo.toml @@ -56,6 +56,7 @@ nightly-avx512 = ["tfhe/nightly-avx512"] [dev-dependencies] testcontainers = "0.21" +fhevm-listener = { path = "../fhevm-listener" } [build-dependencies] tonic-build = "0.12" diff --git a/fhevm-engine/coprocessor/src/lib.rs b/fhevm-engine/coprocessor/src/lib.rs index 233e30fc..fe45378a 100644 --- a/fhevm-engine/coprocessor/src/lib.rs +++ b/fhevm-engine/coprocessor/src/lib.rs @@ -11,7 +11,7 @@ pub mod server; mod tests; pub mod tfhe_worker; pub mod tracing; -mod types; +pub mod types; mod utils; // separate function for testing diff --git a/fhevm-engine/coprocessor/src/tests/mod.rs b/fhevm-engine/coprocessor/src/tests/mod.rs index faab9793..6d9c67cc 100644 --- a/fhevm-engine/coprocessor/src/tests/mod.rs +++ b/fhevm-engine/coprocessor/src/tests/mod.rs @@ -16,6 +16,7 @@ use utils::{ mod errors; mod inputs; mod operators; +mod operators_from_events; mod random; mod scheduling_bench; mod utils; diff --git a/fhevm-engine/coprocessor/src/tests/operators.rs b/fhevm-engine/coprocessor/src/tests/operators.rs index fe4ff122..c59668a8 100644 --- a/fhevm-engine/coprocessor/src/tests/operators.rs +++ b/fhevm-engine/coprocessor/src/tests/operators.rs @@ -19,23 +19,23 @@ use std::{ops::Not, str::FromStr}; use strum::IntoEnumIterator; use tonic::metadata::MetadataValue; -struct BinaryOperatorTestCase { - bits: i32, - operand: i32, - input_types: i32, - expected_output_type: i32, - lhs: BigInt, - rhs: BigInt, - expected_output: BigInt, - is_scalar: bool, +pub struct BinaryOperatorTestCase { + pub bits: i32, + pub operand: i32, + pub input_types: i32, + pub expected_output_type: i32, + pub lhs: BigInt, + pub rhs: BigInt, + pub expected_output: BigInt, + pub is_scalar: bool, } -struct UnaryOperatorTestCase { - bits: i32, - inp: BigInt, - operand: i32, - operand_types: i32, - expected_output: BigInt, +pub struct UnaryOperatorTestCase { + pub bits: i32, + pub inp: BigInt, + pub operand: i32, + pub operand_types: i32, + pub expected_output: BigInt, } fn supported_bits() -> &'static [i32] { @@ -727,7 +727,7 @@ async fn test_fhe_if_then_else() -> Result<(), Box> { Ok(()) } -fn generate_binary_test_cases() -> Vec { +pub fn generate_binary_test_cases() -> Vec { let mut cases = Vec::new(); let bit_shift_ops = [ SupportedFheOperations::FheShl, @@ -836,7 +836,7 @@ fn generate_binary_test_cases() -> Vec { cases } -fn generate_unary_test_cases() -> Vec { +pub fn generate_unary_test_cases() -> Vec { let mut cases = Vec::new(); for bits in supported_bits() { diff --git a/fhevm-engine/coprocessor/src/tests/operators_from_events.rs b/fhevm-engine/coprocessor/src/tests/operators_from_events.rs new file mode 100644 index 00000000..292abc90 --- /dev/null +++ b/fhevm-engine/coprocessor/src/tests/operators_from_events.rs @@ -0,0 +1,490 @@ +use alloy::primitives::{Bytes, FixedBytes, Log}; +use bigdecimal::num_bigint::BigInt; + +use fhevm_listener::contracts::TfheContract; +use fhevm_listener::contracts::TfheContract::TfheContractEvents; +use fhevm_listener::database::tfhe_event_propagate::{Database as ListenerDatabase, Handle, ToType}; + + +use crate::tests::operators::{generate_binary_test_cases, generate_unary_test_cases, supported_types}; +use crate::tests::utils::{default_api_key, setup_test_app, TestInstance}; +use crate::tests::utils::{decrypt_ciphertexts, wait_until_all_ciphertexts_computed}; + +use crate::tests::operators::BinaryOperatorTestCase; +use crate::tests::operators::UnaryOperatorTestCase; + +fn tfhe_event(data: TfheContractEvents) -> Log { + let address = "0x0000000000000000000000000000000000000000".parse().unwrap(); + Log::{address, data} +} + +fn as_scalar(big_int: &BigInt) -> Handle { + let (_, bytes) = big_int.to_bytes_be(); + Handle::from_be_slice(&bytes) +} + +fn to_bytes(big_int: &BigInt) -> Bytes { + let (_, bytes) = big_int.to_bytes_be(); + Bytes::copy_from_slice(&bytes) +} + +fn to_ty(ty: i32) -> ToType { + ToType::from(ty as u8) +} + +fn binary_op_to_event(op: &BinaryOperatorTestCase, lhs: &Handle, rhs: &Handle, r_scalar: &BigInt, result: &Handle) -> TfheContractEvents { + use fhevm_engine_common::types::SupportedFheOperations as S; + use fhevm_listener::contracts::TfheContract as C; + use fhevm_listener::contracts::TfheContract::TfheContractEvents as E; + use fhevm_listener::database::tfhe_event_propagate::ScalarByte; + let caller = "0x0000000000000000000000000000000000000000".parse().unwrap(); + let s_byte = |is_scalar:bool| ScalarByte::from(is_scalar as u8); + #[allow(non_snake_case)] + let scalarByte = s_byte(op.is_scalar); + let lhs = lhs.clone(); + let use_bytes_when_avail = op.is_scalar && op.bits > 256; + let rhs_bytes = to_bytes(&r_scalar); + let rhs = if op.is_scalar && op.bits <= 256 { + as_scalar(r_scalar) + } else { + rhs.clone() + }; + let result = result.clone(); + match S::try_from(op.operand).unwrap() { + S::FheAdd => E::FheAdd(C::FheAdd {caller, lhs, rhs, scalarByte, result}), + S::FheSub => E::FheSub(C::FheSub {caller, lhs, rhs, scalarByte, result}), + S::FheMul => E::FheMul(C::FheMul {caller, lhs, rhs, scalarByte, result}), + S::FheDiv => E::FheDiv(C::FheDiv {caller, lhs, rhs, scalarByte, result}), + S::FheRem => E::FheRem(C::FheRem {caller, lhs, rhs, scalarByte, result}), + S::FheBitAnd => E::FheBitAnd(C::FheBitAnd {caller, lhs, rhs, scalarByte, result}), + S::FheBitOr => E::FheBitOr(C::FheBitOr {caller, lhs, rhs, scalarByte, result}), + S::FheBitXor => E::FheBitXor(C::FheBitXor {caller, lhs, rhs, scalarByte, result}), + S::FheShl => E::FheShl(C::FheShl {caller, lhs, rhs, scalarByte, result}), + S::FheShr => E::FheShr(C::FheShr {caller, lhs, rhs, scalarByte, result}), + S::FheRotl => E::FheRotl(C::FheRotl {caller, lhs, rhs, scalarByte, result}), + S::FheRotr => E::FheRotr(C::FheRotr {caller, lhs, rhs, scalarByte, result}), + S::FheMax => E::FheMax(C::FheMax {caller, lhs, rhs, scalarByte, result}), + S::FheMin => E::FheMin(C::FheMin {caller, lhs, rhs, scalarByte, result}), + S::FheGe => E::FheGe(C::FheGe {caller, lhs, rhs, scalarByte, result}), + S::FheGt => E::FheGt(C::FheGt {caller, lhs, rhs, scalarByte, result}), + S::FheLe => E::FheLe(C::FheLe {caller, lhs, rhs, scalarByte, result}), + S::FheLt => E::FheLt(C::FheLt {caller, lhs, rhs, scalarByte, result}), + S::FheEq => if use_bytes_when_avail { + E::FheEqBytes(C::FheEqBytes {caller, lhs, rhs:rhs_bytes, scalarByte, result}) + } else { + E::FheEq(C::FheEq {caller, lhs, rhs, scalarByte, result}) + }, + S::FheNe => if use_bytes_when_avail { + E::FheNeBytes(C::FheNeBytes {caller, lhs, rhs:rhs_bytes, scalarByte, result}) + } else { + E::FheNe(C::FheNe {caller, lhs, rhs, scalarByte, result}) + }, + _ => panic!("unknown operation: {:?}", op.operand), + } +} + +fn next_handle() -> Handle { + #[allow(non_upper_case_globals)] + static count: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); + let v = count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + Handle::from_limbs([1, 2, 3, v]) +} + +async fn listener_event_to_db(app: &TestInstance) -> ListenerDatabase { + let coprocessor_api_key = sqlx::types::Uuid::parse_str(default_api_key()).unwrap(); + let url = app.db_url().to_string(); + ListenerDatabase::new(&url, &coprocessor_api_key).await +} + +#[tokio::test] +async fn test_fhe_binary_operands_events() -> Result<(), Box> { + use fhevm_engine_common::types::SupportedFheOperations as S; + let app = setup_test_app().await?; + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(2) + .connect(app.db_url()) + .await?; + let mut listener_event_to_db = listener_event_to_db(&app).await; + for op in generate_binary_test_cases() { + let support_bytes = match S::try_from(op.operand).unwrap() { + S::FheEq | S::FheNe => true, + _ => false + }; + if op.bits > 256 && op.is_scalar && !support_bytes { + continue; + } + let lhs_handle = next_handle(); + let rhs_handle = next_handle(); + let output_handle = next_handle(); + + let lhs_bytes = to_bytes(&op.lhs); + let rhs_bytes = to_bytes(&op.rhs); + + println!( + "Operations for binary test bits:{} op:{} is_scalar:{} lhs:{} rhs:{}", + op.bits, + op.operand, + op.is_scalar, + op.lhs.to_string(), + op.rhs.to_string() + ); + let caller = "0x0000000000000000000000000000000000000000".parse().unwrap(); + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::TrivialEncryptBytes(TfheContract::TrivialEncryptBytes {caller, pt:lhs_bytes, toType:to_ty(op.input_types), result:lhs_handle.clone()}) + )).await?; + if !op.is_scalar { + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::TrivialEncryptBytes(TfheContract::TrivialEncryptBytes {caller, pt:rhs_bytes, toType:to_ty(op.input_types), result:rhs_handle.clone()}) + )).await?; + } + let op_event = binary_op_to_event(&op, &lhs_handle, &rhs_handle, &op.rhs, &output_handle); + eprintln!("op_event: {:?}", &op_event); + listener_event_to_db.insert_tfhe_event(&tfhe_event(op_event)).await?; + listener_event_to_db.notify_scheduler().await; + wait_until_all_ciphertexts_computed(&app).await?; + + let decrypt_request = vec![output_handle.to_be_bytes_vec()]; + let resp = decrypt_ciphertexts(&pool, 1, decrypt_request).await?; + let decr_response = &resp[0]; + println!("Checking computation for binary test bits:{} op:{} is_scalar:{} lhs:{} rhs:{} output:{}", + op.bits, op.operand, op.is_scalar, op.lhs.to_string(), op.rhs.to_string(), decr_response.value); + assert_eq!( + decr_response.output_type, op.expected_output_type as i16, + "operand types not equal" + ); + let value_to_compare = match decr_response.value.as_str() { + // for FheBool outputs + "true" => "1", + "false" => "0", + other => other, + }; + assert_eq!( + value_to_compare, + op.expected_output.to_string(), + "operand output values not equal" + ); + } + + Ok(()) +} + +fn unary_op_to_event(op: &UnaryOperatorTestCase, input: &Handle, result: &Handle) -> TfheContractEvents { + use fhevm_engine_common::types::SupportedFheOperations as S; + use fhevm_listener::contracts::TfheContract as C; + use fhevm_listener::contracts::TfheContract::TfheContractEvents as E; + + let caller = "0x0000000000000000000000000000000000000000".parse().unwrap(); + let input = input.clone(); + let result = result.clone(); + + match S::try_from(op.operand).unwrap() { + S::FheNot => E::FheNot(C::FheNot { caller, ct: input, result }), + S::FheNeg => E::FheNeg(C::FheNeg { caller, ct: input, result }), + _ => panic!("unknown unary operation: {:?}", op.operand), + } +} + +#[tokio::test] +async fn test_fhe_unary_operands_events() -> Result<(), Box> { + let ops = generate_unary_test_cases(); + let app = setup_test_app().await?; + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(2) + .connect(app.db_url()) + .await?; + let mut listener_event_to_db = listener_event_to_db(&app).await; + + for op in &ops { + let input_handle = next_handle(); + let output_handle = next_handle(); + + let inp_bytes = to_bytes(&op.inp); + + println!( + "Operations for unary test bits:{} op:{} input:{}", + op.bits, + op.operand, + op.inp.to_string() + ); + + let caller = "0x0000000000000000000000000000000000000000".parse().unwrap(); + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::TrivialEncryptBytes(TfheContract::TrivialEncryptBytes { + caller, + pt: inp_bytes, + toType: to_ty(op.operand_types), + result: input_handle.clone() + }) + )).await?; + + let op_event = unary_op_to_event(&op, &input_handle, &output_handle); + eprintln!("op_event: {:?}", &op_event); + listener_event_to_db.insert_tfhe_event(&tfhe_event(op_event)).await?; + listener_event_to_db.notify_scheduler().await; + wait_until_all_ciphertexts_computed(&app).await?; + + let decrypt_request = vec![output_handle.to_be_bytes_vec()]; + let resp = decrypt_ciphertexts(&pool, 1, decrypt_request).await?; + let decr_response = &resp[0]; + println!( + "Checking computation for unary test bits:{} op:{} input:{} output:{}", + op.bits, + op.operand, + op.inp.to_string(), + decr_response.value + ); + assert_eq!( + decr_response.output_type, op.operand_types as i16, + "operand types not equal" + ); + let expected_value = if op.bits == 1 { + op.expected_output.gt(&BigInt::from(0)).to_string() + } else { op.expected_output.to_string() }; + assert_eq!( + decr_response.value, + expected_value, + "operand output values not equal" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_fhe_if_then_else_events() -> Result<(), Box> { + let app = setup_test_app().await?; + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(2) + .connect(app.db_url()) + .await?; + let mut listener_event_to_db = listener_event_to_db(&app).await; + + let fhe_bool_type = 0; + let false_handle = next_handle(); + let true_handle = next_handle(); + let caller = "0x0000000000000000000000000000000000000000".parse().unwrap(); + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::TrivialEncryptBytes(TfheContract::TrivialEncryptBytes { + caller, + pt: to_bytes(&BigInt::from(0)), + toType: to_ty(fhe_bool_type), + result: false_handle.clone() + }) + )).await?; + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::TrivialEncryptBytes(TfheContract::TrivialEncryptBytes { + caller, + pt: to_bytes(&BigInt::from(1)), + toType: to_ty(fhe_bool_type), + result: true_handle.clone() + }) + )).await?; + + for input_types in supported_types() { + let left_handle = next_handle(); + let right_handle = next_handle(); + let is_input_bool = *input_types == fhe_bool_type; + let (left_input, right_input) = if is_input_bool { (BigInt::from(0), BigInt::from(1)) } else { (BigInt::from(7), BigInt::from(12)) }; + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::TrivialEncryptBytes(TfheContract::TrivialEncryptBytes { + caller, + pt: to_bytes(&left_input), + toType: to_ty(*input_types), + result: left_handle.clone() + }) + )).await?; + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::TrivialEncryptBytes(TfheContract::TrivialEncryptBytes { + caller, + pt: to_bytes(&right_input), + toType: to_ty(*input_types), + result: right_handle.clone() + }) + )).await?; + + for test_value in [false, true] { + let output_handle = next_handle(); + let (expected_result, input_handle) = if test_value { + (&left_input, &true_handle) + } else { + (&right_input, &false_handle) + }; + let expected_result = if *input_types == fhe_bool_type { + (expected_result > &BigInt::from(0)).to_string() + } else { + expected_result.to_string() + }; + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::FheIfThenElse(TfheContract::FheIfThenElse { + caller, + control: input_handle.clone(), + ifTrue: left_handle.clone(), + ifFalse: right_handle.clone(), + result: output_handle.clone() + }) + )).await?; + listener_event_to_db.notify_scheduler().await; + wait_until_all_ciphertexts_computed(&app).await?; + let decrypt_request = vec![output_handle.to_be_bytes_vec()]; + let resp = decrypt_ciphertexts(&pool, 1, decrypt_request).await?; + let decr_response = &resp[0]; + println!( + "Checking if then else computation for test type:{} control:{} lhs:{} rhs:{} output:{}", + *input_types, test_value, left_input, right_input, decr_response.value + ); + assert_eq!( + decr_response.output_type, *input_types as i16, + "operand types not equal" + ); + assert_eq!( + decr_response.value.to_string(), expected_result, + "operand output values not equal" + ); + } + } + wait_until_all_ciphertexts_computed(&app).await?; + + Ok(()) +} + + +#[tokio::test] +async fn test_fhe_cast_events() -> Result<(), Box> { + let app = setup_test_app().await?; + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(2) + .connect(app.db_url()) + .await?; + let mut listener_event_to_db = listener_event_to_db(&app).await; + + let caller = "0x0000000000000000000000000000000000000000".parse().unwrap(); + + let fhe_bool = 0; + for type_from in supported_types() { + for type_to in supported_types() { + let input_handle = next_handle(); + let output_handle = next_handle(); + let input = 7; + let output = if *type_to == fhe_bool || *type_from == fhe_bool { + // if bool output is 1 + 1 + } else { + input + }; + + println!( + "Encrypting inputs for cast test type from:{type_from} type to:{type_to} input:{input} output:{output}", + ); + + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::TrivialEncryptBytes(TfheContract::TrivialEncryptBytes { + caller, + pt: to_bytes(&BigInt::from(input)), + toType: to_ty(*type_from), + result: input_handle.clone() + }) + )).await?; + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::Cast(TfheContract::Cast { + caller, + ct: input_handle.clone(), + toType: to_ty(*type_to), + result: output_handle.clone() + }) + )).await?; + + listener_event_to_db.notify_scheduler().await; + wait_until_all_ciphertexts_computed(&app).await?; + let decrypt_request = vec![output_handle.to_be_bytes_vec()]; + let resp = decrypt_ciphertexts(&pool, 1, decrypt_request).await?; + let decr_response = &resp[0]; + + println!( + "Checking computation for cast test from:{} to:{} input:{} output:{}", + type_from, type_to, input, decr_response.value, + ); + + assert_eq!( + decr_response.output_type, *type_to as i16, + "operand types not equal" + ); + assert_eq!( + decr_response.value.to_string(), + if *type_to == fhe_bool { + (output > 0).to_string() + } else { + output.to_string() + }, + "operand output values not equal" + ); + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_fhe_rand_events() -> Result<(), Box> { + let app = setup_test_app().await?; + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(2) + .connect(app.db_url()) + .await?; + let mut listener_event_to_db = listener_event_to_db(&app).await; + + for &rand_type in supported_types() { + let output1_handle = next_handle(); + let output2_handle = next_handle(); + let output3_handle = next_handle(); + + let caller = "0x0000000000000000000000000000000000000000".parse().unwrap(); + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::FheRand(TfheContract::FheRand { + caller, + randType: to_ty(rand_type), + seed: FixedBytes::from([0u8; 16]), + result: output1_handle.clone() + }) + )).await?; + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::FheRand(TfheContract::FheRand { + caller, + randType: to_ty(rand_type), + seed: FixedBytes::from([1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]), + result: output2_handle.clone() + }) + )).await?; + + listener_event_to_db.insert_tfhe_event(&tfhe_event( + TfheContractEvents::FheRandBounded(TfheContract::FheRandBounded { + caller, + upperBound: as_scalar(&BigInt::from(1)), + randType: to_ty(rand_type), + seed: FixedBytes::from([1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]), + result: output3_handle.clone() + }) + )).await?; + + listener_event_to_db.notify_scheduler().await; + wait_until_all_ciphertexts_computed(&app).await?; + + let decrypt_request = vec![output1_handle.to_be_bytes_vec(), output2_handle.to_be_bytes_vec(), output3_handle.to_be_bytes_vec()]; + let resp = decrypt_ciphertexts(&pool, 1, decrypt_request).await?; + assert_eq!(resp[0].output_type, rand_type as i16); + assert_eq!(resp[1].output_type, rand_type as i16); + assert_eq!(resp[2].output_type, rand_type as i16); + if rand_type != 0 { + assert_eq!(resp[2].value, "0"); + } + } + + Ok(()) +} \ No newline at end of file diff --git a/fhevm-engine/fhevm-listener/.sqlx/query-69e5a39a57d25b91b21c5fa00f45e614a9d71b97e5bdd8118f4d7d986a48aeb5.json b/fhevm-engine/fhevm-listener/.sqlx/query-69e5a39a57d25b91b21c5fa00f45e614a9d71b97e5bdd8118f4d7d986a48aeb5.json new file mode 100644 index 00000000..8d57cc54 --- /dev/null +++ b/fhevm-engine/fhevm-listener/.sqlx/query-69e5a39a57d25b91b21c5fa00f45e614a9d71b97e5bdd8118f4d7d986a48aeb5.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT tenant_id FROM tenants WHERE tenant_api_key = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "tenant_id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false + ] + }, + "hash": "69e5a39a57d25b91b21c5fa00f45e614a9d71b97e5bdd8118f4d7d986a48aeb5" +} diff --git a/fhevm-engine/fhevm-listener/.sqlx/query-b88e9d81c24c47b7df985acf83fdab453de438a41d388eadfe2628d2ba4e1aed.json b/fhevm-engine/fhevm-listener/.sqlx/query-b88e9d81c24c47b7df985acf83fdab453de438a41d388eadfe2628d2ba4e1aed.json new file mode 100644 index 00000000..b8f1f378 --- /dev/null +++ b/fhevm-engine/fhevm-listener/.sqlx/query-b88e9d81c24c47b7df985acf83fdab453de438a41d388eadfe2628d2ba4e1aed.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO computations (\n tenant_id,\n output_handle,\n dependencies,\n fhe_operation,\n is_scalar\n )\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (tenant_id, output_handle) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Bytea", + "ByteaArray", + "Int2", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "b88e9d81c24c47b7df985acf83fdab453de438a41d388eadfe2628d2ba4e1aed" +} diff --git a/fhevm-engine/fhevm-listener/.sqlx/query-f7111b94c0efa152dcf24928a13133a04d9177e1c02db388b3d0fe1308b2b917.json b/fhevm-engine/fhevm-listener/.sqlx/query-f7111b94c0efa152dcf24928a13133a04d9177e1c02db388b3d0fe1308b2b917.json new file mode 100644 index 00000000..92cfca65 --- /dev/null +++ b/fhevm-engine/fhevm-listener/.sqlx/query-f7111b94c0efa152dcf24928a13133a04d9177e1c02db388b3d0fe1308b2b917.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "NOTIFY work_available;", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "f7111b94c0efa152dcf24928a13133a04d9177e1c02db388b3d0fe1308b2b917" +} diff --git a/fhevm-engine/fhevm-listener/Cargo.toml b/fhevm-engine/fhevm-listener/Cargo.toml index 756b2de2..aa8d2e16 100644 --- a/fhevm-engine/fhevm-listener/Cargo.toml +++ b/fhevm-engine/fhevm-listener/Cargo.toml @@ -11,13 +11,19 @@ bench = false [dependencies] # external dependencies -alloy = { version = "0.11", features = ["contract", "json", "providers", "provider-ws", "pubsub", "rpc-types", "sol-types"] } alloy-primitives = "0.8.21" alloy-provider = "0.11.1" alloy-eips = "0.11.1" alloy-rpc-types = "0.11" alloy-sol-types = "0.8" + +# workspace dependency +alloy = { workspace = true, features = ["contract", "json", "providers", "provider-ws", "pubsub", "rpc-types", "sol-types"] } clap = { workspace = true } futures-util = { workspace = true } serde = { workspace = true } +sqlx = { workspace = true } tokio = { workspace = true } + +# local dependencies +fhevm-engine-common = { path = "../fhevm-engine-common" } diff --git a/fhevm-engine/fhevm-listener/README.md b/fhevm-engine/fhevm-listener/README.md index aa557b32..707eed99 100644 --- a/fhevm-engine/fhevm-listener/README.md +++ b/fhevm-engine/fhevm-listener/README.md @@ -8,7 +8,28 @@ Our contracts actively emits events that forms the trace of a symbolic execution ## Command-line -WIP +If already compiled you can just call the binary directly: +``` +../target/debug/listen -coprocessor-api-key 00000000000000000000000000000000 +``` + +If you have no coprocessor-api-key, for local tests, you can do +``` +psql +postgres=# insert into tenants values (13, '00000000000000000000000000000000', 0, 'contract verify', 'contract acl', '0'::bytea, '0'::bytea, '0'::bytea); +``` + +Otherwise you can compile + run with: + +``` +DATABASE_URL=postgresql://postgres:testmdp@0.0.0.0:5432 cargo run -- --coprocessor-api-key 00000000000000000000000000000000 +``` + +DATABASE_URL need to specify an online database to compile SQL requests. + +By default the listener propagate TFHE operation events to the database. +You can change the database url using --database-url, it defaults to a local test database url. +If you want to disable TFHE operation events propagation, you can provide an empty database-url. ## Events in FHEVM diff --git a/fhevm-engine/fhevm-listener/src/bin/main.rs b/fhevm-engine/fhevm-listener/src/bin/main.rs index 0f2b11fd..07404107 100644 --- a/fhevm-engine/fhevm-listener/src/bin/main.rs +++ b/fhevm-engine/fhevm-listener/src/bin/main.rs @@ -1,5 +1,6 @@ use alloy_provider::fillers::{BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller}; use futures_util::stream::StreamExt; +use sqlx::types::Uuid; use std::str::FromStr; use std::time::Duration; @@ -13,6 +14,7 @@ use alloy_sol_types::SolEventInterface; use clap::Parser; use fhevm_listener::contracts::{AclContract, TfheContract}; +use fhevm_listener::database::tfhe_event_propagate::Database; const DEFAULT_CATCHUP: u64 = 5; @@ -34,14 +36,17 @@ pub struct Args { #[arg(long, default_value = None)] pub tfhe_contract_address: Option, - #[arg(long, default_value = None)] - pub database_url: Option, + #[arg(long, default_value = "postgresql://postgres:testmdp@localhost:5432/postgres")] + pub database_url: String, #[arg(long, default_value = None, help = "Can be negative from last block", allow_hyphen_values = true)] pub start_at_block: Option, #[arg(long, default_value = None)] pub end_at_block: Option, + + #[arg(long, default_value = None, help = "A Coprocessor API key is needed for database access")] + pub coprocessor_api_key: Option, } type RProvider = FillProvider>>>, RootProvider>; @@ -194,6 +199,14 @@ async fn main() -> () { }; } + let mut db: Option = None; + if !args.database_url.is_empty() { + if let Some(coprocessor_api_key) = args.coprocessor_api_key { + db = Some(Database::new(&args.database_url, &coprocessor_api_key).await); + } else { + panic!("A Coprocessor API key is required to access the database"); + } + } log_iter.new_log_stream(true).await; while let Some(log) = log_iter.next().await { if let Some(block_number) = log.block_number { @@ -203,7 +216,13 @@ async fn main() -> () { if !args.ignore_tfhe_events { if let Ok(event) = TfheContract::TfheContractEvents::decode_log(&log.inner, true) { // TODO: filter on contract address if known - println!("TFHE {event:#?}"); + println!("\nTFHE {event:#?}"); + if let Some(ref mut db) = db { + match db.insert_tfhe_event(&event).await { + Ok(_) => db.notify_scheduler().await, // we always notify, e.g. for catchup + Err(err) => eprintln!("Error inserting tfhe event: {err}"), + } + } continue; } } diff --git a/fhevm-engine/fhevm-listener/src/build.rs b/fhevm-engine/fhevm-listener/src/build.rs new file mode 100644 index 00000000..b5a4d55e --- /dev/null +++ b/fhevm-engine/fhevm-listener/src/build.rs @@ -0,0 +1,12 @@ +use std::path::Path; +use foundry_compilers::{Project, ProjectPathsConfig}; +fn main() { + let paths = ProjectPathsConfig::hardhat(Path::new(env!("CARGO_MANIFEST_DIR"))).unwrap(); + let project = Project::builder() + .paths(paths) + .build(Default::default()) + .unwrap(); + let output = project.compile().unwrap(); + assert_eq!(output.has_compiler_errors(), false); + project.rerun_if_sources_changed(); +} \ No newline at end of file diff --git a/fhevm-engine/fhevm-listener/src/database/mod.rs b/fhevm-engine/fhevm-listener/src/database/mod.rs new file mode 100644 index 00000000..513c967c --- /dev/null +++ b/fhevm-engine/fhevm-listener/src/database/mod.rs @@ -0,0 +1 @@ +pub mod tfhe_event_propagate; diff --git a/fhevm-engine/fhevm-listener/src/database/tfhe_event_propagate.rs b/fhevm-engine/fhevm-listener/src/database/tfhe_event_propagate.rs new file mode 100644 index 00000000..70124d33 --- /dev/null +++ b/fhevm-engine/fhevm-listener/src/database/tfhe_event_propagate.rs @@ -0,0 +1,354 @@ +use std::time::Duration; + +use alloy_primitives::FixedBytes; +use alloy_primitives::Uint; +use alloy_primitives::Log; +use sqlx::Error as SqlxError; +use sqlx::{Postgres, PgPool}; +use sqlx::postgres::PgConnectOptions; +use sqlx::postgres::PgPoolOptions; +use sqlx::types::Uuid; + +use fhevm_engine_common::types::SupportedFheOperations; + +use crate::contracts::TfheContract; +use crate::contracts::TfheContract::TfheContractEvents; + +type CoprocessorApiKey = Uuid; +type FheOperation = i32; +pub type Handle = Uint<256, 4>; +pub type TenantId = i32; +pub type ToType = FixedBytes<1>; +pub type ScalarByte = FixedBytes<1>; + +const MAX_RETRIES_FOR_NOTIFY: usize = 5; + +pub fn retry_on_sqlx_error(err: &SqlxError) -> bool { + match err { + SqlxError::Io(_) + | SqlxError::PoolTimedOut + | SqlxError::PoolClosed + | SqlxError::WorkerCrashed + | SqlxError::Protocol(_) => true, + // Other errors should be immdiately propagated up + _ => false, + } +} + +// A pool of connection with some cached information and automatic reconnection +pub struct Database { + url: String, + pool: sqlx::Pool, + tenant_id: TenantId, +} + +impl Database { + pub async fn new(url: &str, coprocessor_api_key: &CoprocessorApiKey) -> Self { + let pool = Self::new_pool(&url).await; + let tenant_id = Self::find_tenant_id_or_panic(&pool, coprocessor_api_key).await; + Database { + url: url.into(), + tenant_id, + pool, + } + } + + async fn new_pool(url: &str) -> PgPool { + let options: PgConnectOptions = url.parse().expect("bad url"); + let options = options.options([ + ("statement_timeout", "10000"), // 5 seconds + ]); + let connect = || { + PgPoolOptions::new() + .min_connections(2) + .max_lifetime(Duration::from_secs(10 * 60)) + .max_connections(8) + .connect_with(options.clone()) + }; + let mut pool = connect().await; + while let Err(err) = pool { + eprintln!("Database connection failed. {err}. Will retry indefinitively."); + tokio::time::sleep(Duration::from_secs(5)).await; + pool = connect().await; + } + pool.expect("unreachable") + } + + async fn reconnect(&mut self) { + self.pool.close().await; + tokio::time::sleep(Duration::from_millis(100)).await; + self.pool = Self::new_pool(&self.url).await; + } + + pub async fn find_tenant_id_or_panic(pool: &sqlx::Pool, tenant_api_key: &CoprocessorApiKey) -> TenantId { + let query = || { + sqlx::query_scalar!( + r#"SELECT tenant_id FROM tenants WHERE tenant_api_key = $1"#, + tenant_api_key.into() + ) + .fetch_one(pool) + }; + // retry mecanism + loop { + match query().await { + Ok(tenant_id) => return tenant_id, + Err(err) if retry_on_sqlx_error(&err) => { + eprintln!("Error requesting tenant id, retrying: {err}"); + tokio::time::sleep(Duration::from_secs(1)).await; + } + Err(SqlxError::RowNotFound) => { + panic!("No tenant found for the provided API key, please check your API key") + } + Err(err) => panic!("Error requesting tenant id {err}, aborting"), + } + } + } + + async fn insert_computation_bytes( + &mut self, + tenant_id: TenantId, + result: &Handle, + dependencies_handles: &[&Handle], + dependencies_bytes: &[Vec], // always added after dependencies_handles + fhe_operation: FheOperation, + scalar_byte: &FixedBytes<1>, + ) -> Result<(), SqlxError> { + let dependencies_handles = dependencies_handles + .iter() + .map(|d| d.to_be_bytes_vec()) + .collect::>(); + let dependencies = [&dependencies_handles, dependencies_bytes].concat(); + self.insert_computation_inner(tenant_id, result, dependencies, fhe_operation, scalar_byte) + .await + } + + async fn insert_computation( + &mut self, + tenant_id: TenantId, + result: &Handle, + dependencies: &[&Handle], + fhe_operation: FheOperation, + scalar_byte: &FixedBytes<1>, + ) -> Result<(), SqlxError> { + let dependencies = dependencies.iter().map(|d| d.to_be_bytes_vec()).collect::>(); + self.insert_computation_inner(tenant_id, result, dependencies, fhe_operation, scalar_byte) + .await + } + + async fn insert_computation_inner( + &mut self, + tenant_id: TenantId, + result: &Handle, + dependencies: Vec>, + fhe_operation: FheOperation, + scalar_byte: &FixedBytes<1>, + ) -> Result<(), SqlxError> { + let is_scalar = !scalar_byte.is_zero(); + let output_handle = result.to_be_bytes_vec(); + let query = || { + sqlx::query!( + r#" + INSERT INTO computations ( + tenant_id, + output_handle, + dependencies, + fhe_operation, + is_scalar + ) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (tenant_id, output_handle) DO NOTHING + "#, + tenant_id as i32, + output_handle, + &dependencies, + fhe_operation as i16, + is_scalar + ) + }; + // retry mecanism + loop { + match query().execute(&self.pool).await { + Ok(_) => return Ok(()), + Err(err) if retry_on_sqlx_error(&err) => { + eprintln!("\tDatabase I/O error: {}, will retry indefinitely", err); + self.reconnect().await; + } + Err(sqlx_err) => { + return Err(sqlx_err); + } + } + } + } + + #[rustfmt::skip] + pub async fn insert_tfhe_event( + &mut self, + event: &Log + ) -> Result<(), SqlxError> { + use TfheContract as C; + use TfheContractEvents as E; + const HAS_SCALAR : FixedBytes::<1> = FixedBytes([1]); // if any dependency is a scalar. + const NO_SCALAR : FixedBytes::<1> = FixedBytes([0]); // if all dependencies are handles. + // ciphertext type + let ty = |to_type: &ToType| vec![to_type[0] as u8]; + let as_bytes = |x: &Handle| x.to_be_bytes_vec(); + let tenant_id = self.tenant_id; + let fhe_operation = event_to_op_int(&event); + match &event.data { + E::Cast(C::Cast {ct, toType, result, ..}) + => self.insert_computation_bytes(tenant_id, result, &[ct], &[ty(toType)], fhe_operation, &HAS_SCALAR).await, + + E::FheAdd(C::FheAdd {lhs, rhs, scalarByte, result, ..}) + | E::FheBitAnd(C::FheBitAnd {lhs, rhs, scalarByte, result, ..}) + | E::FheBitOr(C::FheBitOr {lhs, rhs, scalarByte, result, ..}) + | E::FheBitXor(C::FheBitXor {lhs, rhs, scalarByte, result, ..} ) + | E::FheDiv(C::FheDiv {lhs, rhs, scalarByte, result, ..}) + | E::FheMax(C::FheMax {lhs, rhs, scalarByte, result, ..}) + | E::FheMin(C::FheMin {lhs, rhs, scalarByte, result, ..}) + | E::FheMul(C::FheMul {lhs, rhs, scalarByte, result, ..}) + | E::FheRem(C::FheRem {lhs, rhs, scalarByte, result, ..}) + | E::FheRotl(C::FheRotl {lhs, rhs, scalarByte, result, ..}) + | E::FheRotr(C::FheRotr {lhs, rhs, scalarByte, result, ..}) + | E::FheShl(C::FheShl {lhs, rhs, scalarByte, result, ..}) + | E::FheShr(C::FheShr {lhs, rhs, scalarByte, result, ..}) + | E::FheSub(C::FheSub {lhs, rhs, scalarByte, result, ..}) + => self.insert_computation(tenant_id, result, &[lhs, rhs], fhe_operation, scalarByte).await, + + E::FheIfThenElse(C::FheIfThenElse {control, ifTrue, ifFalse, result, ..}) + => self.insert_computation(tenant_id, result, &[control, ifTrue, ifFalse], fhe_operation, &NO_SCALAR).await, + + | E::FheEq(C::FheEq {lhs, rhs, scalarByte, result, ..}) + | E::FheGe(C::FheGe {lhs, rhs, scalarByte, result, ..}) + | E::FheGt(C::FheGt {lhs, rhs, scalarByte, result, ..}) + | E::FheLe(C::FheLe {lhs, rhs, scalarByte, result, ..}) + | E::FheLt(C::FheLt {lhs, rhs, scalarByte, result, ..}) + | E::FheNe(C::FheNe {lhs, rhs, scalarByte, result, ..}) + => self.insert_computation(tenant_id, result, &[lhs, rhs], fhe_operation, scalarByte).await, + + + E::FheNeg(C::FheNeg {ct, result, ..}) + | E::FheNot(C::FheNot {ct, result, ..}) + => self.insert_computation(tenant_id, result, &[ct], fhe_operation, &NO_SCALAR).await, + + | E::FheEqBytes(C::FheEqBytes {lhs, rhs, scalarByte, result, ..}) + | E::FheNeBytes(C::FheNeBytes {lhs, rhs, scalarByte, result, ..}) + => self.insert_computation_bytes(tenant_id, result, &[lhs], &[rhs.to_vec()], fhe_operation, scalarByte).await, + + | E::FheRand(C::FheRand {randType, seed, result, ..}) + => self.insert_computation_bytes(tenant_id, result, &[], &[seed.to_vec(), ty(randType)], fhe_operation, &HAS_SCALAR).await, + + | E::FheRandBounded(C::FheRandBounded {upperBound, randType, seed, result, ..}) + => self.insert_computation_bytes(tenant_id, result, &[], &[seed.to_vec(), as_bytes(&upperBound), ty(randType)], fhe_operation, &HAS_SCALAR).await, + + | E::TrivialEncrypt(C::TrivialEncrypt {pt, toType, result, ..}) + => self.insert_computation_bytes(tenant_id, result, &[pt], &[ty(toType)], fhe_operation, &HAS_SCALAR).await, + + | E::TrivialEncryptBytes(C::TrivialEncryptBytes {pt, toType, result, ..}) + => self.insert_computation_bytes(tenant_id, result, &[], &[pt.to_vec(), ty(toType)], fhe_operation, &HAS_SCALAR).await, + + | E::Initialized(_) + | E::OwnershipTransferStarted(_) + | E::OwnershipTransferred(_) + | E::Upgraded(_) + | E::VerifyCiphertext(_) + => Ok(()), + } + } + + pub async fn notify_scheduler(&mut self) { + let query = || sqlx::query!("NOTIFY work_available;"); + for i in (0..=MAX_RETRIES_FOR_NOTIFY).rev() { + match query().execute(&self.pool).await { + Ok(_) => return (), + Err(err) if retry_on_sqlx_error(&err) => { + eprintln!("\tDatabase I/O error: {}, will retry indefinitely", err); + self.reconnect().await; + } + Err(sqlx_err) => { + if i > 0 { + eprintln!("\tDatabase logic error: {}, will retry a few time ({i}) just in case", sqlx_err); + tokio::time::sleep(Duration::from_secs(1)).await; + } + } + } + } + } +} + +fn event_to_op_int(op: &TfheContractEvents) -> FheOperation { + use SupportedFheOperations as O; + use TfheContractEvents as E; + match op { + E::FheAdd(_) => O::FheAdd as i32, + E::FheSub(_) => O::FheSub as i32, + E::FheMul(_) => O::FheMul as i32, + E::FheDiv(_) => O::FheDiv as i32, + E::FheRem(_) => O::FheRem as i32, + E::FheBitAnd(_) => O::FheBitAnd as i32, + E::FheBitOr(_) => O::FheBitOr as i32, + E::FheBitXor(_) => O::FheBitXor as i32, + E::FheShl(_) => O::FheShl as i32, + E::FheShr(_) => O::FheShr as i32, + E::FheRotl(_) => O::FheRotl as i32, + E::FheRotr(_) => O::FheRotr as i32, + E::FheEq(_) | E::FheEqBytes(_) => O::FheEq as i32, + E::FheNe(_) | E::FheNeBytes(_) => O::FheNe as i32, + E::FheGe(_) => O::FheGe as i32, + E::FheGt(_) => O::FheGt as i32, + E::FheLe(_) => O::FheLe as i32, + E::FheLt(_) => O::FheLt as i32, + E::FheMin(_) => O::FheMin as i32, + E::FheMax(_) => O::FheMax as i32, + E::FheNeg(_) => O::FheNeg as i32, + E::FheNot(_) => O::FheNot as i32, + E::Cast(_) => O::FheCast as i32, + E::TrivialEncrypt(_) | E::TrivialEncryptBytes(_) => O::FheTrivialEncrypt as i32, + E::FheIfThenElse(_) => O::FheIfThenElse as i32, + E::FheRand(_) => O::FheRand as i32, + E::FheRandBounded(_) => O::FheRandBounded as i32, + // Not tfhe ops + E::Initialized(_) + | E::OwnershipTransferStarted(_) + | E::OwnershipTransferred(_) + | E::Upgraded(_) + | E::VerifyCiphertext(_) => -1, + } +} + +pub fn event_name(op: &TfheContractEvents) -> &'static str { + use TfheContractEvents as E; + match op { + E::FheAdd(_) => "FheAdd", + E::FheSub(_) => "FheSub", + E::FheMul(_) => "FheMul", + E::FheDiv(_) => "FheDiv", + E::FheRem(_) => "FheRem", + E::FheBitAnd(_) => "FheBitAnd", + E::FheBitOr(_) => "FheBitOr", + E::FheBitXor(_) => "FheBitXor", + E::FheShl(_) => "FheShl", + E::FheShr(_) => "FheShr", + E::FheRotl(_) => "FheRotl", + E::FheRotr(_) => "FheRotr", + E::FheEq(_) | E::FheEqBytes(_) => "FheEq", + E::FheNe(_) | E::FheNeBytes(_) => "FheNe", + E::FheGe(_) => "FheGe", + E::FheGt(_) => "FheGt", + E::FheLe(_) => "FheLe", + E::FheLt(_) => "FheLt", + E::FheMin(_) => "FheMin", + E::FheMax(_) => "FheMax", + E::FheNeg(_) => "FheNeg", + E::FheNot(_) => "FheNot", + E::Cast(_) => "FheCast", + E::TrivialEncrypt(_) | E::TrivialEncryptBytes(_) => "FheTrivialEncrypt", + E::FheIfThenElse(_) => "FheIfThenElse", + E::FheRand(_) => "FheRand", + E::FheRandBounded(_) => "FheRandBounded", + E::Initialized(_) => "Initialized", + E::OwnershipTransferStarted(_) => "OwnershipTransferStarted", + E::OwnershipTransferred(_) => "OwnershipTransferred", + E::Upgraded(_) => "Upgraded", + E::VerifyCiphertext(_) => "VerifyCiphertext", + } +} diff --git a/fhevm-engine/fhevm-listener/src/lib.rs b/fhevm-engine/fhevm-listener/src/lib.rs index 3f152f8b..de934b9b 100644 --- a/fhevm-engine/fhevm-listener/src/lib.rs +++ b/fhevm-engine/fhevm-listener/src/lib.rs @@ -1 +1,2 @@ pub mod contracts; +pub mod database;