use alloc::string::ToString;
use alloc::vec::Vec;
use core::fmt;
use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockNumber;
use miden_protocol::transaction::{RawOutputNotes, TransactionId, TransactionScript};
use miden_tx::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
#[derive(Debug, Clone)]
pub struct TransactionRecord {
pub id: TransactionId,
pub details: TransactionDetails,
pub script: Option<TransactionScript>,
pub status: TransactionStatus,
}
impl TransactionRecord {
pub fn new(
id: TransactionId,
details: TransactionDetails,
script: Option<TransactionScript>,
status: TransactionStatus,
) -> TransactionRecord {
TransactionRecord { id, details, script, status }
}
pub fn commit_transaction(
&mut self,
commit_height: BlockNumber,
commit_timestamp: u64,
) -> bool {
match self.status {
TransactionStatus::Pending => {
self.status = TransactionStatus::Committed {
block_number: commit_height,
commit_timestamp,
};
true
},
TransactionStatus::Discarded(_) | TransactionStatus::Committed { .. } => false,
}
}
pub fn discard_transaction(&mut self, cause: DiscardCause) -> bool {
match self.status {
TransactionStatus::Pending => {
self.status = TransactionStatus::Discarded(cause);
true
},
TransactionStatus::Discarded(_) | TransactionStatus::Committed { .. } => false,
}
}
}
#[derive(Debug, Clone)]
pub struct TransactionDetails {
pub account_id: AccountId,
pub init_account_state: Word,
pub final_account_state: Word,
pub input_note_nullifiers: Vec<Word>,
pub output_notes: RawOutputNotes,
pub block_num: BlockNumber,
pub submission_height: BlockNumber,
pub expiration_block_num: BlockNumber,
pub creation_timestamp: u64,
}
impl Serializable for TransactionDetails {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.account_id.write_into(target);
self.init_account_state.write_into(target);
self.final_account_state.write_into(target);
self.input_note_nullifiers.write_into(target);
self.output_notes.write_into(target);
self.block_num.write_into(target);
self.submission_height.write_into(target);
self.expiration_block_num.write_into(target);
self.creation_timestamp.write_into(target);
}
}
impl Deserializable for TransactionDetails {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let account_id = AccountId::read_from(source)?;
let init_account_state = Word::read_from(source)?;
let final_account_state = Word::read_from(source)?;
let input_note_nullifiers = Vec::<Word>::read_from(source)?;
let output_notes = RawOutputNotes::read_from(source)?;
let block_num = BlockNumber::read_from(source)?;
let submission_height = BlockNumber::read_from(source)?;
let expiration_block_num = BlockNumber::read_from(source)?;
let creation_timestamp = source.read_u64()?;
Ok(Self {
account_id,
init_account_state,
final_account_state,
input_note_nullifiers,
output_notes,
block_num,
submission_height,
expiration_block_num,
creation_timestamp,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DiscardCause {
Expired,
InputConsumed,
DiscardedInitialState,
Stale,
}
impl DiscardCause {
pub fn from_string(cause: &str) -> Result<Self, DeserializationError> {
match cause {
"Expired" => Ok(DiscardCause::Expired),
"InputConsumed" => Ok(DiscardCause::InputConsumed),
"DiscardedInitialState" => Ok(DiscardCause::DiscardedInitialState),
"Stale" => Ok(DiscardCause::Stale),
_ => Err(DeserializationError::InvalidValue(format!("Invalid discard cause: {cause}"))),
}
}
}
impl fmt::Display for DiscardCause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DiscardCause::Expired => write!(f, "Expired"),
DiscardCause::InputConsumed => write!(f, "InputConsumed"),
DiscardCause::DiscardedInitialState => write!(f, "DiscardedInitialState"),
DiscardCause::Stale => write!(f, "Stale"),
}
}
}
impl Serializable for DiscardCause {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
match self {
DiscardCause::Expired => target.write_u8(0),
DiscardCause::InputConsumed => target.write_u8(1),
DiscardCause::DiscardedInitialState => target.write_u8(2),
DiscardCause::Stale => target.write_u8(3),
}
}
}
impl Deserializable for DiscardCause {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match source.read_u8()? {
0 => Ok(DiscardCause::Expired),
1 => Ok(DiscardCause::InputConsumed),
2 => Ok(DiscardCause::DiscardedInitialState),
3 => Ok(DiscardCause::Stale),
_ => Err(DeserializationError::InvalidValue("Invalid discard cause".to_string())),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TransactionStatus {
Pending,
Committed {
block_number: BlockNumber,
commit_timestamp: u64,
},
Discarded(DiscardCause),
}
pub enum TransactionStatusVariant {
Pending = 0,
Committed = 1,
Discarded = 2,
}
impl TransactionStatus {
pub const fn variant(&self) -> TransactionStatusVariant {
match self {
TransactionStatus::Pending => TransactionStatusVariant::Pending,
TransactionStatus::Committed { .. } => TransactionStatusVariant::Committed,
TransactionStatus::Discarded(_) => TransactionStatusVariant::Discarded,
}
}
}
impl fmt::Display for TransactionStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransactionStatus::Pending => write!(f, "Pending"),
TransactionStatus::Committed { block_number, .. } => {
write!(f, "Committed (Block: {block_number})")
},
TransactionStatus::Discarded(cause) => write!(f, "Discarded ({cause})"),
}
}
}
impl Serializable for TransactionStatus {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
match self {
TransactionStatus::Pending => target.write_u8(self.variant() as u8),
TransactionStatus::Committed { block_number, commit_timestamp } => {
target.write_u8(self.variant() as u8);
block_number.write_into(target);
commit_timestamp.write_into(target);
},
TransactionStatus::Discarded(cause) => {
target.write_u8(self.variant() as u8);
cause.write_into(target);
},
}
}
}
impl Deserializable for TransactionStatus {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match source.read_u8()? {
variant if variant == TransactionStatusVariant::Pending as u8 => {
Ok(TransactionStatus::Pending)
},
variant if variant == TransactionStatusVariant::Committed as u8 => {
let block_number = BlockNumber::read_from(source)?;
let commit_timestamp = source.read_u64()?;
Ok(TransactionStatus::Committed { block_number, commit_timestamp })
},
variant if variant == TransactionStatusVariant::Discarded as u8 => {
let cause = DiscardCause::read_from(source)?;
Ok(TransactionStatus::Discarded(cause))
},
_ => Err(DeserializationError::InvalidValue("Invalid transaction status".to_string())),
}
}
}