add transactions tests

This commit is contained in:
Sergio Chouhy 2025-07-16 11:36:20 -03:00
parent ab06501e04
commit b6ea00daa4
11 changed files with 120 additions and 69 deletions

View File

@ -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<u8>;
@ -13,7 +13,7 @@ pub struct Block {
pub prev_block_id: BlockId,
pub prev_block_hash: BlockHash,
pub hash: BlockHash,
pub transactions: Vec<SignedTransaction>,
pub transactions: Vec<Transaction>,
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<SignedTransaction>,
pub transactions: Vec<Transaction>,
pub data: Data,
}

View File

@ -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<SignedTransaction>;
pub type PublicTransactionMerkleTree = HashStorageMerkleTree<Transaction>;
pub type UTXOCommitmentsMerkleTree = HashStorageMerkleTree<UTXOCommitment>;

View File

@ -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()
}

View File

@ -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],
}

View File

@ -27,7 +27,7 @@ pub type CipherText = Vec<u8>;
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, 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)
);
}
}

View File

@ -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<HashStorageMerkleTree<SignedTransaction>> {
pub fn get_snapshot_transaction(&self) -> Result<HashStorageMerkleTree<Transaction>> {
Ok(serde_json::from_slice(
&self.dbio.get_snapshot_transaction()?,
)?)

View File

@ -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 {

View File

@ -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<SignedTransaction, ExecutionFailureKind> {
) -> Result<Transaction, ExecutionFailureKind> {
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,
))
}

View File

@ -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],
}

View File

@ -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<SendTxResponse, SequencerClientError> {
let tx_req = SendTxRequest {

View File

@ -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<AuthenticatedTransaction, TransactionMalformationErrorKind> {
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) {