Contractless/src/blocks/vanity.rs

218 lines
7.8 KiB
Rust

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<String, Box<dyn std::error::Error>> {
// 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<Self, Box<dyn std::error::Error>> {
// 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<Vec<u8>> {
// 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<Self> {
// 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<dyn std::error::Error + Send + Sync>> {
// 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<dyn std::error::Error + Send + Sync>
})?;
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(())
}
}