add getter for transaction body and test it

This commit is contained in:
Sergio Chouhy 2025-07-16 11:54:11 -03:00
parent e79c30f765
commit c60c7235c1
8 changed files with 94 additions and 55 deletions

View File

@ -58,6 +58,7 @@ impl AddressKeyHolder {
} }
} }
/// Returns the signing key for public transaction signatures
pub fn get_pub_account_signing_key(&self) -> SigningKey { pub fn get_pub_account_signing_key(&self) -> SigningKey {
let field_bytes = FieldBytes::from_slice(&self.pub_account_signing_key); let field_bytes = FieldBytes::from_slice(&self.pub_account_signing_key);
// TODO: remove unwrap // TODO: remove unwrap

View File

@ -8,7 +8,7 @@ pub trait TreeLeavItem {
impl TreeLeavItem for Transaction { impl TreeLeavItem for Transaction {
fn hash(&self) -> TreeHashType { fn hash(&self) -> TreeHashType {
self.body.hash() self.body().hash()
} }
} }

View File

@ -231,16 +231,18 @@ pub type TransactionSignature = Signature;
pub type SignaturePublicKey = VerifyingKey; pub type SignaturePublicKey = VerifyingKey;
pub type SignaturePrivateKey = SigningKey; pub type SignaturePrivateKey = SigningKey;
/// A transaction with a signature. /// A container for a transaction body with a signature.
/// Meant to be sent through the network to the sequencer /// Meant to be sent through the network to the sequencer
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Transaction { pub struct Transaction {
pub body: TransactionBody, body: TransactionBody,
signature: TransactionSignature, signature: TransactionSignature,
public_key: VerifyingKey, public_key: VerifyingKey,
} }
impl Transaction { impl Transaction {
/// Returns a new transaction signed with the provided `private_key`.
/// The signature is generated over the hash of the body as computed by `body.hash()`
pub fn new(body: TransactionBody, private_key: SigningKey) -> Transaction { pub fn new(body: TransactionBody, private_key: SigningKey) -> Transaction {
let hash = body.hash(); let hash = body.hash();
let signature: TransactionSignature = private_key.sign(&hash); let signature: TransactionSignature = private_key.sign(&hash);
@ -252,6 +254,8 @@ impl Transaction {
} }
} }
/// Converts the transaction into an `AuthenticatedTransaction` by verifying its signature.
/// Returns an error if the signature verification fails.
pub fn into_authenticated(self) -> Result<AuthenticatedTransaction, TransactionSignatureError> { pub fn into_authenticated(self) -> Result<AuthenticatedTransaction, TransactionSignatureError> {
let hash = self.body.hash(); let hash = self.body.hash();
@ -264,6 +268,11 @@ impl Transaction {
transaction: self, transaction: self,
}) })
} }
/// Returns the body of the transaction
pub fn body(&self) -> &TransactionBody {
&self.body
}
} }
/// A transaction with a valid signature over the hash of its body. /// A transaction with a valid signature over the hash of its body.
@ -276,14 +285,12 @@ pub struct AuthenticatedTransaction {
} }
impl AuthenticatedTransaction { impl AuthenticatedTransaction {
pub fn as_transaction(&self) -> &Transaction { /// Returns the underlying transaction
pub fn transaction(&self) -> &Transaction {
&self.transaction &self.transaction
} }
pub fn body(&self) -> &TransactionBody { /// Returns the precomputed hash over the body of the transaction
&self.transaction.body
}
pub fn hash(&self) -> &TransactionHash { pub fn hash(&self) -> &TransactionHash {
&self.hash &self.hash
} }
@ -320,6 +327,13 @@ mod tests {
} }
} }
fn test_transaction() -> Transaction {
let body = test_transaction_body();
let key_bytes = FieldBytes::from_slice(&[37; 32]);
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
Transaction::new(body, private_key)
}
#[test] #[test]
fn test_transaction_hash_is_sha256_of_json_bytes() { fn test_transaction_hash_is_sha256_of_json_bytes() {
let body = test_transaction_body(); let body = test_transaction_body();
@ -349,20 +363,26 @@ mod tests {
} }
#[test] #[test]
fn test_into_authenticated_succeeds_for_valid_signature() { fn test_transaction_body_getter() {
let body = test_transaction_body(); let body = test_transaction_body();
let key_bytes = FieldBytes::from_slice(&[37; 32]); let key_bytes = FieldBytes::from_slice(&[37; 32]);
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
let transaction = Transaction::new(body, private_key.clone()); let transaction = Transaction::new(body.clone(), private_key.clone());
assert_eq!(transaction.body(), &body);
}
#[test]
fn test_into_authenticated_succeeds_for_valid_signature() {
let transaction = test_transaction();
let authenticated_tx = transaction.clone().into_authenticated().unwrap(); let authenticated_tx = transaction.clone().into_authenticated().unwrap();
let signature = authenticated_tx.as_transaction().signature; let signature = authenticated_tx.transaction().signature;
let hash = authenticated_tx.hash(); let hash = authenticated_tx.hash();
assert_eq!(authenticated_tx.as_transaction(), &transaction);
assert_eq!(hash, &transaction.body.hash());
assert_eq!(authenticated_tx.transaction(), &transaction);
assert_eq!(hash, &transaction.body.hash());
assert!(authenticated_tx assert!(authenticated_tx
.as_transaction() .transaction()
.public_key .public_key
.verify(hash, &signature) .verify(hash, &signature)
.is_ok()); .is_ok());
@ -380,9 +400,24 @@ mod tests {
this.signature = private_key.sign(b"deadbeef"); this.signature = private_key.sign(b"deadbeef");
this this
}; };
matches!( matches!(
transaction.into_authenticated(), transaction.into_authenticated(),
Err(TransactionSignatureError::InvalidSignature) Err(TransactionSignatureError::InvalidSignature)
); );
} }
#[test]
fn test_authenticated_transaction_getter() {
let transaction = test_transaction();
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
assert_eq!(authenticated_tx.transaction(), &transaction);
}
#[test]
fn test_authenticated_transaction_hash_getter() {
let transaction = test_transaction();
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
assert_eq!(authenticated_tx.hash(), &transaction.body.hash());
}
} }

