use crate::common::binary_conversions::binary_to_string; use crate::common::skein::skein_256_hash_data; use crate::records::memory::mempool::db_client; use crate::to_string; use crate::wallets::structures::Wallet; use crate::Cursor; use crate::Serialize; use crate::{decode, encode}; use crate::{AsyncReadExt, AsyncWriteExt}; use anyhow::{anyhow, Result}; // Burn transactions destroy an existing non-base asset without routing it through a receiver. #[derive(Debug, Serialize, Clone)] // 62 bytes pub struct UnsignedBurnTransaction { pub txtype: u8, // 1 byte txtype should be 10 pub time: u32, // 4 bytes timestamp pub address: String, // 22 bytes wallet address of burner pub coin: String, // 15 bytes token or NFT base name, padded with spaces pub nft_series: u32, // 4 bytes 0 for tokens or 1-of-1 NFTs, otherwise specific NFT series item pub value: u64, // 8 bytes amount of token units or NFT unit value to burn pub txfee: u64, // 8 bytes fee of transaction } #[derive(Debug, Serialize, Clone)] // 728 bytes pub struct BurnTransaction { pub unsigned_burn: UnsignedBurnTransaction, // 62 bytes pub signature: String, // 666 bytes signature of hash } impl BurnTransaction { pub const BYTE_LENGTH: usize = 1 + 4 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + 15 + 4 + 8 + 8 + Wallet::SIGNATURE_LENGTH; } impl UnsignedBurnTransaction { // Create an unsigned burn transaction. pub async fn new( txtype: u8, time: u32, address: &str, coin: &str, nft_series: u32, value: u64, txfee: u64, ) -> Self { Self { txtype, time, address: address.to_string(), coin: coin.to_string(), nft_series, value, txfee, } } // Hash without signing for verification. pub async fn hash(&self) -> String { let serialized = to_string(self).unwrap(); skein_256_hash_data(&serialized) } // Hash the unsigned transaction and sign it. pub async fn hash_and_sign( &self, private_key: &str, ) -> Result> { let serialized = to_string(self)?; let hash = skein_256_hash_data(&serialized); let signature = Wallet::sign_transaction(&hash, private_key).await; Ok(signature) } } impl BurnTransaction { // Create a signed burn transaction. pub async fn new( unsigned_burn: UnsignedBurnTransaction, private_key: &str, ) -> Result> { let signature = unsigned_burn.hash_and_sign(private_key).await?; Ok(Self { unsigned_burn, signature, }) } // Load an existing burn transaction. pub async fn load(unsigned_burn: UnsignedBurnTransaction, signature: &str) -> Self { Self { unsigned_burn, signature: signature.to_string(), } } pub async fn to_bytes(&self) -> tokio::io::Result> { let mut buffer = Vec::with_capacity(Self::BYTE_LENGTH); let mut cursor = Cursor::new(&mut buffer); cursor .write_all(&self.unsigned_burn.txtype.to_le_bytes()) .await?; cursor .write_all(&self.unsigned_burn.time.to_le_bytes()) .await?; let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_burn.address) .ok_or_else(|| tokio::io::Error::other("Invalid burn short address"))?; cursor.write_all(&address_bytes).await?; cursor.write_all(self.unsigned_burn.coin.as_bytes()).await?; cursor .write_all(&self.unsigned_burn.nft_series.to_le_bytes()) .await?; cursor .write_all(&self.unsigned_burn.value.to_le_bytes()) .await?; cursor .write_all(&self.unsigned_burn.txfee.to_le_bytes()) .await?; cursor.write_all(&decode(&self.signature).unwrap()).await?; Ok(buffer) } pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result { if bytes.len() != Self::BYTE_LENGTH - 1 { return Err(tokio::io::Error::other("Invalid Byte Count")); } let mut cursor = Cursor::new(bytes); let time = 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 burn short address bytes"))?; let mut coin_bytes = vec![0; 15]; cursor.read_exact(&mut coin_bytes).await?; let coin = binary_to_string(coin_bytes); let nft_series = cursor.read_u32_le().await?; let value = cursor.read_u64_le().await?; 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_burn = UnsignedBurnTransaction { txtype, time, address, coin, nft_series, value, txfee, }; Ok(BurnTransaction { unsigned_burn, signature, }) } pub async fn add_to_memory(&self) -> Result<()> { let original_data = self .to_bytes() .await .map_err(|_| anyhow!("Failed to serialize transaction"))?; let fee = i64::try_from(self.unsigned_burn.txfee) .map_err(|_| std::io::Error::other("Burn fee exceeds i64 mempool limit"))?; let time = self.unsigned_burn.time as i32; let value = i64::try_from(self.unsigned_burn.value) .map_err(|_| std::io::Error::other("Burn value exceeds i64 mempool limit"))?; let nft_series = self.unsigned_burn.nft_series as i32; let address = &self.unsigned_burn.address; let coin = &self.unsigned_burn.coin; let hash = &self.unsigned_burn.hash().await; let signature = &self.signature; let client_handle = db_client().await?; let client = client_handle.as_ref(); client .execute( r#" INSERT INTO burn ( time, fee, address, coin, nft_series, value, hash, signature, original ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "#, &[ &time, &fee, address, coin, &nft_series, &value, hash, signature, &original_data, ], ) .await?; Ok(()) } }