From 4a535085c509ee5bbd2b1eea803d3b3089c73368 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Fri, 24 Jan 2025 09:10:42 +0200 Subject: [PATCH] fix:chain state validation --- node_core/src/lib.rs | 56 ++++++++++++++++++++++---- node_core/src/sequencer_client/json.rs | 2 + node_core/src/sequencer_client/mod.rs | 6 ++- sequencer_core/src/lib.rs | 31 +++++++++++++- sequencer_rpc/src/process.rs | 2 +- sequencer_rpc/src/types/rpc_structs.rs | 2 + 6 files changed, 88 insertions(+), 11 deletions(-) diff --git a/node_core/src/lib.rs b/node_core/src/lib.rs index 48bbd07..2f1469f 100644 --- a/node_core/src/lib.rs +++ b/node_core/src/lib.rs @@ -146,6 +146,16 @@ impl NodeCore { }) } + pub async fn get_roots(&self) -> [[u8; 32]; 3] { + let storage = self.storage.read().await; + //All roots are non-None after start of node main loop + [ + storage.nullifier_store.curr_root.unwrap(), + storage.utxo_commitments_store.get_root().unwrap(), + storage.pub_tx_store.get_root().unwrap(), + ] + } + pub async fn create_new_account(&mut self) -> AccountAddress { let account = Account::new(); account.log(); @@ -597,6 +607,9 @@ impl NodeCore { acc: AccountAddress, amount: u128, ) -> Result<(SendTxResponse, [u8; 32], [u8; 32])> { + //Considering proof time, needs to be done before proof + let tx_roots = self.get_roots().await; + let point_before_prove = std::time::Instant::now(); let (tx, utxo_hash) = self.mint_utxo_private(acc, amount).await?; tx.log(); @@ -608,7 +621,7 @@ impl NodeCore { info!("Mint utxo proof spent {timedelta:?} milliseconds"); Ok(( - self.sequencer_client.send_tx(tx).await?, + self.sequencer_client.send_tx(tx, tx_roots).await?, utxo_hash, commitment_generated_hash, )) @@ -620,6 +633,9 @@ impl NodeCore { amount: u128, number_of_assets: usize, ) -> Result<(SendTxResponse, Vec<[u8; 32]>, Vec<[u8; 32]>)> { + //Considering proof time, needs to be done before proof + let tx_roots = self.get_roots().await; + let point_before_prove = std::time::Instant::now(); let (tx, utxo_hashes) = self .mint_utxo_multiple_assets_private(acc, amount, number_of_assets) @@ -633,7 +649,7 @@ impl NodeCore { info!("Mint utxo proof spent {timedelta:?} milliseconds"); Ok(( - self.sequencer_client.send_tx(tx).await?, + self.sequencer_client.send_tx(tx, tx_roots).await?, utxo_hashes, commitment_generated_hashes, )) @@ -644,10 +660,13 @@ impl NodeCore { acc: AccountAddress, amount: u128, ) -> Result { + //Considering proof time, needs to be done before proof + let tx_roots = self.get_roots().await; + let tx = self.deposit_money_public(acc, amount); tx.log(); - Ok(self.sequencer_client.send_tx(tx).await?) + Ok(self.sequencer_client.send_tx(tx, tx_roots).await?) } pub async fn send_private_send_tx( @@ -656,6 +675,9 @@ impl NodeCore { comm_hash: [u8; 32], receivers: Vec<(u128, AccountAddress)>, ) -> Result<(SendTxResponse, Vec<([u8; 32], [u8; 32])>)> { + //Considering proof time, needs to be done before proof + let tx_roots = self.get_roots().await; + let point_before_prove = std::time::Instant::now(); let (tx, utxo_hashes) = self .transfer_utxo_private(utxo, comm_hash, receivers) @@ -666,7 +688,10 @@ impl NodeCore { let timedelta = (point_after_prove - point_before_prove).as_millis(); info!("Send private utxo proof spent {timedelta:?} milliseconds"); - Ok((self.sequencer_client.send_tx(tx).await?, utxo_hashes)) + Ok(( + self.sequencer_client.send_tx(tx, tx_roots).await?, + utxo_hashes, + )) } pub async fn send_private_multiple_assets_send_tx( @@ -676,6 +701,9 @@ impl NodeCore { number_to_send: usize, receiver: AccountAddress, ) -> Result<(SendTxResponse, Vec<[u8; 32]>, Vec<[u8; 32]>)> { + //Considering proof time, needs to be done before proof + let tx_roots = self.get_roots().await; + let point_before_prove = std::time::Instant::now(); let (tx, utxo_hashes_received, utxo_hashes_not_spent) = self .transfer_utxo_multiple_assets_private(utxos, comm_hashes, number_to_send, receiver) @@ -687,7 +715,7 @@ impl NodeCore { info!("Send private utxo proof spent {timedelta:?} milliseconds"); Ok(( - self.sequencer_client.send_tx(tx).await?, + self.sequencer_client.send_tx(tx, tx_roots).await?, utxo_hashes_received, utxo_hashes_not_spent, )) @@ -699,6 +727,9 @@ impl NodeCore { amount: u64, receivers: Vec<(u128, AccountAddress)>, ) -> Result<(SendTxResponse, Vec<([u8; 32], [u8; 32])>)> { + //Considering proof time, needs to be done before proof + let tx_roots = self.get_roots().await; + let point_before_prove = std::time::Instant::now(); let (tx, utxo_hashes) = self .transfer_balance_shielded(acc, amount, receivers) @@ -709,7 +740,10 @@ impl NodeCore { let timedelta = (point_after_prove - point_before_prove).as_millis(); info!("Send balance shielded proof spent {timedelta:?} milliseconds"); - Ok((self.sequencer_client.send_tx(tx).await?, utxo_hashes)) + Ok(( + self.sequencer_client.send_tx(tx, tx_roots).await?, + utxo_hashes, + )) } pub async fn send_deshielded_send_tx( @@ -718,6 +752,9 @@ impl NodeCore { comm_gen_hash: [u8; 32], receivers: Vec<(u128, AccountAddress)>, ) -> Result { + //Considering proof time, needs to be done before proof + let tx_roots = self.get_roots().await; + let point_before_prove = std::time::Instant::now(); let tx = self .transfer_utxo_deshielded(utxo, comm_gen_hash, receivers) @@ -728,7 +765,7 @@ impl NodeCore { let timedelta = (point_after_prove - point_before_prove).as_millis(); info!("Send deshielded utxo proof spent {timedelta:?} milliseconds"); - Ok(self.sequencer_client.send_tx(tx).await?) + Ok(self.sequencer_client.send_tx(tx, tx_roots).await?) } pub async fn operate_account_mint_private( @@ -1168,6 +1205,9 @@ impl NodeCore { receivers: Vec<(u128, AccountAddress)>, visibility_list: [bool; 3], ) -> Result<(SendTxResponse, Vec<([u8; 32], [u8; 32])>, Vec<[u8; 32]>)> { + //Considering proof time, needs to be done before proof + let tx_roots = self.get_roots().await; + let point_before_prove = std::time::Instant::now(); let (tx, utxo_hashes) = self .split_utxo(utxo, comm_hash, receivers, visibility_list) @@ -1181,7 +1221,7 @@ impl NodeCore { let commitments = tx.utxo_commitments_created_hashes.clone(); Ok(( - self.sequencer_client.send_tx(tx).await?, + self.sequencer_client.send_tx(tx, tx_roots).await?, utxo_hashes, commitments, )) diff --git a/node_core/src/sequencer_client/json.rs b/node_core/src/sequencer_client/json.rs index e34e39d..7683ea8 100644 --- a/node_core/src/sequencer_client/json.rs +++ b/node_core/src/sequencer_client/json.rs @@ -14,6 +14,8 @@ pub struct RegisterAccountRequest { #[derive(Serialize, Deserialize, Debug)] pub struct SendTxRequest { pub transaction: Transaction, + ///Nullifier Root, UTXO Commitment Root, Pub Tx Root + pub tx_roots: [[u8; 32]; 3], } #[derive(Serialize, Deserialize, Debug)] diff --git a/node_core/src/sequencer_client/mod.rs b/node_core/src/sequencer_client/mod.rs index 2ad1c53..6a65b9e 100644 --- a/node_core/src/sequencer_client/mod.rs +++ b/node_core/src/sequencer_client/mod.rs @@ -100,8 +100,12 @@ impl SequencerClient { pub async fn send_tx( &self, transaction: Transaction, + tx_roots: [[u8; 32]; 3], ) -> Result { - let tx_req = SendTxRequest { transaction }; + let tx_req = SendTxRequest { + transaction, + tx_roots, + }; let req = serde_json::to_value(tx_req)?; diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 08972d8..c6be871 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -32,6 +32,8 @@ pub enum TransactionMalformationErrorKind { TxHashAlreadyPresentInTree { tx: TreeHashType }, NullifierAlreadyPresentInTree { tx: TreeHashType }, UTXOCommitmentAlreadyPresentInTree { tx: TreeHashType }, + MempoolFullForRound { tx: TreeHashType }, + ChainStateFurtherThanTransactionState { tx: TreeHashType }, FailedToInsert { tx: TreeHashType, details: String }, } @@ -57,9 +59,19 @@ impl SequencerCore { } } + pub fn get_tree_roots(&self) -> [[u8; 32]; 3] { + //All roots are non-None after start of sequencer main loop + [ + self.store.nullifier_store.curr_root.unwrap(), + self.store.utxo_commitments_store.get_root().unwrap(), + self.store.pub_tx_store.get_root().unwrap(), + ] + } + pub fn transaction_pre_check( &mut self, tx: &Transaction, + tx_roots: [[u8; 32]; 3], ) -> Result<(), TransactionMalformationErrorKind> { let Transaction { hash, @@ -71,6 +83,22 @@ impl SequencerCore { .. } = tx; + let mempool_size = self.mempool.len(); + + if mempool_size >= self.sequencer_config.max_num_tx_in_block { + return Err(TransactionMalformationErrorKind::MempoolFullForRound { tx: *hash }); + } + + let curr_sequencer_roots = self.get_tree_roots(); + + if tx_roots != curr_sequencer_roots { + return Err( + TransactionMalformationErrorKind::ChainStateFurtherThanTransactionState { + tx: *hash, + }, + ); + } + //Sanity check match tx_kind { TxKind::Public => { @@ -143,8 +171,9 @@ impl SequencerCore { pub fn push_tx_into_mempool_pre_check( &mut self, item: TransactionMempool, + tx_roots: [[u8; 32]; 3], ) -> Result<(), TransactionMalformationErrorKind> { - self.transaction_pre_check(&item.tx)?; + self.transaction_pre_check(&item.tx, tx_roots)?; self.mempool.push_item(item); diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 098eb20..f6db7b2 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -72,7 +72,7 @@ impl JsonHandler { { let mut state = self.sequencer_state.lock().await; - state.push_tx_into_mempool_pre_check(send_tx_req.transaction)?; + state.push_tx_into_mempool_pre_check(send_tx_req.transaction, send_tx_req.tx_roots)?; } let helperstruct = SendTxResponse { diff --git a/sequencer_rpc/src/types/rpc_structs.rs b/sequencer_rpc/src/types/rpc_structs.rs index 5b375b4..4a57588 100644 --- a/sequencer_rpc/src/types/rpc_structs.rs +++ b/sequencer_rpc/src/types/rpc_structs.rs @@ -22,6 +22,8 @@ pub struct RegisterAccountRequest { #[derive(Serialize, Deserialize, Debug)] pub struct SendTxRequest { pub transaction: TransactionMempool, + ///Nullifier Root, UTXO Commitment Root, Pub Tx Root + pub tx_roots: [[u8; 32]; 3], } #[derive(Serialize, Deserialize, Debug)]