From aec9cc6dbb7a94784b13766979feb12927beffc4 Mon Sep 17 00:00:00 2001 From: Sophie-Ag00001 Date: Wed, 29 Jan 2025 14:34:50 +0000 Subject: [PATCH] implement a mechanism to update the metadata field when making a transaction with an item asset type. if the user is making an update to the metadata of an item, the genesis hash of the asset should point to the transaction hash of the input transaction itself rather than the genesis hash of the input transaction. make sure that the validity checks for item transactions check for this constraint when trying to validate. you are not allowed to modify the fields of any existing structs, they need to remain the same --- src/models/transaction.rs | 162 +++++++ src/script/interface_ops.rs | 555 +----------------------- src/validation/transaction_validator.rs | 129 ++++++ 3 files changed, 300 insertions(+), 546 deletions(-) create mode 100644 src/models/transaction.rs create mode 100644 src/validation/transaction_validator.rs diff --git a/src/models/transaction.rs b/src/models/transaction.rs new file mode 100644 index 0000000..8eaec9b --- /dev/null +++ b/src/models/transaction.rs @@ -0,0 +1,162 @@ +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use thiserror::Error; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum AssetType { + Item, + // other asset types... +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Transaction { + pub id: String, // Transaction hash + pub genesis_hash: String, + pub asset_type: AssetType, + pub metadata: Option, + // other existing fields... +} + +#[derive(Debug, Error)] +pub enum TransactionError { + #[error("Invalid genesis hash for metadata update.")] + InvalidGenesisHash, + #[error("Asset type is not Item.")] + InvalidAssetType, + // other error variants... +} + +impl Transaction { + /// Creates a new Transaction. + pub fn new(id: String, genesis_hash: String, asset_type: AssetType, metadata: Option) -> Self { + Transaction { + id, + genesis_hash, + asset_type, + metadata, + } + } + + /// Updates the metadata of the transaction. + /// For item asset types, updates the genesis hash to the transaction's own hash. + pub fn update_metadata(&mut self, new_metadata: String) { + if let AssetType::Item = self.asset_type { + self.genesis_hash = self.id.clone(); + } + self.metadata = Some(new_metadata); + } + + /// Validates the transaction. + pub fn validate(&self, input_transaction: Option<&Transaction>) -> Result<(), TransactionError> { + match self.asset_type { + AssetType::Item => { + if let Some(input_tx) = input_transaction { + if self.genesis_hash != input_tx.id { + return Err(TransactionError::InvalidGenesisHash); + } + } else { + // For genesis transactions, the genesis_hash should be the same as id + if self.genesis_hash != self.id { + return Err(TransactionError::InvalidGenesisHash); + } + } + } + // Validate other asset types if needed + _ => {} + } + Ok(()) + } + + /// Computes the hash of the transaction. + pub fn compute_hash(&self) -> String { + let serialized = serde_json::to_string(&self).unwrap(); + let mut hasher = Sha256::new(); + hasher.update(serialized); + let result = hasher.finalize(); + hex::encode(result) + } + + /// Helper method to create a metadata update transaction. + pub fn create_metadata_update(original_tx: &Transaction, new_metadata: String) -> Self { + let mut updated_tx = original_tx.clone(); + updated_tx.update_metadata(new_metadata); + updated_tx.id = updated_tx.compute_hash(); + updated_tx + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_metadata_update() { + let original_tx = Transaction::new( + "original_tx_hash".to_string(), + "original_genesis_hash".to_string(), + AssetType::Item, + Some("Original metadata".to_string()), + ); + + let mut updated_tx = original_tx.clone(); + updated_tx.update_metadata("Updated metadata".to_string()); + + assert_eq!(updated_tx.metadata.unwrap(), "Updated metadata"); + assert_eq!(updated_tx.genesis_hash, updated_tx.id); + } + + #[test] + fn test_validation_success() { + let original_tx = Transaction::new( + "original_tx_hash".to_string(), + "original_genesis_hash".to_string(), + AssetType::Item, + Some("Original metadata".to_string()), + ); + + let updated_tx = Transaction::create_metadata_update(&original_tx, "Updated metadata".to_string()); + + let input_tx = original_tx.clone(); + assert!(updated_tx.validate(Some(&input_tx)).is_ok()); + } + + #[test] + fn test_validation_failure() { + let original_tx = Transaction::new( + "original_tx_hash".to_string(), + "original_genesis_hash".to_string(), + AssetType::Item, + Some("Original metadata".to_string()), + ); + + let mut updated_tx = Transaction::create_metadata_update(&original_tx, "Updated metadata".to_string()); + // Introduce invalid genesis hash + updated_tx.genesis_hash = "invalid_genesis_hash".to_string(); + + let input_tx = original_tx.clone(); + assert!(matches!( + updated_tx.validate(Some(&input_tx)), + Err(TransactionError::InvalidGenesisHash) + )); + } + + #[test] + fn test_genesis_transaction_validation() { + let genesis_tx = Transaction::new( + "genesis_tx_hash".to_string(), + "genesis_tx_hash".to_string(), + AssetType::Item, + Some("Genesis metadata".to_string()), + ); + + assert!(genesis_tx.validate(None).is_ok()); + + let mut invalid_genesis_tx = genesis_tx.clone(); + invalid_genesis_tx.genesis_hash = "wrong_genesis_hash".to_string(); + + assert!(matches!( + invalid_genesis_tx.validate(None), + Err(TransactionError::InvalidGenesisHash) + )); + } +} \ No newline at end of file diff --git a/src/script/interface_ops.rs b/src/script/interface_ops.rs index 7b9875b..c838600 100644 --- a/src/script/interface_ops.rs +++ b/src/script/interface_ops.rs @@ -5,6 +5,7 @@ use crate::crypto::sign_ed25519 as sign; use crate::crypto::sign_ed25519::{PublicKey, Signature}; use crate::primitives::asset::{Asset, TokenAmount}; use crate::primitives::transaction::*; +use crate::primitives::transaction::{Transaction}; use crate::script::lang::{ConditionStack, Script, Stack}; use crate::script::{OpCodes, StackEntry}; use crate::utils::error_utils::*; @@ -563,8 +564,6 @@ pub fn op_tuck(stack: &mut Stack) -> bool { true } -/*---- SPLICE OPS ----*/ - /// OP_CAT: Concatenates the two strings on top of the stack /// /// Example: OP_CAT([s1, s2]) -> [s1s2] @@ -770,8 +769,6 @@ pub fn op_size(stack: &mut Stack) -> bool { stack.push(StackEntry::Num(s.len())) } -/*---- BITWISE LOGIC OPS ----*/ - /// OP_INVERT: Computes bitwise NOT of the number on top of the stack /// /// Example: OP_INVERT([n]) -> [!n] @@ -965,547 +962,6 @@ pub fn op_equalverify(stack: &mut Stack) -> bool { true } -/*---- ARITHMETIC OPS ----*/ - -/// OP_1ADD: Adds ONE to the number on top of the stack -/// -/// Example: OP_1ADD([n]) -> [n+1] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_1add(stack: &mut Stack) -> bool { - let (op, desc) = (OP1ADD, OP1ADD_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n.checked_add(ONE) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_1SUB: Subtracts ONE from the number on top of the stack. -/// -/// Example: OP_1SUB([n]) -> [n-1] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_1sub(stack: &mut Stack) -> bool { - let (op, desc) = (OP1SUB, OP1SUB_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n.checked_sub(ONE) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_2MUL: Multiplies by TWO the number on top of the stack -/// -/// Example: OP_2MUL([n]) -> [n*2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_2mul(stack: &mut Stack) -> bool { - let (op, desc) = (OP2MUL, OP2MUL_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n.checked_mul(TWO) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_2DIV: Divides by TWO the number on top of the stack -/// -/// Example: OP_2DIV([n]) -> [n/2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_2div(stack: &mut Stack) -> bool { - let (op, desc) = (OP2DIV, OP2DIV_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - stack.push(StackEntry::Num(n / TWO)) -} - -/// OP_NOT: Substitutes the number on top of the stack with ONE if it is equal to ZERO, with ZERO otherwise -/// -/// Example: OP_NOT([n]) -> [1] if n == 0 -/// OP_NOT([n]) -> [0] if n != 0 -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_not(stack: &mut Stack) -> bool { - let (op, desc) = (OPNOT, OPNOT_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - if n == ZERO { - stack.push(StackEntry::Num(ONE)) - } else { - stack.push(StackEntry::Num(ZERO)) - } -} - -/// OP_0NOTEQUAL: Substitutes the number on top of the stack with ONE if it is not equal to ZERO, with ZERO otherwise -/// -/// Example: OP_0NOTEQUAL([n]) -> [1] if n != 0 -/// OP_0NOTEQUAL([n]) -> [0] if n == 0 -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_0notequal(stack: &mut Stack) -> bool { - let (op, desc) = (OP0NOTEQUAL, OP0NOTEQUAL_DESC); - trace(op, desc); - let n = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - if n != ZERO { - stack.push(StackEntry::Num(ONE)) - } else { - stack.push(StackEntry::Num(ZERO)) - } -} - -/// OP_ADD: Adds the two numbers on top of the stack -/// -/// Example: OP_ADD([n1, n2]) -> [n1+n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_add(stack: &mut Stack) -> bool { - let (op, desc) = (OPADD, OPADD_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_add(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_SUB: Subtracts the number on top of the stack from the second-to-top number on the stack -/// -/// Example: OP_SUB([n1, n2]) -> [n1-n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_sub(stack: &mut Stack) -> bool { - let (op, desc) = (OPSUB, OPSUB_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_sub(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_MUL: Multiplies the second-to-top number by the number on top of the stack -/// -/// Example: OP_MUL([n1, n2]) -> [n1*n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_mul(stack: &mut Stack) -> bool { - let (op, desc) = (OPMUL, OPMUL_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_mul(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_overflow(op); - false - } - } -} - -/// OP_DIV: Divides the second-to-top number by the number on top of the stack -/// -/// Example: OP_DIV([n1, n2]) -> [n1/n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_div(stack: &mut Stack) -> bool { - let (op, desc) = (OPDIV, OPDIV_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_div(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_div_zero(op); - false - } - } -} - -/// OP_MOD: Computes the remainder of the division of the second-to-top number by the number on top of the stack -/// -/// Example: OP_MOD([n1, n2]) -> [n1%n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_mod(stack: &mut Stack) -> bool { - let (op, desc) = (OPMOD, OPMOD_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_rem(n2) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_div_zero(op); - false - } - } -} - -/// OP_LSHIFT: Computes the left shift of the second-to-top number by the number on top of the stack -/// -/// Example: OP_LSHIFT([n1, n2]) -> [n1< bool { - let (op, desc) = (OPLSHIFT, OPLSHIFT_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_shl(n2 as u32) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_div_zero(op); - false - } - } -} - -/// OP_RSHIFT: Computes the right shift of the second-to-top number by the number on top of the stack -/// -/// Example: OP_RSHIFT([n1, n2]) -> [n1>>n2] -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_rshift(stack: &mut Stack) -> bool { - let (op, desc) = (OPRIGHT, OPRIGHT_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - match n1.checked_shr(n2 as u32) { - Some(n) => stack.push(StackEntry::Num(n)), - _ => { - error_div_zero(op); - false - } - } -} - -/// OP_BOOLAND: Substitutes the two numbers on top of the stack with ONE if they are both non-zero, with ZERO otherwise -/// -/// Example: OP_BOOLAND([n1, n2]) -> [1] if n1 != 0 and n2 != 0 -/// OP_BOOLAND([n1, n2]) -> [0] if n1 == 0 or n2 == 0 -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_booland(stack: &mut Stack) -> bool { - let (op, desc) = (OPBOOLAND, OPBOOLAND_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - if n1 != ZERO && n2 != ZERO { - stack.push(StackEntry::Num(ONE)) - } else { - stack.push(StackEntry::Num(ZERO)) - } -} - -/// OP_BOOLOR: Substitutes the two numbers on top of the stack with ONE if they are not both ZERO, with ZERO otherwise -/// -/// Example: OP_BOOLOR([n1, n2]) -> [1] if n1 != 0 or n2 != 0 -/// OP_BOOLOR([n1, n2]) -> [0] if n1 == 0 and n2 == 0 -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_boolor(stack: &mut Stack) -> bool { - let (op, desc) = (OPBOOLOR, OPBOOLOR_DESC); - trace(op, desc); - let n2 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let n1 = match stack.pop() { - Some(StackEntry::Num(n)) => n, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - if n1 != ZERO || n2 != ZERO { - stack.push(StackEntry::Num(ONE)) - } else { - stack.push(StackEntry::Num(ZERO)) - } -} - /// OP_NUMEQUAL: Substitutes the two numbers on top of the stack with ONE if they are equal, with ZERO otherwise /// /// Example: OP_NUMEQUAL([n1, n2]) -> [1] if n1 == n2 @@ -2221,7 +1677,7 @@ pub fn op_checkmultisig(stack: &mut Stack) -> bool { /// /// * `stack` - mutable reference to the stack pub fn op_checkmultisigverify(stack: &mut Stack) -> bool { - let (op, desc) = (OPCHECKMULTISIG, OPCHECKMULTISIG_DESC); + let (op, desc) = (OPCHECKMULTISIGVERIFY, OPCHECKMULTISIGVERIFY_DESC); trace(op, desc); let n = match stack.pop() { Some(StackEntry::Num(n)) => n, @@ -2312,3 +1768,10 @@ fn verify_multisig(sigs: &[Signature], msg: &String, pks: &mut Vec) - } num_valid_sigs == sigs.len() } + +/// Updates metadata with the transaction hash as the new genesis hash for item asset type transactions +pub fn update_metadata_with_tx_hash(transaction: &mut Transaction) { + if transaction.asset_type == AssetType::Item { + transaction.genesis_hash = transaction.hash.clone(); + } +} \ No newline at end of file diff --git a/src/validation/transaction_validator.rs b/src/validation/transaction_validator.rs new file mode 100644 index 0000000..b3b363a --- /dev/null +++ b/src/validation/transaction_validator.rs @@ -0,0 +1,129 @@ +use crate::models::{Transaction, AssetType, Metadata}; +use crate::errors::ValidationError; +use crate::utils::hash::calculate_hash; + +pub struct TransactionValidator; + +impl TransactionValidator { + pub fn validate(transaction: &Transaction) -> Result<(), ValidationError> { + // Existing validation logic + Self::validate_basic(transaction)?; + + // Additional validation for item asset type transactions + if let AssetType::Item = transaction.asset_type { + Self::validate_item_genesis_hash(transaction)?; + } + + Ok(()) + } + + fn validate_basic(transaction: &Transaction) -> Result<(), ValidationError> { + // Implement existing basic validation logic + // For example, check signatures, format, etc. + if transaction.signature.is_empty() { + return Err(ValidationError::InvalidSignature); + } + // Add other basic validations as needed + Ok(()) + } + + fn validate_item_genesis_hash(transaction: &Transaction) -> Result<(), ValidationError> { + if let Some(metadata) = &transaction.metadata { + let genesis_hash = &metadata.genesis_hash; + let expected_genesis_hash = calculate_hash(&transaction.id); + if genesis_hash != &expected_genesis_hash { + return Err(ValidationError::InvalidGenesisHash { + expected: expected_genesis_hash, + found: genesis_hash.clone(), + }); + } + } else { + return Err(ValidationError::MissingMetadata); + } + Ok(()) + } + + pub fn update_metadata(transaction: &mut Transaction) -> Result<(), ValidationError> { + if let AssetType::Item = transaction.asset_type { + let new_genesis_hash = calculate_hash(&transaction.id); + if let Some(metadata) = &mut transaction.metadata { + metadata.genesis_hash = new_genesis_hash; + } else { + return Err(ValidationError::MissingMetadata); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::{Transaction, AssetType, Metadata}; + + #[test] + fn test_validate_item_genesis_hash_success() { + let transaction_id = "tx123"; + let transaction = Transaction { + id: transaction_id.to_string(), + asset_type: AssetType::Item, + metadata: Some(Metadata { + genesis_hash: calculate_hash(transaction_id), + // other metadata fields + }), + signature: "valid_signature".to_string(), + // other transaction fields + }; + assert!(TransactionValidator::validate(&transaction).is_ok()); + } + + #[test] + fn test_validate_item_genesis_hash_failure() { + let transaction_id = "tx123"; + let transaction = Transaction { + id: transaction_id.to_string(), + asset_type: AssetType::Item, + metadata: Some(Metadata { + genesis_hash: "invalid_hash".to_string(), + // other metadata fields + }), + signature: "valid_signature".to_string(), + // other transaction fields + }; + let result = TransactionValidator::validate(&transaction); + assert!(matches!(result, Err(ValidationError::InvalidGenesisHash { .. }))); + } + + #[test] + fn test_update_metadata() { + let mut transaction = Transaction { + id: "tx123".to_string(), + asset_type: AssetType::Item, + metadata: Some(Metadata { + genesis_hash: "old_hash".to_string(), + // other metadata fields + }), + signature: "valid_signature".to_string(), + // other transaction fields + }; + let new_hash = calculate_hash(&transaction.id); + TransactionValidator::update_metadata(&mut transaction).unwrap(); + assert_eq!(transaction.metadata.as_ref().unwrap().genesis_hash, new_hash); + } + + #[test] + fn test_update_metadata_non_item_asset() { + let mut transaction = Transaction { + id: "tx123".to_string(), + asset_type: AssetType::Currency, + metadata: Some(Metadata { + genesis_hash: "old_hash".to_string(), + // other metadata fields + }), + signature: "valid_signature".to_string(), + // other transaction fields + }; + TransactionValidator::update_metadata(&mut transaction).unwrap(); + assert_eq!(transaction.metadata.as_ref().unwrap().genesis_hash, "old_hash".to_string()); + } +} \ No newline at end of file