mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-06-08 08:19:26 +00:00
BREAKING CHANGE:
- Crate `nssa` renamed to `lee`; update `Cargo.toml` dependencies from `nssa = { workspace = true }` to `lee = { workspace = true }`.
- Crate `nssa_core` renamed to `lee_core`; update similarly.
- Crate `key_protocol` moved under `lee`; update `Cargo.toml` dependencies from `key_protocol = { workspace = true }` to `lee_key_protocol = { workspace = true }`.
- Type `NSSATransaction` (in `common`) renamed to `LeeTransaction`.
- Error type `nssa::error::NssaError` renamed to `lee::error::LeeError`.
- Error type `nssa_core::error::NssaCoreError` renamed to `lee_core::error::LeeCoreError`.
- All `use nssa::` and `use nssa_core::` import paths must be updated to `use lee::` and `use lee_core::` respectively.
- Guest programs must replace `write_nssa_outputs` with `write_lee_outputs`.
- The sequencer RocksDB column family for the chain state was renamed. Existing databases are incompatible and must be wiped before running the new version.
- Domain separators updated: `"NSSA_seed"` → `"LEE_seed"` (key derivation), `"NSSA/v0.2/KDF-SHA256/"` → `"LEE/v0.2/KDF-SHA256/"` (encryption KDF), `"/NSSA/v0.2/AccountId/PDA/"` →
`"/LEE/v0.2/AccountId/PDA/"` (public PDA address derivation). All previously derived keys, encrypted outputs, and public PDA addresses are invalidated.
191 lines
6.4 KiB
Rust
191 lines
6.4 KiB
Rust
use borsh::{BorshDeserialize, BorshSerialize};
|
|
use lee::{AccountId, V03State, ValidatedStateDiff};
|
|
use lee_core::{BlockId, Timestamp};
|
|
use log::warn;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::HashType;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
|
pub enum LeeTransaction {
|
|
Public(lee::PublicTransaction),
|
|
PrivacyPreserving(lee::PrivacyPreservingTransaction),
|
|
ProgramDeployment(lee::ProgramDeploymentTransaction),
|
|
}
|
|
|
|
impl Serialize for LeeTransaction {
|
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
crate::borsh_base64::serialize(self, serializer)
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for LeeTransaction {
|
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
crate::borsh_base64::deserialize(deserializer)
|
|
}
|
|
}
|
|
|
|
impl LeeTransaction {
|
|
#[must_use]
|
|
pub fn hash(&self) -> HashType {
|
|
HashType(match self {
|
|
Self::Public(tx) => tx.hash(),
|
|
Self::PrivacyPreserving(tx) => tx.hash(),
|
|
Self::ProgramDeployment(tx) => tx.hash(),
|
|
})
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
|
match self {
|
|
Self::ProgramDeployment(tx) => tx.affected_public_account_ids(),
|
|
Self::Public(tx) => tx.affected_public_account_ids(),
|
|
Self::PrivacyPreserving(tx) => tx.affected_public_account_ids(),
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
Self::Public(tx) => {
|
|
if tx.witness_set().is_valid_for(tx.message()) {
|
|
Ok(Self::Public(tx))
|
|
} else {
|
|
Err(TransactionMalformationError::InvalidSignature)
|
|
}
|
|
}
|
|
Self::PrivacyPreserving(tx) => {
|
|
if tx.witness_set().signatures_are_valid_for(tx.message()) {
|
|
Ok(Self::PrivacyPreserving(tx))
|
|
} else {
|
|
Err(TransactionMalformationError::InvalidSignature)
|
|
}
|
|
}
|
|
Self::ProgramDeployment(tx) => Ok(Self::ProgramDeployment(tx)),
|
|
}
|
|
}
|
|
|
|
/// Validates the transaction against the current state and returns the resulting diff
|
|
/// without applying it. Rejects transactions that modify clock, faucet or bridge accounts,
|
|
/// whether directly or indirectly via chain calls.
|
|
///
|
|
/// This check is required for all user transactions. Only sequencer transactions may bypass
|
|
/// this check.
|
|
pub fn validate_on_state(
|
|
&self,
|
|
state: &V03State,
|
|
block_id: BlockId,
|
|
timestamp: Timestamp,
|
|
) -> Result<ValidatedStateDiff, lee::error::LeeError> {
|
|
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 system_accounts = lee::CLOCK_PROGRAM_ACCOUNT_IDS.iter().copied().chain([
|
|
lee::system_faucet_account_id(),
|
|
lee::system_bridge_account_id(),
|
|
]);
|
|
for account_id in system_accounts {
|
|
validate_doesnt_modify_account(state, &diff, account_id)?;
|
|
}
|
|
|
|
Ok(diff)
|
|
}
|
|
|
|
/// Validates the transaction against the current state, rejects modifications to clock
|
|
/// system accounts, and applies the resulting diff to the state.
|
|
pub fn execute_check_on_state(
|
|
self,
|
|
state: &mut V03State,
|
|
block_id: BlockId,
|
|
timestamp: Timestamp,
|
|
) -> Result<Self, lee::error::LeeError> {
|
|
let diff = self
|
|
.validate_on_state(state, block_id, timestamp)
|
|
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
|
state.apply_state_diff(diff);
|
|
Ok(self)
|
|
}
|
|
}
|
|
|
|
impl From<lee::PublicTransaction> for LeeTransaction {
|
|
fn from(value: lee::PublicTransaction) -> Self {
|
|
Self::Public(value)
|
|
}
|
|
}
|
|
|
|
impl From<lee::PrivacyPreservingTransaction> for LeeTransaction {
|
|
fn from(value: lee::PrivacyPreservingTransaction) -> Self {
|
|
Self::PrivacyPreserving(value)
|
|
}
|
|
}
|
|
|
|
impl From<lee::ProgramDeploymentTransaction> for LeeTransaction {
|
|
fn from(value: lee::ProgramDeploymentTransaction) -> Self {
|
|
Self::ProgramDeployment(value)
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
|
)]
|
|
pub enum TxKind {
|
|
Public,
|
|
PrivacyPreserving,
|
|
ProgramDeployment,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
|
|
pub enum TransactionMalformationError {
|
|
#[error("Invalid signature(-s)")]
|
|
InvalidSignature,
|
|
#[error("Failed to decode transaction with hash: {tx:?}")]
|
|
FailedToDecode { tx: HashType },
|
|
#[error("Transaction size {size} exceeds maximum allowed size of {max} bytes")]
|
|
TransactionTooLarge { size: usize, max: usize },
|
|
}
|
|
|
|
/// 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) -> lee::PublicTransaction {
|
|
let message = lee::public_transaction::Message::try_new(
|
|
lee::program::Program::clock().id(),
|
|
clock_core::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(),
|
|
vec![],
|
|
timestamp,
|
|
)
|
|
.expect("Clock invocation message should always be constructable");
|
|
lee::PublicTransaction::new(
|
|
message,
|
|
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
|
)
|
|
}
|
|
|
|
fn validate_doesnt_modify_account(
|
|
state: &V03State,
|
|
diff: &ValidatedStateDiff,
|
|
account_id: AccountId,
|
|
) -> Result<(), lee::error::LeeError> {
|
|
if diff
|
|
.public_diff()
|
|
.get(&account_id)
|
|
.is_some_and(|post| *post != state.get_account_by_id(account_id))
|
|
{
|
|
Err(lee::error::LeeError::InvalidInput(format!(
|
|
"Transaction modifies restricted system account {account_id}"
|
|
)))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|