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 {
let field_bytes = FieldBytes::from_slice(&self.pub_account_signing_key);
// TODO: remove unwrap

View File

@ -8,7 +8,7 @@ pub trait TreeLeavItem {
impl TreeLeavItem for Transaction {
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 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
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Transaction {
pub body: TransactionBody,
body: TransactionBody,
signature: TransactionSignature,
public_key: VerifyingKey,
}
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 {
let hash = body.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> {
let hash = self.body.hash();
@ -264,6 +268,11 @@ impl Transaction {
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.
@ -276,14 +285,12 @@ pub struct AuthenticatedTransaction {
}
impl AuthenticatedTransaction {
pub fn as_transaction(&self) -> &Transaction {
/// Returns the underlying transaction
pub fn transaction(&self) -> &Transaction {
&self.transaction
}
pub fn body(&self) -> &TransactionBody {
&self.transaction.body
}
/// Returns the precomputed hash over the body of the transaction
pub fn hash(&self) -> &TransactionHash {
&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]
fn test_transaction_hash_is_sha256_of_json_bytes() {
let body = test_transaction_body();
@ -349,20 +363,26 @@ mod tests {
}
#[test]
fn test_into_authenticated_succeeds_for_valid_signature() {
fn test_transaction_body_getter() {
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 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 signature = authenticated_tx.as_transaction().signature;
let signature = authenticated_tx.transaction().signature;
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
.as_transaction()
.transaction()
.public_key
.verify(hash, &signature)
.is_ok());
@ -380,9 +400,24 @@ mod tests {
this.signature = private_key.sign(b"deadbeef");
this
};
matches!(
transaction.into_authenticated(),
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;
for tx in &block.transactions {
if !tx.body.execution_input.is_empty() {
let public_action = serde_json::from_slice::<ActionData>(&tx.body.execution_input);
if !tx.body().execution_input.is_empty() {
let public_action =
serde_json::from_slice::<ActionData>(&tx.body().execution_input);
if let Ok(public_action) = public_action {
match public_action {
@ -162,7 +163,7 @@ impl NodeChainStore {
}
self.utxo_commitments_store.add_tx_multiple(
tx.body
tx.body()
.utxo_commitments_created_hashes
.clone()
.into_iter()
@ -170,17 +171,17 @@ impl NodeChainStore {
.collect(),
);
for nullifier in tx.body.nullifier_created_hashes.iter() {
for nullifier in tx.body().nullifier_created_hashes.iter() {
self.nullifier_store.insert(UTXONullifier {
utxo_hash: *nullifier,
});
}
if !tx.body.encoded_data.is_empty() {
if !tx.body().encoded_data.is_empty() {
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 nonce =
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 (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 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();
info!("Mint utxo proof spent {timedelta:?} milliseconds");
@ -888,10 +888,10 @@ impl NodeCore {
let (tx, utxo_hashes) = self
.mint_utxo_multiple_assets_private(acc, amount, number_of_assets)
.await?;
tx.body.log();
tx.body().log();
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();
info!("Mint utxo proof spent {timedelta:?} milliseconds");
@ -961,7 +961,7 @@ impl NodeCore {
let (tx, utxo_hashes) = self
.transfer_utxo_private(utxo, comm_hash, receivers)
.await?;
tx.body.log();
tx.body().log();
let point_after_prove = std::time::Instant::now();
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
.transfer_utxo_multiple_assets_private(utxos, comm_hashes, number_to_send, receiver)
.await?;
tx.body.log();
tx.body().log();
let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis();
@ -1013,7 +1013,7 @@ impl NodeCore {
let (tx, utxo_hashes) = self
.transfer_balance_shielded(acc, amount, receivers)
.await?;
tx.body.log();
tx.body().log();
let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis();
@ -1038,7 +1038,7 @@ impl NodeCore {
let tx = self
.transfer_utxo_deshielded(utxo, comm_gen_hash, receivers)
.await?;
tx.body.log();
tx.body().log();
let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis();
@ -1507,13 +1507,13 @@ impl NodeCore {
let (tx, utxo_hashes) = self
.split_utxo(utxo, comm_hash, receivers, visibility_list)
.await?;
tx.body.log();
tx.body().log();
let point_after_prove = std::time::Instant::now();
let timedelta = (point_after_prove - point_before_prove).as_millis();
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((
self.sequencer_client.send_tx(tx, tx_roots).await?,

View File

@ -303,46 +303,46 @@ impl JsonHandler {
ShowTransactionResponse {
hash: req.tx_hash,
tx_kind: tx.body.tx_kind,
tx_kind: tx.body().tx_kind,
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()
} else {
"".to_string()
},
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()
} else {
"".to_string()
},
utxo_commitments_created_hashes: tx
.body
.body()
.utxo_commitments_created_hashes
.iter()
.map(|val| hex::encode(val.clone()))
.collect::<Vec<_>>(),
utxo_commitments_spent_hashes: tx
.body
.body()
.utxo_commitments_spent_hashes
.iter()
.map(|val| hex::encode(val.clone()))
.collect::<Vec<_>>(),
utxo_nullifiers_created_hashes: tx
.body
.body()
.nullifier_created_hashes
.iter()
.map(|val| hex::encode(val.clone()))
.collect::<Vec<_>>(),
encoded_data: tx
.body
.body()
.encoded_data
.iter()
.map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone())))
.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 mempool::MemPool;
use mempool_transaction::MempoolTransaction;
use sequencer_store::{accounts_store::AccountPublicData, SequecerChainStore};
use serde::{Deserialize, Serialize};
use transaction_mempool::MempoolTransaction;
pub mod config;
pub mod mempool_transaction;
pub mod sequencer_store;
pub mod transaction_mempool;
pub struct SequencerCore {
pub store: SequecerChainStore,
@ -86,7 +86,7 @@ impl SequencerCore {
ref utxo_commitments_created_hashes,
ref nullifier_created_hashes,
..
} = tx.body();
} = tx.transaction().body();
let tx_hash = *tx.hash();
@ -180,7 +180,7 @@ impl SequencerCore {
let mempool_size = self.mempool.len();
if mempool_size >= self.sequencer_config.max_num_tx_in_block {
return Err(TransactionMalformationErrorKind::MempoolFullForRound {
tx: transaction.body.hash(),
tx: transaction.body().hash(),
});
}
@ -193,13 +193,13 @@ impl SequencerCore {
fn execute_check_transaction_on_state(
&mut self,
tx: &MempoolTransaction,
mempool_tx: &MempoolTransaction,
) -> Result<(), TransactionMalformationErrorKind> {
let TransactionBody {
ref utxo_commitments_created_hashes,
ref nullifier_created_hashes,
..
} = tx.tx.body();
} = mempool_tx.auth_tx.transaction().body();
for utxo_comm in utxo_commitments_created_hashes {
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(())
}
@ -248,7 +250,7 @@ impl SequencerCore {
prev_block_id: self.chain_height,
transactions: transactions
.into_iter()
.map(|tx_mem| tx_mem.tx.as_transaction().clone())
.map(|tx_mem| tx_mem.auth_tx.transaction().clone())
.collect(),
data: vec![],
prev_block_hash,
@ -270,9 +272,9 @@ mod tests {
use std::path::PathBuf;
use common::transaction::{SignaturePrivateKey, Transaction, TransactionBody, TxKind};
use mempool_transaction::MempoolTransaction;
use rand::Rng;
use secp256k1_zkp::Tweak;
use transaction_mempool::MempoolTransaction;
fn setup_sequencer_config() -> SequencerConfig {
let mut rng = rand::thread_rng();
@ -320,7 +322,7 @@ mod tests {
fn common_setup(sequencer: &mut SequencerCore) {
let tx = create_dummy_transaction(vec![[9; 32]], vec![[7; 32]], vec![[8; 32]]);
let tx_mempool = MempoolTransaction {
tx: tx.into_authenticated().unwrap(),
auth_tx: tx.into_authenticated().unwrap(),
};
sequencer.mempool.push_item(tx_mempool);
@ -377,7 +379,7 @@ mod tests {
// Fill the mempool
let dummy_tx = MempoolTransaction {
tx: tx.clone().into_authenticated().unwrap(),
auth_tx: tx.clone().into_authenticated().unwrap(),
};
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_mempool = MempoolTransaction {
tx: tx.into_authenticated().unwrap(),
auth_tx: tx.into_authenticated().unwrap(),
};
sequencer.mempool.push_item(tx_mempool);

View File

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