mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-03-27 20:53:12 +00:00
Merge pull request #400 from logos-blockchain/schouhy/add-block-context
Add validity windows to program output
This commit is contained in:
commit
085ca69e42
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -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]]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
artifacts/program_methods/clock.bin
Normal file
BIN
artifacts/program_methods/clock.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
artifacts/test_program_methods/validity_window.bin
Normal file
BIN
artifacts/test_program_methods/validity_window.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/validity_window_chain_caller.bin
Normal file
BIN
artifacts/test_program_methods/validity_window_chain_caller.bin
Normal file
Binary file not shown.
@ -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")?;
|
||||
|
||||
|
||||
@ -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<Self, nssa::error::NssaError> {
|
||||
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:#?}"))?;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
<span class="info-label">"Proof Size:"</span>
|
||||
<span class="info-value">{format!("{proof_len} bytes")}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">"Validity Window:"</span>
|
||||
<span class="info-value">{validity_window.to_string()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>"Public Accounts"</h3>
|
||||
|
||||
@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<nssa::privacy_preserving_transaction::message::Message> 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<nssa::privacy_preserving_transaction::message::Message> for PrivacyPre
|
||||
.into_iter()
|
||||
.map(|(n, d)| (n.into(), d.into()))
|
||||
.collect(),
|
||||
validity_window: validity_window.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PrivacyPreservingMessage> 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<Self, Self::Error> {
|
||||
let PrivacyPreservingMessage {
|
||||
@ -316,6 +318,7 @@ impl TryFrom<PrivacyPreservingMessage> 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<PrivacyPreservingMessage> for nssa::privacy_preserving_transaction:
|
||||
public_post_states: public_post_states
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.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<PrivacyPreservingMessage> 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<PrivacyPreservingTransaction> 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<HashType> for common::HashType {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ValidityWindow conversions
|
||||
// ============================================================================
|
||||
|
||||
impl From<nssa_core::program::ValidityWindow> for ValidityWindow {
|
||||
fn from(value: nssa_core::program::ValidityWindow) -> Self {
|
||||
Self((value.start(), value.end()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ValidityWindow> for nssa_core::program::ValidityWindow {
|
||||
type Error = nssa_core::program::InvalidWindow;
|
||||
|
||||
fn try_from(value: ValidityWindow) -> Result<Self, Self::Error> {
|
||||
value.0.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,6 +235,7 @@ pub struct PrivacyPreservingMessage {
|
||||
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
||||
pub new_commitments: Vec<Commitment>,
|
||||
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<BlockId>, Option<BlockId>));
|
||||
|
||||
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")]
|
||||
|
||||
@ -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![],
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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<Ciphertext>,
|
||||
pub new_commitments: Vec<Commitment>,
|
||||
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();
|
||||
|
||||
@ -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<BlockId>,
|
||||
to: Option<BlockId>,
|
||||
}
|
||||
|
||||
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<BlockId> {
|
||||
self.from
|
||||
}
|
||||
|
||||
/// Exclusive upper bound. `None` means the window has no expiry.
|
||||
#[must_use]
|
||||
pub const fn end(&self) -> Option<BlockId> {
|
||||
self.to
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(Option<BlockId>, Option<BlockId>)> for ValidityWindow {
|
||||
type Error = InvalidWindow;
|
||||
|
||||
fn try_from(value: (Option<BlockId>, Option<BlockId>)) -> Result<Self, Self::Error> {
|
||||
let this = Self {
|
||||
from: value.0,
|
||||
to: value.1,
|
||||
};
|
||||
this.check_window()?;
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<std::ops::Range<BlockId>> for ValidityWindow {
|
||||
type Error = InvalidWindow;
|
||||
|
||||
fn try_from(value: std::ops::Range<BlockId>) -> Result<Self, Self::Error> {
|
||||
(Some(value.start), Some(value.end)).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::ops::RangeFrom<BlockId>> for ValidityWindow {
|
||||
fn from(value: std::ops::RangeFrom<BlockId>) -> Self {
|
||||
Self {
|
||||
from: Some(value.start),
|
||||
to: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::ops::RangeTo<BlockId>> for ValidityWindow {
|
||||
fn from(value: std::ops::RangeTo<BlockId>) -> Self {
|
||||
Self {
|
||||
from: None,
|
||||
to: Some(value.end),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::ops::RangeFull> 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<AccountWithMetadata>,
|
||||
/// The account post states the program execution produced.
|
||||
pub post_states: Vec<AccountPostState>,
|
||||
/// The list of chained calls to other programs.
|
||||
pub chained_calls: Vec<ChainedCall>,
|
||||
/// The window where the program output is valid.
|
||||
pub validity_window: ValidityWindow,
|
||||
}
|
||||
|
||||
impl ProgramOutput {
|
||||
pub const fn new(
|
||||
instruction_data: InstructionData,
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
post_states: Vec<AccountPostState>,
|
||||
) -> 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<ChainedCall>) -> Self {
|
||||
self.chained_calls = chained_calls;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the validity window from an infallible range conversion (`1..`, `..5`, `..`).
|
||||
pub fn with_validity_window<W: Into<ValidityWindow>>(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<W: TryInto<ValidityWindow, Error = InvalidWindow>>(
|
||||
mut self,
|
||||
window: W,
|
||||
) -> Result<Self, InvalidWindow> {
|
||||
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<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionD
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_nssa_outputs(
|
||||
instruction_data: InstructionData,
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
post_states: Vec<AccountPostState>,
|
||||
) {
|
||||
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<AccountWithMetadata>,
|
||||
post_states: Vec<AccountPostState>,
|
||||
chained_calls: Vec<ChainedCall>,
|
||||
) {
|
||||
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 {
|
||||
|
||||
@ -69,6 +69,9 @@ pub enum NssaError {
|
||||
|
||||
#[error("Max account nonce reached")]
|
||||
MaxAccountNonceReached,
|
||||
|
||||
#[error("Execution outside of the validity window")]
|
||||
OutOfValidityWindow,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -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(_))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<EncryptedAccountData>,
|
||||
pub new_commitments: Vec<Commitment>,
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<HashMap<AccountId, Account>, 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)
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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<HashMap<AccountId, Account>, 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(_))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<BlockId>, Option<BlockId>),
|
||||
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<BlockId>, Option<BlockId>),
|
||||
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]);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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<AccountWithMetadata>,
|
||||
post_states: HashMap<AccountId, Account>,
|
||||
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<ProgramOutput>) -> 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();
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -167,10 +167,12 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
||||
tx: NSSATransaction,
|
||||
) -> Result<NSSATransaction, nssa::error::NssaError> {
|
||||
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<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
||||
) -> 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<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
||||
pub fn indexer_client(&self) -> 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,
|
||||
|
||||
@ -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:?}"
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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<Vec<u8>>, 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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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<u8>;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
29
test_program_methods/guest/src/bin/validity_window.rs
Normal file
29
test_program_methods/guest/src/bin/validity_window.rs
Normal file
@ -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::<Instruction>();
|
||||
|
||||
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();
|
||||
}
|
||||
@ -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::<Instruction>();
|
||||
|
||||
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();
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user