diff --git a/ledger/src/scan_state/transaction_logic.rs b/ledger/src/scan_state/transaction_logic.rs index 10e81066e..be433f7c4 100644 --- a/ledger/src/scan_state/transaction_logic.rs +++ b/ledger/src/scan_state/transaction_logic.rs @@ -4311,7 +4311,7 @@ pub mod verifiable { use super::*; - #[derive(Debug)] + #[derive(Clone, Debug)] pub enum UserCommand { SignedCommand(Box), ZkAppCommand(Box), diff --git a/ledger/src/transaction_pool.rs b/ledger/src/transaction_pool.rs index 349bdb6a5..e94338804 100644 --- a/ledger/src/transaction_pool.rs +++ b/ledger/src/transaction_pool.rs @@ -1909,7 +1909,7 @@ impl TransactionPool { } } - fn verify( + pub fn verify( &self, diff: Envelope, accounts: BTreeMap, diff --git a/node/native/src/service.rs b/node/native/src/service.rs index 2f67e58e8..f07c0750c 100644 --- a/node/native/src/service.rs +++ b/node/native/src/service.rs @@ -3,10 +3,13 @@ use std::collections::{BTreeMap, VecDeque}; use std::sync::{Arc, Mutex}; use ledger::scan_state::scan_state::transaction_snark::{SokDigest, Statement}; +use ledger::scan_state::transaction_logic::{verifiable, UserCommand, WithStatus}; +use ledger::verifier::Verifier; use libp2p::identity::Keypair; use mina_p2p_messages::v2::{LedgerProofProdStableV2, TransactionSnarkWorkTStableV2Proofs}; #[cfg(not(feature = "p2p-libp2p"))] use node::p2p::service_impl::mio::MioService; +use node::transaction_pool::VerifyUserCommandsService; use rand::prelude::*; use redux::ActionMeta; use serde::Serialize; @@ -261,6 +264,34 @@ impl SnarkBlockVerifyService for NodeService { } } +impl VerifyUserCommandsService for NodeService { + fn verify_init( + &mut self, + commands: Vec>, + verifier_index: Arc, + verifier_srs: Arc>, + ) { + if self.replayer.is_some() { + return; + } + let tx = self.event_sender.clone(); + rayon::spawn_fifo(move || { + let verifieds: Vec<_> = Verifier + .verify_commands(commands, None) + .into_iter() + .map(|cmd| { + // TODO: Handle invalids + match cmd { + ledger::verifier::VerifyCommandsResult::Valid(cmd) => Ok(cmd), + e => Err(format!("invalid tx: {:?}", e)), + } + }) + .collect(); + // let _ = tx.send(SnarkEvent::WorkVerify(req_id, result).into()); + }); + } +} + impl SnarkWorkVerifyService for NodeService { fn verify_init( &mut self, diff --git a/node/src/transaction_pool/mod.rs b/node/src/transaction_pool/mod.rs index ef985bd56..c5dbd0b45 100644 --- a/node/src/transaction_pool/mod.rs +++ b/node/src/transaction_pool/mod.rs @@ -1,7 +1,15 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use ledger::{scan_state::transaction_logic::UserCommand, transaction_pool::{diff, ApplyDecision}, Account, AccountId, BaseLedger, Mask}; +use std::{ + collections::{BTreeMap, BTreeSet}, + sync::{Arc, Mutex}, +}; + +use ledger::{ + scan_state::transaction_logic::{verifiable, UserCommand, WithStatus}, + transaction_pool::{diff, ApplyDecision}, + Account, AccountId, BaseLedger, Mask, +}; use mina_p2p_messages::v2::LedgerHash; +use snark::{VerifierIndex, VerifierSRS}; use crate::{Service, Store}; @@ -47,9 +55,11 @@ impl TransactionPoolState { is_sender_local, accounts, } => match self.pool.unsafe_apply(diff, &accounts, *is_sender_local) { - Ok((ApplyDecision::Accept, accepted, rejected)) => self.rebroadcast(accepted, rejected), + Ok((ApplyDecision::Accept, accepted, rejected)) => { + self.rebroadcast(accepted, rejected) + } Ok((ApplyDecision::Reject, accepted, rejected)) => todo!(), - Err(_e) => eprintln!("unsafe_apply: {:?}", e), + Err(e) => eprintln!("unsafe_apply error: {:?}", e), }, ApplyTransitionFrontierDiff { best_tip_hash: _, @@ -58,7 +68,7 @@ impl TransactionPoolState { ApplyTransitionFrontierDiffWithAccounts { diff, accounts } => { self.pool.handle_transition_frontier_diff(diff, &accounts); } - Rebroadcast => {}, + Rebroadcast => {} } } } @@ -145,3 +155,12 @@ pub fn transaction_pool_effects( pub trait TransactionPoolLedgerService: redux::Service { fn get_mask(&self, ledger_hash: &LedgerHash) -> Result; } + +pub trait VerifyUserCommandsService: redux::Service { + fn verify_init( + &mut self, + commands: Vec>, + verifier_index: Arc, + verifier_srs: Arc>, + ); +} diff --git a/snark/src/lib.rs b/snark/src/lib.rs index e233c8c5c..ed1b1c004 100644 --- a/snark/src/lib.rs +++ b/snark/src/lib.rs @@ -10,6 +10,7 @@ pub use ledger::proofs::verifier_index::{get_verifier_index, VerifierKind}; pub use merkle_path::calc_merkle_root_hash; pub mod block_verify; +pub mod user_command_verify; pub mod work_verify; mod snark_event; diff --git a/snark/src/snark_actions.rs b/snark/src/snark_actions.rs index 1407945ca..91abd28b7 100644 --- a/snark/src/snark_actions.rs +++ b/snark/src/snark_actions.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use crate::user_command_verify::SnarkUserCommandVerifyAction; + use super::block_verify::SnarkBlockVerifyAction; use super::work_verify::SnarkWorkVerifyAction; @@ -10,4 +12,5 @@ pub type SnarkActionWithMetaRef<'a> = redux::ActionWithMeta<&'a SnarkAction>; pub enum SnarkAction { BlockVerify(SnarkBlockVerifyAction), WorkVerify(SnarkWorkVerifyAction), + UserCommandVerify(SnarkUserCommandVerifyAction), } diff --git a/snark/src/snark_event.rs b/snark/src/snark_event.rs index a863d471d..ff6e7bc4a 100644 --- a/snark/src/snark_event.rs +++ b/snark/src/snark_event.rs @@ -2,11 +2,16 @@ use serde::{Deserialize, Serialize}; use super::block_verify::{SnarkBlockVerifyError, SnarkBlockVerifyId}; use super::work_verify::{SnarkWorkVerifyError, SnarkWorkVerifyId}; +use crate::user_command_verify::{SnarkUserCommandVerifyError, SnarkUserCommandVerifyId}; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum SnarkEvent { BlockVerify(SnarkBlockVerifyId, Result<(), SnarkBlockVerifyError>), WorkVerify(SnarkWorkVerifyId, Result<(), SnarkWorkVerifyError>), + UserCommandVerify( + SnarkUserCommandVerifyId, + Result<(), SnarkUserCommandVerifyError>, + ), } fn res_kind(res: &Result) -> &'static str { @@ -26,6 +31,9 @@ impl std::fmt::Display for SnarkEvent { Self::WorkVerify(id, res) => { write!(f, "WorkVerify, {id}, {}", res_kind(res)) } + Self::UserCommandVerify(id, res) => { + write!(f, "UserCommandVerify, {id}, {}", res_kind(res)) + } } } } diff --git a/snark/src/snark_reducer.rs b/snark/src/snark_reducer.rs index ca7547f1b..f321fdd8b 100644 --- a/snark/src/snark_reducer.rs +++ b/snark/src/snark_reducer.rs @@ -6,6 +6,9 @@ impl SnarkState { match action { SnarkAction::BlockVerify(a) => self.block_verify.reducer(meta.with_action(a)), SnarkAction::WorkVerify(a) => self.work_verify.reducer(meta.with_action(a)), + SnarkAction::UserCommandVerify(a) => { + self.user_command_verify.reducer(meta.with_action(a)) + } } } } diff --git a/snark/src/snark_state.rs b/snark/src/snark_state.rs index 507b19872..24e110475 100644 --- a/snark/src/snark_state.rs +++ b/snark/src/snark_state.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; +use crate::user_command_verify::SnarkUserCommandVerifyState; use crate::SnarkConfig; use super::block_verify::SnarkBlockVerifyState; @@ -9,6 +10,7 @@ use super::work_verify::SnarkWorkVerifyState; pub struct SnarkState { pub block_verify: SnarkBlockVerifyState, pub work_verify: SnarkWorkVerifyState, + pub user_command_verify: SnarkUserCommandVerifyState, } impl SnarkState { @@ -19,6 +21,10 @@ impl SnarkState { config.block_verifier_srs, ), work_verify: SnarkWorkVerifyState::new( + config.work_verifier_index.clone(), + config.work_verifier_srs.clone(), + ), + user_command_verify: SnarkUserCommandVerifyState::new( config.work_verifier_index, config.work_verifier_srs, ), diff --git a/snark/src/user_command_verify/mod.rs b/snark/src/user_command_verify/mod.rs new file mode 100644 index 000000000..685ed5107 --- /dev/null +++ b/snark/src/user_command_verify/mod.rs @@ -0,0 +1,32 @@ +mod snark_user_command_verify_state; +pub use snark_user_command_verify_state::*; + +mod snark_user_command_verify_actions; +pub use snark_user_command_verify_actions::*; + +mod snark_user_command_verify_reducer; + +mod snark_user_command_verify_effects; + +mod snark_user_command_verify_service; +pub use snark_user_command_verify_service::*; + +use serde::{Deserialize, Serialize}; + +pub struct SnarkUserCommandVerifyIdType; +impl openmina_core::requests::RequestIdType for SnarkUserCommandVerifyIdType { + fn request_id_type() -> &'static str { + "SnarkUserCommandVerifyId" + } +} + +pub type SnarkUserCommandVerifyId = + openmina_core::requests::RequestId; + +#[derive(Serialize, Deserialize, Debug, Clone, thiserror::Error)] +pub enum SnarkUserCommandVerifyError { + #[error("verification failed")] + VerificationFailed, + #[error("validator thread crashed")] + ValidatorThreadCrashed, +} diff --git a/snark/src/user_command_verify/snark_user_command_verify_actions.rs b/snark/src/user_command_verify/snark_user_command_verify_actions.rs new file mode 100644 index 000000000..d743ea3bc --- /dev/null +++ b/snark/src/user_command_verify/snark_user_command_verify_actions.rs @@ -0,0 +1,66 @@ +use ledger::scan_state::transaction_logic::verifiable; +use serde::{Deserialize, Serialize}; + +use openmina_core::{snark::Snark, ActionEvent}; + +use super::{SnarkUserCommandVerifyError, SnarkUserCommandVerifyId}; + +pub type SnarkUserCommandVerifyActionWithMeta = redux::ActionWithMeta; +pub type SnarkUserCommandVerifyActionWithMetaRef<'a> = + redux::ActionWithMeta<&'a SnarkUserCommandVerifyAction>; + +#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] +#[action_event(level = trace, fields(display(req_id), display(error)))] +pub enum SnarkUserCommandVerifyAction { + #[action_event(level = info)] + Init { + req_id: SnarkUserCommandVerifyId, + #[serde(skip)] // TODO + commands: Vec, + sender: String, + }, + Pending { + req_id: SnarkUserCommandVerifyId, + }, + Error { + req_id: SnarkUserCommandVerifyId, + error: SnarkUserCommandVerifyError, + }, + #[action_event(level = info)] + Success { + req_id: SnarkUserCommandVerifyId, + }, + Finish { + req_id: SnarkUserCommandVerifyId, + }, +} + +impl redux::EnablingCondition for SnarkUserCommandVerifyAction { + fn is_enabled(&self, state: &crate::SnarkState, _time: redux::Timestamp) -> bool { + match self { + SnarkUserCommandVerifyAction::Init { + req_id, commands, .. + } => !commands.is_empty() && state.user_command_verify.jobs.next_req_id() == *req_id, + SnarkUserCommandVerifyAction::Pending { req_id } => state + .user_command_verify + .jobs + .get(*req_id) + .map_or(false, |v| v.is_init()), + SnarkUserCommandVerifyAction::Error { req_id, .. } => state + .user_command_verify + .jobs + .get(*req_id) + .map_or(false, |v| v.is_pending()), + SnarkUserCommandVerifyAction::Success { req_id } => state + .user_command_verify + .jobs + .get(*req_id) + .map_or(false, |v| v.is_pending()), + SnarkUserCommandVerifyAction::Finish { req_id } => state + .user_command_verify + .jobs + .get(*req_id) + .map_or(false, |v| v.is_finished()), + } + } +} diff --git a/snark/src/user_command_verify/snark_user_command_verify_effects.rs b/snark/src/user_command_verify/snark_user_command_verify_effects.rs new file mode 100644 index 000000000..2341f52e0 --- /dev/null +++ b/snark/src/user_command_verify/snark_user_command_verify_effects.rs @@ -0,0 +1,33 @@ +use redux::ActionMeta; + +use super::{SnarkUserCommandVerifyAction, SnarkUserCommandVerifyService}; + +impl SnarkUserCommandVerifyAction { + pub fn effects(self, _: &ActionMeta, store: &mut Store) + where + Store: crate::SnarkStore, + Store::Service: SnarkUserCommandVerifyService, + SnarkUserCommandVerifyAction: redux::EnablingCondition, + { + match self { + SnarkUserCommandVerifyAction::Init { + req_id, commands, .. + } => { + let verifier_index = store.state().work_verify.verifier_index.clone(); + let verifier_srs = store.state().work_verify.verifier_srs.clone(); + store + .service() + .verify_init(req_id, verifier_index, verifier_srs, commands); + store.dispatch(SnarkUserCommandVerifyAction::Pending { req_id }); + } + SnarkUserCommandVerifyAction::Error { req_id, .. } => { + store.dispatch(SnarkUserCommandVerifyAction::Finish { req_id }); + } + SnarkUserCommandVerifyAction::Success { req_id } => { + store.dispatch(SnarkUserCommandVerifyAction::Finish { req_id }); + } + SnarkUserCommandVerifyAction::Pending { .. } => {} + SnarkUserCommandVerifyAction::Finish { .. } => {} + } + } +} diff --git a/snark/src/user_command_verify/snark_user_command_verify_reducer.rs b/snark/src/user_command_verify/snark_user_command_verify_reducer.rs new file mode 100644 index 000000000..7ec22ba16 --- /dev/null +++ b/snark/src/user_command_verify/snark_user_command_verify_reducer.rs @@ -0,0 +1,67 @@ +use super::{ + SnarkUserCommandVerifyAction, SnarkUserCommandVerifyActionWithMetaRef, + SnarkUserCommandVerifyState, SnarkUserCommandVerifyStatus, +}; + +impl SnarkUserCommandVerifyState { + pub fn reducer(&mut self, action: SnarkUserCommandVerifyActionWithMetaRef<'_>) { + let (action, meta) = action.split(); + match action { + SnarkUserCommandVerifyAction::Init { + commands, sender, .. + } => { + self.jobs.add(SnarkUserCommandVerifyStatus::Init { + time: meta.time(), + commands: commands.clone(), + sender: sender.clone(), + }); + } + SnarkUserCommandVerifyAction::Pending { req_id } => { + if let Some(req) = self.jobs.get_mut(*req_id) { + *req = match req { + SnarkUserCommandVerifyStatus::Init { + commands, sender, .. + } => SnarkUserCommandVerifyStatus::Pending { + time: meta.time(), + commands: std::mem::take(commands), + sender: std::mem::take(sender), + }, + _ => return, + }; + } + } + SnarkUserCommandVerifyAction::Error { req_id, error } => { + if let Some(req) = self.jobs.get_mut(*req_id) { + *req = match req { + SnarkUserCommandVerifyStatus::Pending { + commands, sender, .. + } => SnarkUserCommandVerifyStatus::Error { + time: meta.time(), + commands: std::mem::take(commands), + sender: std::mem::take(sender), + error: error.clone(), + }, + _ => return, + }; + } + } + SnarkUserCommandVerifyAction::Success { req_id } => { + if let Some(req) = self.jobs.get_mut(*req_id) { + *req = match req { + SnarkUserCommandVerifyStatus::Pending { + commands, sender, .. + } => SnarkUserCommandVerifyStatus::Success { + time: meta.time(), + commands: std::mem::take(commands), + sender: std::mem::take(sender), + }, + _ => return, + }; + } + } + SnarkUserCommandVerifyAction::Finish { req_id } => { + self.jobs.remove(*req_id); + } + } + } +} diff --git a/snark/src/user_command_verify/snark_user_command_verify_service.rs b/snark/src/user_command_verify/snark_user_command_verify_service.rs new file mode 100644 index 000000000..95c8ea859 --- /dev/null +++ b/snark/src/user_command_verify/snark_user_command_verify_service.rs @@ -0,0 +1,17 @@ +use std::sync::{Arc, Mutex}; + +use ledger::scan_state::transaction_logic::verifiable; + +use crate::{VerifierIndex, VerifierSRS}; + +use super::SnarkUserCommandVerifyId; + +pub trait SnarkUserCommandVerifyService: redux::Service { + fn verify_init( + &mut self, + req_id: SnarkUserCommandVerifyId, + verifier_index: Arc, + verifier_srs: Arc>, + commands: Vec, + ); +} diff --git a/snark/src/user_command_verify/snark_user_command_verify_state.rs b/snark/src/user_command_verify/snark_user_command_verify_state.rs new file mode 100644 index 000000000..1c0ee3b54 --- /dev/null +++ b/snark/src/user_command_verify/snark_user_command_verify_state.rs @@ -0,0 +1,105 @@ +use std::sync::{Arc, Mutex}; + +use ledger::scan_state::transaction_logic::verifiable; +use serde::{Deserialize, Serialize}; + +use openmina_core::requests::PendingRequests; + +use crate::{VerifierIndex, VerifierSRS}; + +use super::{SnarkUserCommandVerifyError, SnarkUserCommandVerifyId, SnarkUserCommandVerifyIdType}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct SnarkUserCommandVerifyState { + pub verifier_index: Arc, + pub verifier_srs: Arc>, + pub jobs: PendingRequests, +} + +impl SnarkUserCommandVerifyState { + pub fn new(verifier_index: Arc, verifier_srs: Arc>) -> Self { + Self { + verifier_index, + verifier_srs, + jobs: Default::default(), + } + } + + pub fn next_req_id(&self) -> SnarkUserCommandVerifyId { + self.jobs.next_req_id() + } +} + +impl std::fmt::Debug for SnarkUserCommandVerifyState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SnarkUserCommandVerifyState") + // TODO(binier): display hashes instead. + .field("verifier_index", &"") + .field("verifier_srs", &"") + .field("jobs", &self.jobs) + .finish() + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum SnarkUserCommandVerifyStatus { + Init { + time: redux::Timestamp, + #[serde(skip)] // TODO + commands: Vec, + // TODO(binier): move p2p/src/identity to shared crate and use + // `PeerId` here. + sender: String, + }, + Pending { + time: redux::Timestamp, + #[serde(skip)] // TODO + commands: Vec, + sender: String, + }, + Error { + time: redux::Timestamp, + #[serde(skip)] // TODO + commands: Vec, + sender: String, + error: SnarkUserCommandVerifyError, + }, + Success { + time: redux::Timestamp, + #[serde(skip)] // TODO + commands: Vec, + sender: String, + }, +} + +impl SnarkUserCommandVerifyStatus { + pub fn is_init(&self) -> bool { + matches!(self, Self::Init { .. }) + } + + pub fn is_pending(&self) -> bool { + matches!(self, Self::Pending { .. }) + } + + pub fn is_finished(&self) -> bool { + matches!(self, Self::Error { .. } | Self::Success { .. }) + } + + pub fn commands(&self) -> &[verifiable::UserCommand] { + match self { + Self::Init { commands, .. } => commands, + Self::Pending { commands, .. } => commands, + Self::Error { commands, .. } => commands, + Self::Success { commands, .. } => commands, + } + } + + pub fn sender(&self) -> &str { + match self { + Self::Init { sender, .. } => sender, + Self::Pending { sender, .. } => sender, + Self::Error { sender, .. } => sender, + Self::Success { sender, .. } => sender, + } + } +}