diff --git a/Cargo.lock b/Cargo.lock index 726149dd..c3b8c5f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1956,7 +1956,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2105,7 +2105,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2406,7 +2406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5952,7 +5952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5965,7 +5965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.117", @@ -6883,7 +6883,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7811,7 +7811,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8976,7 +8976,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 4be8a4d5..3c5dc07d 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/associated_token_account.bin b/artifacts/program_methods/associated_token_account.bin index edf209ca..2229f944 100644 Binary files a/artifacts/program_methods/associated_token_account.bin and b/artifacts/program_methods/associated_token_account.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index 3390b016..24ca8f1e 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/clock.bin b/artifacts/program_methods/clock.bin new file mode 100644 index 00000000..c3f8af15 Binary files /dev/null and b/artifacts/program_methods/clock.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index d7ee19f2..1ca1da1c 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index 85e799bd..8662e7e5 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index f3894811..3356ff8d 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index 49848eef..1f5f0f67 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin index a00f2991..f859956b 100644 Binary files a/artifacts/test_program_methods/burner.bin and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin index 6402d170..f26e9642 100644 Binary files a/artifacts/test_program_methods/chain_caller.bin and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin index 3f44174a..b5b18ca0 100644 Binary files a/artifacts/test_program_methods/changer_claimer.bin and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin index 20fcad17..a981ebe0 100644 Binary files a/artifacts/test_program_methods/claimer.bin and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin index 2099a180..8eb4c87c 100644 Binary files a/artifacts/test_program_methods/data_changer.bin and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin index 30fb2670..5b3b299f 100644 Binary files a/artifacts/test_program_methods/extra_output.bin and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin index 1cf2536e..d2a25ebe 100644 Binary files a/artifacts/test_program_methods/malicious_authorization_changer.bin and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin index 908082e6..c2c51f9b 100644 Binary files a/artifacts/test_program_methods/minter.bin and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin index 439ab0fa..77794cc7 100644 Binary files a/artifacts/test_program_methods/missing_output.bin and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin index e666047e..c417948e 100644 Binary files a/artifacts/test_program_methods/modified_transfer.bin and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin index d82d7cb2..fde6e1bd 100644 Binary files a/artifacts/test_program_methods/nonce_changer.bin and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin index b97a64f7..b106e773 100644 Binary files a/artifacts/test_program_methods/noop.bin and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin index 760a6a17..e97267a2 100644 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin index 1603437b..6df96057 100644 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/artifacts/test_program_methods/validity_window.bin b/artifacts/test_program_methods/validity_window.bin new file mode 100644 index 00000000..1a01c568 Binary files /dev/null and b/artifacts/test_program_methods/validity_window.bin differ diff --git a/artifacts/test_program_methods/validity_window_chain_caller.bin b/artifacts/test_program_methods/validity_window_chain_caller.bin new file mode 100644 index 00000000..e6f06294 Binary files /dev/null and b/artifacts/test_program_methods/validity_window_chain_caller.bin differ diff --git a/bedrock_client/src/lib.rs b/bedrock_client/src/lib.rs index fdd14f72..4e9bfffd 100644 --- a/bedrock_client/src/lib.rs +++ b/bedrock_client/src/lib.rs @@ -46,7 +46,7 @@ impl BedrockClient { info!("Creating Bedrock client with node URL {node_url}"); let client = Client::builder() //Add more fields if needed - .timeout(std::time::Duration::from_secs(60)) + .timeout(std::time::Duration::from_mins(1)) .build() .context("Failed to build HTTP client")?; diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 9563251a..1862dcc8 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -3,7 +3,7 @@ use log::warn; use nssa::{AccountId, V03State}; use serde::{Deserialize, Serialize}; -use crate::HashType; +use crate::{HashType, block::BlockId}; #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum NSSATransaction { @@ -68,10 +68,13 @@ impl NSSATransaction { pub fn execute_check_on_state( self, state: &mut V03State, + block_id: BlockId, ) -> Result { match &self { - Self::Public(tx) => state.transition_from_public_transaction(tx), - Self::PrivacyPreserving(tx) => state.transition_from_privacy_preserving_transaction(tx), + Self::Public(tx) => state.transition_from_public_transaction(tx, block_id), + Self::PrivacyPreserving(tx) => { + state.transition_from_privacy_preserving_transaction(tx, block_id) + } Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx), } .inspect_err(|err| warn!("Error at transition {err:#?}"))?; diff --git a/examples/program_deployment/methods/guest/src/bin/hello_world.rs b/examples/program_deployment/methods/guest/src/bin/hello_world.rs index 3391eb5d..381b71c3 100644 --- a/examples/program_deployment/methods/guest/src/bin/hello_world.rs +++ b/examples/program_deployment/methods/guest/src/bin/hello_world.rs @@ -1,5 +1,5 @@ use nssa_core::program::{ - AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, + AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs, }; // Hello-world example program. @@ -56,5 +56,7 @@ fn main() { // The output is a proposed state difference. It will only succeed if the pre states coincide // with the previous values of the accounts, and the transition to the post states conforms // with the NSSA program rules. - write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]); + // WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be + // called to commit the output. + ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state]).write(); } diff --git a/examples/program_deployment/methods/guest/src/bin/hello_world_with_authorization.rs b/examples/program_deployment/methods/guest/src/bin/hello_world_with_authorization.rs index e327ca47..d90c072b 100644 --- a/examples/program_deployment/methods/guest/src/bin/hello_world_with_authorization.rs +++ b/examples/program_deployment/methods/guest/src/bin/hello_world_with_authorization.rs @@ -1,5 +1,5 @@ use nssa_core::program::{ - AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, + AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs, }; // Hello-world with authorization example program. @@ -63,5 +63,7 @@ fn main() { // The output is a proposed state difference. It will only succeed if the pre states coincide // with the previous values of the accounts, and the transition to the post states conforms // with the NSSA program rules. - write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]); + // WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be + // called to commit the output. + ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state]).write(); } diff --git a/examples/program_deployment/methods/guest/src/bin/hello_world_with_move_function.rs b/examples/program_deployment/methods/guest/src/bin/hello_world_with_move_function.rs index 65f0f9cd..0b2885a8 100644 --- a/examples/program_deployment/methods/guest/src/bin/hello_world_with_move_function.rs +++ b/examples/program_deployment/methods/guest/src/bin/hello_world_with_move_function.rs @@ -1,7 +1,7 @@ use nssa_core::{ account::{Account, AccountWithMetadata, Data}, program::{ - AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, + AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs, }, }; @@ -95,5 +95,7 @@ fn main() { _ => panic!("invalid params"), }; - write_nssa_outputs(instruction_words, pre_states, post_states); + // WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be + // called to commit the output. + ProgramOutput::new(instruction_words, pre_states, post_states).write(); } diff --git a/examples/program_deployment/methods/guest/src/bin/simple_tail_call.rs b/examples/program_deployment/methods/guest/src/bin/simple_tail_call.rs index 01389085..d2c04083 100644 --- a/examples/program_deployment/methods/guest/src/bin/simple_tail_call.rs +++ b/examples/program_deployment/methods/guest/src/bin/simple_tail_call.rs @@ -1,6 +1,5 @@ use nssa_core::program::{ - AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, - write_nssa_outputs_with_chained_call, + AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_nssa_inputs, }; // Tail Call example program. @@ -53,11 +52,10 @@ fn main() { pda_seeds: vec![], }; - // Write the outputs - write_nssa_outputs_with_chained_call( - instruction_data, - vec![pre_state], - vec![post_state], - vec![chained_call], - ); + // Write the outputs. + // WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be + // called to commit the output. + ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state]) + .with_chained_calls(vec![chained_call]) + .write(); } diff --git a/examples/program_deployment/methods/guest/src/bin/tail_call_with_pda.rs b/examples/program_deployment/methods/guest/src/bin/tail_call_with_pda.rs index 3ebcabd2..564efc2b 100644 --- a/examples/program_deployment/methods/guest/src/bin/tail_call_with_pda.rs +++ b/examples/program_deployment/methods/guest/src/bin/tail_call_with_pda.rs @@ -1,6 +1,6 @@ use nssa_core::program::{ - AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs, - write_nssa_outputs_with_chained_call, + AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, + read_nssa_inputs, }; // Tail Call with PDA example program. @@ -65,11 +65,10 @@ fn main() { pda_seeds: vec![PDA_SEED], }; - // Write the outputs - write_nssa_outputs_with_chained_call( - instruction_data, - vec![pre_state], - vec![post_state], - vec![chained_call], - ); + // Write the outputs. + // WARNING: constructing a `ProgramOutput` has no effect on its own. `.write()` must be + // called to commit the output. + ProgramOutput::new(instruction_data, vec![pre_state], vec![post_state]) + .with_chained_calls(vec![chained_call]) + .write(); } diff --git a/explorer_service/src/pages/transaction_page.rs b/explorer_service/src/pages/transaction_page.rs index c0fc0296..ed3d8aac 100644 --- a/explorer_service/src/pages/transaction_page.rs +++ b/explorer_service/src/pages/transaction_page.rs @@ -177,6 +177,7 @@ pub fn TransactionPage() -> impl IntoView { encrypted_private_post_states, new_commitments, new_nullifiers, + validity_window } = message; let WitnessSet { signatures_and_public_keys: _, @@ -212,6 +213,10 @@ pub fn TransactionPage() -> impl IntoView { "Proof Size:" {format!("{proof_len} bytes")} +
+ "Validity Window:" + {validity_window.to_string()} +

"Public Accounts"

diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index f3722b17..e4534f76 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -125,7 +125,7 @@ impl IndexerStore { transaction .clone() .transaction_stateless_check()? - .execute_check_on_state(&mut state_guard)?; + .execute_check_on_state(&mut state_guard, block.header.block_id)?; } } diff --git a/indexer/service/protocol/src/convert.rs b/indexer/service/protocol/src/convert.rs index 6114620f..2777b512 100644 --- a/indexer/service/protocol/src/convert.rs +++ b/indexer/service/protocol/src/convert.rs @@ -7,7 +7,7 @@ use crate::{ CommitmentSetDigest, Data, EncryptedAccountData, EphemeralPublicKey, HashType, MantleMsgId, Nullifier, PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage, ProgramDeploymentTransaction, ProgramId, Proof, PublicKey, PublicMessage, PublicTransaction, - Signature, Transaction, WitnessSet, + Signature, Transaction, ValidityWindow, WitnessSet, }; // ============================================================================ @@ -287,6 +287,7 @@ impl From for PrivacyPre encrypted_private_post_states, new_commitments, new_nullifiers, + validity_window, } = value; Self { public_account_ids: public_account_ids.into_iter().map(Into::into).collect(), @@ -301,12 +302,13 @@ impl From for PrivacyPre .into_iter() .map(|(n, d)| (n.into(), d.into())) .collect(), + validity_window: validity_window.into(), } } } impl TryFrom for nssa::privacy_preserving_transaction::message::Message { - type Error = nssa_core::account::data::DataTooBigError; + type Error = nssa::error::NssaError; fn try_from(value: PrivacyPreservingMessage) -> Result { let PrivacyPreservingMessage { @@ -316,6 +318,7 @@ impl TryFrom for nssa::privacy_preserving_transaction: encrypted_private_post_states, new_commitments, new_nullifiers, + validity_window, } = value; Ok(Self { public_account_ids: public_account_ids.into_iter().map(Into::into).collect(), @@ -326,7 +329,8 @@ impl TryFrom for nssa::privacy_preserving_transaction: public_post_states: public_post_states .into_iter() .map(TryInto::try_into) - .collect::, _>>()?, + .collect::, _>>() + .map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?, encrypted_private_post_states: encrypted_private_post_states .into_iter() .map(Into::into) @@ -336,6 +340,9 @@ impl TryFrom for nssa::privacy_preserving_transaction: .into_iter() .map(|(n, d)| (n.into(), d.into())) .collect(), + validity_window: validity_window + .try_into() + .map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?, }) } } @@ -479,14 +486,7 @@ impl TryFrom for nssa::PrivacyPreservingTransactio witness_set, } = value; - Ok(Self::new( - message - .try_into() - .map_err(|err: nssa_core::account::data::DataTooBigError| { - nssa::error::NssaError::InvalidInput(err.to_string()) - })?, - witness_set.try_into()?, - )) + Ok(Self::new(message.try_into()?, witness_set.try_into()?)) } } @@ -687,3 +687,21 @@ impl From for common::HashType { Self(value.0) } } + +// ============================================================================ +// ValidityWindow conversions +// ============================================================================ + +impl From for ValidityWindow { + fn from(value: nssa_core::program::ValidityWindow) -> Self { + Self((value.start(), value.end())) + } +} + +impl TryFrom for nssa_core::program::ValidityWindow { + type Error = nssa_core::program::InvalidWindow; + + fn try_from(value: ValidityWindow) -> Result { + value.0.try_into() + } +} diff --git a/indexer/service/protocol/src/lib.rs b/indexer/service/protocol/src/lib.rs index d61f62a6..0d8a7e14 100644 --- a/indexer/service/protocol/src/lib.rs +++ b/indexer/service/protocol/src/lib.rs @@ -235,6 +235,7 @@ pub struct PrivacyPreservingMessage { pub encrypted_private_post_states: Vec, pub new_commitments: Vec, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, + pub validity_window: ValidityWindow, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] @@ -300,6 +301,20 @@ pub struct Nullifier( pub [u8; 32], ); +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] +pub struct ValidityWindow(pub (Option, Option)); + +impl Display for ValidityWindow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + (Some(start), Some(end)) => write!(f, "[{start}, {end})"), + (Some(start), None) => write!(f, "[{start}, \u{221e})"), + (None, Some(end)) => write!(f, "(-\u{221e}, {end})"), + (None, None) => write!(f, "(-\u{221e}, \u{221e})"), + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] pub struct CommitmentSetDigest( #[serde(with = "base64::arr")] diff --git a/indexer/service/src/mock_service.rs b/indexer/service/src/mock_service.rs index eb6f11f7..c5891b41 100644 --- a/indexer/service/src/mock_service.rs +++ b/indexer/service/src/mock_service.rs @@ -13,7 +13,7 @@ use indexer_service_protocol::{ CommitmentSetDigest, Data, EncryptedAccountData, HashType, MantleMsgId, PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage, ProgramDeploymentTransaction, ProgramId, PublicMessage, PublicTransaction, Signature, - Transaction, WitnessSet, + Transaction, ValidityWindow, WitnessSet, }; use jsonrpsee::{ core::{SubscriptionResult, async_trait}, @@ -124,6 +124,7 @@ impl MockIndexerService { indexer_service_protocol::Nullifier([tx_idx as u8; 32]), CommitmentSetDigest([0xff; 32]), )], + validity_window: ValidityWindow((None, None)), }, witness_set: WitnessSet { signatures_and_public_keys: vec![], diff --git a/integration_tests/src/config.rs b/integration_tests/src/config.rs index 88c37bde..1dd726eb 100644 --- a/integration_tests/src/config.rs +++ b/integration_tests/src/config.rs @@ -211,7 +211,7 @@ pub fn sequencer_config( max_block_size, mempool_max_size, block_create_timeout, - retry_pending_blocks_timeout: Duration::from_secs(120), + retry_pending_blocks_timeout: Duration::from_mins(2), initial_public_accounts: Some(initial_data.sequencer_initial_public_accounts()), initial_private_accounts: Some(initial_data.sequencer_initial_private_accounts()), signing_key: [37; 32], diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index 56d63022..f9cd9239 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -5,7 +5,7 @@ use crate::{ NullifierSecretKey, SharedSecretKey, account::{Account, AccountWithMetadata}, encryption::Ciphertext, - program::{ProgramId, ProgramOutput}, + program::{ProgramId, ProgramOutput, ValidityWindow}, }; #[derive(Serialize, Deserialize)] @@ -36,6 +36,7 @@ pub struct PrivacyPreservingCircuitOutput { pub ciphertexts: Vec, pub new_commitments: Vec, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, + pub validity_window: ValidityWindow, } #[cfg(feature = "host")] @@ -101,6 +102,7 @@ mod tests { ), [0xab; 32], )], + validity_window: (Some(1), None).try_into().unwrap(), }; let bytes = output.to_bytes(); let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap(); diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 31b76b0f..b9fa5de2 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,5 +1,7 @@ use std::collections::HashSet; +#[cfg(any(feature = "host", test))] +use borsh::{BorshDeserialize, BorshSerialize}; use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer}; use serde::{Deserialize, Serialize}; @@ -151,15 +153,163 @@ impl AccountPostState { } } +pub type BlockId = u64; + +#[derive(Clone, Copy, Serialize, Deserialize)] +#[cfg_attr( + any(feature = "host", test), + derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize) +)] +pub struct ValidityWindow { + from: Option, + to: Option, +} + +impl ValidityWindow { + /// Creates a window with no bounds, valid for every block ID. + #[must_use] + pub const fn new_unbounded() -> Self { + Self { + from: None, + to: None, + } + } + + /// Returns `true` if `id` falls within the half-open range `[from, to)`. + /// A `None` bound on either side is treated as unbounded in that direction. + #[must_use] + pub fn is_valid_for_block_id(&self, id: BlockId) -> bool { + self.from.is_none_or(|start| id >= start) && self.to.is_none_or(|end| id < end) + } + + /// Returns `Err(InvalidWindow)` if both bounds are set and `from >= to`. + const fn check_window(&self) -> Result<(), InvalidWindow> { + if let (Some(from_id), Some(until_id)) = (self.from, self.to) + && from_id >= until_id + { + Err(InvalidWindow) + } else { + Ok(()) + } + } + + /// Inclusive lower bound. `None` means the window starts at the beginning of the chain. + #[must_use] + pub const fn start(&self) -> Option { + self.from + } + + /// Exclusive upper bound. `None` means the window has no expiry. + #[must_use] + pub const fn end(&self) -> Option { + self.to + } +} + +impl TryFrom<(Option, Option)> for ValidityWindow { + type Error = InvalidWindow; + + fn try_from(value: (Option, Option)) -> Result { + let this = Self { + from: value.0, + to: value.1, + }; + this.check_window()?; + Ok(this) + } +} + +impl TryFrom> for ValidityWindow { + type Error = InvalidWindow; + + fn try_from(value: std::ops::Range) -> Result { + (Some(value.start), Some(value.end)).try_into() + } +} + +impl From> for ValidityWindow { + fn from(value: std::ops::RangeFrom) -> Self { + Self { + from: Some(value.start), + to: None, + } + } +} + +impl From> for ValidityWindow { + fn from(value: std::ops::RangeTo) -> Self { + Self { + from: None, + to: Some(value.end), + } + } +} + +impl From for ValidityWindow { + fn from(_: std::ops::RangeFull) -> Self { + Self::new_unbounded() + } +} + +#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)] +#[error("Invalid window")] +pub struct InvalidWindow; + #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +#[must_use = "ProgramOutput does nothing unless written"] pub struct ProgramOutput { /// The instruction data the program received to produce this output. pub instruction_data: InstructionData, /// The account pre states the program received to produce this output. pub pre_states: Vec, + /// The account post states the program execution produced. pub post_states: Vec, + /// The list of chained calls to other programs. pub chained_calls: Vec, + /// The window where the program output is valid. + pub validity_window: ValidityWindow, +} + +impl ProgramOutput { + pub const fn new( + instruction_data: InstructionData, + pre_states: Vec, + post_states: Vec, + ) -> Self { + Self { + instruction_data, + pre_states, + post_states, + chained_calls: Vec::new(), + validity_window: ValidityWindow::new_unbounded(), + } + } + + pub fn write(self) { + env::commit(&self); + } + + pub fn with_chained_calls(mut self, chained_calls: Vec) -> Self { + self.chained_calls = chained_calls; + self + } + + /// Sets the validity window from an infallible range conversion (`1..`, `..5`, `..`). + pub fn with_validity_window>(mut self, window: W) -> Self { + self.validity_window = window.into(); + self + } + + /// Sets the validity window from a fallible range conversion (`1..5`). + /// Returns `Err` if the range is empty. + pub fn try_with_validity_window>( + mut self, + window: W, + ) -> Result { + self.validity_window = window.try_into()?; + Ok(self) + } } /// Representation of a number as `lo + hi * 2^128`. @@ -219,35 +369,6 @@ pub fn read_nssa_inputs() -> (ProgramInput, InstructionD ) } -pub fn write_nssa_outputs( - instruction_data: InstructionData, - pre_states: Vec, - post_states: Vec, -) { - let output = ProgramOutput { - instruction_data, - pre_states, - post_states, - chained_calls: Vec::new(), - }; - env::commit(&output); -} - -pub fn write_nssa_outputs_with_chained_call( - instruction_data: InstructionData, - pre_states: Vec, - post_states: Vec, - chained_calls: Vec, -) { - let output = ProgramOutput { - instruction_data, - pre_states, - post_states, - chained_calls, - }; - env::commit(&output); -} - /// Validates well-behaved program execution. /// /// # Parameters @@ -342,6 +463,132 @@ fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> boo mod tests { use super::*; + #[test] + fn validity_window_unbounded_accepts_any_block() { + let w = ValidityWindow::new_unbounded(); + assert!(w.is_valid_for_block_id(0)); + assert!(w.is_valid_for_block_id(u64::MAX)); + } + + #[test] + fn validity_window_bounded_range_includes_from_excludes_to() { + let w: ValidityWindow = (Some(5), Some(10)).try_into().unwrap(); + assert!(!w.is_valid_for_block_id(4)); + assert!(w.is_valid_for_block_id(5)); + assert!(w.is_valid_for_block_id(9)); + assert!(!w.is_valid_for_block_id(10)); + } + + #[test] + fn validity_window_only_from_bound() { + let w: ValidityWindow = (Some(5), None).try_into().unwrap(); + assert!(!w.is_valid_for_block_id(4)); + assert!(w.is_valid_for_block_id(5)); + assert!(w.is_valid_for_block_id(u64::MAX)); + } + + #[test] + fn validity_window_only_to_bound() { + let w: ValidityWindow = (None, Some(5)).try_into().unwrap(); + assert!(w.is_valid_for_block_id(0)); + assert!(w.is_valid_for_block_id(4)); + assert!(!w.is_valid_for_block_id(5)); + } + + #[test] + fn validity_window_adjacent_bounds_are_invalid() { + // [5, 5) is an empty range — from == to + assert!(ValidityWindow::try_from((Some(5), Some(5))).is_err()); + } + + #[test] + fn validity_window_inverted_bounds_are_invalid() { + assert!(ValidityWindow::try_from((Some(10), Some(5))).is_err()); + } + + #[test] + fn validity_window_getters_match_construction() { + let w: ValidityWindow = (Some(3), Some(7)).try_into().unwrap(); + assert_eq!(w.start(), Some(3)); + assert_eq!(w.end(), Some(7)); + } + + #[test] + fn validity_window_getters_for_unbounded() { + let w = ValidityWindow::new_unbounded(); + assert_eq!(w.start(), None); + assert_eq!(w.end(), None); + } + + #[test] + fn validity_window_from_range() { + let w = ValidityWindow::try_from(5_u64..10).unwrap(); + assert_eq!(w.start(), Some(5)); + assert_eq!(w.end(), Some(10)); + } + + #[test] + fn validity_window_from_range_empty_is_invalid() { + assert!(ValidityWindow::try_from(5_u64..5).is_err()); + } + + #[test] + fn validity_window_from_range_inverted_is_invalid() { + let from = 10_u64; + let to = 5_u64; + assert!(ValidityWindow::try_from(from..to).is_err()); + } + + #[test] + fn validity_window_from_range_from() { + let w: ValidityWindow = (5_u64..).into(); + assert_eq!(w.start(), Some(5)); + assert_eq!(w.end(), None); + } + + #[test] + fn validity_window_from_range_to() { + let w: ValidityWindow = (..10_u64).into(); + assert_eq!(w.start(), None); + assert_eq!(w.end(), Some(10)); + } + + #[test] + fn validity_window_from_range_full() { + let w: ValidityWindow = (..).into(); + assert_eq!(w.start(), None); + assert_eq!(w.end(), None); + } + + #[test] + fn program_output_try_with_validity_window_range() { + let output = ProgramOutput::new(vec![], vec![], vec![]) + .try_with_validity_window(10_u64..100) + .unwrap(); + assert_eq!(output.validity_window.start(), Some(10)); + assert_eq!(output.validity_window.end(), Some(100)); + } + + #[test] + fn program_output_with_validity_window_range_from() { + let output = ProgramOutput::new(vec![], vec![], vec![]).with_validity_window(10_u64..); + assert_eq!(output.validity_window.start(), Some(10)); + assert_eq!(output.validity_window.end(), None); + } + + #[test] + fn program_output_with_validity_window_range_to() { + let output = ProgramOutput::new(vec![], vec![], vec![]).with_validity_window(..100_u64); + assert_eq!(output.validity_window.start(), None); + assert_eq!(output.validity_window.end(), Some(100)); + } + + #[test] + fn program_output_try_with_validity_window_empty_range_fails() { + let result = ProgramOutput::new(vec![], vec![], vec![]).try_with_validity_window(5_u64..5); + assert!(result.is_err()); + } + #[test] fn post_state_new_with_claim_constructor() { let account = Account { diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 3576b366..15d4f044 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -69,6 +69,9 @@ pub enum NssaError { #[error("Max account nonce reached")] MaxAccountNonceReached, + + #[error("Execution outside of the validity window")] + OutOfValidityWindow, } #[cfg(test)] diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 2ab141a3..0ae7eaac 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -174,12 +174,13 @@ mod tests { #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] use nssa_core::{ - Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, + Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, }; use super::*; use crate::{ + error::NssaError, privacy_preserving_transaction::circuit::execute_and_prove, program::Program, state::{ @@ -364,4 +365,46 @@ mod tests { .unwrap(); assert_eq!(recipient_post, expected_private_account_2); } + + #[test] + fn circuit_fails_when_chained_validity_windows_have_empty_intersection() { + let account_keys = test_private_account_keys_1(); + let pre = AccountWithMetadata::new( + Account::default(), + false, + AccountId::from(&account_keys.npk()), + ); + + let validity_window_chain_caller = Program::validity_window_chain_caller(); + let validity_window = Program::validity_window(); + + let instruction = Program::serialize_instruction(( + Some(1_u64), + Some(4_u64), + validity_window.id(), + Some(4_u64), + Some(7_u64), + )) + .unwrap(); + + let esk = [3; 32]; + let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk()); + + let program_with_deps = ProgramWithDependencies::new( + validity_window_chain_caller, + [(validity_window.id(), validity_window)].into(), + ); + + let result = execute_and_prove( + vec![pre], + instruction, + vec![2], + vec![(account_keys.npk(), shared_secret)], + vec![], + vec![None], + &program_with_deps, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 02c7a614..755b54f3 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -3,6 +3,7 @@ use nssa_core::{ Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput, account::{Account, Nonce}, encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey}, + program::ValidityWindow, }; use sha2::{Digest as _, Sha256}; @@ -52,6 +53,7 @@ pub struct Message { pub encrypted_private_post_states: Vec, pub new_commitments: Vec, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, + pub validity_window: ValidityWindow, } impl std::fmt::Debug for Message { @@ -77,6 +79,7 @@ impl std::fmt::Debug for Message { ) .field("new_commitments", &self.new_commitments) .field("new_nullifiers", &nullifiers) + .field("validity_window", &self.validity_window) .finish() } } @@ -109,6 +112,7 @@ impl Message { encrypted_private_post_states, new_commitments: output.new_commitments, new_nullifiers: output.new_nullifiers, + validity_window: output.validity_window, }) } } @@ -161,6 +165,7 @@ pub mod tests { encrypted_private_post_states, new_commitments, new_nullifiers, + validity_window: (None, None).try_into().unwrap(), } } diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index aafbe0cb..b1c30109 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -7,6 +7,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, account::{Account, AccountWithMetadata}, + program::{BlockId, ValidityWindow}, }; use sha2::{Digest as _, digest::FixedOutput as _}; @@ -35,6 +36,7 @@ impl PrivacyPreservingTransaction { pub(crate) fn validate_and_produce_public_state_diff( &self, state: &V03State, + block_id: BlockId, ) -> Result, NssaError> { let message = &self.message; let witness_set = &self.witness_set; @@ -91,6 +93,11 @@ impl PrivacyPreservingTransaction { } } + // Verify validity window + if !message.validity_window.is_valid_for_block_id(block_id) { + return Err(NssaError::OutOfValidityWindow); + } + // Build pre_states for proof verification let public_pre_states: Vec<_> = message .public_account_ids @@ -112,6 +119,7 @@ impl PrivacyPreservingTransaction { &message.encrypted_private_post_states, &message.new_commitments, &message.new_nullifiers, + &message.validity_window, )?; // 5. Commitment freshness @@ -173,6 +181,7 @@ fn check_privacy_preserving_circuit_proof_is_valid( encrypted_private_post_states: &[EncryptedAccountData], new_commitments: &[Commitment], new_nullifiers: &[(Nullifier, CommitmentSetDigest)], + validity_window: &ValidityWindow, ) -> Result<(), NssaError> { let output = PrivacyPreservingCircuitOutput { public_pre_states: public_pre_states.to_vec(), @@ -184,6 +193,7 @@ fn check_privacy_preserving_circuit_proof_is_valid( .collect(), new_commitments: new_commitments.to_vec(), new_nullifiers: new_nullifiers.to_vec(), + validity_window: validity_window.to_owned(), }; proof .is_valid_for(&output) diff --git a/nssa/src/program.rs b/nssa/src/program.rs index a1694260..b87fcf35 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -292,6 +292,20 @@ mod tests { // `program_methods` Self::new(MODIFIED_TRANSFER_ELF.to_vec()).unwrap() } + + #[must_use] + pub fn validity_window() -> Self { + use test_program_methods::VALIDITY_WINDOW_ELF; + // This unwrap won't panic since the `VALIDITY_WINDOW_ELF` comes from risc0 build of + // `program_methods` + Self::new(VALIDITY_WINDOW_ELF.to_vec()).unwrap() + } + + #[must_use] + pub fn validity_window_chain_caller() -> Self { + use test_program_methods::VALIDITY_WINDOW_CHAIN_CALLER_ELF; + Self::new(VALIDITY_WINDOW_CHAIN_CALLER_ELF.to_vec()).unwrap() + } } #[test] diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 8151f8cf..8aaf039e 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use log::debug; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, - program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, + program::{BlockId, ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, }; use sha2::{Digest as _, digest::FixedOutput as _}; @@ -70,6 +70,7 @@ impl PublicTransaction { pub(crate) fn validate_and_produce_public_state_diff( &self, state: &V03State, + block_id: BlockId, ) -> Result, NssaError> { let message = self.message(); let witness_set = self.witness_set(); @@ -190,6 +191,14 @@ impl PublicTransaction { NssaError::InvalidProgramBehavior ); + // Verify validity window + ensure!( + program_output + .validity_window + .is_valid_for_block_id(block_id), + NssaError::OutOfValidityWindow + ); + for post in program_output .post_states .iter_mut() @@ -359,7 +368,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state); + let result = tx.validate_and_produce_public_state_diff(&state, 1); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } @@ -379,7 +388,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state); + let result = tx.validate_and_produce_public_state_diff(&state, 1); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } @@ -400,7 +409,7 @@ pub mod tests { let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state); + let result = tx.validate_and_produce_public_state_diff(&state, 1); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } @@ -420,7 +429,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state); + let result = tx.validate_and_produce_public_state_diff(&state, 1); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } @@ -436,7 +445,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_produce_public_state_diff(&state); + let result = tx.validate_and_produce_public_state_diff(&state, 1); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 5df860f3..9b4b4980 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier, account::{Account, AccountId, Nonce}, - program::ProgramId, + program::{BlockId, ProgramId}, }; use crate::{ @@ -158,8 +158,9 @@ impl V03State { pub fn transition_from_public_transaction( &mut self, tx: &PublicTransaction, + block_id: BlockId, ) -> Result<(), NssaError> { - let state_diff = tx.validate_and_produce_public_state_diff(self)?; + let state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?; #[expect( clippy::iter_over_hash_type, @@ -182,9 +183,10 @@ impl V03State { pub fn transition_from_privacy_preserving_transaction( &mut self, tx: &PrivacyPreservingTransaction, + block_id: BlockId, ) -> Result<(), NssaError> { // 1. Verify the transaction satisfies acceptance criteria - let public_state_diff = tx.validate_and_produce_public_state_diff(self)?; + let public_state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?; let message = tx.message(); @@ -339,7 +341,7 @@ pub mod tests { Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey}, - program::{PdaSeed, ProgramId}, + program::{BlockId, PdaSeed, ProgramId, ValidityWindow}, }; use crate::{ @@ -374,6 +376,7 @@ pub mod tests { self.insert_program(Program::amm()); self.insert_program(Program::claimer()); self.insert_program(Program::changer_claimer()); + self.insert_program(Program::validity_window()); self } @@ -569,7 +572,7 @@ pub mod tests { let balance_to_move = 5; let tx = transfer_transaction(from, &key, 0, to, balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); assert_eq!(state.get_account_by_id(from).balance, 95); assert_eq!(state.get_account_by_id(to).balance, 5); @@ -590,7 +593,7 @@ pub mod tests { assert!(state.get_account_by_id(from).balance < balance_to_move); let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); assert_eq!(state.get_account_by_id(from).balance, 100); @@ -614,7 +617,7 @@ pub mod tests { let balance_to_move = 8; let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); assert_eq!(state.get_account_by_id(from).balance, 192); assert_eq!(state.get_account_by_id(to).balance, 108); @@ -634,10 +637,10 @@ pub mod tests { let balance_to_move = 5; let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let balance_to_move = 3; let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); assert_eq!(state.get_account_by_id(account_id1).balance, 95); assert_eq!(state.get_account_by_id(account_id2).balance, 2); @@ -659,7 +662,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -676,7 +679,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -693,7 +696,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -717,7 +720,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -741,7 +744,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -765,7 +768,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -789,7 +792,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -817,7 +820,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -842,7 +845,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -860,7 +863,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -889,7 +892,7 @@ pub mod tests { .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -1082,7 +1085,7 @@ pub mod tests { assert!(!state.private_state.0.contains(&expected_new_commitment)); state - .transition_from_privacy_preserving_transaction(&tx) + .transition_from_privacy_preserving_transaction(&tx, 1) .unwrap(); let sender_post = state.get_account_by_id(sender_keys.account_id()); @@ -1152,7 +1155,7 @@ pub mod tests { assert!(!state.private_state.1.contains(&expected_new_nullifier)); state - .transition_from_privacy_preserving_transaction(&tx) + .transition_from_privacy_preserving_transaction(&tx, 1) .unwrap(); assert_eq!(state.public_state, previous_public_state); @@ -1216,7 +1219,7 @@ pub mod tests { assert!(!state.private_state.1.contains(&expected_new_nullifier)); state - .transition_from_privacy_preserving_transaction(&tx) + .transition_from_privacy_preserving_transaction(&tx, 1) .unwrap(); let recipient_post = state.get_account_by_id(recipient_keys.account_id()); @@ -2144,7 +2147,7 @@ pub mod tests { ); state - .transition_from_privacy_preserving_transaction(&tx) + .transition_from_privacy_preserving_transaction(&tx, 1) .unwrap(); let sender_private_account = Account { @@ -2162,7 +2165,7 @@ pub mod tests { &state, ); - let result = state.transition_from_privacy_preserving_transaction(&tx); + let result = state.transition_from_privacy_preserving_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidInput(_)))); let NssaError::InvalidInput(error_message) = result.err().unwrap() else { @@ -2239,7 +2242,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let recipient_post = state.get_account_by_id(to); @@ -2282,7 +2285,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let from_post = state.get_account_by_id(from); let to_post = state.get_account_by_id(to); @@ -2322,7 +2325,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!( result, Err(NssaError::MaxChainedCallsDepthExceeded) @@ -2363,7 +2366,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let from_post = state.get_account_by_id(from); let to_post = state.get_account_by_id(to); @@ -2419,7 +2422,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let from_post = state.get_account_by_id(from); let to_post = state.get_account_by_id(to); @@ -2528,7 +2531,7 @@ pub mod tests { let transaction = PrivacyPreservingTransaction::new(message, witness_set); state - .transition_from_privacy_preserving_transaction(&transaction) + .transition_from_privacy_preserving_transaction(&transaction, 1) .unwrap(); // Assert @@ -2584,7 +2587,7 @@ pub mod tests { .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); // Execution of winner's token holding account initialization let instruction = token_core::Instruction::InitializeAccount; @@ -2597,7 +2600,7 @@ pub mod tests { .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); // Submit a solution to the pinata program to claim the prize let solution: u128 = 989_106; @@ -2614,7 +2617,7 @@ pub mod tests { .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id); assert_eq!( @@ -2644,7 +2647,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); } @@ -2690,7 +2693,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]); let tx = PublicTransaction::new(message, witness_set); - let res = state.transition_from_public_transaction(&tx); + let res = state.transition_from_public_transaction(&tx, 1); assert!(matches!(res, Err(NssaError::InvalidProgramBehavior))); let sender_post = state.get_account_by_id(sender_id); @@ -2759,7 +2762,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, proof, &[]); let tx = PrivacyPreservingTransaction::new(message, witness_set); - let result = state.transition_from_privacy_preserving_transaction(&tx); + let result = state.transition_from_privacy_preserving_transaction(&tx, 1); assert!(result.is_ok()); let nullifier = Nullifier::for_account_initialization(&private_keys.npk()); @@ -2812,7 +2815,7 @@ pub mod tests { // Claim should succeed assert!( state - .transition_from_privacy_preserving_transaction(&tx) + .transition_from_privacy_preserving_transaction(&tx, 1) .is_ok() ); @@ -2861,7 +2864,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); // Should succeed - no changes made, no claim needed assert!(result.is_ok()); @@ -2886,7 +2889,7 @@ pub mod tests { let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); + let result = state.transition_from_public_transaction(&tx, 1); // Should fail - cannot modify data without claiming the account assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); @@ -2998,6 +3001,119 @@ pub mod tests { assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } + #[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")] + #[test_case::test_case((Some(1), Some(3)), 2; "inside range")] + #[test_case::test_case((Some(1), Some(3)), 0; "below range")] + #[test_case::test_case((Some(1), Some(3)), 1; "at lower bound")] + #[test_case::test_case((Some(1), Some(3)), 4; "above range")] + #[test_case::test_case((Some(1), None), 1; "lower bound only - at bound")] + #[test_case::test_case((Some(1), None), 10; "lower bound only - above")] + #[test_case::test_case((Some(1), None), 0; "lower bound only - below")] + #[test_case::test_case((None, Some(3)), 3; "upper bound only - at bound")] + #[test_case::test_case((None, Some(3)), 0; "upper bound only - below")] + #[test_case::test_case((None, Some(3)), 4; "upper bound only - above")] + #[test_case::test_case((None, None), 0; "no bounds - always valid")] + #[test_case::test_case((None, None), 100; "no bounds - always valid 2")] + fn validity_window_works_in_public_transactions( + validity_window: (Option, Option), + block_id: BlockId, + ) { + let validity_window: ValidityWindow = validity_window.try_into().unwrap(); + let validity_window_program = Program::validity_window(); + let account_keys = test_public_account_keys_1(); + let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id()); + let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let tx = { + let account_ids = vec![pre.account_id]; + let nonces = vec![]; + let program_id = validity_window_program.id(); + let message = public_transaction::Message::try_new( + program_id, + account_ids, + nonces, + validity_window, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + PublicTransaction::new(message, witness_set) + }; + let result = state.transition_from_public_transaction(&tx, block_id); + let is_inside_validity_window = match (validity_window.start(), validity_window.end()) { + (Some(s), Some(e)) => s <= block_id && block_id < e, + (Some(s), None) => s <= block_id, + (None, Some(e)) => block_id < e, + (None, None) => true, + }; + if is_inside_validity_window { + assert!(result.is_ok()); + } else { + assert!(matches!(result, Err(NssaError::OutOfValidityWindow))); + } + } + + #[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")] + #[test_case::test_case((Some(1), Some(3)), 2; "inside range")] + #[test_case::test_case((Some(1), Some(3)), 0; "below range")] + #[test_case::test_case((Some(1), Some(3)), 1; "at lower bound")] + #[test_case::test_case((Some(1), Some(3)), 4; "above range")] + #[test_case::test_case((Some(1), None), 1; "lower bound only - at bound")] + #[test_case::test_case((Some(1), None), 10; "lower bound only - above")] + #[test_case::test_case((Some(1), None), 0; "lower bound only - below")] + #[test_case::test_case((None, Some(3)), 3; "upper bound only - at bound")] + #[test_case::test_case((None, Some(3)), 0; "upper bound only - below")] + #[test_case::test_case((None, Some(3)), 4; "upper bound only - above")] + #[test_case::test_case((None, None), 0; "no bounds - always valid")] + #[test_case::test_case((None, None), 100; "no bounds - always valid 2")] + fn validity_window_works_in_privacy_preserving_transactions( + validity_window: (Option, Option), + block_id: BlockId, + ) { + let validity_window: ValidityWindow = validity_window.try_into().unwrap(); + let validity_window_program = Program::validity_window(); + let account_keys = test_private_account_keys_1(); + let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk()); + let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let tx = { + let esk = [3; 32]; + let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk()); + let epk = EphemeralPublicKey::from_scalar(esk); + + let (output, proof) = circuit::execute_and_prove( + vec![pre], + Program::serialize_instruction(validity_window).unwrap(), + vec![2], + vec![(account_keys.npk(), shared_secret)], + vec![], + vec![None], + &validity_window_program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![(account_keys.npk(), account_keys.vpk(), epk)], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + PrivacyPreservingTransaction::new(message, witness_set) + }; + let result = state.transition_from_privacy_preserving_transaction(&tx, block_id); + let is_inside_validity_window = match (validity_window.start(), validity_window.end()) { + (Some(s), Some(e)) => s <= block_id && block_id < e, + (Some(s), None) => s <= block_id, + (None, Some(e)) => block_id < e, + (None, None) => true, + }; + if is_inside_validity_window { + assert!(result.is_ok()); + } else { + assert!(matches!(result, Err(NssaError::OutOfValidityWindow))); + } + } + #[test] fn state_serialization_roundtrip() { let account_id_1 = AccountId::new([1; 32]); diff --git a/program_methods/guest/src/bin/amm.rs b/program_methods/guest/src/bin/amm.rs index 00fd39d3..748630d9 100644 --- a/program_methods/guest/src/bin/amm.rs +++ b/program_methods/guest/src/bin/amm.rs @@ -9,7 +9,7 @@ use std::num::NonZero; use amm_core::Instruction; -use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call}; +use nssa_core::program::{ProgramInput, ProgramOutput, read_nssa_inputs}; fn main() { let ( @@ -133,10 +133,7 @@ fn main() { } }; - write_nssa_outputs_with_chained_call( - instruction_words, - pre_states_clone, - post_states, - chained_calls, - ); + ProgramOutput::new(instruction_words, pre_states_clone, post_states) + .with_chained_calls(chained_calls) + .write(); } diff --git a/program_methods/guest/src/bin/associated_token_account.rs b/program_methods/guest/src/bin/associated_token_account.rs index 89fb171b..55d5824b 100644 --- a/program_methods/guest/src/bin/associated_token_account.rs +++ b/program_methods/guest/src/bin/associated_token_account.rs @@ -1,5 +1,5 @@ use ata_core::Instruction; -use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call}; +use nssa_core::program::{ProgramInput, ProgramOutput, read_nssa_inputs}; fn main() { let ( @@ -56,10 +56,7 @@ fn main() { } }; - write_nssa_outputs_with_chained_call( - instruction_words, - pre_states_clone, - post_states, - chained_calls, - ); + ProgramOutput::new(instruction_words, pre_states_clone, post_states) + .with_chained_calls(chained_calls) + .write(); } diff --git a/program_methods/guest/src/bin/authenticated_transfer.rs b/program_methods/guest/src/bin/authenticated_transfer.rs index 7835f733..20f4dd68 100644 --- a/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/program_methods/guest/src/bin/authenticated_transfer.rs @@ -1,7 +1,7 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, program::{ - AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, + AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs, }, }; @@ -84,5 +84,5 @@ fn main() { _ => panic!("invalid params"), }; - write_nssa_outputs(instruction_words, pre_states, post_states); + ProgramOutput::new(instruction_words, pre_states, post_states).write(); } diff --git a/program_methods/guest/src/bin/pinata.rs b/program_methods/guest/src/bin/pinata.rs index c9fc0735..cfe0a7e4 100644 --- a/program_methods/guest/src/bin/pinata.rs +++ b/program_methods/guest/src/bin/pinata.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; use risc0_zkvm::sha::{Impl, Sha256 as _}; const PRIZE: u128 = 150; @@ -78,12 +78,13 @@ fn main() { .checked_add(PRIZE) .expect("Overflow when adding prize to winner"); - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![pinata, winner], vec![ AccountPostState::new_claimed_if_default(pinata_post), AccountPostState::new(winner_post), ], - ); + ) + .write(); } diff --git a/program_methods/guest/src/bin/pinata_token.rs b/program_methods/guest/src/bin/pinata_token.rs index f1bbdc87..3dee05b7 100644 --- a/program_methods/guest/src/bin/pinata_token.rs +++ b/program_methods/guest/src/bin/pinata_token.rs @@ -1,8 +1,7 @@ use nssa_core::{ account::Data, program::{ - AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs, - write_nssa_outputs_with_chained_call, + AccountPostState, ChainedCall, PdaSeed, ProgramInput, ProgramOutput, read_nssa_inputs, }, }; use risc0_zkvm::sha::{Impl, Sha256 as _}; @@ -97,7 +96,7 @@ fn main() { ) .with_pda_seeds(vec![PdaSeed::new([0; 32])]); - write_nssa_outputs_with_chained_call( + ProgramOutput::new( instruction_words, vec![ pinata_definition, @@ -109,6 +108,7 @@ fn main() { AccountPostState::new(pinata_token_holding_post), AccountPostState::new(winner_token_holding_post), ], - vec![chained_call], - ); + ) + .with_chained_calls(vec![chained_call]) + .write(); } diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 99782d7f..c1119dd8 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -11,7 +11,7 @@ use nssa_core::{ compute_digest_for_path, program::{ AccountPostState, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, ProgramId, - ProgramOutput, validate_execution, + ProgramOutput, ValidityWindow, validate_execution, }, }; use risc0_zkvm::{guest::env, serde::to_vec}; @@ -20,11 +20,31 @@ use risc0_zkvm::{guest::env, serde::to_vec}; struct ExecutionState { pre_states: Vec, post_states: HashMap, + validity_window: ValidityWindow, } impl ExecutionState { /// Validate program outputs and derive the overall execution state. pub fn derive_from_outputs(program_id: ProgramId, program_outputs: Vec) -> Self { + let valid_from_id = program_outputs + .iter() + .filter_map(|output| output.validity_window.start()) + .max(); + let valid_until_id = program_outputs + .iter() + .filter_map(|output| output.validity_window.end()) + .min(); + + let validity_window = (valid_from_id, valid_until_id).try_into().expect( + "There should be non empty intersection in the program output validity windows", + ); + + let mut execution_state = Self { + pre_states: Vec::new(), + post_states: HashMap::new(), + validity_window, + }; + let Some(first_output) = program_outputs.first() else { panic!("No program outputs provided"); }; @@ -37,11 +57,6 @@ impl ExecutionState { }; let mut chained_calls = VecDeque::from_iter([(initial_call, None)]); - let mut execution_state = Self { - pre_states: Vec::new(), - post_states: HashMap::new(), - }; - let mut program_outputs_iter = program_outputs.into_iter(); let mut chain_calls_counter = 0; @@ -210,6 +225,7 @@ fn compute_circuit_output( ciphertexts: Vec::new(), new_commitments: Vec::new(), new_nullifiers: Vec::new(), + validity_window: execution_state.validity_window, }; let states_iter = execution_state.into_states_iter(); diff --git a/program_methods/guest/src/bin/token.rs b/program_methods/guest/src/bin/token.rs index 0bc3d245..421d43ef 100644 --- a/program_methods/guest/src/bin/token.rs +++ b/program_methods/guest/src/bin/token.rs @@ -6,7 +6,7 @@ //! Token program accepts [`Instruction`] as input, refer to the corresponding documentation //! for more details. -use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{ProgramInput, ProgramOutput, read_nssa_inputs}; use token_program::core::Instruction; fn main() { @@ -81,5 +81,5 @@ fn main() { } }; - write_nssa_outputs(instruction_words, pre_states_clone, post_states); + ProgramOutput::new(instruction_words, pre_states_clone, post_states).write(); } diff --git a/programs/amm/src/tests.rs b/programs/amm/src/tests.rs index d5c8e7b0..86fdb4ff 100644 --- a/programs/amm/src/tests.rs +++ b/programs/amm/src/tests.rs @@ -2733,7 +2733,7 @@ fn simple_amm_remove() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -2813,7 +2813,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -2897,7 +2897,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -2969,7 +2969,7 @@ fn simple_amm_new_definition_uninitialized_pool() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -3031,7 +3031,7 @@ fn simple_amm_add() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -3088,7 +3088,7 @@ fn simple_amm_swap_1() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); @@ -3138,7 +3138,7 @@ fn simple_amm_swap_2() { ); let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx).unwrap(); + state.transition_from_public_transaction(&tx, 1).unwrap(); let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id()); let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id()); diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index 81d44142..545c63fa 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -167,10 +167,12 @@ impl SequencerCore Result { match &tx { - NSSATransaction::Public(tx) => self.state.transition_from_public_transaction(tx), + NSSATransaction::Public(tx) => self + .state + .transition_from_public_transaction(tx, self.next_block_id()), NSSATransaction::PrivacyPreserving(tx) => self .state - .transition_from_privacy_preserving_transaction(tx), + .transition_from_privacy_preserving_transaction(tx, self.next_block_id()), NSSATransaction::ProgramDeployment(tx) => self .state .transition_from_program_deployment_transaction(tx), @@ -204,10 +206,7 @@ impl SequencerCore Result<(SignedMantleTx, MsgId)> { let now = Instant::now(); - let new_block_height = self - .chain_height - .checked_add(1) - .with_context(|| format!("Max block height reached: {}", self.chain_height))?; + let new_block_height = self.next_block_id(); let mut valid_transactions = vec![]; @@ -354,6 +353,12 @@ impl SequencerCore IC { self.indexer_client.clone() } + + fn next_block_id(&self) -> u64 { + self.chain_height + .checked_add(1) + .unwrap_or_else(|| panic!("Max block height reached: {}", self.chain_height)) + } } /// Load signing key from file or generate a new one if it doesn't exist. @@ -418,7 +423,7 @@ mod tests { node_url: "http://not-used-in-unit-tests".parse().unwrap(), auth: None, }, - retry_pending_blocks_timeout: Duration::from_secs(60 * 4), + retry_pending_blocks_timeout: Duration::from_mins(4), indexer_rpc_url: "ws://localhost:8779".parse().unwrap(), initial_public_accounts: None, initial_private_accounts: None, diff --git a/storage/src/indexer/mod.rs b/storage/src/indexer/mod.rs index 299d3484..b8ff5c51 100644 --- a/storage/src/indexer/mod.rs +++ b/storage/src/indexer/mod.rs @@ -188,7 +188,7 @@ impl RocksDBIO { "transaction pre check failed with err {err:?}" )) })? - .execute_check_on_state(&mut breakpoint) + .execute_check_on_state(&mut breakpoint, block.header.block_id) .map_err(|err| { DbError::db_interaction_error(format!( "transaction execution failed with err {err:?}" diff --git a/test_program_methods/guest/src/bin/burner.rs b/test_program_methods/guest/src/bin/burner.rs index a2256aa3..991091c0 100644 --- a/test_program_methods/guest/src/bin/burner.rs +++ b/test_program_methods/guest/src/bin/burner.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = u128; @@ -19,9 +19,10 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance = account_post.balance.saturating_sub(balance_to_burn); - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![pre], vec![AccountPostState::new(account_post)], - ); + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/chain_caller.rs b/test_program_methods/guest/src/bin/chain_caller.rs index 7e67fa9b..98154258 100644 --- a/test_program_methods/guest/src/bin/chain_caller.rs +++ b/test_program_methods/guest/src/bin/chain_caller.rs @@ -1,6 +1,6 @@ use nssa_core::program::{ - AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs, - write_nssa_outputs_with_chained_call, + AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, + read_nssa_inputs, }; use risc0_zkvm::serde::to_vec; @@ -54,13 +54,14 @@ fn main() { }; } - write_nssa_outputs_with_chained_call( + ProgramOutput::new( instruction_words, vec![sender_pre.clone(), recipient_pre.clone()], vec![ AccountPostState::new(sender_pre.account), AccountPostState::new(recipient_pre.account), ], - chained_calls, - ); + ) + .with_chained_calls(chained_calls) + .write(); } diff --git a/test_program_methods/guest/src/bin/changer_claimer.rs b/test_program_methods/guest/src/bin/changer_claimer.rs index 37079737..fb5505f7 100644 --- a/test_program_methods/guest/src/bin/changer_claimer.rs +++ b/test_program_methods/guest/src/bin/changer_claimer.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = (Option>, bool); @@ -33,5 +33,5 @@ fn main() { AccountPostState::new(account_post) }; - write_nssa_outputs(instruction_words, vec![pre], vec![post_state]); + ProgramOutput::new(instruction_words, vec![pre], vec![post_state]).write(); } diff --git a/test_program_methods/guest/src/bin/claimer.rs b/test_program_methods/guest/src/bin/claimer.rs index 897ca6a6..57e7e4e5 100644 --- a/test_program_methods/guest/src/bin/claimer.rs +++ b/test_program_methods/guest/src/bin/claimer.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = (); @@ -17,5 +17,5 @@ fn main() { let account_post = AccountPostState::new_claimed(pre.account.clone()); - write_nssa_outputs(instruction_words, vec![pre], vec![account_post]); + ProgramOutput::new(instruction_words, vec![pre], vec![account_post]).write(); } diff --git a/test_program_methods/guest/src/bin/data_changer.rs b/test_program_methods/guest/src/bin/data_changer.rs index c689dce5..55f4e2a0 100644 --- a/test_program_methods/guest/src/bin/data_changer.rs +++ b/test_program_methods/guest/src/bin/data_changer.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = Vec; @@ -22,9 +22,10 @@ fn main() { .try_into() .expect("provided data should fit into data limit"); - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![pre], vec![AccountPostState::new_claimed(account_post)], - ); + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/extra_output.rs b/test_program_methods/guest/src/bin/extra_output.rs index 4d67df6e..3adc591c 100644 --- a/test_program_methods/guest/src/bin/extra_output.rs +++ b/test_program_methods/guest/src/bin/extra_output.rs @@ -1,6 +1,6 @@ use nssa_core::{ account::Account, - program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}, + program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}, }; type Instruction = (); @@ -14,12 +14,13 @@ fn main() { let account_pre = pre.account.clone(); - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![pre], vec![ AccountPostState::new(account_pre), AccountPostState::new(Account::default()), ], - ); + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/malicious_authorization_changer.rs b/test_program_methods/guest/src/bin/malicious_authorization_changer.rs index 56ba7e72..7452d337 100644 --- a/test_program_methods/guest/src/bin/malicious_authorization_changer.rs +++ b/test_program_methods/guest/src/bin/malicious_authorization_changer.rs @@ -1,8 +1,7 @@ use nssa_core::{ account::AccountWithMetadata, program::{ - AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, - write_nssa_outputs_with_chained_call, + AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_nssa_inputs, }, }; use risc0_zkvm::serde::to_vec; @@ -40,13 +39,14 @@ fn main() { pda_seeds: vec![], }; - write_nssa_outputs_with_chained_call( + ProgramOutput::new( instruction_words, vec![sender.clone(), receiver.clone()], vec![ AccountPostState::new(sender.account), AccountPostState::new(receiver.account), ], - vec![chained_call], - ); + ) + .with_chained_calls(vec![chained_call]) + .write(); } diff --git a/test_program_methods/guest/src/bin/minter.rs b/test_program_methods/guest/src/bin/minter.rs index a602df56..ac29e4d3 100644 --- a/test_program_methods/guest/src/bin/minter.rs +++ b/test_program_methods/guest/src/bin/minter.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = (); @@ -16,9 +16,10 @@ fn main() { .checked_add(1) .expect("Balance overflow"); - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![pre], vec![AccountPostState::new(account_post)], - ); + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/missing_output.rs b/test_program_methods/guest/src/bin/missing_output.rs index 52ca6e2f..b485e87a 100644 --- a/test_program_methods/guest/src/bin/missing_output.rs +++ b/test_program_methods/guest/src/bin/missing_output.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = (); @@ -11,9 +11,10 @@ fn main() { let account_pre1 = pre1.account.clone(); - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![pre1, pre2], vec![AccountPostState::new(account_pre1)], - ); + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/modified_transfer.rs b/test_program_methods/guest/src/bin/modified_transfer.rs index 3aee3816..a89c72fb 100644 --- a/test_program_methods/guest/src/bin/modified_transfer.rs +++ b/test_program_methods/guest/src/bin/modified_transfer.rs @@ -5,7 +5,7 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, - program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}, + program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}, }; /// Initializes a default account under the ownership of this program. @@ -80,5 +80,5 @@ fn main() { } _ => panic!("invalid params"), }; - write_nssa_outputs(instruction_data, pre_states, post_states); + ProgramOutput::new(instruction_data, pre_states, post_states).write(); } diff --git a/test_program_methods/guest/src/bin/nonce_changer.rs b/test_program_methods/guest/src/bin/nonce_changer.rs index 52d2e392..0cecdc81 100644 --- a/test_program_methods/guest/src/bin/nonce_changer.rs +++ b/test_program_methods/guest/src/bin/nonce_changer.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = (); @@ -13,9 +13,10 @@ fn main() { let mut account_post = account_pre.clone(); account_post.nonce.public_account_nonce_increment(); - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![pre], vec![AccountPostState::new(account_post)], - ); + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/noop.rs b/test_program_methods/guest/src/bin/noop.rs index 79dd1dec..35a07765 100644 --- a/test_program_methods/guest/src/bin/noop.rs +++ b/test_program_methods/guest/src/bin/noop.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = (); @@ -9,5 +9,5 @@ fn main() { .iter() .map(|account| AccountPostState::new(account.account.clone())) .collect(); - write_nssa_outputs(instruction_words, pre_states, post_states); + ProgramOutput::new(instruction_words, pre_states, post_states).write(); } diff --git a/test_program_methods/guest/src/bin/program_owner_changer.rs b/test_program_methods/guest/src/bin/program_owner_changer.rs index 4b7de0f7..7e421351 100644 --- a/test_program_methods/guest/src/bin/program_owner_changer.rs +++ b/test_program_methods/guest/src/bin/program_owner_changer.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = (); @@ -13,9 +13,10 @@ fn main() { let mut account_post = account_pre.clone(); account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7]; - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![pre], vec![AccountPostState::new(account_post)], - ); + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/test_program_methods/guest/src/bin/simple_balance_transfer.rs index 55bbfcef..9ee715e8 100644 --- a/test_program_methods/guest/src/bin/simple_balance_transfer.rs +++ b/test_program_methods/guest/src/bin/simple_balance_transfer.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs}; type Instruction = u128; @@ -26,12 +26,13 @@ fn main() { .checked_add(balance) .expect("Overflow when adding balance"); - write_nssa_outputs( + ProgramOutput::new( instruction_words, vec![sender_pre, receiver_pre], vec![ AccountPostState::new(sender_post), AccountPostState::new(receiver_post), ], - ); + ) + .write(); } diff --git a/test_program_methods/guest/src/bin/validity_window.rs b/test_program_methods/guest/src/bin/validity_window.rs new file mode 100644 index 00000000..00e8e5e8 --- /dev/null +++ b/test_program_methods/guest/src/bin/validity_window.rs @@ -0,0 +1,29 @@ +use nssa_core::program::{ + AccountPostState, ProgramInput, ProgramOutput, ValidityWindow, read_nssa_inputs, +}; + +type Instruction = ValidityWindow; + +fn main() { + let ( + ProgramInput { + pre_states, + instruction: validity_window, + }, + instruction_words, + ) = read_nssa_inputs::(); + + let Ok([pre]) = <[_; 1]>::try_from(pre_states) else { + return; + }; + + let post = pre.account.clone(); + + ProgramOutput::new( + instruction_words, + vec![pre], + vec![AccountPostState::new(post)], + ) + .with_validity_window(validity_window) + .write(); +} diff --git a/test_program_methods/guest/src/bin/validity_window_chain_caller.rs b/test_program_methods/guest/src/bin/validity_window_chain_caller.rs new file mode 100644 index 00000000..cbd110dd --- /dev/null +++ b/test_program_methods/guest/src/bin/validity_window_chain_caller.rs @@ -0,0 +1,42 @@ +use nssa_core::program::{ + AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, ValidityWindow, + read_nssa_inputs, +}; +use risc0_zkvm::serde::to_vec; + +/// A program that sets a validity window on its output and chains to another program with a +/// potentially different validity window. +/// +/// Instruction: (`window`, `chained_program_id`, `chained_window`) +/// The initial output uses `window` and chains to `chained_program_id` with `chained_window`. +type Instruction = (ValidityWindow, ProgramId, ValidityWindow); + +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (validity_window, chained_program_id, chained_validity_window), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let [pre] = <[_; 1]>::try_from(pre_states.clone()).expect("Expected exactly one pre state"); + let post = pre.account.clone(); + + let chained_instruction = to_vec(&chained_validity_window).unwrap(); + let chained_call = ChainedCall { + program_id: chained_program_id, + instruction_data: chained_instruction, + pre_states, + pda_seeds: vec![], + }; + + ProgramOutput::new( + instruction_words, + vec![pre], + vec![AccountPostState::new(post)], + ) + .with_validity_window(validity_window) + .with_chained_calls(vec![chained_call]) + .write(); +} diff --git a/wallet-ffi/src/pinata.rs b/wallet-ffi/src/pinata.rs index 7ec2fc48..5807db7b 100644 --- a/wallet-ffi/src/pinata.rs +++ b/wallet-ffi/src/pinata.rs @@ -77,9 +77,7 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata( match block_on(pinata.claim(pinata_id, winner_id, solution)) { Ok(tx_hash) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); - + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; (*out_result).success = true; @@ -184,8 +182,7 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata_private_owned_already_initializ ) { Ok((tx_hash, _shared_key)) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; @@ -270,8 +267,7 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata_private_owned_not_initialized( match block_on(pinata.claim_private_owned_account(pinata_id, winner_id, solution)) { Ok((tx_hash, _shared_key)) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs index 5b1e27d2..739832ae 100644 --- a/wallet-ffi/src/transfer.rs +++ b/wallet-ffi/src/transfer.rs @@ -75,8 +75,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_public( match block_on(transfer.send_public_transfer(from_id, to_id, amount)) { Ok(tx_hash) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; @@ -165,8 +164,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded( ) { Ok((tx_hash, _shared_key)) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; @@ -246,8 +244,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_deshielded( match block_on(transfer.send_deshielded_transfer(from_id, to_id, amount)) { Ok((tx_hash, _shared_key)) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; @@ -335,8 +332,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private( { Ok((tx_hash, _shared_key)) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; @@ -419,8 +415,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_shielded_owned( match block_on(transfer.send_shielded_transfer(from_id, to_id, amount)) { Ok((tx_hash, _shared_key)) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; @@ -503,8 +498,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_private_owned( match block_on(transfer.send_private_transfer_to_owned_account(from_id, to_id, amount)) { Ok((tx_hash, _shared_keys)) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; @@ -575,8 +569,7 @@ pub unsafe extern "C" fn wallet_ffi_register_public_account( match block_on(transfer.register_account(account_id)) { Ok(tx_hash) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash; @@ -647,8 +640,7 @@ pub unsafe extern "C" fn wallet_ffi_register_private_account( match block_on(transfer.register_account_private(account_id)) { Ok((tx_hash, _secret)) => { let tx_hash = CString::new(tx_hash.to_string()) - .map(std::ffi::CString::into_raw) - .unwrap_or(ptr::null_mut()); + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); unsafe { (*out_result).tx_hash = tx_hash;