use crate::common::skein::skein_256_hash_data; use crate::records::memory::mempool::DB; use crate::to_string; use crate::wallets::structures::Wallet; use crate::Cursor; use crate::Serialize; use crate::{decode, encode}; use crate::{AsyncReadExt, AsyncWriteExt}; #[derive(Debug, Serialize, Clone)] // 57 bytes pub struct UnsignedVanityAddressTransaction { pub txtype: u8, // 1 byte transaction type, should be 12 pub timestamp: u32, // 4 bytes transaction timestamp pub address: String, // 22 bytes real short address receiving the vanity mapping pub vanity_address: String, // 22 bytes vanity short address being registered pub txfee: u64, // 8 bytes fee paid for vanity registration } #[derive(Debug, Serialize, Clone)] // 723 bytes pub struct VanityAddressTransaction { pub unsigned_vanity_address: UnsignedVanityAddressTransaction, // 57 bytes pub signature: String, // 666 bytes } impl VanityAddressTransaction { pub const BYTE_LENGTH: usize = 1 + 4 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::SHORT_ADDRESS_BYTES_LENGTH + 8 + Wallet::SIGNATURE_LENGTH; } impl UnsignedVanityAddressTransaction { // Create the unsigned vanity-address registration payload. pub async fn new( txtype: u8, timestamp: u32, address: &str, vanity_address: &str, txfee: u64, ) -> Self { Self { txtype, timestamp, address: address.to_string(), vanity_address: vanity_address.to_string(), txfee, } } pub async fn hash(&self) -> String { // Hash the unsigned payload for verification and mempool indexing. let serialized = to_string(self).unwrap(); skein_256_hash_data(&serialized) } pub async fn hash_and_sign( &self, private_key: &str, ) -> Result> { // Sign the unsigned vanity transaction with the registering wallet. let serialized = to_string(self)?; let hash = skein_256_hash_data(&serialized); let signature = Wallet::sign_transaction(&hash, private_key).await; Ok(signature) } } impl VanityAddressTransaction { pub async fn new( unsigned_vanity_address: UnsignedVanityAddressTransaction, private_key: &str, ) -> Result> { // Create a signed vanity-address transaction. let signature = unsigned_vanity_address.hash_and_sign(private_key).await?; Ok(Self { unsigned_vanity_address, signature, }) } pub async fn load( unsigned_vanity_address: UnsignedVanityAddressTransaction, signature: &str, ) -> Self { // Rebuild a vanity transaction already read from storage. Self { unsigned_vanity_address, signature: signature.to_string(), } } pub async fn to_bytes(&self) -> tokio::io::Result> { // Serialize into the fixed vanity transaction byte layout. let mut buffer = Vec::with_capacity(Self::BYTE_LENGTH); let mut cursor = Cursor::new(&mut buffer); cursor .write_all(&self.unsigned_vanity_address.txtype.to_le_bytes()) .await?; cursor .write_all(&self.unsigned_vanity_address.timestamp.to_le_bytes()) .await?; let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_vanity_address.address) .ok_or_else(|| tokio::io::Error::other("Invalid sender short address"))?; // Vanity addresses use the same 22-byte width as short addresses // but are encoded through the vanity-specific byte conversion. let vanity_bytes = Wallet::vanity_address_to_bytes(&self.unsigned_vanity_address.vanity_address) .ok_or_else(|| tokio::io::Error::other("Invalid vanity short address"))?; cursor.write_all(&address_bytes).await?; cursor.write_all(&vanity_bytes).await?; cursor .write_all(&self.unsigned_vanity_address.txfee.to_le_bytes()) .await?; let signature_bytes = decode(&self.signature).map_err(|err| { tokio::io::Error::other(format!("Invalid vanity signature hex: {err}")) })?; cursor.write_all(&signature_bytes).await?; Ok(buffer) } pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result { // The block parser already consumed the transaction type byte. if bytes.len() != Self::BYTE_LENGTH - 1 { return Err(tokio::io::Error::other("Invalid Byte Count")); } let mut cursor = Cursor::new(bytes); // Decode timestamp, real short address, vanity address, fee, and signature. let timestamp = cursor.read_u32_le().await?; let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH]; cursor.read_exact(&mut address_bytes).await?; let address = Wallet::bytes_to_short_address(&address_bytes) .ok_or_else(|| tokio::io::Error::other("Invalid sender short address bytes"))?; let mut vanity_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH]; cursor.read_exact(&mut vanity_bytes).await?; let vanity_address = Wallet::bytes_to_vanity_address(&vanity_bytes) .ok_or_else(|| tokio::io::Error::other("Invalid vanity short address bytes"))?; let txfee = cursor.read_u64_le().await?; let mut signature_bytes = vec![0; Wallet::SIGNATURE_LENGTH]; cursor.read_exact(&mut signature_bytes).await?; let signature = encode(&signature_bytes); let unsigned_vanity_address = UnsignedVanityAddressTransaction { txtype, timestamp, address, vanity_address, txfee, }; Ok(Self { unsigned_vanity_address, signature, }) } pub async fn add_to_memory(&self) -> Result<(), Box> { // Store the original bytes so the mempool can later rebuild the // exact transaction submitted by the wallet. let original_data = self.to_bytes().await?; // PostgreSQL uses signed integer types for these persisted fields. let time = self.unsigned_vanity_address.timestamp as i32; let fee = i64::try_from(self.unsigned_vanity_address.txfee) .map_err(|_| std::io::Error::other("Vanity fee exceeds i64 mempool limit"))?; let address = &self.unsigned_vanity_address.address; let vanity_address = &self.unsigned_vanity_address.vanity_address; let hash = &self.unsigned_vanity_address.hash().await; let signature = &self.signature; // Vanity transactions are written to the vanity mempool table // until the transaction is mined or removed. let client = DB.get().ok_or_else(|| { Box::new(std::io::Error::other("DB not initialized")) as Box })?; client .execute( r#" INSERT INTO vanity_address ( time, fee, address, vanity_address, hash, signature, original ) VALUES ($1, $2, $3, $4, $5, $6, $7) "#, &[ &time, &fee, address, vanity_address, hash, signature, &original_data, ], ) .await?; Ok(()) } }