use crate::tn_public_address::tn_pubkey_to_address_string;
use crate::tn_signature_encoding::tn_signature_to_string;
use crate::txn_lib::{self, Transaction, WireTxnHdrV1};
use blake3;
use std::{collections::HashSet, mem};
use tracing::{debug, error, warn};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
pub type FdPubkey = [u8; 32];
pub type FdSignature = [u8; 64];
pub type FdBlake3Hash = [u8; 32];
#[derive(Debug, Clone)]
pub struct BlockParseResult {
pub block_hash: [u8; 32], pub block_producer: [u8; 32], pub transactions: Vec<Vec<u8>>, }
#[derive(Debug, thiserror::Error)]
pub enum BlockParseError {
#[error("Invalid block structure: {0}")]
InvalidBlockStructure(String),
#[error("Blake3 hash computation failed: {0}")]
HashComputationFailed(String),
#[error("Header signature verification failed: {0}")]
HeaderSignatureInvalid(String),
#[error("Block signature verification failed: {0}")]
BlockSignatureInvalid(String),
#[error("Ed25519 key error: {0}")]
Ed25519KeyError(String),
#[error("Account extraction failed: {0}")]
AccountExtractionFailed(String),
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TnBlockHeader {
pub block_header_sig: FdSignature,
pub body: TnBlockHeaderBody,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TnBlockHeaderBody {
pub block_version: u8,
pub padding: [u8; 7],
pub block_producer: FdPubkey,
pub bond_amount_lock_up: u64,
pub expiry_timestamp: u64,
pub start_slot: u64,
pub expiry_after: u32,
pub max_block_size: u32,
pub max_compute_units: u64,
pub max_state_units: u32,
pub reserved: [u8; 4],
pub weight_slot: u64,
pub block_time_ns: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TnBlockFooter {
pub body: TnBlockFooterBody,
pub block_hash: FdBlake3Hash,
pub block_sig: FdSignature,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TnBlockFooterBody {
pub attestor_payment: u64,
}
pub struct BlockParser;
impl BlockParser {
pub fn parse_block(data: &[u8]) -> Result<BlockParseResult, BlockParseError> {
if data.is_empty() {
return Ok(BlockParseResult {
block_hash: [0u8; 32],
block_producer: [0u8; 32],
transactions: Vec::new(),
});
}
debug!(
"Parsing block data of {} bytes with cryptographic verification",
data.len()
);
let header_size = mem::size_of::<TnBlockHeader>();
let footer_size = mem::size_of::<TnBlockFooter>();
if data.len() < header_size + footer_size {
return Err(BlockParseError::InvalidBlockStructure(format!(
"Block too small: {} bytes, need at least {}",
data.len(),
header_size + footer_size
)));
}
let header = Self::parse_header_verified(&data[..header_size])?;
debug!(
"Parsed block header: version={}, start_slot={}, producer={}",
header.body.block_version,
header.body.start_slot,
tn_pubkey_to_address_string(&header.body.block_producer)
);
Self::verify_header_signature(&header)?;
debug!("Block header signature verified successfully");
let block_hash = Self::compute_block_hash(data)?;
debug!("Block hash computed successfully");
let footer_start = data.len() - footer_size;
let footer = Self::parse_footer_verified(&data[footer_start..])?;
debug!(
"Parsed block footer: attestor_payment={}",
footer.body.attestor_payment
);
Self::verify_block_signature(&block_hash, &footer, &header.body.block_producer)?;
debug!("Block signature verified successfully");
let transactions_data = &data[header_size..footer_start];
debug!(
"Transaction data section: {} bytes",
transactions_data.len()
);
let transactions = if transactions_data.is_empty() {
debug!("No transaction data in block");
Vec::new()
} else {
Self::parse_transactions(transactions_data)
.map_err(|e| BlockParseError::InvalidBlockStructure(e))?
};
debug!("Extracted {} transactions from block", transactions.len());
Ok(BlockParseResult {
block_hash,
block_producer: header.body.block_producer,
transactions,
})
}
fn compute_block_hash(data: &[u8]) -> Result<[u8; 32], BlockParseError> {
let footer_size = mem::size_of::<TnBlockFooter>();
if data.len() < footer_size {
return Err(BlockParseError::HashComputationFailed(
"Block too small to contain footer".to_string(),
));
}
let sig_size = mem::size_of::<FdSignature>();
let hash_size = mem::size_of::<FdBlake3Hash>();
let hash_data_end = data.len() - sig_size - hash_size;
let hash_data = &data[..hash_data_end];
debug!(
"Computing Blake3 hash over {} bytes (excluding {} byte signature and {} byte hash)",
hash_data.len(),
sig_size,
hash_size
);
let mut hasher = blake3::Hasher::new();
hasher.update(hash_data);
let hash_output = *hasher.finalize().as_bytes();
debug!("Blake3 hash computation completed successfully");
Ok(hash_output)
}
fn verify_header_signature(header: &TnBlockHeader) -> Result<(), BlockParseError> {
debug!("Starting header signature verification");
if header.block_header_sig.iter().all(|&b| b == 0) {
debug!("Header signature is all zeros - treating as unsigned block");
return Err(BlockParseError::HeaderSignatureInvalid(
"Block header is not signed (all-zero signature)".to_string(),
));
}
let body_size = mem::size_of::<TnBlockHeaderBody>();
let body_bytes =
unsafe { std::slice::from_raw_parts(&header.body as *const _ as *const u8, body_size) };
debug!(
"Verifying header signature over {} bytes of header body data",
body_bytes.len()
);
let signature = match Signature::from_slice(&header.block_header_sig) {
Ok(sig) => sig,
Err(e) => {
error!("Invalid header signature format: {}", e);
return Err(BlockParseError::HeaderSignatureInvalid(format!(
"Signature format error: {}",
e
)));
}
};
let verifying_key = VerifyingKey::from_bytes(&header.body.block_producer).map_err(|e| {
error!("Invalid producer public key format: {}", e);
BlockParseError::Ed25519KeyError(format!("Invalid producer public key: {}", e))
})?;
verifying_key.verify(body_bytes, &signature).map_err(|e| {
error!("Header signature verification failed: {}", e);
BlockParseError::HeaderSignatureInvalid(format!("Verification failed: {}", e))
})?;
debug!("Header signature verification successful");
Ok(())
}
fn verify_block_signature(
block_hash: &[u8; 32],
footer: &TnBlockFooter,
producer_key: &[u8; 32],
) -> Result<(), BlockParseError> {
debug!("Starting block signature verification");
if footer.block_sig.iter().all(|&b| b == 0) {
debug!("Block signature is all zeros - treating as unsigned block");
return Err(BlockParseError::BlockSignatureInvalid(
"Block is not signed (all-zero signature)".to_string(),
));
}
debug!("Verifying block signature against computed hash");
let signature = match Signature::from_slice(&footer.block_sig) {
Ok(sig) => sig,
Err(e) => {
error!("Invalid block signature format: {}", e);
return Err(BlockParseError::BlockSignatureInvalid(format!(
"Signature format error: {}",
e
)));
}
};
let verifying_key = VerifyingKey::from_bytes(producer_key).map_err(|e| {
error!("Invalid producer public key for block verification: {}", e);
BlockParseError::Ed25519KeyError(format!("Invalid producer public key: {}", e))
})?;
verifying_key.verify(block_hash, &signature).map_err(|e| {
error!("Block signature verification failed: {}", e);
BlockParseError::BlockSignatureInvalid(format!("Verification failed: {}", e))
})?;
debug!("Block signature verification successful");
Ok(())
}
fn parse_header_verified(data: &[u8]) -> Result<TnBlockHeader, BlockParseError> {
Self::parse_header(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
}
fn parse_footer_verified(data: &[u8]) -> Result<TnBlockFooter, BlockParseError> {
Self::parse_footer(data).map_err(|e| BlockParseError::InvalidBlockStructure(e))
}
#[deprecated(note = "Use parse_block instead")]
pub fn parse_block_legacy(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
if data.is_empty() {
return Ok(Vec::new());
}
debug!("Parsing block data of {} bytes", data.len());
let header_size = mem::size_of::<TnBlockHeader>();
let footer_size = mem::size_of::<TnBlockFooter>();
if data.len() < header_size + footer_size {
return Err(format!(
"Block too small: {} bytes, need at least {}",
data.len(),
header_size + footer_size
));
}
let header = Self::parse_header(&data[..header_size])?;
debug!(
"Parsed block header: version={}, start_slot={}",
header.body.block_version, header.body.start_slot
);
if header.body.block_version != 1 {
return Err(format!(
"Unsupported block version: {}",
header.body.block_version
));
}
let footer_start = data.len() - footer_size;
let footer = Self::parse_footer(&data[footer_start..])?;
debug!(
"Parsed block footer: attestor_payment={}",
footer.body.attestor_payment
);
let transactions_data = &data[header_size..footer_start];
debug!(
"Transaction data section: {} bytes",
transactions_data.len()
);
if transactions_data.is_empty() {
debug!("No transaction data in block");
return Ok(Vec::new());
}
let transactions = Self::parse_transactions(transactions_data)?;
debug!("Extracted {} transactions from block", transactions.len());
Ok(transactions)
}
fn parse_header(data: &[u8]) -> Result<TnBlockHeader, String> {
if data.len() < mem::size_of::<TnBlockHeader>() {
return Err("Insufficient data for block header".to_string());
}
let header = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockHeader) };
debug!(
"Block header: version={}, producer={:?}",
header.body.block_version,
tn_pubkey_to_address_string(&header.body.block_producer)
);
Ok(header)
}
fn parse_footer(data: &[u8]) -> Result<TnBlockFooter, String> {
if data.len() < mem::size_of::<TnBlockFooter>() {
return Err("Insufficient data for block footer".to_string());
}
let footer = unsafe { std::ptr::read(data.as_ptr() as *const TnBlockFooter) };
debug!(
"Block footer: attestor_payment={}",
footer.body.attestor_payment
);
Ok(footer)
}
fn parse_transactions(data: &[u8]) -> Result<Vec<Vec<u8>>, String> {
let mut transactions = Vec::new();
let mut offset = 0;
while offset < data.len() {
let wire_header_size = mem::size_of::<WireTxnHdrV1>();
if offset + wire_header_size > data.len() {
debug!(
"Remaining data too small for transaction header: {} bytes",
data.len() - offset
);
break;
}
let remaining_data = &data[offset..];
match Self::try_parse_transaction_at_offset(remaining_data) {
Ok((transaction_size, transaction_data)) => {
transactions.push(transaction_data);
debug!(
"Parsed transaction {} of size {} bytes",
transactions.len(),
transaction_size
);
offset += transaction_size;
}
Err(parse_error) => {
warn!(
"Failed to parse transaction at offset {}: {}",
offset, parse_error
);
return Err(parse_error);
}
}
}
Ok(transactions)
}
fn try_parse_transaction_at_offset(data: &[u8]) -> Result<(usize, Vec<u8>), String> {
let wire_header_size = mem::size_of::<WireTxnHdrV1>();
if data.len() < wire_header_size {
return Err("Not enough data for transaction header".to_string());
}
let total_size = txn_lib::tn_txn_size(data).map_err(|e| e.to_string())?;
if data.len() < total_size {
return Err(format!(
"Not enough data for complete transaction: need {} bytes, have {}",
total_size,
data.len()
));
}
let transaction_data = data[..total_size].to_vec();
if Transaction::from_wire(&transaction_data).is_none() {
return Err("Transaction::from_wire failed to parse transaction".to_string());
}
Ok((total_size, transaction_data))
}
pub fn extract_transaction_signature(tx_data: &[u8]) -> Result<String, String> {
if tx_data.len() < txn_lib::TN_TXN_SIGNATURE_SZ {
return Err("Transaction too short to contain a signature".to_string());
}
let sig_start = tx_data.len() - txn_lib::TN_TXN_SIGNATURE_SZ;
let signature_bytes = &tx_data[sig_start..];
let mut sig_array = [0u8; 64];
sig_array.copy_from_slice(signature_bytes);
let signature = tn_signature_to_string(&sig_array);
debug!("Extracted signature: {}", signature);
Ok(signature)
}
pub fn extract_account_mentions(
transactions: &[Vec<u8>],
) -> Result<HashSet<String>, BlockParseError> {
let mut accounts = HashSet::new();
debug!(
"Extracting account mentions from {} transactions",
transactions.len()
);
for (i, tx_data) in transactions.iter().enumerate() {
match Self::extract_transaction_accounts(tx_data) {
Ok(tx_accounts) => {
debug!(
"Transaction {} contains {} account references: {:?}",
i,
tx_accounts.len(),
tx_accounts
);
accounts.extend(tx_accounts);
}
Err(e) => {
warn!("Failed to extract accounts from transaction {}: {}", i, e);
}
}
}
debug!(
"Extracted {} unique account addresses from block: {:?}",
accounts.len(),
accounts
);
Ok(accounts)
}
fn extract_transaction_accounts(tx_data: &[u8]) -> Result<Vec<String>, BlockParseError> {
let header_size = 112;
let signature_size = txn_lib::TN_TXN_SIGNATURE_SZ;
let min_txn_size = header_size + signature_size;
if tx_data.len() < min_txn_size {
return Err(BlockParseError::AccountExtractionFailed(
format!("Transaction too small: {} bytes, need at least {} (header={}, signature={})",
tx_data.len(), min_txn_size, header_size, signature_size),
));
}
debug!(
"Extracting accounts from transaction of {} bytes",
tx_data.len()
);
let mut accounts = Vec::new();
let fee_payer_offset = 48;
if tx_data.len() >= fee_payer_offset + 32 {
let fee_payer_pubkey: [u8; 32] = tx_data[fee_payer_offset..fee_payer_offset + 32]
.try_into()
.map_err(|_| {
BlockParseError::AccountExtractionFailed(
"Failed to convert fee_payer_pubkey to [u8; 32]".to_string(),
)
})?;
let fee_payer_address = tn_pubkey_to_address_string(&fee_payer_pubkey);
debug!(
"Extracted fee_payer at offset {}: {:?} -> {}",
fee_payer_offset,
&fee_payer_pubkey[..8],
fee_payer_address
);
accounts.push(fee_payer_address);
}
let program_offset = 80;
if tx_data.len() >= program_offset + 32 {
let program_pubkey: [u8; 32] = tx_data[program_offset..program_offset + 32]
.try_into()
.map_err(|_| {
BlockParseError::AccountExtractionFailed(
"Failed to convert program_pubkey to [u8; 32]".to_string(),
)
})?;
let program_address = tn_pubkey_to_address_string(&program_pubkey);
debug!(
"Extracted program at offset {}: {:?} -> {}",
program_offset,
&program_pubkey[..8],
program_address
);
accounts.push(program_address);
}
let header_size = 112;
if tx_data.len() > header_size {
let readwrite_accounts_cnt = u16::from_le_bytes([tx_data[2], tx_data[3]]);
let readonly_accounts_cnt = u16::from_le_bytes([tx_data[4], tx_data[5]]);
debug!(
"Transaction has {} readwrite and {} readonly accounts",
readwrite_accounts_cnt, readonly_accounts_cnt
);
let additional_accounts_count =
(readwrite_accounts_cnt + readonly_accounts_cnt) as usize;
let additional_accounts_size = additional_accounts_count * 32;
if tx_data.len() >= header_size + additional_accounts_size {
for i in 0..additional_accounts_count {
let account_offset = header_size + (i * 32);
let account_pubkey: [u8; 32] = tx_data[account_offset..account_offset + 32]
.try_into()
.map_err(|_| {
BlockParseError::AccountExtractionFailed(format!(
"Failed to convert account_pubkey {} to [u8; 32]",
i
))
})?;
let account_address = tn_pubkey_to_address_string(&account_pubkey);
accounts.push(account_address);
}
}
}
Ok(accounts)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_transaction_signature() {
let mut transaction_data = vec![0u8; 100];
let sig_start = transaction_data.len() - 64;
for i in 0..64 {
transaction_data[sig_start + i] = (i % 256) as u8;
}
let result = BlockParser::extract_transaction_signature(&transaction_data);
assert!(result.is_ok());
let signature = result.unwrap();
assert!(!signature.is_empty());
assert_eq!(
signature.len(),
90,
"Signature should be 90 characters in ts... format"
);
assert!(
signature.starts_with("ts"),
"Signature should start with 'ts'"
);
}
#[test]
fn test_extract_transaction_signature_too_short() {
let transaction_data = vec![0u8; 32]; let result = BlockParser::extract_transaction_signature(&transaction_data);
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
"Transaction too short to contain a signature"
);
}
#[test]
fn test_parse_empty_block() {
let empty_data = vec![];
let result = BlockParser::parse_block(&empty_data);
assert!(result.is_ok());
let block_result = result.unwrap();
assert_eq!(block_result.transactions.len(), 0);
assert_eq!(block_result.block_hash, [0u8; 32]);
assert_eq!(block_result.block_producer, [0u8; 32]);
}
#[test]
fn test_parse_block_too_small() {
let small_data = vec![0u8; 50]; let result = BlockParser::parse_block(&small_data);
assert!(result.is_err());
let error_msg = format!("{}", result.unwrap_err());
assert!(error_msg.contains("Block too small"));
}
#[test]
fn test_parse_block_header_footer_only() {
let header_size = std::mem::size_of::<TnBlockHeader>();
let footer_size = std::mem::size_of::<TnBlockFooter>();
let mut block_data = vec![0u8; header_size + footer_size];
block_data[0] = 1;
let result = BlockParser::parse_block(&block_data);
match result {
Ok(block_result) => {
assert_eq!(block_result.transactions.len(), 0);
assert_eq!(block_result.block_producer, [0u8; 32]);
assert_eq!(block_result.block_hash.len(), 32);
}
Err(error) => {
let error_msg = format!("{}", error);
assert!(
error_msg.contains("signature")
|| error_msg.contains("key")
|| error_msg.contains("Invalid")
);
}
}
}
#[test]
fn test_try_parse_transaction_at_offset() {
let header_size = 112;
let signature_size = 64;
let min_txn_size = header_size + signature_size;
let mut tx_data = vec![0u8; min_txn_size];
tx_data[0] = 1;
tx_data[2] = 0;
tx_data[3] = 0;
tx_data[4] = 0;
tx_data[5] = 0;
tx_data[6] = 0;
tx_data[7] = 0;
let result = BlockParser::try_parse_transaction_at_offset(&tx_data);
match result {
Ok((size, data)) => {
assert_eq!(size, min_txn_size);
assert_eq!(data.len(), min_txn_size);
}
Err(_) => {
}
}
}
#[test]
fn test_parse_transactions_empty_data() {
let empty_data = vec![];
let result = BlockParser::parse_transactions(&empty_data);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_parse_transactions_insufficient_data() {
let short_data = vec![0u8; 32]; let result = BlockParser::parse_transactions(&short_data);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0); }
}