diff --git a/cartezcash-lightwalletd/src/service_impl.rs b/cartezcash-lightwalletd/src/service_impl.rs index 6755acb..6c6bb4f 100644 --- a/cartezcash-lightwalletd/src/service_impl.rs +++ b/cartezcash-lightwalletd/src/service_impl.rs @@ -55,7 +55,7 @@ where &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("send_transaction called. Fowarding to InputBox contract"); + tracing::debug!("send_transaction called. Fowarding to InputBox contract"); let provider = Provider::::try_from("http://127.0.0.1:8545").unwrap(); let wallet: LocalWallet = @@ -91,7 +91,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_latest_block called"); + tracing::debug!("get_latest_block called"); let res: zebra_state::ReadResponse = self .state_read_service @@ -104,13 +104,13 @@ where .unwrap(); if let ReadResponse::Tip(Some((height, hash))) = res { - tracing::info!("returning tip: {:?}", res); + tracing::debug!("returning tip: {:?}", res); Ok(tonic::Response::new(BlockId { hash: hash.0.to_vec(), height: height.0 as u64, })) } else { - tracing::info!("unexpected response"); + tracing::debug!("unexpected response"); Err(tonic::Status::not_found( "Could not find the latest block in the state store", )) @@ -122,7 +122,7 @@ where &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_block_range called with: {:?} ", request); + tracing::debug!("get_block_range called with: {:?} ", request); let (tx, rx) = mpsc::channel(10); // these sometimes come in reverse order... @@ -141,7 +141,7 @@ where let mut state_read_service = self.state_read_service.clone(); for height in range { - tracing::info!("fetching block at height: {}", height); + tracing::debug!("fetching block at height: {}", height); let res: zebra_state::ReadResponse = state_read_service .ready() .await @@ -155,7 +155,7 @@ where let block = match res { ReadResponse::Block(Some(block)) => block, _ => { - tracing::info!("unexpected response"); + tracing::debug!("unexpected response"); return Err(tonic::Status::not_found( "Could not find the block in the state store", )); @@ -220,7 +220,7 @@ where &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_transaction called"); + tracing::debug!("get_transaction called"); let mut state_read_service = self.state_read_service.clone(); let request = zebra_state::ReadRequest::Transaction( @@ -239,7 +239,7 @@ where height: transaction.height.0 as u64, })) } else { - tracing::info!("unexpected response"); + tracing::debug!("unexpected response"); Err(tonic::Status::not_found( "Could not find the transaction in the state store", )) @@ -251,7 +251,7 @@ where &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_taddress_txids called with {:?}", request); + tracing::debug!("get_taddress_txids called with {:?}", request); let request = request.into_inner(); let address = transparent::Address::from_str(&request.address).unwrap(); @@ -277,7 +277,7 @@ where if let ReadResponse::AddressesTransactionIds(txns) = res { let (tx, rx) = mpsc::channel(10); - tracing::info!("{:?} transactions found", txns.len()); + tracing::debug!("{:?} transactions found", txns.len()); for (_location, tx_id) in txns.iter() { tracing::debug!("got txid: {:?}", tx_id); @@ -317,7 +317,7 @@ where &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_tree_state called"); + tracing::debug!("get_tree_state called"); let mut read_service = self.state_read_service.clone(); let height: Height = Height(request.into_inner().height.try_into().unwrap()); @@ -370,7 +370,7 @@ where hash: hash.to_string(), time: block.header.time.timestamp() as u32, }; - tracing::info!("returning tree state: {:?}", tree_state); + tracing::debug!("returning tree state: {:?}", tree_state); Ok(tonic::Response::new(tree_state)) } @@ -379,7 +379,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_lightd_info called"); + tracing::debug!("get_lightd_info called"); let block_height = 0; // TODO: fetch this from the store @@ -415,7 +415,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_mempool_stream called"); + tracing::debug!("get_mempool_stream called"); // let (tx, rx) = mpsc::channel(4); // TODO: Send the txiods into the tx end of the channel Err(tonic::Status::unimplemented( @@ -432,7 +432,7 @@ where tonic::Response, tonic::Status, > { - tracing::info!("get_block called. Ignoring request"); + tracing::debug!("get_block called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -446,7 +446,7 @@ where tonic::Response, tonic::Status, > { - tracing::info!("get_block_nullifiers called. Ignoring request"); + tracing::debug!("get_block_nullifiers called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -461,7 +461,7 @@ where _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_block_range_nullifiers called. Ignoring request"); + tracing::debug!("get_block_range_nullifiers called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -471,7 +471,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_taddress_balance called. Ignoring request"); + tracing::debug!("get_taddress_balance called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -481,7 +481,7 @@ where &self, _request: tonic::Request>, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_taddress_balance_stream called. Ignoring request"); + tracing::debug!("get_taddress_balance_stream called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -503,7 +503,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_mempool_tx called. Ignoring request"); + tracing::debug!("get_mempool_tx called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -513,7 +513,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_latest_tree_state called. Ignoring request"); + tracing::debug!("get_latest_tree_state called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -527,7 +527,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_subtree_roots called. Ignoring request"); + tracing::debug!("get_subtree_roots called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -536,7 +536,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_address_utxos called. Ignoring request"); + tracing::debug!("get_address_utxos called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -549,7 +549,7 @@ where _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("get_address_utxos_stream called. Ignoring request"); + tracing::debug!("get_address_utxos_stream called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) @@ -560,7 +560,7 @@ where &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - tracing::info!("ping called. Ignoring request"); + tracing::debug!("ping called. Ignoring request"); Err(tonic::Status::unimplemented( "gRPC endpoint not supported for cartezcash", )) diff --git a/src/main.rs b/src/main.rs index 0d129d3..83a740c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ use futures_util::future::FutureExt; use zebra_chain::{block, parameters::Network}; - #[cfg(feature = "lightwalletd")] use cartezcash_lightwalletd::{ proto::service::compact_tx_streamer_server::CompactTxStreamerServer, @@ -148,11 +147,8 @@ impl Service for CarteZcashApp { async fn initialize_network(tinycash: &mut S) -> Result<(), BoxError> where - S: Service< - tiny_cash::write::Request, - Response = tiny_cash::write::Response, - Error = BoxError, - > + Send + S: Service + + Send + Clone + 'static, S::Future: Send + 'static, diff --git a/tiny-cash/src/write.rs b/tiny-cash/src/write.rs index c09be08..b1d511a 100644 --- a/tiny-cash/src/write.rs +++ b/tiny-cash/src/write.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; use futures_util::future::FutureExt; -use zebra_state::DuplicateNullifierError; use std::{ collections::{HashMap, HashSet, VecDeque}, future::Future, @@ -10,6 +9,8 @@ use std::{ }; use tower::{BoxError, Service, ServiceExt}; +use incrementalmerkletree::frontier::Frontier; +use zebra_chain::orchard::tree::NoteCommitmentTree; use zebra_chain::transparent; use zebra_chain::{ amount::{Amount, NonNegative}, @@ -28,7 +29,6 @@ use zebra_chain::{ use zebra_consensus::script; use zebra_consensus::transaction as tx; use zebra_consensus::transaction::Verifier as TxVerifier; -use incrementalmerkletree::frontier::Frontier; use crate::extract_burn_info; @@ -48,14 +48,15 @@ pub struct TinyCashWriteService { utxos_set: HashMap, // The frontier of the commitment tree. This is the Merkle path of the last added commitment - // The frontier is all that is needed to update the root and produce a new frontier when new commitments are added + // Internally this just stores the frontier which is the Merkle path of the most recently added note + // This is all that is needed to update the root and produce a new frontier when new commitments are added // So for a fixed depth this is constant size! - // commitment_tree_frontier: OrchardFrontier, + commitment_tree_frontier: NoteCommitmentTree, // A FILO queue of orchard commitment tree roots. // Only a fixed window are kept so we can have constant state. // This means older witnesses become invalid after a period of time - // commitment_tree_roots: VecDeque, + historical_tree_roots: VecDeque, // A set of all nullifiers that have been seen. This prevents double spends. // sadly this is not fixed size but it could be replaced with a Sparse Merkle Tree @@ -70,6 +71,8 @@ impl TinyCashWriteService { tip_height: None, tip_hash: None, + commitment_tree_frontier: NoteCommitmentTree::default(), + historical_tree_roots: VecDeque::new(), utxos_set: HashMap::new(), nullifier_set: HashSet::new(), } @@ -171,14 +174,32 @@ where self.utxos_set .extend(new_outputs.iter().map(|(k, v)| (k.clone(), v.clone()))); - // check this block doesn't reuse a known nullifier then add the block nullifiers to the state for nullifier in block.clone().orchard_nullifiers() { if self.nullifier_set.contains(nullifier) { panic!("duplicate nullifier detected"); } } - self.nullifier_set.extend(block.orchard_nullifiers().cloned()); + self.nullifier_set + .extend(block.orchard_nullifiers().cloned()); + + // update the commitment tree frontier and add the new root to the queue + for commitment in block.orchard_note_commitments() { + self.commitment_tree_frontier + .append(*commitment) + .expect("failed to append to tree"); + } + self.historical_tree_roots + .push_back(self.commitment_tree_frontier.root()); + + // ensure the anchors/tree-roots referenced by the transaction are in the state + if let Some(tx) = tx_to_verify.as_ref() { + if let Some(data) = tx.orchard_shielded_data() { + if !self.historical_tree_roots.contains(&data.shared_anchor) { + panic!("Tree root not found in state. Witness may be invalid or too old"); + } + } + } async move { if height > Height(0) { @@ -299,7 +320,7 @@ where &orchard_shielded_data, &shielded_sighash, )?) - }, + } _ => panic!("Only V5 transactions are supported"), }; async_checks.check().await