lssa/common/src/transaction.rs

173 lines
5.9 KiB
Rust
Raw Normal View History

2025-09-25 11:53:42 +03:00
use borsh::{BorshDeserialize, BorshSerialize};
use log::warn;
use nssa::{AccountId, V03State, ValidatedStateDiff};
2026-03-28 03:52:14 -03:00
use nssa_core::{BlockId, Timestamp};
2025-07-21 18:46:50 -03:00
use serde::{Deserialize, Serialize};
2024-10-10 14:09:31 +03:00
use crate::HashType;
2024-12-22 16:14:52 +02:00
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
2025-09-08 10:11:04 +03:00
pub enum NSSATransaction {
Public(nssa::PublicTransaction),
PrivacyPreserving(nssa::PrivacyPreservingTransaction),
ProgramDeployment(nssa::ProgramDeploymentTransaction),
2025-09-08 10:11:04 +03:00
}
impl Serialize for NSSATransaction {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
crate::borsh_base64::serialize(self, serializer)
}
}
impl<'de> Deserialize<'de> for NSSATransaction {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
crate::borsh_base64::deserialize(deserializer)
}
}
impl NSSATransaction {
2026-03-03 23:21:08 +03:00
#[must_use]
pub fn hash(&self) -> HashType {
HashType(match self {
2026-03-09 18:27:56 +03:00
Self::Public(tx) => tx.hash(),
Self::PrivacyPreserving(tx) => tx.hash(),
Self::ProgramDeployment(tx) => tx.hash(),
})
}
2026-02-23 10:55:00 +02:00
2026-03-03 23:21:08 +03:00
#[must_use]
2026-02-23 10:55:00 +02:00
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
match self {
2026-03-09 18:27:56 +03:00
Self::ProgramDeployment(tx) => tx.affected_public_account_ids(),
Self::Public(tx) => tx.affected_public_account_ids(),
Self::PrivacyPreserving(tx) => tx.affected_public_account_ids(),
2026-02-23 10:55:00 +02:00
}
}
// TODO: Introduce type-safe wrapper around checked transaction, e.g. AuthenticatedTransaction
pub fn transaction_stateless_check(self) -> Result<Self, TransactionMalformationError> {
// Stateless checks here
match self {
2026-03-09 18:27:56 +03:00
Self::Public(tx) => {
2026-02-23 10:55:00 +02:00
if tx.witness_set().is_valid_for(tx.message()) {
2026-03-09 18:27:56 +03:00
Ok(Self::Public(tx))
2026-02-23 10:55:00 +02:00
} else {
Err(TransactionMalformationError::InvalidSignature)
}
}
2026-03-09 18:27:56 +03:00
Self::PrivacyPreserving(tx) => {
2026-02-23 10:55:00 +02:00
if tx.witness_set().signatures_are_valid_for(tx.message()) {
2026-03-09 18:27:56 +03:00
Ok(Self::PrivacyPreserving(tx))
2026-02-23 10:55:00 +02:00
} else {
Err(TransactionMalformationError::InvalidSignature)
}
}
2026-03-09 18:27:56 +03:00
Self::ProgramDeployment(tx) => Ok(Self::ProgramDeployment(tx)),
2026-02-23 10:55:00 +02:00
}
}
/// Validates the transaction against the current state and returns the resulting diff
/// without applying it. Rejects transactions that modify clock system accounts.
pub fn validate_on_state(
&self,
state: &V03State,
block_id: BlockId,
timestamp: Timestamp,
) -> Result<ValidatedStateDiff, nssa::error::NssaError> {
let diff = match self {
Self::Public(tx) => {
ValidatedStateDiff::from_public_transaction(tx, state, block_id, timestamp)
}
Self::PrivacyPreserving(tx) => ValidatedStateDiff::from_privacy_preserving_transaction(
tx, state, block_id, timestamp,
),
Self::ProgramDeployment(tx) => {
ValidatedStateDiff::from_program_deployment_transaction(tx, state)
}
}?;
let public_diff = diff.public_diff();
let touches_clock = nssa::CLOCK_PROGRAM_ACCOUNT_IDS.iter().any(|id| {
public_diff
.get(id)
.is_some_and(|post| *post != state.get_account_by_id(*id))
});
if touches_clock {
return Err(nssa::error::NssaError::InvalidInput(
"Transaction modifies system clock accounts".into(),
));
}
Ok(diff)
}
/// Validates the transaction against the current state, rejects modifications to clock
/// system accounts, and applies the resulting diff to the state.
2026-02-23 10:55:00 +02:00
pub fn execute_check_on_state(
self,
state: &mut V03State,
2026-03-19 18:55:19 -03:00
block_id: BlockId,
2026-03-28 03:47:25 -03:00
timestamp: Timestamp,
2026-02-23 10:55:00 +02:00
) -> Result<Self, nssa::error::NssaError> {
let diff = self
.validate_on_state(state, block_id, timestamp)
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
state.apply_state_diff(diff);
2026-02-23 10:55:00 +02:00
Ok(self)
}
}
2025-09-08 10:11:04 +03:00
impl From<nssa::PublicTransaction> for NSSATransaction {
fn from(value: nssa::PublicTransaction) -> Self {
Self::Public(value)
}
}
impl From<nssa::PrivacyPreservingTransaction> for NSSATransaction {
fn from(value: nssa::PrivacyPreservingTransaction) -> Self {
Self::PrivacyPreserving(value)
}
}
2025-10-15 20:14:19 -03:00
impl From<nssa::ProgramDeploymentTransaction> for NSSATransaction {
fn from(value: nssa::ProgramDeploymentTransaction) -> Self {
Self::ProgramDeployment(value)
}
}
2025-09-25 11:53:42 +03:00
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
2025-09-25 11:53:42 +03:00
)]
pub enum TxKind {
Public,
2025-08-28 12:00:04 +03:00
PrivacyPreserving,
ProgramDeployment,
}
2026-03-09 18:27:56 +03:00
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
2026-01-30 10:25:34 +02:00
pub enum TransactionMalformationError {
#[error("Invalid signature(-s)")]
2026-01-30 10:25:34 +02:00
InvalidSignature,
2026-02-23 10:55:00 +02:00
#[error("Failed to decode transaction with hash: {tx:?}")]
2026-01-30 10:25:34 +02:00
FailedToDecode { tx: HashType },
#[error("Transaction size {size} exceeds maximum allowed size of {max} bytes")]
TransactionTooLarge { size: usize, max: usize },
2026-01-30 10:25:34 +02:00
}
/// Returns the canonical Clock Program invocation transaction for the given block timestamp.
/// Every valid block must end with exactly one occurrence of this transaction.
#[must_use]
pub fn clock_invocation(timestamp: clock_core::Instruction) -> nssa::PublicTransaction {
let message = nssa::public_transaction::Message::try_new(
nssa::program::Program::clock().id(),
clock_core::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(),
vec![],
timestamp,
)
.expect("Clock invocation message should always be constructable");
nssa::PublicTransaction::new(
message,
nssa::public_transaction::WitnessSet::from_raw_parts(vec![]),
)
}