Contractless/src/blocks/burn.rs

219 lines
7.1 KiB
Rust

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<String, Box<dyn std::error::Error>> {
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<Self, Box<dyn std::error::Error>> {
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<Vec<u8>> {
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<Self> {
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(())
}
}