From b6ea00daa43be0508b41a98e8d5270088ad76447 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 16 Jul 2025 11:36:20 -0300 Subject: [PATCH] add transactions tests --- common/src/block.rs | 6 +- common/src/merkle_tree_public/merkle_tree.rs | 4 +- .../src/merkle_tree_public/tree_leav_item.rs | 4 +- common/src/rpc_primitives/requests.rs | 4 +- common/src/transaction.rs | 100 ++++++++++++++---- node_core/src/chain_storage/block_store.rs | 4 +- node_core/src/chain_storage/mod.rs | 6 +- node_core/src/lib.rs | 33 +++--- node_core/src/sequencer_client/json.rs | 4 +- node_core/src/sequencer_client/mod.rs | 4 +- sequencer_core/src/lib.rs | 20 ++-- 11 files changed, 120 insertions(+), 69 deletions(-) diff --git a/common/src/block.rs b/common/src/block.rs index 4553245..7757667 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,7 +1,7 @@ use rs_merkle::Hasher; use serde::{Deserialize, Serialize}; -use crate::{merkle_tree_public::hasher::OwnHasher, transaction::SignedTransaction}; +use crate::{merkle_tree_public::hasher::OwnHasher, transaction::Transaction}; pub type BlockHash = [u8; 32]; pub type Data = Vec; @@ -13,7 +13,7 @@ pub struct Block { pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, pub hash: BlockHash, - pub transactions: Vec, + pub transactions: Vec, pub data: Data, } @@ -22,7 +22,7 @@ pub struct HashableBlockData { pub block_id: BlockId, pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, - pub transactions: Vec, + pub transactions: Vec, pub data: Data, } diff --git a/common/src/merkle_tree_public/merkle_tree.rs b/common/src/merkle_tree_public/merkle_tree.rs index 8886745..bf98eb6 100644 --- a/common/src/merkle_tree_public/merkle_tree.rs +++ b/common/src/merkle_tree_public/merkle_tree.rs @@ -8,7 +8,7 @@ use serde::{ }; use crate::{ - transaction::{AuthenticatedTransaction, SignedTransaction, TransactionBody}, + transaction::{AuthenticatedTransaction, Transaction, TransactionBody}, utxo_commitment::UTXOCommitment, }; @@ -84,7 +84,7 @@ impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> serde::Deserialize<'de> } } -pub type PublicTransactionMerkleTree = HashStorageMerkleTree; +pub type PublicTransactionMerkleTree = HashStorageMerkleTree; pub type UTXOCommitmentsMerkleTree = HashStorageMerkleTree; diff --git a/common/src/merkle_tree_public/tree_leav_item.rs b/common/src/merkle_tree_public/tree_leav_item.rs index 5599172..fadc4c8 100644 --- a/common/src/merkle_tree_public/tree_leav_item.rs +++ b/common/src/merkle_tree_public/tree_leav_item.rs @@ -1,4 +1,4 @@ -use crate::{transaction::SignedTransaction, utxo_commitment::UTXOCommitment}; +use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment}; use super::TreeHashType; @@ -6,7 +6,7 @@ pub trait TreeLeavItem { fn hash(&self) -> TreeHashType; } -impl TreeLeavItem for SignedTransaction { +impl TreeLeavItem for Transaction { fn hash(&self) -> TreeHashType { self.body.hash() } diff --git a/common/src/rpc_primitives/requests.rs b/common/src/rpc_primitives/requests.rs index 95b004f..532cfb7 100644 --- a/common/src/rpc_primitives/requests.rs +++ b/common/src/rpc_primitives/requests.rs @@ -1,6 +1,6 @@ use crate::block::Block; use crate::parse_request; -use crate::transaction::SignedTransaction; +use crate::transaction::Transaction; use crate::transaction::TransactionBody; use super::errors::RpcParseError; @@ -21,7 +21,7 @@ pub struct RegisterAccountRequest { #[derive(Serialize, Deserialize, Debug)] pub struct SendTxRequest { - pub transaction: SignedTransaction, + pub transaction: Transaction, ///UTXO Commitment Root, Pub Tx Root pub tx_roots: [[u8; 32]; 2], } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 68bf640..d79c832 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -27,7 +27,7 @@ pub type CipherText = Vec; pub type Nonce = GenericArray, B1>, B0>, B0>>; pub type Tag = u8; -#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum TxKind { Public, Private, @@ -35,7 +35,7 @@ pub enum TxKind { Deshielded, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] ///General transaction object pub struct TransactionBody { pub tx_kind: TxKind, @@ -233,18 +233,15 @@ pub type SignaturePrivateKey = SigningKey; /// A transaction with a signature. /// Meant to be sent through the network to the sequencer -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct SignedTransaction { +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Transaction { pub body: TransactionBody, signature: TransactionSignature, public_key: VerifyingKey, } -impl SignedTransaction { - pub fn from_transaction_body( - body: TransactionBody, - private_key: SigningKey, - ) -> SignedTransaction { +impl Transaction { + pub fn new(body: TransactionBody, private_key: SigningKey) -> Transaction { let hash = body.hash(); let signature: TransactionSignature = private_key.sign(&hash); let public_key = VerifyingKey::from(&private_key); @@ -264,27 +261,27 @@ impl SignedTransaction { Ok(AuthenticatedTransaction { hash, - signed_tx: self, + transaction: self, }) } } /// A transaction with a valid signature over the hash of its body. -/// Can only be constructed from an `SignedTransaction` +/// Can only be constructed from an `Transaction` /// if the signature is valid -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct AuthenticatedTransaction { hash: TransactionHash, - signed_tx: SignedTransaction, + transaction: Transaction, } impl AuthenticatedTransaction { - pub fn as_signed(&self) -> &SignedTransaction { - &self.signed_tx + pub fn as_transaction(&self) -> &Transaction { + &self.transaction } pub fn body(&self) -> &TransactionBody { - &self.signed_tx.body + &self.transaction.body } pub fn hash(&self) -> &TransactionHash { @@ -294,17 +291,18 @@ impl AuthenticatedTransaction { #[cfg(test)] mod tests { + use super::*; + use k256::FieldBytes; use secp256k1_zkp::{constants::SECRET_KEY_SIZE, Tweak}; use sha2::{digest::FixedOutput, Digest}; use crate::{ merkle_tree_public::TreeHashType, - transaction::{TransactionBody, TxKind}, + transaction::{Transaction, TransactionBody, TxKind}, }; - #[test] - fn test_transaction_hash_is_sha256_of_json_bytes() { - let tx = TransactionBody { + fn test_transaction_body() -> TransactionBody { + TransactionBody { tx_kind: TxKind::Public, execution_input: vec![1, 2, 3, 4], execution_output: vec![5, 6, 7, 8], @@ -319,16 +317,72 @@ mod tests { secret_r: [8; 32], sc_addr: "someAddress".to_string(), state_changes: (serde_json::Value::Null, 10), - }; + } + } + + #[test] + fn test_transaction_hash_is_sha256_of_json_bytes() { + let body = test_transaction_body(); let expected_hash = { - let data = serde_json::to_vec(&tx).unwrap(); + let data = serde_json::to_vec(&body).unwrap(); let mut hasher = sha2::Sha256::new(); hasher.update(&data); TreeHashType::from(hasher.finalize_fixed()) }; - let hash = tx.hash(); + let hash = body.hash(); assert_eq!(expected_hash, hash); } + + #[test] + fn test_transaction_constructor() { + let body = test_transaction_body(); + let key_bytes = FieldBytes::from_slice(&[37; 32]); + let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); + let transaction = Transaction::new(body.clone(), private_key.clone()); + assert_eq!( + transaction.public_key, + SignaturePublicKey::from(&private_key) + ); + assert_eq!(transaction.body, body); + } + + #[test] + fn test_into_authenticated_succeeds_for_valid_signature() { + let body = test_transaction_body(); + let key_bytes = FieldBytes::from_slice(&[37; 32]); + let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); + let transaction = Transaction::new(body, private_key.clone()); + let authenticated_tx = transaction.clone().into_authenticated().unwrap(); + + let signature = authenticated_tx.as_transaction().signature; + let hash = authenticated_tx.hash(); + assert_eq!(authenticated_tx.as_transaction(), &transaction); + assert_eq!(hash, &transaction.body.hash()); + + assert!(authenticated_tx + .as_transaction() + .public_key + .verify(hash, &signature) + .is_ok()); + } + + #[test] + fn test_into_authenticated_fails_for_invalid_signature() { + let body = test_transaction_body(); + let key_bytes = FieldBytes::from_slice(&[37; 32]); + let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); + let transaction = { + let mut this = Transaction::new(body, private_key.clone()); + // Modify the signature to make it invalid + // We do this by changing it to the signature of something else + this.signature = private_key.sign(b"deadbeef"); + this + }; + matches!( + transaction.into_authenticated(), + Err(TransactionSignatureError::InvalidSignature) + ); + } } diff --git a/node_core/src/chain_storage/block_store.rs b/node_core/src/chain_storage/block_store.rs index c6bc386..bcb988b 100644 --- a/node_core/src/chain_storage/block_store.rs +++ b/node_core/src/chain_storage/block_store.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Result}; use common::block::Block; use common::merkle_tree_public::merkle_tree::HashStorageMerkleTree; use common::nullifier::UTXONullifier; -use common::transaction::{AuthenticatedTransaction, SignedTransaction, TransactionBody}; +use common::transaction::{AuthenticatedTransaction, Transaction, TransactionBody}; use common::utxo_commitment::UTXOCommitment; use log::error; use storage::sc_db_utils::{DataBlob, DataBlobChangeVariant}; @@ -87,7 +87,7 @@ impl NodeBlockStore { )?) } - pub fn get_snapshot_transaction(&self) -> Result> { + pub fn get_snapshot_transaction(&self) -> Result> { Ok(serde_json::from_slice( &self.dbio.get_snapshot_transaction()?, )?) diff --git a/node_core/src/chain_storage/mod.rs b/node_core/src/chain_storage/mod.rs index bb8641a..32a2eea 100644 --- a/node_core/src/chain_storage/mod.rs +++ b/node_core/src/chain_storage/mod.rs @@ -279,7 +279,7 @@ mod tests { use accounts::account_core::Account; use common::block::{Block, Data}; use common::merkle_tree_public::TreeHashType; - use common::transaction::{SignaturePrivateKey, SignedTransaction, TransactionBody, TxKind}; + use common::transaction::{SignaturePrivateKey, Transaction, TransactionBody, TxKind}; use secp256k1_zkp::Tweak; use std::path::PathBuf; use tempfile::tempdir; @@ -301,7 +301,7 @@ mod tests { nullifier_created_hashes: Vec<[u8; 32]>, utxo_commitments_spent_hashes: Vec<[u8; 32]>, utxo_commitments_created_hashes: Vec<[u8; 32]>, - ) -> SignedTransaction { + ) -> Transaction { let mut rng = rand::thread_rng(); let body = TransactionBody { @@ -320,7 +320,7 @@ mod tests { sc_addr: "sc_addr".to_string(), state_changes: (serde_json::Value::Null, 0), }; - SignedTransaction::from_transaction_body(body, SignaturePrivateKey::random(&mut rng)) + Transaction::new(body, SignaturePrivateKey::random(&mut rng)) } fn create_sample_block(block_id: u64, prev_block_id: u64) -> Block { diff --git a/node_core/src/lib.rs b/node_core/src/lib.rs index 0c10578..45b0988 100644 --- a/node_core/src/lib.rs +++ b/node_core/src/lib.rs @@ -3,7 +3,7 @@ use std::sync::{ Arc, }; -use common::{transaction::SignedTransaction, ExecutionFailureKind}; +use common::{transaction::Transaction, ExecutionFailureKind}; use accounts::account_core::{Account, AccountAddress}; use anyhow::Result; @@ -189,7 +189,7 @@ impl NodeCore { &self, acc: AccountAddress, amount: u128, - ) -> Result<(SignedTransaction, [u8; 32]), ExecutionFailureKind> { + ) -> Result<(Transaction, [u8; 32]), ExecutionFailureKind> { let (utxo, receipt) = prove_mint_utxo(amount, acc)?; let result_hash = utxo.hash; @@ -267,7 +267,7 @@ impl NodeCore { let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); Ok(( - SignedTransaction::from_transaction_body(transaction_body, key_to_sign_transaction), + Transaction::new(transaction_body, key_to_sign_transaction), result_hash, )) } @@ -277,7 +277,7 @@ impl NodeCore { acc: AccountAddress, amount: u128, number_of_assets: usize, - ) -> Result<(SignedTransaction, Vec<[u8; 32]>), ExecutionFailureKind> { + ) -> Result<(Transaction, Vec<[u8; 32]>), ExecutionFailureKind> { let (utxos, receipt) = prove_mint_utxo_multiple_assets(amount, number_of_assets, acc)?; let result_hashes = utxos.iter().map(|utxo| utxo.hash).collect(); @@ -364,7 +364,7 @@ impl NodeCore { let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); Ok(( - SignedTransaction::from_transaction_body(transaction_body, key_to_sign_transaction), + Transaction::new(transaction_body, key_to_sign_transaction), result_hashes, )) } @@ -374,7 +374,7 @@ impl NodeCore { utxo: UTXO, commitment_in: [u8; 32], receivers: Vec<(u128, AccountAddress)>, - ) -> Result<(SignedTransaction, Vec<(AccountAddress, [u8; 32])>), ExecutionFailureKind> { + ) -> Result<(Transaction, Vec<(AccountAddress, [u8; 32])>), ExecutionFailureKind> { let acc_map_read_guard = self.storage.read().await; let account = acc_map_read_guard.acc_map.get(&utxo.owner).unwrap(); @@ -481,7 +481,7 @@ impl NodeCore { let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); Ok(( - SignedTransaction::from_transaction_body(transaction_body, key_to_sign_transaction), + Transaction::new(transaction_body, key_to_sign_transaction), utxo_hashes, )) } @@ -492,7 +492,7 @@ impl NodeCore { commitments_in: Vec<[u8; 32]>, number_to_send: usize, receiver: AccountAddress, - ) -> Result<(SignedTransaction, Vec<[u8; 32]>, Vec<[u8; 32]>), ExecutionFailureKind> { + ) -> Result<(Transaction, Vec<[u8; 32]>, Vec<[u8; 32]>), ExecutionFailureKind> { let acc_map_read_guard = self.storage.read().await; let account = acc_map_read_guard.acc_map.get(&utxos[0].owner).unwrap(); @@ -627,7 +627,7 @@ impl NodeCore { let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); Ok(( - SignedTransaction::from_transaction_body(transaction_body, key_to_sign_transaction), + Transaction::new(transaction_body, key_to_sign_transaction), utxo_hashes_receiver, utxo_hashes_not_spent, )) @@ -638,7 +638,7 @@ impl NodeCore { acc: AccountAddress, balance: u64, receivers: Vec<(u128, AccountAddress)>, - ) -> Result<(SignedTransaction, Vec<(AccountAddress, [u8; 32])>), ExecutionFailureKind> { + ) -> Result<(Transaction, Vec<(AccountAddress, [u8; 32])>), ExecutionFailureKind> { let acc_map_read_guard = self.storage.read().await; let account = acc_map_read_guard.acc_map.get(&acc).unwrap(); @@ -757,7 +757,7 @@ impl NodeCore { let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); Ok(( - SignedTransaction::from_transaction_body(transaction_body, key_to_sign_transaction), + Transaction::new(transaction_body, key_to_sign_transaction), utxo_hashes, )) } @@ -767,7 +767,7 @@ impl NodeCore { utxo: UTXO, comm_gen_hash: [u8; 32], receivers: Vec<(u128, AccountAddress)>, - ) -> Result { + ) -> Result { let acc_map_read_guard = self.storage.read().await; let commitment_in = acc_map_read_guard @@ -847,10 +847,7 @@ impl NodeCore { let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); - Ok(SignedTransaction::from_transaction_body( - transaction_body, - key_to_sign_transaction, - )) + Ok(Transaction::new(transaction_body, key_to_sign_transaction)) } pub async fn send_private_mint_tx( @@ -1369,7 +1366,7 @@ impl NodeCore { commitment_in: [u8; 32], receivers: Vec<(u128, AccountAddress)>, visibility_list: [bool; 3], - ) -> Result<(SignedTransaction, Vec<(AccountAddress, [u8; 32])>), ExecutionFailureKind> { + ) -> Result<(Transaction, Vec<(AccountAddress, [u8; 32])>), ExecutionFailureKind> { let acc_map_read_guard = self.storage.read().await; let account = acc_map_read_guard.acc_map.get(&utxo.owner).unwrap(); @@ -1490,7 +1487,7 @@ impl NodeCore { let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); Ok(( - SignedTransaction::from_transaction_body(transaction_body, key_to_sign_transaction), + Transaction::new(transaction_body, key_to_sign_transaction), utxo_hashes, )) } diff --git a/node_core/src/sequencer_client/json.rs b/node_core/src/sequencer_client/json.rs index 9275ca5..df94ca2 100644 --- a/node_core/src/sequencer_client/json.rs +++ b/node_core/src/sequencer_client/json.rs @@ -1,11 +1,11 @@ -use common::transaction::SignedTransaction; +use common::transaction::Transaction; use serde::{Deserialize, Serialize}; //Requests #[derive(Serialize, Deserialize, Debug)] pub struct SendTxRequest { - pub transaction: SignedTransaction, + pub transaction: Transaction, ///UTXO Commitment Root, Pub Tx Root pub tx_roots: [[u8; 32]; 2], } diff --git a/node_core/src/sequencer_client/mod.rs b/node_core/src/sequencer_client/mod.rs index 95a3378..7accd5f 100644 --- a/node_core/src/sequencer_client/mod.rs +++ b/node_core/src/sequencer_client/mod.rs @@ -4,7 +4,7 @@ use common::rpc_primitives::requests::{ GetBlockDataRequest, GetBlockDataResponse, GetGenesisIdRequest, GetGenesisIdResponse, RegisterAccountRequest, RegisterAccountResponse, }; -use common::transaction::SignedTransaction; +use common::transaction::Transaction; use common::{SequencerClientError, SequencerRpcError}; use json::{SendTxRequest, SendTxResponse, SequencerRpcRequest, SequencerRpcResponse}; use k256::elliptic_curve::group::GroupEncoding; @@ -72,7 +72,7 @@ impl SequencerClient { pub async fn send_tx( &self, - transaction: SignedTransaction, + transaction: Transaction, tx_roots: [[u8; 32]; 2], ) -> Result { let tx_req = SendTxRequest { diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 174763e..3a74119 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -5,7 +5,7 @@ use common::{ block::{Block, HashableBlockData}, merkle_tree_public::TreeHashType, nullifier::UTXONullifier, - transaction::{AuthenticatedTransaction, SignedTransaction, TransactionBody, TxKind}, + transaction::{AuthenticatedTransaction, Transaction, TransactionBody, TxKind}, utxo_commitment::UTXOCommitment, }; use config::SequencerConfig; @@ -72,7 +72,7 @@ impl SequencerCore { pub fn transaction_pre_check( &mut self, - tx: SignedTransaction, + tx: Transaction, tx_roots: [[u8; 32]; 2], ) -> Result { let tx = tx @@ -174,17 +174,17 @@ impl SequencerCore { pub fn push_tx_into_mempool_pre_check( &mut self, - signed_tx: SignedTransaction, + transaction: Transaction, tx_roots: [[u8; 32]; 2], ) -> Result<(), TransactionMalformationErrorKind> { let mempool_size = self.mempool.len(); if mempool_size >= self.sequencer_config.max_num_tx_in_block { return Err(TransactionMalformationErrorKind::MempoolFullForRound { - tx: signed_tx.body.hash(), + tx: transaction.body.hash(), }); } - let authenticated_tx = self.transaction_pre_check(signed_tx, tx_roots)?; + let authenticated_tx = self.transaction_pre_check(transaction, tx_roots)?; self.mempool.push_item(authenticated_tx.into()); @@ -213,7 +213,7 @@ impl SequencerCore { }); } - self.store.pub_tx_store.add_tx(tx.tx.as_signed()); + self.store.pub_tx_store.add_tx(tx.tx.as_transaction()); Ok(()) } @@ -248,7 +248,7 @@ impl SequencerCore { prev_block_id: self.chain_height, transactions: transactions .into_iter() - .map(|tx_mem| tx_mem.tx.as_signed().clone()) + .map(|tx_mem| tx_mem.tx.as_transaction().clone()) .collect(), data: vec![], prev_block_hash, @@ -269,7 +269,7 @@ mod tests { use super::*; use std::path::PathBuf; - use common::transaction::{SignaturePrivateKey, SignedTransaction, TransactionBody, TxKind}; + use common::transaction::{SignaturePrivateKey, Transaction, TransactionBody, TxKind}; use rand::Rng; use secp256k1_zkp::Tweak; use transaction_mempool::MempoolTransaction; @@ -295,7 +295,7 @@ mod tests { nullifier_created_hashes: Vec<[u8; 32]>, utxo_commitments_spent_hashes: Vec<[u8; 32]>, utxo_commitments_created_hashes: Vec<[u8; 32]>, - ) -> SignedTransaction { + ) -> Transaction { let mut rng = rand::thread_rng(); let body = TransactionBody { @@ -314,7 +314,7 @@ mod tests { sc_addr: "sc_addr".to_string(), state_changes: (serde_json::Value::Null, 0), }; - SignedTransaction::from_transaction_body(body, SignaturePrivateKey::random(&mut rng)) + Transaction::new(body, SignaturePrivateKey::random(&mut rng)) } fn common_setup(sequencer: &mut SequencerCore) {