View File

@ -127,8 +127,9 @@ impl NodeChainStore {
let block_id = block.block_id; let block_id = block.block_id;
for tx in &block.transactions { for tx in &block.transactions {
if !tx.body.execution_input.is_empty() { if !tx.body().execution_input.is_empty() {
let public_action = serde_json::from_slice::<ActionData>(&tx.body.execution_input); let public_action =
serde_json::from_slice::<ActionData>(&tx.body().execution_input);
if let Ok(public_action) = public_action { if let Ok(public_action) = public_action {
match public_action { match public_action {
@ -162,7 +163,7 @@ impl NodeChainStore {
} }
self.utxo_commitments_store.add_tx_multiple( self.utxo_commitments_store.add_tx_multiple(
tx.body tx.body()
.utxo_commitments_created_hashes .utxo_commitments_created_hashes
.clone() .clone()
.into_iter() .into_iter()
@ -170,17 +171,17 @@ impl NodeChainStore {
.collect(), .collect(),
); );
for nullifier in tx.body.nullifier_created_hashes.iter() { for nullifier in tx.body().nullifier_created_hashes.iter() {
self.nullifier_store.insert(UTXONullifier { self.nullifier_store.insert(UTXONullifier {
utxo_hash: *nullifier, utxo_hash: *nullifier,
}); });
} }
if !tx.body.encoded_data.is_empty() { if !tx.body().encoded_data.is_empty() {
let ephemeral_public_key_sender = let ephemeral_public_key_sender =
serde_json::from_slice::<AffinePoint>(&tx.body.ephemeral_pub_key)?; serde_json::from_slice::<AffinePoint>(&tx.body().ephemeral_pub_key)?;
for (ciphertext, nonce, tag) in tx.body.encoded_data.clone() { for (ciphertext, nonce, tag) in tx.body().encoded_data.clone() {
let slice = nonce.as_slice(); let slice = nonce.as_slice();
let nonce = let nonce =
accounts::key_management::constants_types::Nonce::clone_from_slice(slice); accounts::key_management::constants_types::Nonce::clone_from_slice(slice);

View File

@ -860,10 +860,10 @@ impl NodeCore {
let point_before_prove = std::time::Instant::now(); let point_before_prove = std::time::Instant::now();
let (tx, utxo_hash) = self.mint_utxo_private(acc, amount).await?; let (tx, utxo_hash) = self.mint_utxo_private(acc, amount).await?;
tx.body.log(); tx.body().log();
let point_after_prove = std::time::Instant::now(); let point_after_prove = std::time::Instant::now();
let commitment_generated_hash = tx.body.utxo_commitments_created_hashes[0]; let commitment_generated_hash = tx.body().utxo_commitments_created_hashes[0];
let timedelta = (point_after_prove - point_before_prove).as_millis(); let timedelta = (point_after_prove - point_before_prove).as_millis();
info!("Mint utxo proof spent {timedelta:?} milliseconds"); info!("Mint utxo proof spent {timedelta:?} milliseconds");
@ -888,10 +888,10 @@ impl NodeCore {
let (tx, utxo_hashes) = self let (tx, utxo_hashes) = self
.mint_utxo_multiple_assets_private(acc, amount, number_of_assets) .mint_utxo_multiple_assets_private(acc, amount, number_of_assets)
.await?; .await?;
tx.body.log(); tx.body().log();
let point_after_prove = std::time::Instant::now(); let point_after_prove = std::time::Instant::now();
let commitment_generated_hashes = tx.body.utxo_commitments_created_hashes.clone(); let commitment_generated_hashes = tx.body().utxo_commitments_created_hashes.clone();
let timedelta = (point_after_prove - point_before_prove).as_millis(); let timedelta = (point_after_prove - point_before_prove).as_millis();
info!("Mint utxo proof spent {timedelta:?} milliseconds"); info!("Mint utxo proof spent {timedelta:?} milliseconds");
@ -961,7 +961,7 @@ impl NodeCore {
let (tx, utxo_hashes) = self let (tx, utxo_hashes) = self
.transfer_utxo_private(utxo, comm_hash, receivers) .transfer_utxo_private(utxo, comm_hash, receivers)
.await?; .await?;
tx.body.log(); tx.body().log();
let point_after_prove = std::time::Instant::now(); let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis(); let timedelta = (point_after_prove - point_before_prove).as_millis();
@ -987,7 +987,7 @@ impl NodeCore {
let (tx, utxo_hashes_received, utxo_hashes_not_spent) = self let (tx, utxo_hashes_received, utxo_hashes_not_spent) = self
.transfer_utxo_multiple_assets_private(utxos, comm_hashes, number_to_send, receiver) .transfer_utxo_multiple_assets_private(utxos, comm_hashes, number_to_send, receiver)
.await?; .await?;
tx.body.log(); tx.body().log();
let point_after_prove = std::time::Instant::now(); let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis(); let timedelta = (point_after_prove - point_before_prove).as_millis();
@ -1013,7 +1013,7 @@ impl NodeCore {
let (tx, utxo_hashes) = self let (tx, utxo_hashes) = self
.transfer_balance_shielded(acc, amount, receivers) .transfer_balance_shielded(acc, amount, receivers)
.await?; .await?;
tx.body.log(); tx.body().log();
let point_after_prove = std::time::Instant::now(); let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis(); let timedelta = (point_after_prove - point_before_prove).as_millis();
@ -1038,7 +1038,7 @@ impl NodeCore {
let tx = self let tx = self
.transfer_utxo_deshielded(utxo, comm_gen_hash, receivers) .transfer_utxo_deshielded(utxo, comm_gen_hash, receivers)
.await?; .await?;
tx.body.log(); tx.body().log();
let point_after_prove = std::time::Instant::now(); let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis(); let timedelta = (point_after_prove - point_before_prove).as_millis();
@ -1507,13 +1507,13 @@ impl NodeCore {
let (tx, utxo_hashes) = self let (tx, utxo_hashes) = self
.split_utxo(utxo, comm_hash, receivers, visibility_list) .split_utxo(utxo, comm_hash, receivers, visibility_list)
.await?; .await?;
tx.body.log(); tx.body().log();
let point_after_prove = std::time::Instant::now(); let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis(); let timedelta = (point_after_prove - point_before_prove).as_millis();
info!("Send private utxo proof spent {timedelta:?} milliseconds"); info!("Send private utxo proof spent {timedelta:?} milliseconds");
let commitments = tx.body.utxo_commitments_created_hashes.clone(); let commitments = tx.body().utxo_commitments_created_hashes.clone();
Ok(( Ok((
self.sequencer_client.send_tx(tx, tx_roots).await?, self.sequencer_client.send_tx(tx, tx_roots).await?,

View File

@ -303,46 +303,46 @@ impl JsonHandler {
ShowTransactionResponse { ShowTransactionResponse {
hash: req.tx_hash, hash: req.tx_hash,
tx_kind: tx.body.tx_kind, tx_kind: tx.body().tx_kind,
public_input: if let Ok(action) = public_input: if let Ok(action) =
serde_json::from_slice::<ActionData>(&tx.body.execution_input) serde_json::from_slice::<ActionData>(&tx.body().execution_input)
{ {
action.into_hexed_print() action.into_hexed_print()
} else { } else {
"".to_string() "".to_string()
}, },
public_output: if let Ok(action) = public_output: if let Ok(action) =
serde_json::from_slice::<ActionData>(&tx.body.execution_output) serde_json::from_slice::<ActionData>(&tx.body().execution_output)
{ {
action.into_hexed_print() action.into_hexed_print()
} else { } else {
"".to_string() "".to_string()
}, },
utxo_commitments_created_hashes: tx utxo_commitments_created_hashes: tx
.body .body()
.utxo_commitments_created_hashes .utxo_commitments_created_hashes
.iter() .iter()
.map(|val| hex::encode(val.clone())) .map(|val| hex::encode(val.clone()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
utxo_commitments_spent_hashes: tx utxo_commitments_spent_hashes: tx
.body .body()
.utxo_commitments_spent_hashes .utxo_commitments_spent_hashes
.iter() .iter()
.map(|val| hex::encode(val.clone())) .map(|val| hex::encode(val.clone()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
utxo_nullifiers_created_hashes: tx utxo_nullifiers_created_hashes: tx
.body .body()
.nullifier_created_hashes .nullifier_created_hashes
.iter() .iter()
.map(|val| hex::encode(val.clone())) .map(|val| hex::encode(val.clone()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
encoded_data: tx encoded_data: tx
.body .body()
.encoded_data .encoded_data
.iter() .iter()
.map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone()))) .map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone())))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
ephemeral_pub_key: hex::encode(tx.body.ephemeral_pub_key.clone()), ephemeral_pub_key: hex::encode(tx.body().ephemeral_pub_key.clone()),
} }
} }
}; };

View File

@ -10,13 +10,13 @@ use common::{
}; };
use config::SequencerConfig; use config::SequencerConfig;
use mempool::MemPool; use mempool::MemPool;
use mempool_transaction::MempoolTransaction;
use sequencer_store::{accounts_store::AccountPublicData, SequecerChainStore}; use sequencer_store::{accounts_store::AccountPublicData, SequecerChainStore};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use transaction_mempool::MempoolTransaction;
pub mod config; pub mod config;
pub mod mempool_transaction;
pub mod sequencer_store; pub mod sequencer_store;
pub mod transaction_mempool;
pub struct SequencerCore { pub struct SequencerCore {
pub store: SequecerChainStore, pub store: SequecerChainStore,
@ -86,7 +86,7 @@ impl SequencerCore {
ref utxo_commitments_created_hashes, ref utxo_commitments_created_hashes,
ref nullifier_created_hashes, ref nullifier_created_hashes,
.. ..
} = tx.body(); } = tx.transaction().body();
let tx_hash = *tx.hash(); let tx_hash = *tx.hash();
@ -180,7 +180,7 @@ impl SequencerCore {
let mempool_size = self.mempool.len(); let mempool_size = self.mempool.len();
if mempool_size >= self.sequencer_config.max_num_tx_in_block { if mempool_size >= self.sequencer_config.max_num_tx_in_block {
return Err(TransactionMalformationErrorKind::MempoolFullForRound { return Err(TransactionMalformationErrorKind::MempoolFullForRound {
tx: transaction.body.hash(), tx: transaction.body().hash(),
}); });
} }
@ -193,13 +193,13 @@ impl SequencerCore {
fn execute_check_transaction_on_state( fn execute_check_transaction_on_state(
&mut self, &mut self,
tx: &MempoolTransaction, mempool_tx: &MempoolTransaction,
) -> Result<(), TransactionMalformationErrorKind> { ) -> Result<(), TransactionMalformationErrorKind> {
let TransactionBody { let TransactionBody {
ref utxo_commitments_created_hashes, ref utxo_commitments_created_hashes,
ref nullifier_created_hashes, ref nullifier_created_hashes,
.. ..
} = tx.tx.body(); } = mempool_tx.auth_tx.transaction().body();
for utxo_comm in utxo_commitments_created_hashes { for utxo_comm in utxo_commitments_created_hashes {
self.store self.store
@ -213,7 +213,9 @@ impl SequencerCore {
}); });
} }
self.store.pub_tx_store.add_tx(tx.tx.as_transaction()); self.store
.pub_tx_store
.add_tx(mempool_tx.auth_tx.transaction());
Ok(()) Ok(())
} }
@ -248,7 +250,7 @@ impl SequencerCore {
prev_block_id: self.chain_height, prev_block_id: self.chain_height,
transactions: transactions transactions: transactions
.into_iter() .into_iter()
.map(|tx_mem| tx_mem.tx.as_transaction().clone()) .map(|tx_mem| tx_mem.auth_tx.transaction().clone())
.collect(), .collect(),
data: vec![], data: vec![],
prev_block_hash, prev_block_hash,
@ -270,9 +272,9 @@ mod tests {
use std::path::PathBuf; use std::path::PathBuf;
use common::transaction::{SignaturePrivateKey, Transaction, TransactionBody, TxKind}; use common::transaction::{SignaturePrivateKey, Transaction, TransactionBody, TxKind};
use mempool_transaction::MempoolTransaction;
use rand::Rng; use rand::Rng;
use secp256k1_zkp::Tweak; use secp256k1_zkp::Tweak;
use transaction_mempool::MempoolTransaction;
fn setup_sequencer_config() -> SequencerConfig { fn setup_sequencer_config() -> SequencerConfig {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -320,7 +322,7 @@ mod tests {
fn common_setup(sequencer: &mut SequencerCore) { fn common_setup(sequencer: &mut SequencerCore) {
let tx = create_dummy_transaction(vec![[9; 32]], vec![[7; 32]], vec![[8; 32]]); let tx = create_dummy_transaction(vec![[9; 32]], vec![[7; 32]], vec![[8; 32]]);
let tx_mempool = MempoolTransaction { let tx_mempool = MempoolTransaction {
tx: tx.into_authenticated().unwrap(), auth_tx: tx.into_authenticated().unwrap(),
}; };
sequencer.mempool.push_item(tx_mempool); sequencer.mempool.push_item(tx_mempool);
@ -377,7 +379,7 @@ mod tests {
// Fill the mempool // Fill the mempool
let dummy_tx = MempoolTransaction { let dummy_tx = MempoolTransaction {
tx: tx.clone().into_authenticated().unwrap(), auth_tx: tx.clone().into_authenticated().unwrap(),
}; };
sequencer.mempool.push_item(dummy_tx); sequencer.mempool.push_item(dummy_tx);
@ -411,7 +413,7 @@ mod tests {
let tx = create_dummy_transaction(vec![[94; 32]], vec![[7; 32]], vec![[8; 32]]); let tx = create_dummy_transaction(vec![[94; 32]], vec![[7; 32]], vec![[8; 32]]);
let tx_mempool = MempoolTransaction { let tx_mempool = MempoolTransaction {
tx: tx.into_authenticated().unwrap(), auth_tx: tx.into_authenticated().unwrap(),
}; };
sequencer.mempool.push_item(tx_mempool); sequencer.mempool.push_item(tx_mempool);

View File

@ -3,12 +3,12 @@ use mempool::mempoolitem::MemPoolItem;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub struct MempoolTransaction { pub struct MempoolTransaction {
pub tx: AuthenticatedTransaction, pub auth_tx: AuthenticatedTransaction,
} }
impl From<AuthenticatedTransaction> for MempoolTransaction { impl From<AuthenticatedTransaction> for MempoolTransaction {
fn from(value: AuthenticatedTransaction) -> Self { fn from(auth_tx: AuthenticatedTransaction) -> Self {
Self { tx: value } Self { auth_tx }
} }
} }
@ -16,6 +16,6 @@ impl MemPoolItem for MempoolTransaction {
type Identifier = TreeHashType; type Identifier = TreeHashType;
fn identifier(&self) -> Self::Identifier { fn identifier(&self) -> Self::Identifier {
*self.tx.hash() *self.auth_tx.hash()
} }
} }