diff --git a/Cargo.lock b/Cargo.lock index b42cdafbee..1888a1906b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12214,6 +12214,7 @@ dependencies = [ "sha2 0.10.9", "tari_common", "tari_common_types", + "tari_consensus", "tari_crypto", "tari_engine", "tari_engine_types", diff --git a/applications/tari_indexer/src/storage_sqlite/store_factory.rs b/applications/tari_indexer/src/storage_sqlite/store_factory.rs index c1c70f61fb..c50dd3f2c5 100644 --- a/applications/tari_indexer/src/storage_sqlite/store_factory.rs +++ b/applications/tari_indexer/src/storage_sqlite/store_factory.rs @@ -333,7 +333,7 @@ mod tests { let (_dir, store) = temp_store().await; - let transaction = Transaction::builder_localnet().build_and_seal(&PrivateKey::from(123u64)); + let transaction = Transaction::builder_localnet(Epoch(1)).build_and_seal(&PrivateKey::from(123u64)); let tx_id = transaction.calculate_id(); store .with_write_tx(move |tx| tx.insert_or_ignore_transaction(&transaction)) diff --git a/applications/tari_indexer/web_ui/src/routes/Transaction/components/Result.tsx b/applications/tari_indexer/web_ui/src/routes/Transaction/components/Result.tsx index 00dbe7dd26..d332d2983c 100644 --- a/applications/tari_indexer/web_ui/src/routes/Transaction/components/Result.tsx +++ b/applications/tari_indexer/web_ui/src/routes/Transaction/components/Result.tsx @@ -44,11 +44,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { saveAs } from "file-saver"; import { useState } from "react"; import { useGetTransaction, useGetTransactionResult } from "../../../api/hooks/useTransactions"; -import { - Accordion, - AccordionDetails, - AccordionSummary, -} from "../../../Components/Accordion"; +import { Accordion, AccordionDetails, AccordionSummary } from "../../../Components/Accordion"; import FetchStatusCheck from "../../../Components/FetchStatusCheck"; import StatusChip from "../../../Components/StatusChip"; import { DataTableCell } from "../../../Components/StyledComponents"; @@ -122,20 +118,15 @@ function Result({ transaction_id }: IndexerGetTransactionResultRequest) { } const execResult: any = - data?.result && isFinalized(data.result) - ? data.result.Finalized.execution_result?.finalize?.result - : undefined; + data?.result && isFinalized(data.result) ? data.result.Finalized.execution_result?.finalize?.result : undefined; const rejected = data?.result && isRejected(data.result) ? data.result.Rejected : undefined; const handleChange = (panel: string) => (_event: React.SyntheticEvent, isExpanded: boolean) => { - setExpandedPanels((prev) => - isExpanded ? [...prev, panel] : prev.filter((p) => p !== panel), - ); + setExpandedPanels((prev) => (isExpanded ? [...prev, panel] : prev.filter((p) => p !== panel))); }; - const expandAll = () => - setExpandedPanels(["p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10"]); + const expandAll = () => setExpandedPanels(["p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10"]); const collapseAll = () => setExpandedPanels([]); @@ -205,6 +196,18 @@ function Result({ transaction_id }: IndexerGetTransactionResultRequest) { {data.result.Finalized.abort_details} )} + {transaction?.min_epoch != null && ( + + Min Epoch + {transaction.min_epoch.toString()} + + )} + {transaction?.max_epoch != null && ( + + Max Epoch + {transaction.max_epoch.toString()} + + )} Download @@ -278,15 +281,10 @@ function Result({ transaction_id }: IndexerGetTransactionResultRequest) { {/* Blobs */} - - Blobs ({transaction?.blob_hashes?.length ?? 0}) - + Blobs ({transaction?.blob_hashes?.length ?? 0}) - + diff --git a/applications/tari_validator_node/src/bootstrap.rs b/applications/tari_validator_node/src/bootstrap.rs index 1d2087b312..88f28695de 100644 --- a/applications/tari_validator_node/src/bootstrap.rs +++ b/applications/tari_validator_node/src/bootstrap.rs @@ -36,6 +36,7 @@ use tari_consensus::consensus_constants::ConsensusConstants; #[cfg(not(feature = "metrics"))] use tari_consensus::traits::hooks::NoopHooks; use tari_crypto::tari_utilities::ByteArray; +use tari_engine_types::Epoch; use tari_epoch_manager::{ EpochManagerReader, service::{EpochManagerConfig, EpochManagerHandle}, @@ -81,6 +82,7 @@ use tari_ootle_transaction_validation::{ BasicValidations, BlobReferenceValidator, EpochRangeValidator, + NoopValidator, PublishTemplateLimitValidator, SignatureLimitValidator, StealthTransactionLimitsValidator, @@ -89,6 +91,7 @@ use tari_ootle_transaction_validation::{ TransactionNetworkValidator, TransactionSignatureValidator, TransactionValidationError, + TransactionValidityWindowValidator, TransactionWeightValidator, Validator, }; @@ -360,8 +363,10 @@ pub async fn spawn_services( ); // The executor resolves the exhaust burn rate for each transaction's execution epoch. let transaction_executor = TariBlockTransactionExecutor::new(transaction_processor, consensus_constants.clone()); + let transaction_validator = TariBlockTransactionValidator::new( - create_mempool_transaction_validator(config.network, template_provider.clone()).boxed(), + create_structural_transaction_validator(config.network, template_provider.clone(), &consensus_constants) + .boxed(), EpochRangeValidator::new().boxed(), ); @@ -397,7 +402,7 @@ pub async fn spawn_services( let (mempool, join_handle) = mempool::spawn( epoch_manager.clone(), - create_mempool_transaction_validator(config.network, template_provider.clone()), + create_mempool_transaction_validator(config.network, template_provider.clone(), &consensus_constants), state_store.clone(), consensus_handle.clone(), networking.clone(), @@ -515,11 +520,11 @@ async fn spawn_p2p_rpc( Ok(handle) } -pub fn create_mempool_transaction_validator( +pub fn create_structural_transaction_validator( network: Network, template_manager: TProvider, -) -> impl Validator { - let max_transaction_weight = ConsensusConstants::from(network).max_transaction_weight; + constants: &ConsensusConstants, +) -> impl Validator + use { TransactionNetworkValidator::new(network) .and_then(TransactionDryRunValidator) .and_then(BasicValidations::new()) @@ -527,7 +532,7 @@ pub fn create_mempool_transaction_validator( // fail at execution, and unreferenced blobs would never fail at all. .and_then(BlobReferenceValidator::new()) // Cheap structural check — reject over-weight transactions before verifying signatures. - .and_then(TransactionWeightValidator::new(max_transaction_weight)) + .and_then(TransactionWeightValidator::new(constants.max_transaction_weight)) // Reject transactions whose aggregate stealth-transfer work exceeds the per-transaction caps before // verifying signatures or executing. .and_then(StealthTransactionLimitsValidator::new()) @@ -538,6 +543,22 @@ pub fn create_mempool_transaction_validator( .and_then(TemplateExistsValidator::new(template_manager)) } +pub fn create_mempool_transaction_validator( + network: Network, + template_manager: TProvider, + constants: &ConsensusConstants, +) -> impl Validator + use { + NoopValidator::::new() + .map_context( + |_| (), + create_structural_transaction_validator(network, template_manager, constants), + ) + .and_then(EpochRangeValidator::new()) + .and_then(TransactionValidityWindowValidator::new( + constants.max_transaction_validity_epochs, + )) +} + async fn create_base_layer_client( network: Network, config: &BaseLayerOracleConfig, diff --git a/applications/tari_validator_node/src/p2p/services/mempool/initializer.rs b/applications/tari_validator_node/src/p2p/services/mempool/initializer.rs index 17a05c9aaf..4e9593d885 100644 --- a/applications/tari_validator_node/src/p2p/services/mempool/initializer.rs +++ b/applications/tari_validator_node/src/p2p/services/mempool/initializer.rs @@ -23,6 +23,7 @@ use log::*; use tari_epoch_manager::service::EpochManagerHandle; use tari_networking::{GossipMessage, NetworkingHandle}; +use tari_ootle_common_types::Epoch; use tari_ootle_p2p::{PeerAddress, TariMessagingSpec}; use tari_ootle_storage::StateStore; use tari_ootle_transaction::Transaction; @@ -48,7 +49,7 @@ pub fn spawn( #[cfg(feature = "metrics")] metrics_registry: &mut prometheus_client::registry::Registry, ) -> (MempoolHandle, JoinHandle>) where - TValidator: Validator + Send + Sync + 'static, + TValidator: Validator + Send + Sync + 'static, TStateStore: StateStore + Send + Sync + 'static, { // This channel only needs to be size 1, because each mempool request must wait for a reply and the mempool is diff --git a/applications/tari_validator_node/src/p2p/services/mempool/service.rs b/applications/tari_validator_node/src/p2p/services/mempool/service.rs index 5c0fe2930e..9573ff9d45 100644 --- a/applications/tari_validator_node/src/p2p/services/mempool/service.rs +++ b/applications/tari_validator_node/src/p2p/services/mempool/service.rs @@ -27,7 +27,7 @@ use log::*; use tari_consensus::hotstuff::HotstuffEvent; use tari_epoch_manager::{EpochManagerReader, service::EpochManagerHandle}; use tari_networking::{GossipMessage, NetworkingHandle}; -use tari_ootle_common_types::optional::Optional; +use tari_ootle_common_types::{Epoch, optional::Optional}; use tari_ootle_p2p::{NewTransactionMessage, PeerAddress, TariMessage, TariMessagingSpec}; use tari_ootle_storage::{StateStore, StateStoreReadTransaction, StorageError, consensus_models::TransactionRecord}; use tari_ootle_transaction::{Transaction, TransactionId}; @@ -67,7 +67,7 @@ pub struct MempoolService { impl MempoolService where - TValidator: Validator, + TValidator: Validator, TStateStore: StateStore, { pub(super) fn new( @@ -263,7 +263,11 @@ where self.metrics.on_transaction_received(&transaction); let is_local = gossip_validation.is_none(); - let validation_result = self.before_execute_validator.validate(&(), &transaction); + // The epoch-dependent rules are checked here, alongside the structural ones, so that a + // transaction outside its validity window is refused before it is admitted or re-gossiped + // rather than after. Both feed the single acceptance verdict below. + let current_epoch = self.consensus_handle.current_view().get_epoch(); + let validation_result = self.before_execute_validator.validate(¤t_epoch, &transaction); // Reported here rather than at the end of this function: everything below is about whether // *we* act on the transaction, not whether it is valid, and gossipsub only holds a message @@ -290,8 +294,6 @@ where return Err(e.into()); } - let current_epoch = self.consensus_handle.current_view().get_epoch(); - let local_committee_shard = self.epoch_manager.get_local_committee_info(current_epoch).await?; let is_involved = transaction.is_involved(&local_committee_shard); diff --git a/applications/tari_validator_node/web_ui/src/routes/Transactions/TransactionDetails.tsx b/applications/tari_validator_node/web_ui/src/routes/Transactions/TransactionDetails.tsx index 97b601d03e..d1da32acd5 100644 --- a/applications/tari_validator_node/web_ui/src/routes/Transactions/TransactionDetails.tsx +++ b/applications/tari_validator_node/web_ui/src/routes/Transactions/TransactionDetails.tsx @@ -190,6 +190,16 @@ export default function TransactionDetails() { Total Fees {fee?.toString()} + {transaction?.min_epoch != null && ( + + Min Epoch + {transaction.min_epoch.toString()} + + )} + + Max Epoch + {transaction?.max_epoch?.toString()} + {final_decision && ( Status diff --git a/applications/tari_wallet_cli/src/command/transaction.rs b/applications/tari_wallet_cli/src/command/transaction.rs index e04e47d217..143e0b13d4 100644 --- a/applications/tari_wallet_cli/src/command/transaction.rs +++ b/applications/tari_wallet_cli/src/command/transaction.rs @@ -125,10 +125,30 @@ pub struct CommonSubmitArgs { pub fee_account: Option, #[clap(long)] pub min_epoch: Option, + /// The last epoch this transaction may be sequenced in. Defaults to the wallet daemon's + /// configured window past the current epoch. #[clap(long)] pub max_epoch: Option, } +impl CommonSubmitArgs { + /// The caller's explicit `max_epoch`, or the daemon's default window past the current epoch. + /// Without either — the daemon could not reach its indexer and no `--max-epoch` was given — + /// there is no window the network is known to accept, so fail rather than guess. + fn resolve_max_epoch(&self, current_epoch: Option, default_validity_epochs: u64) -> anyhow::Result { + if let Some(max_epoch) = self.max_epoch { + return Ok(Epoch(max_epoch)); + } + let current_epoch = current_epoch.ok_or_else(|| { + anyhow!( + "The wallet daemon could not reach its indexer, so the current epoch is unknown. Pass --max-epoch to \ + choose the transaction's validity window explicitly." + ) + })?; + Ok(Epoch(current_epoch.as_u64().saturating_add(default_validity_epochs))) + } +} + #[derive(Debug, Args, Clone)] pub struct SubmitManifestArgs { manifest: PathBuf, @@ -256,16 +276,21 @@ pub async fn handle_submit(args: SubmitArgs, client: &mut WalletDaemonClient) -> .owner_key_id .ok_or_else(|| anyhow!("Fee account does not have an owner key ID"))?; - let SettingsGetResponse { network, .. } = client.get_settings().await?; + let SettingsGetResponse { + network, + current_epoch, + default_transaction_validity_epochs, + .. + } = client.get_settings().await?; + let max_epoch = common.resolve_max_epoch(current_epoch, default_transaction_validity_epochs)?; - let mut builder = Transaction::builder(network.byte) + let mut builder = Transaction::builder(network.byte, max_epoch) .call_method(fee_account.component_address, "withdraw", args![common.max_fee,]) .put_last_instruction_output_on_workspace("fee_bucket") .pay_fee_from_bucket("fee_bucket") .add_instruction(instruction) .with_inputs(common.inputs) - .with_min_epoch(common.min_epoch.map(Epoch)) - .with_max_epoch(common.max_epoch.map(Epoch)); + .with_min_epoch(common.min_epoch.map(Epoch)); if let Some(dump_account) = common.dump_outputs_into { let AccountGetResponse { account, .. } = client.accounts_get(dump_account).await?; @@ -336,9 +361,15 @@ async fn handle_submit_manifest( .owner_key_id .ok_or_else(|| anyhow!("Fee account does not have an owner key ID"))?; - let SettingsGetResponse { network, .. } = client.get_settings().await?; + let SettingsGetResponse { + network, + current_epoch, + default_transaction_validity_epochs, + .. + } = client.get_settings().await?; + let max_epoch = common.resolve_max_epoch(current_epoch, default_transaction_validity_epochs)?; - let builder = Transaction::builder(network.byte) + let builder = Transaction::builder(network.byte, max_epoch) .with_fee_instructions_builder(|builder| { builder.with_instructions(instructions.fee_instructions).call_method( fee_account.component_address, @@ -348,8 +379,7 @@ async fn handle_submit_manifest( }) .with_instructions(instructions.instructions) .with_inputs(common.inputs) - .with_min_epoch(common.min_epoch.map(Epoch)) - .with_max_epoch(common.max_epoch.map(Epoch)); + .with_min_epoch(common.min_epoch.map(Epoch)); let transaction = builder.build_unsigned(); summarize_transaction(&transaction); diff --git a/applications/tari_walletd/Cargo.toml b/applications/tari_walletd/Cargo.toml index 496a54ff2e..b20724f9c2 100644 --- a/applications/tari_walletd/Cargo.toml +++ b/applications/tari_walletd/Cargo.toml @@ -93,6 +93,9 @@ dbus-secret-service-keyring-store = { workspace = true, features = ["crypto-rust windows-native-keyring-store = { workspace = true } [dev-dependencies] +# Test-only, so the wallet binary does not carry the consensus engine: lets the config guard +# assert against the real network ceiling rather than drifting from a hand-copied number. +tari_consensus = { workspace = true } tari_utilities = { workspace = true } [package.metadata.cargo-machete] diff --git a/applications/tari_walletd/examples/transaction_request_flow.rs b/applications/tari_walletd/examples/transaction_request_flow.rs index a3f65a9443..1967133484 100644 --- a/applications/tari_walletd/examples/transaction_request_flow.rs +++ b/applications/tari_walletd/examples/transaction_request_flow.rs @@ -35,7 +35,7 @@ use std::time::Duration; use anyhow::{Context, anyhow, bail}; use clap::Parser; use tari_engine_types::component::derive_component_address_from_public_key; -use tari_ootle_transaction::{TransactionBuilder, UnsignedTransaction, args}; +use tari_ootle_transaction::{Epoch, TransactionBuilder, UnsignedTransaction, args}; use tari_ootle_wallet_sdk::models::EffectiveStatus; use tari_ootle_walletd_client::{ WalletDaemonClient, @@ -225,7 +225,7 @@ fn build_transfer( max_fee: u64, ) -> UnsignedTransaction { let dest = derive_component_address_from_public_key(&ACCOUNT_TEMPLATE_ADDRESS, &dest_pk); - TransactionBuilder::new(network) + TransactionBuilder::new(network, Epoch(100)) .create_account(dest_pk) .pay_fee_from_component(source, Amount::from_integer(max_fee)) .call_method(source, "withdraw", args![TARI_TOKEN, Amount::from_integer(amount)]) diff --git a/applications/tari_walletd/src/config.rs b/applications/tari_walletd/src/config.rs index bfed48bafb..856620e51f 100644 --- a/applications/tari_walletd/src/config.rs +++ b/applications/tari_walletd/src/config.rs @@ -102,9 +102,43 @@ pub struct WalletDaemonConfig { /// transactions when new burn proof files are detected, deferring each claim to the next epoch as required. #[serde(default = "return_default_auto_claim_burns")] pub auto_claim_burns: bool, + /// How many epochs ahead of the current epoch to stamp `max_epoch` on transactions this wallet + /// builds, when the caller does not supply one. Every transaction must declare the last epoch it + /// may be sequenced in; past it the transaction can never land, so this decides how long a built + /// transaction stays submittable. Must not exceed the network's `max_transaction_validity_epochs` + /// ceiling — the default is far below it, leaving the long windows to callers that ask for one + /// explicitly (e.g. offline or multi-party signing). + #[serde(default = "return_default_transaction_validity_epochs")] + pub default_transaction_validity_epochs: u64, } impl WalletDaemonConfig { + /// Rejects a configuration that cannot build a usable transaction. + /// + /// A zero window stamps `max_epoch == current_epoch`, so every transaction the daemon builds + /// must be sequenced within the epoch it was built in and one built near a boundary expires + /// before it can land. An oversized window is only warned about: it is the network's ceiling + /// that decides, and this process cannot read it. + pub fn validate(&self) -> Result<(), anyhow::Error> { + if self.default_transaction_validity_epochs == 0 { + return Err(anyhow::anyhow!( + "default_transaction_validity_epochs must be at least 1: a zero window expires transactions in the \ + epoch they are built in" + )); + } + if self.default_transaction_validity_epochs > IMPLAUSIBLE_TRANSACTION_VALIDITY_EPOCHS { + log::warn!( + target: LOG_TARGET, + "default_transaction_validity_epochs ({}) is beyond the transaction validity ceiling known to this \ + build ({}). If the network enforces that ceiling, every transaction this wallet builds will be \ + refused as out of range.", + self.default_transaction_validity_epochs, + IMPLAUSIBLE_TRANSACTION_VALIDITY_EPOCHS, + ); + } + Ok(()) + } + pub fn get_burn_proof_dir(&self, network: Network) -> PathBuf { self.burn_proof_dir .clone() @@ -116,6 +150,20 @@ fn return_default_auto_claim_burns() -> bool { true } +/// Three epochs is roughly an hour at the ~20 minute epoch target: long enough for an interactive +/// approval flow, short enough that an abandoned transaction stops being submittable quickly. +fn return_default_transaction_validity_epochs() -> u64 { + 3 +} + +/// Mirrors `ConsensusConstants::max_transaction_validity_epochs`. The wallet binary does not carry +/// the consensus crate, so the value is duplicated here and pinned to the real one by a test; it is +/// used only to warn an operator that their configured window looks implausible, never as a hard +/// limit, which would wrongly refuse to start against a network with a larger ceiling. +const IMPLAUSIBLE_TRANSACTION_VALIDITY_EPOCHS: u64 = 2160; + +const LOG_TARGET: &str = "tari::ootle::wallet_daemon::config"; + fn return_default_transaction_request_ttl() -> Duration { Duration::from_secs(30 * 60) } @@ -153,6 +201,7 @@ impl Default for WalletDaemonConfig { burn_proof_dir: None, override_keyring_password: None, auto_claim_burns: true, + default_transaction_validity_epochs: return_default_transaction_validity_epochs(), } } } @@ -215,3 +264,39 @@ impl FromStr for WalletDaemonAuth { } } } + +#[cfg(test)] +mod tests { + use tari_consensus::consensus_constants::ConsensusConstants; + + use super::*; + + /// The warning threshold is only useful while it tracks the rule it mirrors. If the consensus + /// ceiling is lowered and this is not, the wallet keeps accepting a window under which every + /// transaction it builds is unsequenceable. + #[test] + fn the_warning_threshold_tracks_the_consensus_ceiling() { + assert_eq!( + IMPLAUSIBLE_TRANSACTION_VALIDITY_EPOCHS, + ConsensusConstants::mainnet().max_transaction_validity_epochs + ); + } + + #[test] + fn a_zero_validity_window_is_refused() { + let config = WalletDaemonConfig { + default_transaction_validity_epochs: 0, + ..Default::default() + }; + assert!(config.validate().is_err()); + } + + #[test] + fn a_window_at_the_ceiling_is_accepted() { + let config = WalletDaemonConfig { + default_transaction_validity_epochs: IMPLAUSIBLE_TRANSACTION_VALIDITY_EPOCHS, + ..Default::default() + }; + config.validate().unwrap(); + } +} diff --git a/applications/tari_walletd/src/handlers/accounts.rs b/applications/tari_walletd/src/handlers/accounts.rs index ab9e7b4489..071feebc9f 100644 --- a/applications/tari_walletd/src/handlers/accounts.rs +++ b/applications/tari_walletd/src/handlers/accounts.rs @@ -15,7 +15,7 @@ use tari_engine_types::{ confidential::ClaimBurnOutputData, substate::SubstateId, }; -use tari_ootle_common_types::{SubstateRequirement, optional::Optional}; +use tari_ootle_common_types::{Epoch, SubstateRequirement, optional::Optional}; use tari_ootle_transaction::{Transaction, args}; use tari_ootle_wallet_crypto::{ OutputWitness, @@ -528,6 +528,7 @@ pub async fn handle_claim_burn( &account, proof_contents, max_fee, + context.transaction_max_epoch().await?, is_dry_run, proof_file_name, ) @@ -544,6 +545,7 @@ pub(crate) async fn execute_claim_burn( account: &AccountWithAddress, proof_contents: ClaimBurnProofContents, max_fee: u64, + max_epoch: Epoch, is_dry_run: bool, proof_file_name: Option, ) -> Result { @@ -667,7 +669,7 @@ pub(crate) async fn execute_claim_burn( encrypted_data: claimed_encrypted_data, }; - let transaction = Transaction::builder(network.as_byte()) + let transaction = Transaction::builder(network.as_byte(), max_epoch) .with_fee_instructions_builder(|fee_builder| { fee_builder // Mint the UTXO @@ -814,6 +816,7 @@ pub async fn handle_create_free_test_coins( let transaction = context .transaction_builder() + .await? .with_fee_instructions_builder(|fee_builder| { fee_builder .create_account(*account.address.account_public_key()) @@ -937,7 +940,10 @@ pub async fn handle_transfer( .await .optional()?; - let builder = context.transaction_builder().create_account(req.destination_public_key); + let builder = context + .transaction_builder() + .await? + .create_account(req.destination_public_key); if let Some(ValidatorScanResult { id: address, substate }) = existing_dest_account { inputs.insert(address.into()); @@ -1101,6 +1107,7 @@ pub async fn handle_confidential_transfer( )])?; let transaction_service = context.transaction_service().clone(); + let max_epoch = context.transaction_max_epoch().await?; // Spawn here is to prevent the async block from being aborted if the caller aborts the request early as this can // cause funds to remain locked indefinitely. @@ -1117,6 +1124,7 @@ pub async fn handle_confidential_transfer( let transfer = sdk .confidential_transfer_api() .transfer(ConfidentialTransferParams { + max_epoch, from_account: source_account_address, input_selection: req.input_selection, amount: req.amount, @@ -1276,6 +1284,7 @@ pub async fn handle_stealth_transfer( .collect::>()?; let params = StealthTransferParams { + max_epoch: context.transaction_max_epoch().await?, fee_params: req.fee_params, input_selection: req.input_selection, resource_address: req.resource_address, diff --git a/applications/tari_walletd/src/handlers/context.rs b/applications/tari_walletd/src/handlers/context.rs index f80ce625c7..32373e04ba 100644 --- a/applications/tari_walletd/src/handlers/context.rs +++ b/applications/tari_walletd/src/handlers/context.rs @@ -2,14 +2,14 @@ // SPDX-License-Identifier: BSD-3-Clause use std::{ - sync::Arc, + sync::{Arc, Mutex}, time::{Duration, Instant}, }; use axum_extra::headers::authorization::Bearer; use dashmap::DashMap; -use tari_ootle_transaction::{Transaction, TransactionBuilder}; -use tari_ootle_wallet_sdk::models::WalletEvent; +use tari_ootle_transaction::{Epoch, Transaction, TransactionBuilder, UnsignedTransaction}; +use tari_ootle_wallet_sdk::{models::WalletEvent, network::WalletNetworkInterface}; use tari_ootle_wallet_sdk_services::{ account_monitor::AccountMonitorHandle, notify::Notify, @@ -59,8 +59,16 @@ pub struct HandlerContext { /// throttle in `api_key_touch_last_used` stays as belt-and-braces /// against process restart and racing shim invocations. api_key_last_used_bumps: Arc>, + /// Last epoch read from the network, with the time it was read. Every transaction build needs + /// the current epoch to stamp `max_epoch`; epochs turn over on the order of tens of minutes, so + /// a short cache keeps a burst of builds from making an indexer round-trip each. + cached_epoch: Arc>>, } +/// How long a read of the current epoch is reused before the network is asked again. Well under an +/// epoch, so the stamped window is never short by more than this. +const EPOCH_CACHE_TTL: Duration = Duration::from_secs(30); + impl HandlerContext { pub fn new( wallet_sdk: WalletSdk, @@ -83,6 +91,7 @@ impl HandlerContext { jwt_secret, shutdown_signal, api_key_last_used_bumps: Arc::new(DashMap::new()), + cached_epoch: Arc::new(Mutex::new(None)), } } @@ -286,6 +295,52 @@ impl HandlerContext { self.authenticator.webauthn() } + /// Discards the cached epoch, forcing the next read to go to the network. + /// + /// Must be called whenever the daemon is repointed at a different indexer: the cached value + /// describes the previous indexer's chain, and stamping a `max_epoch` derived from it onto a + /// transaction for a different chain yields a window that chain will not accept. + pub fn invalidate_epoch_cache(&self) { + *self.cached_epoch.lock().unwrap() = None; + } + + /// The current epoch, re-read from the network at most every [`EPOCH_CACHE_TTL`]. + pub async fn current_epoch(&self) -> Result { + if let Some((epoch, read_at)) = *self.cached_epoch.lock().unwrap() && + read_at.elapsed() < EPOCH_CACHE_TTL + { + return Ok(epoch); + } + + let epoch = self.wallet_sdk.get_network_interface().get_current_epoch().await?; + *self.cached_epoch.lock().unwrap() = Some((epoch, Instant::now())); + Ok(epoch) + } + + /// The `max_epoch` this wallet stamps on transactions it builds: the current epoch plus the + /// configured validity window. + pub async fn transaction_max_epoch(&self) -> Result { + let current_epoch = self.current_epoch().await?; + Ok(Epoch( + current_epoch + .as_u64() + .saturating_add(self.config().default_transaction_validity_epochs), + )) + } + + /// A builder seeded from a caller-supplied transaction. + /// + /// The caller has already chosen everything this would otherwise resolve — including its own + /// `max_epoch` — so unlike [`Self::transaction_builder`] this needs no epoch and makes no + /// network call. Submitting a pre-built transaction therefore does not depend on the indexer + /// being reachable. + pub fn transaction_builder_from_unsigned>( + &self, + transaction: T, + ) -> TransactionBuilder { + TransactionBuilder::from_unsigned(transaction) + } + /// Returns a TransactionBuilder with the current network configured. /// /// The builder is stamped with a random nonce so that repeated identical intents (same @@ -293,7 +348,13 @@ impl HandlerContext { /// caller-provided bytes replace the whole unsigned transaction via /// `with_unsigned_transaction`, which discards the stamp — the caller's bytes are preserved /// verbatim there. - pub fn transaction_builder(&self) -> TransactionBuilder { - Transaction::builder(self.config().network.as_byte()).with_nonce(rand::random()) + /// + /// `max_epoch` is stamped `default_transaction_validity_epochs` ahead of the current epoch. + /// Building therefore depends on the network being reachable: without a current epoch there is + /// no way to choose a window the network will accept, so this fails rather than guessing. + /// Callers that want a different window override it with `with_max_epoch`. + pub async fn transaction_builder(&self) -> Result { + let max_epoch = self.transaction_max_epoch().await?; + Ok(Transaction::builder(self.config().network.as_byte(), max_epoch).with_nonce(rand::random())) } } diff --git a/applications/tari_walletd/src/handlers/nfts.rs b/applications/tari_walletd/src/handlers/nfts.rs index fbab3731d2..c551557310 100644 --- a/applications/tari_walletd/src/handlers/nfts.rs +++ b/applications/tari_walletd/src/handlers/nfts.rs @@ -119,6 +119,7 @@ pub async fn handle_mint_faucet( let fee = req.max_fee.unwrap_or(3000); let transaction = context .transaction_builder() + .await? .pay_fee_from_component(account.component_address, fee) .call_method(NFT_FAUCET_COMPONENT_ADDRESS, "mint", args![ req.number_to_mint, @@ -274,7 +275,7 @@ pub async fn handle_transfer( ); // TODO: this can be simplified - let mut builder = context.transaction_builder(); + let mut builder = context.transaction_builder().await?; // collect all instructions let non_fungible_api = sdk.non_fungible_api(); diff --git a/applications/tari_walletd/src/handlers/settings.rs b/applications/tari_walletd/src/handlers/settings.rs index bb1af72d59..d2fc61f3ec 100644 --- a/applications/tari_walletd/src/handlers/settings.rs +++ b/applications/tari_walletd/src/handlers/settings.rs @@ -35,6 +35,10 @@ pub async fn handle_get( .optional()? .unwrap_or_default(); + // Deliberately not fatal: the indexer being down must not make settings unreadable, since this + // is where the indexer URL is corrected. + let current_epoch = context.current_epoch().await.ok(); + Ok(SettingsGetResponse { indexer_url, network: NetworkInfo { @@ -43,6 +47,8 @@ pub async fn handle_get( }, advanced_ui_features, claimed_accounts, + current_epoch, + default_transaction_validity_epochs: context.config().default_transaction_validity_epochs, }) } @@ -56,6 +62,8 @@ pub async fn handle_set( if let Some(indexer_url) = req.indexer_url { sdk.config_api().set(ConfigKey::IndexerUrl, &indexer_url)?; sdk.get_network_interface().set_endpoint(indexer_url); + // The cached epoch describes the indexer we just stopped using. + context.invalidate_epoch_cache(); } if let Some(advanced_ui_features) = &req.advanced_ui_features { sdk.config_api() diff --git a/applications/tari_walletd/src/handlers/transaction.rs b/applications/tari_walletd/src/handlers/transaction.rs index 4172cd2ddf..bcf45d0025 100644 --- a/applications/tari_walletd/src/handlers/transaction.rs +++ b/applications/tari_walletd/src/handlers/transaction.rs @@ -83,10 +83,14 @@ pub async fn handle_submit_instruction( })?; let transaction = context .transaction_builder() + .await? .with_instructions(req.instructions) .pay_fee_from_component(*fee_account.component_address(), req.max_fee.max(1)) .with_min_epoch(req.min_epoch.map(Epoch)) - .with_max_epoch(req.max_epoch.map(Epoch)) + .then(|b| match req.max_epoch { + Some(max_epoch) => b.with_max_epoch(Epoch(max_epoch)), + None => b, + }) .with_inputs(req.inputs) .build_unsigned(); @@ -184,8 +188,7 @@ async fn submit_inner( // Signatures collected out of band are attached first; walletd's own // signatures are added on top and the seal signature commits to all of them. let mut transaction = context - .transaction_builder() - .with_unsigned_transaction(req.transaction) + .transaction_builder_from_unsigned(req.transaction) .with_inputs(detected_inputs) .with_signatures(req.signatures); @@ -379,8 +382,7 @@ pub async fn handle_detect_inputs( .collect::>(); let transaction = context - .transaction_builder() - .with_unsigned_transaction(req.transaction) + .transaction_builder_from_unsigned(req.transaction) .with_inputs(detected) .build_unsigned(); @@ -430,8 +432,7 @@ async fn submit_dry_run_inner( }; let mut transaction = context - .transaction_builder() - .with_unsigned_transaction(req.transaction) + .transaction_builder_from_unsigned(req.transaction) .with_inputs(detected_inputs) .with_dry_run(true) .with_signatures(req.signatures); @@ -513,6 +514,7 @@ pub async fn handle_submit_manifest( let mut transaction = context .transaction_builder() + .await? .with_dry_run(req.dry_run) .with_fee_instructions_builder(|builder| { if instructions.fee_instructions.is_empty() { @@ -761,6 +763,7 @@ pub async fn handle_publish_template( let builder = context .transaction_builder() + .await? .pay_fee_from_component(*fee_account.component_address(), max_fee); let builder = match metadata_hash { Some(hash) => builder.publish_template_with_metadata(wasm_binary, hash), diff --git a/applications/tari_walletd/src/handlers/validator.rs b/applications/tari_walletd/src/handlers/validator.rs index 9ae96e08b3..c919e3329f 100644 --- a/applications/tari_walletd/src/handlers/validator.rs +++ b/applications/tari_walletd/src/handlers/validator.rs @@ -144,7 +144,7 @@ pub async fn handle_claim_validator_fees( let max_fee = req.max_fee.max(1); let account_public_key = *account.address.account_public_key(); - let builder = context.transaction_builder().with_dry_run(req.dry_run); + let builder = context.transaction_builder().await?.with_dry_run(req.dry_run); let builder = if req.output_to_revealed { let (first, rest) = fee_pool_addresses diff --git a/applications/tari_walletd/src/lib.rs b/applications/tari_walletd/src/lib.rs index b293399ad8..532b5a3006 100644 --- a/applications/tari_walletd/src/lib.rs +++ b/applications/tari_walletd/src/lib.rs @@ -84,6 +84,8 @@ pub async fn run_tari_ootle_walletd( // Uncomment to enable tokio tracing via tokio-console // console_subscriber::init(); + config.ootle_wallet_daemon.validate()?; + let wallet_store = init_wallet_store(&config)?; let mut wallet_sdk: WalletSdk = initialize_wallet_sdk(&config, wallet_store.clone())?; diff --git a/applications/tari_walletd/src/services/auto_claim_burn_service.rs b/applications/tari_walletd/src/services/auto_claim_burn_service.rs index e2f9cf03aa..bb072b285f 100644 --- a/applications/tari_walletd/src/services/auto_claim_burn_service.rs +++ b/applications/tari_walletd/src/services/auto_claim_burn_service.rs @@ -31,6 +31,9 @@ use crate::{ const LOG_TARGET: &str = "tari::ootle::wallet_daemon::auto_claim_burn"; const EPOCH_CHECK_INTERVAL: Duration = Duration::from_secs(30); +/// Validity window for an unattended claim: a few epochs is ample for the submit itself, and a +/// claim that does not land in that time is retried with a fresh window. +const CLAIM_TRANSACTION_VALIDITY_EPOCHS: u64 = 3; /// Maximum retries for network/submission errors (indexer unreachable, tx service down). const MAX_RETRIES_NETWORK: u32 = 10; /// Maximum retries for file read/parse errors (file still being written on macOS). @@ -340,7 +343,7 @@ impl AutoClaimBurnService { .collect(); for file_name in ready { - match self.try_submit_claim(&file_name).await { + match self.try_submit_claim(&file_name, current_epoch).await { Ok(tx_id) => { info!( target: LOG_TARGET, @@ -429,7 +432,12 @@ impl AutoClaimBurnService { .with_context(|| format!("Failed to parse burn proof file: {}", path.display())) } - async fn try_submit_claim(&self, file_name: &str) -> Result { + async fn try_submit_claim( + &self, + file_name: &str, + current_epoch: Epoch, + ) -> Result { + let max_epoch = claim_max_epoch(current_epoch); let complete_proof = self .read_proof_file(file_name) .await @@ -458,6 +466,7 @@ impl AutoClaimBurnService { &account, proof_contents.clone(), 1, + max_epoch, true, Some(file_name.to_string()), ) @@ -500,6 +509,7 @@ impl AutoClaimBurnService { &account, proof_contents, required_fees, + max_epoch, false, Some(file_name.to_string()), ) @@ -542,6 +552,14 @@ impl PendingClaim { } /// Categorises errors to determine retry behaviour for auto-claims. +/// The validity window stamped on claim transactions this service builds. Claims are submitted +/// unattended, so the window only has to cover the submission itself. Derived from the epoch the +/// caller already resolved: re-querying it here would turn a momentary indexer outage into a +/// permanent claim failure. +fn claim_max_epoch(current_epoch: Epoch) -> Epoch { + Epoch(current_epoch.as_u64().saturating_add(CLAIM_TRANSACTION_VALIDITY_EPOCHS)) +} + enum ClaimError { /// A permanent error (account not in this wallet, invalid proof data). Remove from queue; the /// proof file remains in `burn_proof_dir` for the user to inspect and retry manually. diff --git a/applications/tari_walletd/web_ui/src/routes/Transactions/TransactionDetails.tsx b/applications/tari_walletd/web_ui/src/routes/Transactions/TransactionDetails.tsx index bc03098996..5322e396c9 100644 --- a/applications/tari_walletd/web_ui/src/routes/Transactions/TransactionDetails.tsx +++ b/applications/tari_walletd/web_ui/src/routes/Transactions/TransactionDetails.tsx @@ -205,6 +205,18 @@ export default function TransactionDetails() { {data.invalid_reason ? data.invalid_reason : renderResult(data?.result)} + {transaction?.min_epoch != null && ( + + Min Epoch + {transaction.min_epoch.toString()} + + )} + {transaction?.max_epoch != null && ( + + Max Epoch + {transaction.max_epoch.toString()} + + )} JSON diff --git a/bindings/src/types/EncodedMerkleProof.ts b/bindings/src/types/EncodedMerkleProof.ts index 9eebb01ee1..b59a91465f 100644 --- a/bindings/src/types/EncodedMerkleProof.ts +++ b/bindings/src/types/EncodedMerkleProof.ts @@ -1,3 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type EncodedMerkleProof = { block_hash: string; encoded_merkle_proof: string; leaf_index: number | bigint | string }; +export type EncodedMerkleProof = { + block_hash: string; + encoded_merkle_proof: string; + leaf_index: number | bigint | string; +}; diff --git a/bindings/src/types/PrunedUnsignedTransactionV1.ts b/bindings/src/types/PrunedUnsignedTransactionV1.ts index d9bd63c5cd..c87a78f630 100644 --- a/bindings/src/types/PrunedUnsignedTransactionV1.ts +++ b/bindings/src/types/PrunedUnsignedTransactionV1.ts @@ -16,7 +16,7 @@ export type PrunedUnsignedTransactionV1 = { instructions: Array; inputs: Array; min_epoch: Epoch | null; - max_epoch: Epoch | null; + max_epoch: Epoch; is_seal_signer_authorized: boolean; dry_run: boolean; /** diff --git a/bindings/src/types/TransactionPoolRecord.ts b/bindings/src/types/TransactionPoolRecord.ts index 10a676b172..611e1c85b5 100644 --- a/bindings/src/types/TransactionPoolRecord.ts +++ b/bindings/src/types/TransactionPoolRecord.ts @@ -22,7 +22,7 @@ export type TransactionPoolRecord = { /** * The maximum epoch for which this transaction is valid. */ - max_epoch: Epoch | null; + max_epoch: Epoch; /** * Epoch to use when executing the transaction. This updates as foreign proposals are received * until the transaction is executed. diff --git a/bindings/src/types/UnsignedTransactionV1.ts b/bindings/src/types/UnsignedTransactionV1.ts index 647d894467..c14553c11d 100644 --- a/bindings/src/types/UnsignedTransactionV1.ts +++ b/bindings/src/types/UnsignedTransactionV1.ts @@ -13,7 +13,7 @@ export type UnsignedTransactionV1 = { */ inputs: Array; min_epoch: Epoch | null; - max_epoch: Epoch | null; + max_epoch: Epoch; is_seal_signer_authorized: boolean; dry_run: boolean; /** diff --git a/bindings/src/types/tari-indexer-client/GetNonFungiblesRequest.ts b/bindings/src/types/tari-indexer-client/GetNonFungiblesRequest.ts index b5ea14c838..1d9d881cd1 100644 --- a/bindings/src/types/tari-indexer-client/GetNonFungiblesRequest.ts +++ b/bindings/src/types/tari-indexer-client/GetNonFungiblesRequest.ts @@ -1,4 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ResourceAddress } from "../ResourceAddress"; -export type GetNonFungiblesRequest = { address: ResourceAddress; start_index: number | bigint | string; end_index: number | bigint | string }; +export type GetNonFungiblesRequest = { + address: ResourceAddress; + start_index: number | bigint | string; + end_index: number | bigint | string; +}; diff --git a/bindings/src/types/wallet-types/InputSelection.ts b/bindings/src/types/wallet-types/InputSelection.ts index 31c3c3bbf8..31c9a78e84 100644 --- a/bindings/src/types/wallet-types/InputSelection.ts +++ b/bindings/src/types/wallet-types/InputSelection.ts @@ -3,4 +3,7 @@ import type { Amount } from "../Amount"; import type { UtxoAddress } from "../UtxoAddress"; import type { UtxoInputSelection } from "../UtxoInputSelection"; -export type InputSelection = { "FromBucket": { revealed_amount: Amount, } } | { "Selection": UtxoInputSelection } | { "Specific": { utxo_addresses: Array, } }; +export type InputSelection = + | { FromBucket: { revealed_amount: Amount } } + | { Selection: UtxoInputSelection } + | { Specific: { utxo_addresses: Array } }; diff --git a/bindings/src/types/wallet-types/KeyId.ts b/bindings/src/types/wallet-types/KeyId.ts index d08c2c5336..572ccd8591 100644 --- a/bindings/src/types/wallet-types/KeyId.ts +++ b/bindings/src/types/wallet-types/KeyId.ts @@ -1,4 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { KeyBranch } from "./KeyBranch"; -export type KeyId = { Derived: { key_branch: KeyBranch; index: number | bigint | string } } | { Imported: { local_key_id: number | bigint | string } }; +export type KeyId = + | { Derived: { key_branch: KeyBranch; index: number | bigint | string } } + | { Imported: { local_key_id: number | bigint | string } }; diff --git a/bindings/src/types/wallet-types/SettingsGetResponse.ts b/bindings/src/types/wallet-types/SettingsGetResponse.ts index 1e409b32cc..a1ec6a107e 100644 --- a/bindings/src/types/wallet-types/SettingsGetResponse.ts +++ b/bindings/src/types/wallet-types/SettingsGetResponse.ts @@ -7,4 +7,6 @@ export type SettingsGetResponse = { network: NetworkInfo; advanced_ui_features: AdvancedUiFeatures; claimed_accounts: Array; + current_epoch: number | null; + default_transaction_validity_epochs: number; }; diff --git a/clients/wallet_daemon_client/src/types.rs b/clients/wallet_daemon_client/src/types.rs index 0cc7dd6b85..1ce4f6c2ea 100644 --- a/clients/wallet_daemon_client/src/types.rs +++ b/clients/wallet_daemon_client/src/types.rs @@ -32,6 +32,7 @@ use tari_engine_types::{ }; use tari_ootle_address::OotleAddress; use tari_ootle_common_types::{ + Epoch, ShardGroup, SubstateAddress, SubstateRequirement, @@ -1335,6 +1336,16 @@ pub struct SettingsGetResponse { pub network: NetworkInfo, pub advanced_ui_features: AdvancedUiFeatures, pub claimed_accounts: Vec, + /// The network's current epoch, as the wallet daemon sees it. Callers that build transactions + /// themselves need it to choose a `max_epoch` the network will accept. `None` when the indexer + /// is unreachable — settings must stay readable while the network is down, not least so the + /// caller can see and correct the indexer URL. + #[cfg_attr(feature = "ts", ts(type = "number | null"))] + pub current_epoch: Option, + /// How many epochs past `current_epoch` the wallet daemon stamps `max_epoch` when a caller does + /// not choose one. + #[cfg_attr(feature = "ts", ts(type = "number"))] + pub default_transaction_validity_epochs: u64, } #[derive(Debug, Clone, Default, Deserialize, Serialize)] diff --git a/crates/consensus/src/consensus_constants.rs b/crates/consensus/src/consensus_constants.rs index 95c2ed6d20..7821cd77fa 100644 --- a/crates/consensus/src/consensus_constants.rs +++ b/crates/consensus/src/consensus_constants.rs @@ -98,6 +98,16 @@ pub struct ConsensusConstants { /// totals in block headers. Use `exhaust_burn_rate` to resolve the rate for a given epoch rather than reading /// this field directly. pub exhaust_burn_rate_bps: u16, + /// The furthest ahead of the current epoch a transaction's `max_epoch` may be set. Every + /// transaction declares a mandatory `max_epoch`, so this caps how long any transaction can + /// remain sequenceable: a wallet can declare a transaction permanently dead once this many + /// epochs have passed, and an aborted attempt — which consensus deliberately allows to be + /// re-sequenced — cannot be retried beyond its window. This is a ceiling, not a default — + /// wallets stamp a much shorter window for ordinary traffic and only long-lived flows + /// (offline or multi-party signing) approach it. Enforced at mempool admission and in + /// consensus sequencing. CONSENSUS RULE: must be uniform network-wide, otherwise nodes + /// diverge on which transactions may be sequenced. + pub max_transaction_validity_epochs: u64, /// Number of base-layer blocks of leeway a voter is allowed when accepting `EndEpoch` proposals. /// If the voter's oracle has not yet crossed the next epoch boundary but its lagged scan height /// is within this many blocks of the boundary, the voter accepts `EndEpoch` from peers whose @@ -143,6 +153,7 @@ impl ConsensusConstants { // proposals are never rejected. max_block_validation_execution_points: 7_100_000_000, exhaust_burn_rate_bps: 500, // 5% + max_transaction_validity_epochs: 2160, epoch_end_spread_blocks: 10, } } @@ -183,6 +194,7 @@ impl ConsensusConstants { // proposals are never rejected. max_block_validation_execution_points: 7_100_000_000, exhaust_burn_rate_bps: 500, // 5% + max_transaction_validity_epochs: 2160, epoch_end_spread_blocks: 1, } } @@ -223,6 +235,7 @@ impl ConsensusConstants { // proposals are never rejected. max_block_validation_execution_points: 7_100_000_000, exhaust_burn_rate_bps: 500, // 5% + max_transaction_validity_epochs: 2160, epoch_end_spread_blocks: 5, } } @@ -263,6 +276,7 @@ impl ConsensusConstants { // proposals are never rejected. max_block_validation_execution_points: 7_100_000_000, exhaust_burn_rate_bps: 500, // 5% + max_transaction_validity_epochs: 2160, epoch_end_spread_blocks: 5, } } @@ -302,6 +316,23 @@ mod tests { use super::*; + /// A consensus rule must be identical on every network, otherwise a transaction admitted on one + /// is refused on another and nodes diverge on which transactions may be sequenced. + #[test] + fn the_transaction_validity_ceiling_is_uniform_across_networks() { + let expected = ConsensusConstants::mainnet().max_transaction_validity_epochs; + for constants in [ + ConsensusConstants::devnet(7), + ConsensusConstants::esmeralda(), + ConsensusConstants::testnet(), + ] { + assert_eq!(constants.max_transaction_validity_epochs, expected); + } + // A zero ceiling would admit only transactions expiring in the current epoch, leaving no + // room to submit one at all. + assert!(expected > 0); + } + #[test] fn validation_budgets_always_admit_honest_proposals() { for constants in [ diff --git a/crates/consensus/src/hotstuff/transaction_manager/manager.rs b/crates/consensus/src/hotstuff/transaction_manager/manager.rs index 29d319558d..9c954d9f0e 100644 --- a/crates/consensus/src/hotstuff/transaction_manager/manager.rs +++ b/crates/consensus/src/hotstuff/transaction_manager/manager.rs @@ -11,6 +11,7 @@ use tari_engine_types::{ substate::{Substate, SubstateId}, }; use tari_ootle_common_types::{ + Epoch, LockIntent, SubstateRequirement, SubstateRequirementRef, @@ -47,19 +48,44 @@ const LOG_TARGET: &str = "tari::ootle::consensus::hotstuff::block_transaction_ex #[derive(Debug, Clone)] pub struct ConsensusTransactionManager { executor: TExecutor, + max_transaction_validity_epochs: u64, _store: PhantomData, } impl> ConsensusTransactionManager { - pub fn new(executor: TExecutor) -> Self { + pub fn new(executor: TExecutor, max_transaction_validity_epochs: u64) -> Self { Self { executor, + max_transaction_validity_epochs, _store: PhantomData, } } + /// The abort a transaction earns for declaring a window outside the epoch it was pinned to, or + /// `None` if the window is acceptable. + /// + /// Both rules are evaluated against the pinned epoch — agreed across shard groups before + /// execution — so every node reaches the same verdict regardless of how far its own view had + /// lagged when the transaction was admitted. An out-of-window transaction is therefore sequenced + /// as an abort, which all shard groups adopt, rather than being dropped by whichever group + /// happened to be behind. + fn window_abort_reason(&self, pinned_epoch: Epoch, max_epoch: Epoch) -> Option { + if pinned_epoch > max_epoch { + return Some(AbortReason::EpochExpired); + } + let latest_permitted = Epoch( + pinned_epoch + .as_u64() + .saturating_add(self.max_transaction_validity_epochs), + ); + if max_epoch > latest_permitted { + return Some(AbortReason::ValidityWindowTooLong); + } + None + } + pub fn prepare( &self, store: &mut PendingSubstateStore, @@ -220,22 +246,18 @@ impl> execution_epoch: LockedEpoch, pledged_transaction: PledgedTransaction, ) -> Result { - // Abort before execution if the execution epoch exceeds the transaction's max_epoch - if let Some(max_epoch) = pledged_transaction.transaction.transaction().max_epoch() && - execution_epoch.epoch() > max_epoch - { + let max_epoch = pledged_transaction.transaction.transaction().max_epoch(); + if let Some(reason) = self.window_abort_reason(execution_epoch.epoch(), max_epoch) { warn!( target: LOG_TARGET, - "⏰ Transaction {} has expired: execution epoch {} exceeds max_epoch {}", + "⏰ Transaction {} is outside its validity window ({reason}): execution epoch {} against max_epoch {}", pledged_transaction.transaction.id(), execution_epoch, max_epoch, ); return Ok(TransactionExecution::abort( pledged_transaction.transaction.id(), - RejectReason::Abort { - reason: AbortReason::EpochExpired, - }, + RejectReason::Abort { reason }, )); } @@ -269,19 +291,17 @@ impl> block: &LeafBlock, execution_locked_epoch: LockedEpoch, ) -> Result { - // Abort before execution if the execution epoch exceeds the transaction's max_epoch - if let Some(max_epoch) = transaction.transaction().max_epoch() && - execution_locked_epoch.epoch() > max_epoch - { + let max_epoch = transaction.transaction().max_epoch(); + if let Some(reason) = self.window_abort_reason(execution_locked_epoch.epoch(), max_epoch) { warn!( target: LOG_TARGET, - "⏰ Transaction {} has expired: execution epoch {} exceeds max_epoch {}", + "⏰ Transaction {} is outside its validity window ({reason}): execution epoch {} against max_epoch {}", transaction.id(), execution_locked_epoch.epoch(), max_epoch, ); return Ok(TransactionExecution::abort(transaction.id(), RejectReason::Abort { - reason: AbortReason::EpochExpired, + reason, })); } diff --git a/crates/consensus/src/hotstuff/worker.rs b/crates/consensus/src/hotstuff/worker.rs index eb8b3bfe6d..2d5dac41d3 100644 --- a/crates/consensus/src/hotstuff/worker.rs +++ b/crates/consensus/src/hotstuff/worker.rs @@ -157,7 +157,10 @@ impl HotstuffWorker { ProposalVoteCollector::new(state_store.clone(), epoch_manager.clone(), signing_service.clone()); let timeout_vote_collector = TimeoutVoteCollector::new(state_store.clone(), epoch_manager.clone(), signing_service.clone()); - let transaction_manager = ConsensusTransactionManager::new(transaction_executor.clone()); + let transaction_manager = ConsensusTransactionManager::new( + transaction_executor.clone(), + config.consensus_constants.max_transaction_validity_epochs, + ); Self { local_validator_addr: local_validator_addr.clone(), diff --git a/crates/consensus_tests/src/consensus.rs b/crates/consensus_tests/src/consensus.rs index 184d11d7c8..698b7b61f9 100644 --- a/crates/consensus_tests/src/consensus.rs +++ b/crates/consensus_tests/src/consensus.rs @@ -705,7 +705,7 @@ async fn multishard_local_inputs_foreign_outputs() { let outputs_2 = test.build_outputs_for_committee(2, 1); let tx1 = build_transaction_from( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_inputs(inputs.iter().cloned().map(|i| i.into())) .build_and_seal(&PrivateKey::default()), ); @@ -762,7 +762,7 @@ async fn multishard_local_inputs_foreign_outputs_abort() { let inputs = test.create_substates_on_vns(TestVnDestination::Committee(0), 2); let outputs = test.build_outputs_for_committee(1, 1); - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .with_inputs(inputs.iter().cloned().map(|i| i.into())) .build_and_seal(&PrivateKey::default()); @@ -842,7 +842,7 @@ async fn multishard_local_inputs_and_outputs_foreign_outputs() { let outputs_2 = test.build_outputs_for_committee(2, 5); let tx1 = build_transaction_from( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_inputs(inputs_0.iter().chain(&inputs_1).cloned().map(|i| i.into())) .build_and_seal(&PrivateKey::from_canonical_bytes(&[1; 32]).unwrap()), ); @@ -919,7 +919,7 @@ async fn multishard_output_conflict_abort() { .await; let inputs = test.create_substates_on_vns(TestVnDestination::All, 1); - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .with_inputs(inputs.iter().cloned().map(|i| i.into())) .build_and_seal(&Default::default()); let tx2 = build_transaction_from(tx); @@ -1006,7 +1006,7 @@ async fn single_shard_inputs_from_previous_outputs() { .map(|output| SubstateRequirement::versioned(output.clone(), 0)) .collect::>(); - let tx2 = Transaction::builder_localnet() + let tx2 = Transaction::builder_localnet(Epoch(1)) .with_inputs(prev_outputs.clone()) .build_and_seal(&Default::default()); let tx2 = build_transaction_from(tx2.clone()); @@ -1070,7 +1070,7 @@ async fn multishard_inputs_from_previous_outputs() { .map(|output| SubstateRequirement::versioned(output.clone(), 0)) .collect::>(); - let tx2 = Transaction::builder_localnet() + let tx2 = Transaction::builder_localnet(Epoch(1)) .with_inputs(prev_outputs.clone()) .build_and_seal(&Default::default()); let tx2 = build_transaction_from(tx2.clone()); @@ -1130,12 +1130,12 @@ async fn single_shard_input_conflict() { let secret1 = PrivateKey::from_canonical_bytes(&[1u8; 32]).unwrap(); let secret2 = PrivateKey::from_canonical_bytes(&[2u8; 32]).unwrap(); - let tx1 = Transaction::builder_localnet() + let tx1 = Transaction::builder_localnet(Epoch(1)) .add_input(substate_id.clone()) .build_and_seal(&secret1); let tx1 = TransactionRecord::new(tx1); - let tx2 = Transaction::builder_localnet() + let tx2 = Transaction::builder_localnet(Epoch(1)) .add_input(substate_id.clone()) .build_and_seal(&secret2); let tx2 = TransactionRecord::new(tx2); @@ -1267,7 +1267,7 @@ async fn single_shard_unversioned_inputs() { let unversioned_inputs = inputs .iter() .map(|i| SubstateRequirement::new(i.substate_id().clone(), None)); - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .with_inputs(unversioned_inputs) .build_and_seal(&PrivateKey::default()); let tx = TransactionRecord::new(tx); @@ -1349,13 +1349,13 @@ async fn multishard_unversioned_input_conflict() { // Distinct sealers: the transaction id excludes the seal nonce, so an identical body sealed // by the same key would be one transaction, not two conflicting ones. - let tx1 = Transaction::builder_localnet() + let tx1 = Transaction::builder_localnet(Epoch(1)) .add_input(SubstateRequirement::unversioned(id0.substate_id().clone())) .add_input(SubstateRequirement::unversioned(id1.substate_id().clone())) .build_and_seal(&PrivateKey::from_canonical_bytes(&[1u8; 32]).unwrap()); let tx1 = TransactionRecord::new(tx1); - let tx2 = Transaction::builder_localnet() + let tx2 = Transaction::builder_localnet(Epoch(1)) .add_input(SubstateRequirement::unversioned(id0.substate_id().clone())) .add_input(SubstateRequirement::unversioned(id1.substate_id().clone())) .build_and_seal(&PrivateKey::from_canonical_bytes(&[2u8; 32]).unwrap()); @@ -1453,13 +1453,13 @@ async fn multishard_unversioned_input_conflict_delay_prepare() { .pop() .unwrap(); - let tx1 = Transaction::builder_localnet() + let tx1 = Transaction::builder_localnet(Epoch(1)) .add_input(SubstateRequirement::unversioned(id0.substate_id().clone())) .add_input(SubstateRequirement::unversioned(id1.substate_id().clone())) .build_and_seal(&Default::default()); let tx1 = TransactionRecord::new(tx1); - let tx2 = Transaction::builder_localnet() + let tx2 = Transaction::builder_localnet(Epoch(1)) .add_input(SubstateRequirement::unversioned(id0.substate_id().clone())) .add_input(SubstateRequirement::unversioned(id2.substate_id().clone())) .build_and_seal(&Default::default()); @@ -1543,7 +1543,7 @@ async fn multishard_publish_template() { let (sk, pk) = create_key_pair(); let wasm = load_binary_fixture("state.wasm"); let expected_binary_hash = hash_template_code(&wasm); - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .publish_template(wasm) .with_inputs(inputs.iter().cloned().map(Into::into)) .build_and_seal(&sk); @@ -1614,7 +1614,7 @@ async fn multishard_validator_fee_claim() { .await; // Create and send publish template transaction let address = derive_fee_pool_address(&claim_bytes, test.num_preshards(), Shard::first()); - let claim_tx = Transaction::builder_localnet() + let claim_tx = Transaction::builder_localnet(Epoch(1)) .claim_validator_fees(address) .add_input(address) .build_and_seal(&claim_sk); @@ -1751,9 +1751,9 @@ async fn single_transaction_epoch_expired() { let inputs = test.create_substates_on_vns(TestVnDestination::All, 1); let tx = build_transaction_from( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_inputs(inputs.iter().cloned().map(|i| i.into())) - .with_max_epoch(Some(Epoch(0))) + .with_max_epoch(Epoch(0)) .call_function(Default::default(), "foo", args![]) .build_and_seal(&PrivateKey::default()), ); @@ -1805,9 +1805,9 @@ async fn multishard_transaction_epoch_expired() { let inputs = test.create_substates_on_vns(TestVnDestination::Committee(0), 2); let outputs = test.build_outputs_for_committee(1, 1); let tx = build_transaction_from( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_inputs(inputs.iter().cloned().map(|i| i.into())) - .with_max_epoch(Some(Epoch(0))) + .with_max_epoch(Epoch(0)) .call_function(Default::default(), "foo", args![]) .build_and_seal(&PrivateKey::default()), ); diff --git a/crates/consensus_tests/src/support/harness.rs b/crates/consensus_tests/src/support/harness.rs index b08f9f543c..b3cfbb7f31 100644 --- a/crates/consensus_tests/src/support/harness.rs +++ b/crates/consensus_tests/src/support/harness.rs @@ -667,6 +667,7 @@ impl TestBuilder { missed_proposal_suspend_threshold: 5, missed_proposal_evict_threshold: 10, missed_proposal_recovery_threshold: 5, + max_transaction_validity_epochs: 100, // Keep the weight budget effectively unbounded in tests so behaviour stays // count-limited (as before) unless a test specifically exercises the weight budget. max_block_weight: 1_000_000, diff --git a/crates/consensus_tests/src/support/transaction.rs b/crates/consensus_tests/src/support/transaction.rs index 825a04b112..8d8cd7c3ef 100644 --- a/crates/consensus_tests/src/support/transaction.rs +++ b/crates/consensus_tests/src/support/transaction.rs @@ -187,7 +187,7 @@ pub fn random_substates_ids_for_committee_generator( pub fn build_transaction(inputs: Vec) -> TransactionRecord { let k = PrivateKey::default(); - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .call_function(Default::default(), "foo", args![]) .with_inputs(inputs) .build_and_seal(&k); diff --git a/crates/engine/examples/native_points_calibrate.rs b/crates/engine/examples/native_points_calibrate.rs index 7680f9fea8..daaa606aa4 100644 --- a/crates/engine/examples/native_points_calibrate.rs +++ b/crates/engine/examples/native_points_calibrate.rs @@ -58,7 +58,7 @@ use tari_crypto::{ }; use tari_engine::fees::FeeTable; use tari_engine_types::{fees::FeeSource, stealth::validate_transfer}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::stealth::StealthTransferStatement; use tari_template_test_tooling::{ TemplateTest, @@ -302,7 +302,7 @@ fn measure(host: Host) -> Calibration { // One measured execution: returns (WasmExecution points, wall-clock sample in ms). let run = |test: &mut TemplateTest, rounds: u64| -> (u64, Sample) { let execute = |test: &mut TemplateTest| -> (u64, f64) { - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, MAX_FEE) .call_function(bench, "bench_div_u64", args![rounds]) .build_and_seal(&key); diff --git a/crates/engine/tests/access_rules.rs b/crates/engine/tests/access_rules.rs index 964afcb053..281b3b2c6c 100644 --- a/crates/engine/tests/access_rules.rs +++ b/crates/engine/tests/access_rules.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashMap}; use tari_engine::runtime::{ActionIdent, LockError, RuntimeError}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::{ args::ComponentAction, types::{ @@ -60,7 +60,7 @@ mod component_access_rules { .default(AccessRule::DenyAll); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ // Owner OwnerRule::ByAccessRule(owner_rule), @@ -86,7 +86,7 @@ mod component_access_rules { .clone(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_value", args![1]) .build_and_seal(&owner2_key), vec![owner2_proof], @@ -95,7 +95,7 @@ mod component_access_rules { let (unauth_proof, _, unauth_key) = test.create_owner_proof(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_value", args![1]) .build_and_seal(&unauth_key), vec![unauth_proof], @@ -118,7 +118,7 @@ mod component_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ // Owner OwnerRule::OwnedBySigner, @@ -139,7 +139,7 @@ mod component_access_rules { // Access Denied let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_value", args![1]) .build_and_seal(&user_key), vec![user_proof.clone()], @@ -152,7 +152,7 @@ mod component_access_rules { // Allow user to call set_value test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_component_access_rules", args![ ComponentAccessRules::new() .add_method_rule("set_value", rule!(non_fungible(user_proof.clone()))) @@ -163,14 +163,14 @@ mod component_access_rules { ); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_value", args![1]) .build_and_seal(&user_key), vec![user_proof.clone()], ); test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_component_access_rules", args![ ComponentAccessRules::new().default(AccessRule::AllowAll) ]) @@ -189,7 +189,7 @@ mod component_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ // Owner OwnerRule::None, @@ -210,7 +210,7 @@ mod component_access_rules { // Owner cannot set access rules let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_component_access_rules", args![ ComponentAccessRules::new().default(AccessRule::AllowAll) ]) @@ -241,7 +241,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ // Owner OwnerRule::OwnedBySigner, @@ -262,7 +262,7 @@ mod resource_access_rules { // User cannot get tokens let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -274,7 +274,7 @@ mod resource_access_rules { // Owner can get tokens test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -284,7 +284,7 @@ mod resource_access_rules { // Owner gives user permission to withdraw tokens test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "update_tokens_access_rule", args![ ResourceAuthAction::Withdraw, rule!(non_fungible(user_proof.clone())) @@ -295,7 +295,7 @@ mod resource_access_rules { // User can get tokens, and deposit them in the owners account (deposit is default allow) test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -316,7 +316,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ // Owner - Everyone! OwnerRule::ByAccessRule(AccessRule::AllowAll), @@ -337,7 +337,7 @@ mod resource_access_rules { // Give the user a withdraw and deposit badge test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_badge_by_name", args!["withdraw"]) .put_last_instruction_output_on_workspace("withdraw_perm") .call_method(component_address, "take_badge_by_name", args!["deposit"]) @@ -363,7 +363,7 @@ mod resource_access_rules { // Now try recall them. This won't succeed because recall only respects access rules not ownership, so the call // is denied for the owner. let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "recall_badge", args![ user_badge_vault_id, "withdraw" @@ -387,7 +387,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "using_badge_rules", args![]) .build_and_seal(&owner_key), vec![owner_proof.clone()], @@ -413,7 +413,7 @@ mod resource_access_rules { // User cannot get the tokens let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(user_account, "deposit", args![Workspace("tokens")]) @@ -425,7 +425,7 @@ mod resource_access_rules { // Give the user a withdraw and deposit badge test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_badge_by_name", args!["withdraw"]) .put_last_instruction_output_on_workspace("withdraw_perm") .call_method(component_address, "take_badge_by_name", args!["deposit"]) @@ -438,7 +438,7 @@ mod resource_access_rules { // User can take tokens let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(user_account, "create_proof_by_non_fungible_ids", args![ badge_resource, vec![ @@ -467,7 +467,7 @@ mod resource_access_rules { // Recall badge test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "recall_badge", args![ user_badge_vault_id, "withdraw" @@ -478,7 +478,7 @@ mod resource_access_rules { // User can no longer withdraw tokens let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(user_account, "create_proof_for_resource", args![badge_resource]) .put_last_instruction_output_on_workspace("proof") .call_method(user_account, "withdraw", args![token_resource, 10]) @@ -503,7 +503,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "using_resource_rules", args![]) .build_and_seal(&owner_key), vec![owner_proof.clone()], @@ -527,7 +527,7 @@ mod resource_access_rules { // User cannot get the tokens let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(access_rules_component, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(user_account, "deposit", args![Workspace("tokens")]) @@ -539,7 +539,7 @@ mod resource_access_rules { // Give the user a badge test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(access_rules_component, "mint_new_badge", args![]) .put_last_instruction_output_on_workspace("permission") .call_method(user_account, "deposit", args![Workspace("permission")]) @@ -549,7 +549,7 @@ mod resource_access_rules { // User can take tokens test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(user_account, "create_proof_by_amount", args![badge_resource, 1]) .put_last_instruction_output_on_workspace("proof") .call_method(access_rules_component, "take_tokens_using_proof", args![ @@ -574,7 +574,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ // Owner OwnerRule::OwnedBySigner, @@ -607,7 +607,7 @@ mod resource_access_rules { // Take some tokens, generate a proof from the bucket (locking them up), and then try withdrawing them let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![1000]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -624,7 +624,7 @@ mod resource_access_rules { // Drop the proof before withdraw/deposit test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![1000]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -652,7 +652,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "using_resource_rules", args![]) .build_and_seal(&owner_key), vec![owner_proof.clone()], @@ -678,7 +678,7 @@ mod resource_access_rules { // Try to take tokens without proof. Even though I'm the owner of the resource, the scope does not carry over // when cross-template calls are made. let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(cross_call_template, "call_component_with_args", args![ component_address, "take_tokens", @@ -695,7 +695,7 @@ mod resource_access_rules { // Do a cross template call using a proof test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "mint_new_badge", args![]) .put_last_instruction_output_on_workspace("badge") .call_method(owner_account, "deposit", args![Workspace("badge")]) @@ -727,7 +727,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "using_badge_rules", args![]) .build_and_seal(&owner_key), vec![owner_proof.clone()], @@ -751,7 +751,7 @@ mod resource_access_rules { // User cannot get the tokens let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(user_account, "deposit", args![Workspace("tokens")]) @@ -763,7 +763,7 @@ mod resource_access_rules { // Give the user a withdraw and deposit badge test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_badge_by_name", args!["withdraw"]) .put_last_instruction_output_on_workspace("withdraw_perm") .call_method(component_address, "take_badge_by_name", args!["deposit"]) @@ -776,7 +776,7 @@ mod resource_access_rules { // Side case: we try deposit back the badges before we drop the proof. This is invalid. let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method( user_account, "withdraw_many_non_fungibles", @@ -818,7 +818,7 @@ mod resource_access_rules { // User can take tokens, using a proof obtained from a bucket test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method( user_account, "withdraw_many_non_fungibles", @@ -862,7 +862,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function( access_rules_template, "resource_actions_restricted_to_component", @@ -890,7 +890,7 @@ mod resource_access_rules { // Minting using a template function will fail let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "mint_resource", args![token_resource]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -902,7 +902,7 @@ mod resource_access_rules { // Minting in a component context will succeed test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "mint_more_tokens", args![1000]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -921,7 +921,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_auth_hook", args![true, "valid_auth_hook"]) .build_and_seal(&owner_key), vec![owner_proof.clone()], @@ -932,7 +932,7 @@ mod resource_access_rules { .unwrap(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -950,7 +950,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_auth_hook", args![false, "valid_auth_hook"]) .build_and_seal(&owner_key), vec![owner_proof.clone()], @@ -961,7 +961,7 @@ mod resource_access_rules { .unwrap(); let result = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -985,7 +985,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_auth_hook", args![ true, "malicious_auth_hook_set_state" @@ -999,7 +999,7 @@ mod resource_access_rules { .unwrap(); let result = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(user_account, "deposit", args![Workspace("tokens")]) @@ -1023,7 +1023,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_auth_hook", args![ true, "malicious_auth_hook_call_mut" @@ -1037,7 +1037,7 @@ mod resource_access_rules { .unwrap(); let result = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(user_account, "deposit", args![Workspace("tokens")]) @@ -1063,7 +1063,7 @@ mod resource_access_rules { // User has a state component let state_template = test.get_template_address("State"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(state_template, "restricted", args![]) .build_and_seal(&user_key), vec![owner_proof.clone()], @@ -1076,7 +1076,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_auth_hook_attack_component", args![ state_component ]) @@ -1089,7 +1089,7 @@ mod resource_access_rules { .unwrap(); let result = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(state_component, "set", args![1]) @@ -1124,7 +1124,7 @@ mod resource_access_rules { .iter() .for_each(|hook| { let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_auth_hook", args![true, hook]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -1148,7 +1148,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ OwnerRule::OwnedBySigner, ComponentAccessRules::new().default(AccessRule::AllowAll), @@ -1168,7 +1168,7 @@ mod resource_access_rules { new_metadata.insert("description", "updated"); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_tokens_metadata", args![new_metadata]) .build_and_seal(&user_key), vec![user_proof], @@ -1187,7 +1187,7 @@ mod resource_access_rules { let access_rules_template = test.get_template_address("AccessRulesTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ OwnerRule::OwnedBySigner, ComponentAccessRules::new().default(AccessRule::AllowAll), @@ -1206,7 +1206,7 @@ mod resource_access_rules { new_metadata.insert("description", "updated by user"); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "set_tokens_metadata", args![new_metadata]) .build_and_seal(&user_key), vec![user_proof], @@ -1225,7 +1225,7 @@ mod resource_access_rules { // Withdraw starts denied; the updater is `OWNER`, so the owner can change the rule later. let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ OwnerRule::OwnedBySigner, ComponentAccessRules::new().default(AccessRule::AllowAll), @@ -1242,7 +1242,7 @@ mod resource_access_rules { // Owner relaxes the withdraw rule. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "update_tokens_access_rule", args![ ResourceAuthAction::Withdraw, rule!(non_fungible(user_proof.clone())) @@ -1253,7 +1253,7 @@ mod resource_access_rules { // User holding the new badge can now withdraw and deposit. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) @@ -1273,7 +1273,7 @@ mod resource_access_rules { // Default ResourceAccessRules leaves the mint updater as `Locked`, so even the owner // cannot change the mint rule. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ OwnerRule::OwnedBySigner, ComponentAccessRules::new().default(AccessRule::AllowAll), @@ -1292,7 +1292,7 @@ mod resource_access_rules { .unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "update_tokens_access_rule", args![ ResourceAuthAction::Mint, AccessRule::AllowAll @@ -1319,7 +1319,7 @@ mod resource_access_rules { // Withdraw rule starts denied; the updater requires the user's badge — not the owner. let updater_rule = rule!(non_fungible(user_proof.clone())); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(access_rules_template, "with_configured_rules", args![ OwnerRule::OwnedBySigner, ComponentAccessRules::new().default(AccessRule::AllowAll), @@ -1339,7 +1339,7 @@ mod resource_access_rules { // Owner cannot update — they do not hold the badge. let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "update_tokens_access_rule", args![ ResourceAuthAction::Withdraw, AccessRule::AllowAll @@ -1353,7 +1353,7 @@ mod resource_access_rules { // Badge holder (the "user" identity) can update. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "update_tokens_access_rule", args![ ResourceAuthAction::Withdraw, AccessRule::AllowAll @@ -1364,7 +1364,7 @@ mod resource_access_rules { // And the relaxed rule is in effect. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "take_tokens", args![10]) .put_last_instruction_output_on_workspace("tokens") .call_method(owner_account, "deposit", args![Workspace("tokens")]) diff --git a/crates/engine/tests/account.rs b/crates/engine/tests/account.rs index 9ad28405ba..2ce16c7f8c 100644 --- a/crates/engine/tests/account.rs +++ b/crates/engine/tests/account.rs @@ -4,7 +4,7 @@ use ootle_byte_type::ToByteType; use tari_crypto::{keys::PublicKey, ristretto::RistrettoPublicKey}; use tari_engine::runtime::{ActionIdent, RuntimeError}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; use tari_template_lib::types::{Amount, access_rules::ComponentAccessRules, constants::TARI_TOKEN, rule}; use tari_template_test_tooling::{ @@ -25,7 +25,7 @@ fn basic_faucet_transfer() { let result = template_test .build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(sender_address, "withdraw", args![TARI_TOKEN, 100]) .put_last_instruction_output_on_workspace("foo_bucket") .call_method(receiver_address, "deposit", args![Workspace("foo_bucket")]) @@ -56,7 +56,7 @@ fn withdraw_from_account_prevented() { let (dest_address, non_owning_token, non_owning_key) = test.create_empty_account(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(source_account, "withdraw", args![TARI_TOKEN, 100]) .put_last_instruction_output_on_workspace("stolen_coins") .call_method(dest_address, "deposit", args![Workspace("stolen_coins")]) @@ -86,7 +86,7 @@ fn attempt_to_overwrite_account() { let null: Option<()> = None; let overwriting_tx = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // Create component with the same ID // The create account instruction is idempotent, so we'll call the template directly to force an overwrite attempt .call_function( @@ -122,7 +122,7 @@ fn create_account_is_idempotent() { let source_account_pk = RistrettoPublicKey::from_secret_key(&source_account_sk); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // Create component with the same ID .create_account(source_account_pk.to_byte_type()) // Signed by source account so that it can pay the fees for the new account creation @@ -156,7 +156,7 @@ fn create_account_is_idempotent_with_deposit() { let source_account_pk = RistrettoPublicKey::from_secret_key(&source_account_sk); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // Create component with the same ID .create_account(source_account_pk.to_byte_type()) .put_last_instruction_output_on_workspace("account") @@ -194,7 +194,7 @@ fn gasless() { let fee_account_pk = RistrettoPublicKey::from_secret_key(&fee_account_sk); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(fee_account, 1000u64) .call_method(user_account, "withdraw", args![TARI_TOKEN, 100]) .put_last_instruction_output_on_workspace("b") @@ -227,7 +227,7 @@ fn custom_access_rules() { .default(rule!(allow_all)); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // Create component with the same ID .create_account_custom::<&str>( public_key.to_byte_type(), @@ -251,7 +251,7 @@ fn custom_access_rules() { // We create another account and we we will withdraw from the custom one let (user2_account, user2_account_proof, user2_secret_key) = test.create_funded_account(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(user_account, "withdraw", args![TARI_TOKEN, 100]) .put_last_instruction_output_on_workspace("b") .call_method(user2_account, "deposit", args![Workspace("b")]) @@ -268,7 +268,7 @@ fn take_from_bucket() { let (bob, _proof, _bob_sk) = test.create_empty_account(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(alice, "withdraw_all", args![TARI_TOKEN]) .put_last_instruction_output_on_workspace("coins") .take_from_bucket("coins", 100u64, "foo_bucket") @@ -306,7 +306,7 @@ fn put_into_bucket_merges_same_resource() { let (bob, _proof, _bob_sk) = test.create_empty_account(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(alice, "withdraw", args![TARI_TOKEN, 600u64]) .put_last_instruction_output_on_workspace("first") .call_method(alice, "withdraw", args![TARI_TOKEN, 400u64]) @@ -336,7 +336,7 @@ fn put_into_bucket_rejects_resource_mismatch() { // Mint a non-TARI fungible resource and fund alice with it. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("faucet_address") .call_function(faucet_template, "mint_with_opts", args![ Amount::from(1_000_000u64), @@ -357,7 +357,7 @@ fn put_into_bucket_rejects_resource_mismatch() { .expect("TT resource not found"); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(alice, "withdraw", args![TARI_TOKEN, 100u64]) .put_last_instruction_output_on_workspace("tari_bucket") .call_method(alice, "withdraw", args![tt_resource, 100u64]) diff --git a/crates/engine/tests/address_allocation.rs b/crates/engine/tests/address_allocation.rs index 0b835e8361..a9ddf85de1 100644 --- a/crates/engine/tests/address_allocation.rs +++ b/crates/engine/tests/address_allocation.rs @@ -3,7 +3,7 @@ use tari_engine::runtime::TransactionCommitError; use tari_engine_types::{component::derive_component_address_from_public_key, indexed_value::IndexedValue}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; use tari_template_lib::types::{ComponentAddress, ResourceAddress}; use tari_template_test_tooling::{TemplateTest, support::assert_error::assert_reject_reason, xtr_faucet_component}; @@ -15,7 +15,7 @@ fn it_allocates_addresses_in_template_code() { let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/address_allocation"]); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(test.get_template_address("AddressAllocationTest"), "create", args![]) .build_and_seal(test.secret_key()), vec![], @@ -67,7 +67,7 @@ fn it_fails_if_address_allocation_is_not_used() { let template_addr = test.get_template_address("AddressAllocationTest"); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "drop_component_allocation", args![]) .build_and_seal(test.secret_key()), vec![], @@ -75,7 +75,7 @@ fn it_fails_if_address_allocation_is_not_used() { assert_reject_reason(reason, TransactionCommitError::DanglingAddressAllocations { count: 1 }); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "drop_resource_allocation", args![]) .build_and_seal(test.secret_key()), vec![], @@ -89,7 +89,7 @@ fn it_fails_if_instruction_allocated_addresses_are_not_used() { let mut test = TemplateTest::new(CRATE_PATH, ["tests/templates/address_allocation"]); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("my_addr") .allocate_resource_address("my_res") .build_and_seal(test.secret_key()), @@ -106,7 +106,7 @@ fn it_allocates_an_address_using_instructions() { let template_addr = test.get_template_address("AddressAllocationTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("my_addr") .allocate_resource_address("my_res") .call_function(template_addr, "get_component_allocation_address", args![Workspace( @@ -155,7 +155,7 @@ fn it_allows_calls_to_component_using_the_allocated_address() { let template_addr = test.get_template_address("AddressAllocationTest"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("my_addr") .allocate_resource_address("my_res") .call_function(template_addr, "create_from_allocations", args![ @@ -185,7 +185,7 @@ fn it_allows_calls_to_component_using_a_component_on_the_workspace() { derive_component_address_from_public_key(&ACCOUNT_TEMPLATE_ADDRESS, &test.to_public_key_bytes()); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .create_account(test.to_public_key_bytes()) // Put the created account address on the workspace, and .put_last_instruction_output_on_workspace("account") diff --git a/crates/engine/tests/airdrop.rs b/crates/engine/tests/airdrop.rs index 3003ede895..e935202b68 100644 --- a/crates/engine/tests/airdrop.rs +++ b/crates/engine/tests/airdrop.rs @@ -4,7 +4,7 @@ use ootle_byte_type::ToByteType; use tari_engine_types::substate::SubstateId; use tari_ootle_common_types::substate_type::SubstateType; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{Amount, ComponentAddress}; use tari_template_test_tooling::TemplateTest; @@ -24,7 +24,7 @@ fn airdrop() { let total_supply: Amount = test.call_method(airdrop, "total_supply", args![], vec![test.owner_proof()]); assert_eq!(total_supply, Amount::from(100u64)); - let builder = Transaction::builder_localnet().then(|builder| { + let builder = Transaction::builder_localnet(Epoch(1)).then(|builder| { // Create 50 accounts (0..50).fold(builder, |builder, _| { let (_, owner_public_key, _) = test.create_owner_proof(); @@ -45,7 +45,7 @@ fn airdrop() { test.call_method::<()>(airdrop, "open_airdrop", args![], vec![test.owner_proof()]); test.build_and_execute( - Transaction::builder_localnet().then(|builder| { + Transaction::builder_localnet(Epoch(1)).then(|builder| { addresses.iter().fold(builder, |builder, addr| { builder.call_method(airdrop, "add_recipient", args![addr]) }) @@ -55,7 +55,7 @@ fn airdrop() { .unwrap_success(); let result = test.build_and_execute( - Transaction::builder_localnet().then(|builder| { + Transaction::builder_localnet(Epoch(1)).then(|builder| { addresses.iter().fold(builder, |builder, addr| { builder .call_method(airdrop, "claim_any", args![addr]) diff --git a/crates/engine/tests/asserts.rs b/crates/engine/tests/asserts.rs index 3998196ef1..ef67b34fa8 100644 --- a/crates/engine/tests/asserts.rs +++ b/crates/engine/tests/asserts.rs @@ -5,7 +5,7 @@ use std::vec; use tari_crypto::ristretto::RistrettoSecretKey; use tari_engine::runtime::{AssertError, RuntimeError}; -use tari_ootle_transaction::{Assertion, CheckOrd, Instruction, Transaction, args, args::WorkspaceOffsetId}; +use tari_ootle_transaction::{Assertion, CheckOrd, Epoch, Instruction, Transaction, args, args::WorkspaceOffsetId}; use tari_template_lib::types::{ ComponentAddress, NonFungibleAddress, @@ -52,7 +52,7 @@ mod assert_bucket_contains { let mut test: AssertTest = setup(); test.template_test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account, "withdraw", args![test.faucet_resource, 1000u64]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_bucket_contains_at_least("faucet_bucket", test.faucet_resource, FAUCET_WITHDRAWAL_AMOUNT) @@ -72,7 +72,7 @@ mod assert_bucket_contains { let invalid_resource_address = NFT_FAUCET_RESOURCE_ADDRESS; let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account, "withdraw", args![test.faucet_resource, 1000u64]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_bucket_contains_at_least("faucet_bucket", invalid_resource_address, FAUCET_WITHDRAWAL_AMOUNT) @@ -98,7 +98,7 @@ mod assert_bucket_contains { let min_amount = FAUCET_WITHDRAWAL_AMOUNT + 1; let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account, "withdraw", args![test.faucet_resource, 1000u64]) .put_last_instruction_output_on_workspace("faucet_bucket") // Passes @@ -127,7 +127,7 @@ mod assert_bucket_contains { let mut test: AssertTest = setup(); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account, "withdraw", args![test.faucet_resource, 1000u64]) // we are going to assert a workspace value that is NOT a bucket .call_method(test.account, "get_balances", args![]) @@ -151,7 +151,7 @@ mod assert_bucket_contains { let mut test: AssertTest = setup(); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account, "withdraw", args![test.faucet_resource, 1000u64]) .put_last_instruction_output_on_workspace("faucet_bucket") // we are going to assert a key that does not exist in the workspace @@ -184,7 +184,7 @@ mod assert_is_not_null { let mut test: AssertTest = setup(); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // we are going to assert a key that does not exist in the workspace // assert_bucket_contains would panic if called with a non-existing key .add_instruction(Instruction::Assert { @@ -207,7 +207,7 @@ mod assert_is_not_null { let mut test: AssertTest = setup(); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account, "withdraw", args![test.faucet_resource, 1000u64]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_workspace_item_is_not_null("faucet_bucket") @@ -232,7 +232,7 @@ mod assert_bucket_contains_non_fungibles { let mut test: AssertTest = setup(); test.template_test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(NFT_FAUCET_COMPONENT_ADDRESS, "mint", args![5, tari_bor::Value::Null]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_bucket_contains_non_fungibles_all("faucet_bucket", NFT_FAUCET_RESOURCE_ADDRESS, vec![ @@ -270,7 +270,7 @@ mod assert_bucket_contains_non_fungibles { // Take some tokens from the faucet to get a bucket with non-fungibles let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account, "withdraw", args![test.faucet_resource, 1000u64]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_bucket_contains_non_fungibles_all("faucet_bucket", test.faucet_resource, vec![]) @@ -290,7 +290,7 @@ mod assert_bucket_contains_non_fungibles { let mut test: AssertTest = setup(); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(NFT_FAUCET_COMPONENT_ADDRESS, "mint", args![5, tari_bor::Value::Null]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_bucket_contains_non_fungibles_all("faucet_bucket", NFT_FAUCET_RESOURCE_ADDRESS, vec![ @@ -313,7 +313,7 @@ mod assert_bucket_contains_non_fungibles { let mut test: AssertTest = setup(); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(NFT_FAUCET_COMPONENT_ADDRESS, "mint", args![5, tari_bor::Value::Null]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_bucket_contains_non_fungibles_any("faucet_bucket", NFT_FAUCET_RESOURCE_ADDRESS, vec![ @@ -335,7 +335,7 @@ mod assert_bucket_contains_non_fungibles { let mut test: AssertTest = setup(); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(NFT_FAUCET_COMPONENT_ADDRESS, "mint", args![5, tari_bor::Value::Null]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_bucket_contains_non_fungibles_none_of("faucet_bucket", NFT_FAUCET_RESOURCE_ADDRESS, vec![ @@ -358,7 +358,7 @@ mod assert_bucket_contains_non_fungibles { let mut test: AssertTest = setup(); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(NFT_FAUCET_COMPONENT_ADDRESS, "mint", args![5, tari_bor::Value::Null]) .put_last_instruction_output_on_workspace("faucet_bucket") .assert_bucket_contains_non_fungibles_not_any_of("faucet_bucket", NFT_FAUCET_RESOURCE_ADDRESS, vec![ diff --git a/crates/engine/tests/burn.rs b/crates/engine/tests/burn.rs index aca2cb30de..ed467118db 100644 --- a/crates/engine/tests/burn.rs +++ b/crates/engine/tests/burn.rs @@ -1,7 +1,7 @@ // Copyright 2025 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{Amount, ComponentAddress, confidential::ConfidentialOutputStatement}; use tari_template_test_tooling::TemplateTest; @@ -17,7 +17,7 @@ fn it_burns_all_resource_types() { let initial_supply = ConfidentialOutputStatement::mint_revealed(1000u64); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(recall_template, "new", args![initial_supply]) .build_and_seal(test.secret_key()), vec![], @@ -51,7 +51,7 @@ fn it_burns_all_resource_types() { } test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "burn_all", args![]) .build_and_seal(test.secret_key()), vec![], diff --git a/crates/engine/tests/complex_fee_payment.rs b/crates/engine/tests/complex_fee_payment.rs index 8a8de2fdef..04fe5b57b6 100644 --- a/crates/engine/tests/complex_fee_payment.rs +++ b/crates/engine/tests/complex_fee_payment.rs @@ -11,7 +11,7 @@ use tari_engine::fees::FeeTable; use tari_engine_types::{fees::FeeSource, limits::FREE_COMPUTE_GRACE_POINTS}; use tari_ootle_common_types::substate_type::SubstateType; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{Amount, ComponentAddress, ResourceAddress, constants::TARI_TOKEN}; use tari_template_test_tooling::TemplateTest; @@ -64,7 +64,7 @@ fn fee_paid_via_amm_swap_fits_within_grace() { // A non-TARI resource the account holds and will swap for TARI to pay its fee. let (token_faucet, token) = create_faucet(&mut test, "SWAP"); test.build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(token_faucet, "take_free_coins_custom", args![Amount::from( 1_000_000_000u64 )]) @@ -77,7 +77,7 @@ fn fee_paid_via_amm_swap_fits_within_grace() { // A (TARI, token) pool, seeded with liquidity from the funded account. let pool = create_pool(&mut test, TARI_TOKEN, token); test.build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(account, "withdraw", args![TARI_TOKEN, Amount::from(POOL_LIQUIDITY)]) .put_last_instruction_output_on_workspace("tari") .call_method(account, "withdraw", args![token, Amount::from(POOL_LIQUIDITY)]) @@ -99,7 +99,7 @@ fn fee_paid_via_amm_swap_fits_within_grace() { // The worst-case fee payment: source TARI by swapping `token` through the pool inside the fee // intent, then pay from the resulting bucket. Runs entirely on credit before `pay_fee`. - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder .call_method(account, "withdraw", args![token, Amount::from(SWAP_INPUT)]) diff --git a/crates/engine/tests/compute_fee_budget.rs b/crates/engine/tests/compute_fee_budget.rs index 14e5812e29..62c74236da 100644 --- a/crates/engine/tests/compute_fee_budget.rs +++ b/crates/engine/tests/compute_fee_budget.rs @@ -11,7 +11,7 @@ use tari_crypto::ristretto::RistrettoSecretKey; use tari_engine::fees::FeeTable; use tari_engine_types::{commit_result::RejectReason, fees::FeeSource, limits::FREE_COMPUTE_GRACE_POINTS}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{ComponentAddress, NonFungibleAddress, TemplateAddress}; use tari_template_test_tooling::TemplateTest; @@ -50,7 +50,7 @@ fn setup() -> Harness { let per_round = { let mut points = |rounds: u64| -> u64 { - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 900_000_000u64) .call_function(bench, "bench_div_u64", args![rounds]) .build_and_seal(&key); @@ -101,7 +101,7 @@ fn underpaid_compute_is_capped_at_what_the_payment_funds() { } = setup(); let rounds = ABOVE_GRACE_POINTS / per_round; - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, FEE_PAYMENT) .call_function(bench, "bench_div_u64", args![rounds]) .build_and_seal(&key); @@ -153,7 +153,7 @@ fn grace_does_not_extend_past_the_fee_checkpoint() { } = setup(); let rounds = BELOW_GRACE_POINTS / per_round; - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, FEE_PAYMENT) .call_function(bench, "bench_div_u64", args![rounds]) .build_and_seal(&key); @@ -179,7 +179,7 @@ fn paying_more_raises_the_compute_allowance() { // Sized from the call's point cost (1 fee unit per point) plus margin for the fixed charges, // so it keeps funding the call as the grace constant moves. let fee_payment = ABOVE_GRACE_POINTS + 10_000_000; - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, fee_payment) .call_function(bench, "bench_div_u64", args![rounds]) .build_and_seal(&key); @@ -201,7 +201,7 @@ fn fee_intent_may_spend_compute_within_grace_before_paying() { } = setup(); let rounds = BELOW_GRACE_POINTS / per_round; - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder .call_function(bench, "bench_div_u64", args![rounds]) @@ -227,7 +227,7 @@ fn fee_intent_cannot_exceed_grace_compute() { } = setup(); let rounds = ABOVE_GRACE_POINTS / per_round; - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder .call_function(bench, "bench_div_u64", args![rounds]) diff --git a/crates/engine/tests/confidential.rs b/crates/engine/tests/confidential.rs index 88eb60d68a..5315035ae9 100644 --- a/crates/engine/tests/confidential.rs +++ b/crates/engine/tests/confidential.rs @@ -16,7 +16,7 @@ use tari_engine_types::{ substate::SubstateId, }; use tari_ootle_common_types::substate_type::SubstateType; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::{ models::Account, types::{ @@ -120,7 +120,7 @@ fn minting_a_commitment_without_a_value_proof_is_rejected() { let commitment = commit_amount(&mint_mask, Amount::from(42u64)).unwrap().to_byte_type(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + test.transaction() .call_method(faucet, "mint_more", args![mint_proof, ValueProofs::new()]) .build_and_seal(test.secret_key()), vec![owner], @@ -144,7 +144,7 @@ fn mint_more_later() { let withdraw_proof = generate_withdraw_proof(&mask, 100, None, 0u64); template_test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "take_free_coins", args![withdraw_proof.proof]) .put_last_instruction_output_on_workspace("coins") .call_method(user_account, "deposit", args![Workspace("coins")]) @@ -495,7 +495,7 @@ fn mint_and_transfer_revealed() { let withdraw = generate_withdraw_proof_with_inputs(&[], 123u64, 100, None, 23); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "take_free_coins", args![withdraw.proof]) .put_last_instruction_output_on_workspace("b") .call_method(user_account, "deposit", args![Workspace("b")]) @@ -515,7 +515,7 @@ fn mint_revealed_with_invalid_proof() { let (mut test, faucet, _faucet_resx) = setup(confidential_proof, value_proofs, None); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "mint_revealed_with_bad_range_proof", args![123]) .build_and_seal(test.secret_key()), vec![], @@ -559,7 +559,7 @@ fn mint_with_view_key() { let withdraw_proof = generate_withdraw_proof_with_view_key(&mask, 100, 55, Some(100 - 55), 0u64, view_key); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "take_free_coins", args![withdraw_proof.proof]) .put_last_instruction_output_on_workspace("coins") .call_method(user_account, "deposit", args![Workspace("coins")]) @@ -612,7 +612,7 @@ fn freeze_then_attempt_spend() { let owner = test.owner_proof(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "freeze_confidential_outputs", args![vec![commitment]]) .build_and_seal(test.secret_key()), vec![owner.clone()], @@ -622,7 +622,7 @@ fn freeze_then_attempt_spend() { let withdraw_proof = generate_withdraw_proof(&mask, 100, None, 0u64); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "take_free_coins", args![withdraw_proof.proof.clone()]) .put_last_instruction_output_on_workspace("coins") .call_method(user_account, "deposit", args![Workspace("coins")]) @@ -635,14 +635,14 @@ fn freeze_then_attempt_spend() { }); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "unfreeze_confidential_outputs", args![vec![commitment]]) .build_and_seal(test.secret_key()), vec![owner], ); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "take_free_coins", args![withdraw_proof.proof]) .put_last_instruction_output_on_workspace("coins") .call_method(user_account, "deposit", args![Workspace("coins")]) @@ -665,7 +665,7 @@ fn unfreeze_and_spend_in_one_transaction_downs_the_output() { let owner = test.owner_proof(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "freeze_confidential_outputs", args![vec![commitment]]) .build_and_seal(test.secret_key()), vec![owner.clone()], @@ -675,7 +675,7 @@ fn unfreeze_and_spend_in_one_transaction_downs_the_output() { let withdraw_proof = generate_withdraw_proof(&mask, 100, None, 0u64); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "unfreeze_confidential_outputs", args![vec![commitment]]) .call_method(faucet, "take_free_coins", args![withdraw_proof.proof]) .put_last_instruction_output_on_workspace("coins") @@ -708,7 +708,7 @@ fn minting_a_duplicate_commitment_is_rejected() { let (mint_proof, mint_mask, mint_value_proofs) = mint_statement(42, None); let owner = test.owner_proof(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "mint_more", args![ mint_proof.clone(), mint_value_proofs.clone() @@ -720,7 +720,7 @@ fn minting_a_duplicate_commitment_is_rejected() { let commitment = commit_amount(&mint_mask, Amount::from(42u64)).unwrap().to_byte_type(); let address = ConfidentialOutputAddress::new(faucet_resx.as_resource_address().unwrap(), commitment); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "mint_more", args![mint_proof, mint_value_proofs]) .build_and_seal(test.secret_key()), vec![owner], @@ -743,13 +743,13 @@ fn a_transaction_over_the_withdraw_cap_is_rejected() { // Revealed funds make each withdraw a revealed-only confidential withdraw: no commitments are needed // and the per-withdraw work is trivial, so only the per-transaction cap is exercised. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "mint_revealed", args![Amount::new(max_withdraws as u128 + 1)]) .build_and_seal(test.secret_key()), vec![owner.clone()], ); - let mut builder = Transaction::builder_localnet(); + let mut builder = Transaction::builder_localnet(Epoch(1)); for _ in 0..=max_withdraws { builder = builder.call_method(faucet, "take_free_coins", args![ ConfidentialWithdrawProof::revealed_withdraw(Amount::new(1)) @@ -784,7 +784,7 @@ fn minting_and_burning_a_commitment_tracks_total_supply() { // Move the whole 500 out of the commitment and into revealed funds. let reveal_proof = generate_reveal_proof(&mask, 500); test.execute_expect_success( - Transaction::builder_localnet() + test.transaction() .call_method(faucet, "take_free_coins", args![reveal_proof]) .put_last_instruction_output_on_workspace("coins") .call_function(utilities, "burn_bucket", args![Workspace("coins")]) @@ -809,7 +809,7 @@ fn burning_a_bucket_holding_commitments_without_a_value_proof_is_rejected() { .unwrap() .to_byte_type(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + test.transaction() .call_method(faucet, "take_free_coins", args![withdraw_proof.proof]) .put_last_instruction_output_on_workspace("coins") .call_function(utilities, "burn_bucket", args![Workspace("coins")]) @@ -846,7 +846,7 @@ fn burning_a_bucket_holding_commitments_with_value_proofs_decreases_total_supply )]); test.execute_expect_success( - Transaction::builder_localnet() + test.transaction() .call_method(faucet, "take_free_coins", args![withdraw_proof.proof]) .put_last_instruction_output_on_workspace("coins") .call_function(utilities, "burn_bucket_with_value_proofs", args![ diff --git a/crates/engine/tests/cross_template.rs b/crates/engine/tests/cross_template.rs index 5e2a555e09..500f566ff0 100644 --- a/crates/engine/tests/cross_template.rs +++ b/crates/engine/tests/cross_template.rs @@ -7,7 +7,7 @@ use tari_engine_types::{ commit_result::{ExecuteResult, RejectReason}, limits, }; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{Amount, ComponentAddress, ResourceAddress, TemplateAddress}; use tari_template_test_tooling::{ TemplateTest, @@ -90,7 +90,7 @@ fn create_resource_and_fund_account(test: &mut TemplateTest, account: ComponentA let initial_supply = Amount::from(1_000_000_000_000u64); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("faucet_address") .call_function(faucet_template, "mint_with_opts", args![ initial_supply, @@ -134,7 +134,7 @@ fn it_allows_function_to_method_calls() { // create a new cross_template component, this time using a constructor that gets information from a method call let res = test.template_test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("component_1") .call_function(test.cross_call_template, "new_from_component", args![ Workspace("component_1"), @@ -227,7 +227,7 @@ fn it_fails_on_invalid_calls() { let result = test .template_test .try_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method( components.cross_template_component, "call_method_that_does_not_exist", @@ -262,7 +262,7 @@ fn it_does_not_propagate_permissions() { let result = test .template_test .try_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(components.cross_template_component, "malicious_withdraw", args![ victim_account, fungible_resource, @@ -336,7 +336,7 @@ fn it_fails_when_surpassing_recursion_limit_with_many_nested_components() { let result = test .template_test .try_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(last_composability_component, "get_nested_value", args![]) .build_and_seal(&private_key), vec![], @@ -357,7 +357,7 @@ fn it_fails_when_surpassing_recursion_limit() { let components = initialize_composability(&mut test); test.template_test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(components.cross_template_component, "recursion", args![ // -1 to account for the initial call max_call_depth - 1 @@ -366,7 +366,7 @@ fn it_fails_when_surpassing_recursion_limit() { vec![], ); let reason = test.template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(components.cross_template_component, "recursion", args![max_call_depth]) .build_and_seal(&private_key), vec![], diff --git a/crates/engine/tests/events.rs b/crates/engine/tests/events.rs index fab50fc506..cbd047849f 100644 --- a/crates/engine/tests/events.rs +++ b/crates/engine/tests/events.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use tari_engine::runtime::RuntimeError; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; use tari_template_lib::types::Amount; use tari_template_test_tooling::{ @@ -43,7 +43,7 @@ fn cannot_use_standard_topic() { let (_, _, private_key) = template_test.create_funded_account(); let invalid_topic = "std.mytopic"; let reason = template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(event_emitter_template, "test_function", args![invalid_topic]) .build_and_seal(&private_key), [].into(), @@ -65,7 +65,7 @@ fn builtin_vault_events() { // transfer some tokens between accounts let amount = Amount::from(100u64); let result = test.build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(sender_address, "withdraw", args![TARI_TOKEN, amount]) .put_last_instruction_output_on_workspace("foo_bucket") .call_method(receiver_address, "deposit", args![Workspace("foo_bucket")]), diff --git a/crates/engine/tests/fees.rs b/crates/engine/tests/fees.rs index 2f55e2de39..388e94f4ba 100644 --- a/crates/engine/tests/fees.rs +++ b/crates/engine/tests/fees.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use tari_engine_types::{commit_result::RejectReason, fees::FeeSource}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{Amount, ComponentAddress, constants::STEALTH_TARI_RESOURCE_ADDRESS}; use tari_template_test_tooling::{TemplateTest, support::assert_error::assert_reject_reason, xtr_faucet_component}; @@ -19,7 +19,7 @@ fn deducts_fees_from_payments_and_refunds_the_rest() { test.enable_fees(); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 1000u64) .call_function(test.get_template_address("State"), "new", args![]) .build_and_seal(&private_key), @@ -53,7 +53,7 @@ fn deducts_fees_when_transaction_fails() { test.enable_fees(); let result = test.execute_and_commit_on_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 1000u64) .call_function(test.get_template_address("State"), "this_doesnt_exist", args![]) .build_and_seal(&private_key), @@ -81,7 +81,7 @@ fn deposit_from_faucet_then_pay() { test.enable_fees(); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder // Faucet deposits free coins into the account @@ -120,7 +120,7 @@ fn another_account_pays_partially_for_fees() { const FAUCET_CAP: u64 = 100; let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // Faucet pays a little .pay_fee_from_component(account_fee, Amount::from(FAUCET_CAP)) // Account pays the rest @@ -169,7 +169,7 @@ fn failed_fee_transaction() { test.enable_fees(); let result = test .try_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder // This instruction will fail @@ -211,7 +211,7 @@ fn fail_partial_paid_fees() { const FEE_PAID: u64 = 100; let result = test.execute_expect_commit( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // Pay less fees than the cost of the main transaction .pay_fee_from_component(account, Amount::from(FEE_PAID)) // These instructions should not be applied @@ -261,7 +261,7 @@ fn fail_pay_negative_fee() { test.enable_fees(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| builder.call_method(account, "pay_fee", args![-100])) .build_and_seal(&private_key), vec![owner_token], @@ -286,7 +286,7 @@ fn fail_pay_less_fees_than_fee_transaction() { let result = test .try_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { (0u32..=0).fold(builder, |builder, i| { builder.call_method( @@ -366,7 +366,7 @@ fn fail_pay_too_little_no_fee_instruction() { test.enable_fees(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder // These instructions should not be applied @@ -407,7 +407,7 @@ fn failure_pay_fee_in_main_instructions() { test.enable_fees(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // Pay in fee intent, enough to pass this step .pay_fee_from_component(account, 100u64) // Call pay_fee in main instructions (outside fee instructions) not permitted @@ -431,7 +431,7 @@ fn dangling_bucket_pay_fees() { test.enable_fees(); let result = test.execute_and_commit_on_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, Amount::from(500u64)) .call_method(account, "withdraw", args![STEALTH_TARI_RESOURCE_ADDRESS, 10]) .put_last_instruction_output_on_workspace("dangling_bucket") @@ -471,7 +471,7 @@ fn template_load_fee_charged_once_per_template_per_transaction() { // Single State call — establishes the baseline TemplateLoad fee for {Account, State}. let single = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 1000u64) .call_method(state, "set", args![1u32]) .build_and_seal(&private_key), @@ -481,7 +481,7 @@ fn template_load_fee_charged_once_per_template_per_transaction() { // Five State calls — same template touched five extra times. Without dedup, TemplateLoad // would scale with call count; with dedup it must match the single-call baseline. let many = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 1000u64) .call_method(state, "set", args![1u32]) .call_method(state, "set", args![2u32]) diff --git a/crates/engine/tests/freeze.rs b/crates/engine/tests/freeze.rs index 7ef116dbe3..7828f57404 100644 --- a/crates/engine/tests/freeze.rs +++ b/crates/engine/tests/freeze.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use tari_engine::runtime::RuntimeError; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::{ args::VaultFreezeFlag, types::{ComponentAddress, ResourceAddress, VaultId}, @@ -21,7 +21,7 @@ fn it_freezes_vaults_containing_a_freezable_resource() { // Create a new Freeze component and deposit some resources into the account let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("freeze_comp") .call_function(template, "new", args![Workspace("freeze_comp")]) .call_method("freeze_comp", "withdraw", args![1000]) @@ -39,7 +39,7 @@ fn it_freezes_vaults_containing_a_freezable_resource() { // Freeze the account's vault test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "freeze", args![vault_id]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -47,7 +47,7 @@ fn it_freezes_vaults_containing_a_freezable_resource() { // Attempt to withdraw from the frozen vault - FAIL let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(account, "withdraw", args![resource, 10]) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) @@ -62,7 +62,7 @@ fn it_freezes_vaults_containing_a_freezable_resource() { // Unfreeze the vault test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "unfreeze", args![vault_id]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -70,7 +70,7 @@ fn it_freezes_vaults_containing_a_freezable_resource() { // Withdraw from the un-frozen vault - SUCCESS test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(account, "withdraw", args![resource, 10]) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) diff --git a/crates/engine/tests/fungible.rs b/crates/engine/tests/fungible.rs index e66ec8dce5..1a8978a345 100644 --- a/crates/engine/tests/fungible.rs +++ b/crates/engine/tests/fungible.rs @@ -3,7 +3,7 @@ use ootle_byte_type::ToByteType; use tari_engine_types::{crypto::commit_amount, vault::Vault}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{Amount, ComponentAddress, ResourceType}; use tari_template_test_tooling::{ TemplateTest, @@ -18,7 +18,7 @@ fn it_does_not_overflow_when_minting_a_huge_initial_supply() { let template = test.get_template_address("Fungible"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template, "with_supply", args![Amount::MAX]) .build_and_seal(test.secret_key()), vec![], @@ -27,7 +27,7 @@ fn it_does_not_overflow_when_minting_a_huge_initial_supply() { let component: ComponentAddress = result.finalize.execution_results[0].decode().unwrap(); let all_to_confidential = generate_withdraw_proof_with_inputs(&[], u64::MAX, u64::MAX, None, 0); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "convert", args![all_to_confidential.proof]) .build_and_seal(test.secret_key()), vec![], @@ -53,7 +53,7 @@ fn it_does_not_overflow_when_minting_more_then_amount_max_fungible_tokens() { let template = test.get_template_address("Fungible"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template, "with_supply", args![i64::MAX]) .build_and_seal(test.secret_key()), vec![], @@ -62,7 +62,7 @@ fn it_does_not_overflow_when_minting_more_then_amount_max_fungible_tokens() { let component: ComponentAddress = result.finalize.execution_results[0].decode().unwrap(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "fungible_mint_more", args![i64::MAX]) .call_method(component, "fungible_mint_more", args![i64::MAX]) .build_and_seal(test.secret_key()), @@ -83,12 +83,12 @@ fn it_does_not_overflow_when_minting_more_then_amount_max_confidential_tokens() let all_to_confidential = generate_withdraw_proof_with_inputs(&[], u64::MAX, u64::MAX, None, 0); test.execute_expect_success( - Transaction::builder_localnet().build_and_seal(test.secret_key()), + Transaction::builder_localnet(Epoch(1)).build_and_seal(test.secret_key()), vec![], ); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("fungible") .call_function(template, "with_address_and_supply", args![ Workspace("fungible"), @@ -105,7 +105,7 @@ fn it_does_not_overflow_when_minting_more_then_amount_max_confidential_tokens() let (more_supply2, _mask, _) = generate_confidential_output_statement(u64::MAX, None); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "confidential_mint_more", args![more_supply1]) .call_method(component, "confidential_mint_more", args![more_supply2]) .build_and_seal(test.secret_key()), diff --git a/crates/engine/tests/guessing_game.rs b/crates/engine/tests/guessing_game.rs index 1612b38912..1b96492874 100644 --- a/crates/engine/tests/guessing_game.rs +++ b/crates/engine/tests/guessing_game.rs @@ -1,7 +1,7 @@ // Copyright 2025 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::{ prelude::ComponentAddress, types::{NonFungibleAddress, NonFungibleId}, @@ -24,7 +24,7 @@ fn it_works() { // turned off by default. To turn them on use `test.enable_fees()`, you will then need to add fee instructions to // pay the fee. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) // Allocate a new component address .allocate_component_address("guessing_game") // Construct the component by calling the `new` function of the template, passing the address allocation as an argument @@ -52,7 +52,7 @@ fn it_works() { // Just to demonstrate, we'll test an "unhappy path": user 1 makes a bad guess, since our template requires guesses // between 0 and 10. let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(game_address, "guess", args![100, user1_account]) .build_and_seal(&user1_secret), vec![], @@ -66,7 +66,7 @@ fn it_works() { // Let's make a correct guess with user 1 test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(game_address, "guess", args![5, user1_account]) .build_and_seal(&user1_secret), vec![], @@ -74,7 +74,7 @@ fn it_works() { // User 2 makes a correct guess test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(game_address, "guess", args![7, user2_account]) .build_and_seal(&user2_secret), vec![], @@ -82,7 +82,7 @@ fn it_works() { // User 3 makes a correct guess test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(game_address, "guess", args![3, user3_account]) .build_and_seal(&user3_secret), vec![], @@ -90,7 +90,7 @@ fn it_works() { // Now let's end the game and check the results. let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(game_address, "end_game_and_payout", args![]) .build_and_seal(test.secret_key()), vec![], diff --git a/crates/engine/tests/limits.rs b/crates/engine/tests/limits.rs index 8f5cb8bcc7..531e196e42 100644 --- a/crates/engine/tests/limits.rs +++ b/crates/engine/tests/limits.rs @@ -5,7 +5,7 @@ use std::slice; use tari_engine::{runtime::LimitError, wasm::WasmExecutionError}; use tari_engine_types::limits; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_abi::CallInfo; use tari_template_lib::types::bytes::Bytes; use tari_template_test_tooling::{TemplateTest, support::assert_error::assert_reject_reason}; @@ -24,7 +24,7 @@ fn max_call_size_limit() { let overhead = call_size - limits::ENGINE_LIMITS.max_call_size; test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function( template, "new", @@ -35,7 +35,7 @@ fn max_call_size_limit() { ); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function( template, "new", @@ -61,7 +61,7 @@ fn max_random_bytes_len_limit() { assert_eq!(bytes.len(), max_len as usize); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template, "request_random_bytes", args!(max_len + 1)) .build_and_seal(test.secret_key()), vec![], diff --git a/crates/engine/tests/metering_budget.rs b/crates/engine/tests/metering_budget.rs index 5a61820e4c..741f15c2dd 100644 --- a/crates/engine/tests/metering_budget.rs +++ b/crates/engine/tests/metering_budget.rs @@ -7,7 +7,7 @@ //! total is capped across calls. use tari_engine_types::{commit_result::RejectReason, fees::FeeSource}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_test_tooling::TemplateTest; const CRATE_PATH: &str = env!("CARGO_MANIFEST_DIR"); @@ -27,7 +27,7 @@ fn per_transaction_budget_caps_total_across_calls() { test.enable_fees(); let call = |rounds: u64, n: usize| { - let mut builder = Transaction::builder_localnet().pay_fee_from_component(account, 900_000_000u64); + let mut builder = Transaction::builder_localnet(Epoch(1)).pay_fee_from_component(account, 900_000_000u64); for _ in 0..n { builder = builder.call_function(addr, "bench_div_u64", args![rounds]); } diff --git a/crates/engine/tests/native_compute_budget.rs b/crates/engine/tests/native_compute_budget.rs index eea7d82047..67e0c5d9ae 100644 --- a/crates/engine/tests/native_compute_budget.rs +++ b/crates/engine/tests/native_compute_budget.rs @@ -18,7 +18,7 @@ use tari_engine_types::{ limits::{FREE_COMPUTE_GRACE_POINTS, MAX_NATIVE_POINTS_PER_TRANSACTION, NativeExecutionPoints, STEALTH_LIMITS}, }; use tari_ootle_common_types::substate_type::SubstateType; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{ComponentAddress, NonFungibleAddress, ResourceAddress}; use tari_template_test_tooling::{ TemplateTest, @@ -90,7 +90,7 @@ fn setup_faucet( let template_addr = test.get_template_address(TEMPLATE_NAME); let initial_supply = transfer_data.statement.inputs_statement.revealed_amount; - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "new", args![ initial_supply, transfer_data.statement, @@ -136,7 +136,7 @@ fn unpaid_native_verification_traps_before_the_crypto_runs() { garbage.statement.outputs_statement.agg_range_proof = rp.try_into().unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| builder.stealth_transfer(faucet_resx, garbage.statement)) .build_and_seal(test.secret_key()), vec![], @@ -196,7 +196,7 @@ fn in_flight_wasm_counts_toward_the_native_allowance() { // Calibrate points-per-round with a paid transaction. let points_for = |test: &mut TemplateTest, rounds: u64| -> u64 { - let tx = Transaction::builder_localnet() + let tx = Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 100_000_000u64) .call_method(faucet, "burn_compute", args![rounds]) .build_and_seal(&key); @@ -222,7 +222,7 @@ fn in_flight_wasm_counts_toward_the_native_allowance() { // Runs in the fee intent, which is where the credit applies — the main instructions are funded by // the payment alone and would trap on the WASM grind long before the native charge. let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder.call_method(faucet, "burn_compute_then_transfer", args![rounds, transfer.statement]) }) @@ -260,7 +260,7 @@ fn paid_native_verification_is_charged() { let mask_badge = NonFungibleAddress::from_public_key(RistrettoPublicKey::from_secret_key(&mint.output_masks[0]).to_byte_type()); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 900_000_000u64) .stealth_transfer(faucet_resx, transfer.statement) .finish() @@ -316,7 +316,7 @@ fn view_key_surcharge_is_charged_per_output() { let mask_badge = NonFungibleAddress::from_public_key(RistrettoPublicKey::from_secret_key(&mint.output_masks[0]).to_byte_type()); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 900_000_000u64) .stealth_transfer(faucet_resx, transfer.statement) .finish() diff --git a/crates/engine/tests/no_concurrency.rs b/crates/engine/tests/no_concurrency.rs index e4282f99ce..d44bef434a 100644 --- a/crates/engine/tests/no_concurrency.rs +++ b/crates/engine/tests/no_concurrency.rs @@ -1,7 +1,7 @@ // Copyright 2025 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_test_tooling::{TemplateTest, support::assert_error::assert_reject_reason}; const TEMPLATE_NAME: &str = "NoConcurrency"; @@ -13,7 +13,7 @@ fn it_panics_if_template_attempts_to_spawn_a_thread() { let mut test = TemplateTest::new(CRATE_PATH, TEMPLATE_PATHS); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function( test.get_template_address(TEMPLATE_NAME), "try_to_spawn_a_thread_static", diff --git a/crates/engine/tests/no_std.rs b/crates/engine/tests/no_std.rs index caaa308b2c..d467fa8b4f 100644 --- a/crates/engine/tests/no_std.rs +++ b/crates/engine/tests/no_std.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use tari_engine_types::indexed_value::IndexedValue; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::ComponentAddress; use tari_template_test_tooling::{TemplateTest, support::assert_error::assert_reject_reason}; @@ -15,7 +15,7 @@ fn it_can_call_a_method() { let template = test.get_template_address("NoStdCounter"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("no_std_counter") .call_function(template, "with_address", args![Workspace("no_std_counter")]) .call_method("no_std_counter", "increment", args![]) @@ -44,7 +44,7 @@ fn it_can_call_a_function() { let template = test.get_template_address("NoStdCounter"); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template, "simple", args![]) .build_and_seal(test.secret_key()), vec![], @@ -58,7 +58,7 @@ fn it_panics() { let template = test.get_template_address("NoStdCounter"); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template, "panic_works", args![]) .build_and_seal(test.secret_key()), vec![], diff --git a/crates/engine/tests/publish_template.rs b/crates/engine/tests/publish_template.rs index fc4d442866..5078ad1c0c 100644 --- a/crates/engine/tests/publish_template.rs +++ b/crates/engine/tests/publish_template.rs @@ -13,7 +13,7 @@ use tari_engine_types::{ published_template::{PublishedTemplateAddress, TemplateBlob}, substate::{SubstateId, SubstateValue}, }; -use tari_ootle_transaction::Transaction; +use tari_ootle_transaction::{Epoch, Transaction}; use tari_template_test_tooling::{ TemplateTest, compile::compile_template, @@ -32,7 +32,7 @@ fn publish_template_success() { PublishedTemplateAddress::from_author_and_binary_hash(&public_key.to_byte_type(), &expected_binary_hash); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account_address, 200_000u64) .publish_template(template.into_code()) .build_and_seal(&account_key), @@ -63,7 +63,7 @@ fn publish_template_invalid_binary() { let mut test = TemplateTest::new(CRATE_PATH, &[] as &[&str]); let (account_address, owner_proof, account_key, _) = test.create_funded_account_with_keypair(); let result = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account_address, 200_000u64) // Main intent instruction #1 .publish_template(vec![1u8, 2, 3]) @@ -85,7 +85,7 @@ fn publish_template_too_big_binary() { let random_wasm_binary = generate_random_binary(limits::ENGINE_LIMITS.max_template_binary_size_bytes + 1); let wasm_binary_size = random_wasm_binary.len(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account_address, 200_000u64) // SAFETY: We are intentionally publishing an oversized binary to test size limits. .publish_template(unsafe { TemplateBlob::new_unchecked(random_wasm_binary) }) @@ -106,7 +106,7 @@ fn rejects_more_than_one_publish_template() { // The per-transaction publish-template cap is checked before any binary is validated, so these (invalid) binaries // never reach WASM validation: the transaction is rejected for carrying too many PublishTemplate instructions. - let mut builder = Transaction::builder_localnet().pay_fee_from_component(account_address, 200_000u64); + let mut builder = Transaction::builder_localnet(Epoch(1)).pay_fee_from_component(account_address, 200_000u64); for i in 0..=limits::MAX_PUBLISH_TEMPLATES_PER_TRANSACTION { builder = builder.publish_template(vec![i as u8]); } diff --git a/crates/engine/tests/recall.rs b/crates/engine/tests/recall.rs index e441a04711..5fe50b89e1 100644 --- a/crates/engine/tests/recall.rs +++ b/crates/engine/tests/recall.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use ootle_byte_type::ToByteType; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{Amount, ComponentAddress, NonFungibleId, ResourceAddress, VaultId}; use tari_template_test_tooling::{ TemplateTest, @@ -27,7 +27,7 @@ fn it_recalls_all_resource_types() { let value_proofs = value_proofs_for_commitment(1000u64, &mask); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(recall_template, "new", args![initial_supply, value_proofs]) .build_and_seal(test.secret_key()), vec![], @@ -43,7 +43,7 @@ fn it_recalls_all_resource_types() { let withdraw = generate_withdraw_proof(&mask, 10, Some(980), 10u64); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(recall_component, "withdraw_some", args![withdraw.proof]) .put_last_instruction_output_on_workspace("buckets") .call_method(account, "deposit", args![Workspace("buckets.0")]) @@ -67,7 +67,7 @@ fn it_recalls_all_resource_types() { .to_byte_type(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(recall_component, "recall_fungible", args![fungible_vault, 6]) .call_method(recall_component, "recall_non_fungibles", args![non_fungible_vault, [ NonFungibleId::from_u32(1) diff --git a/crates/engine/tests/reentrancy.rs b/crates/engine/tests/reentrancy.rs index 3c854ce625..f07d5ee583 100644 --- a/crates/engine/tests/reentrancy.rs +++ b/crates/engine/tests/reentrancy.rs @@ -3,7 +3,7 @@ use tari_engine::runtime::{LockError, LockState}; use tari_engine_types::lock::LockFlag; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::{ prelude::TARI_TOKEN, types::{ComponentAddress, constants::TARI}, @@ -19,7 +19,7 @@ fn it_prevents_reentrant_withdraw() { let (account, _, account_secret) = test.create_funded_account(); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(account, "withdraw", args![TARI_TOKEN, 1000 * TARI]) .put_last_instruction_output_on_workspace("bucket") .call_function(template_addr, "with_bucket", args![Workspace("bucket")]) @@ -32,7 +32,7 @@ fn it_prevents_reentrant_withdraw() { .unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(reentrancy, "reentrant_withdraw", args![1000]) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) @@ -52,7 +52,7 @@ fn it_allows_multiple_immutable_access_to_component() { let reentrancy: ComponentAddress = test.call_function("Reentrancy", "new", args![], vec![]); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(reentrancy, "reentrant_access_immutable", args![]) .build_and_seal(test.secret_key()), vec![], @@ -66,7 +66,7 @@ fn it_prevents_read_access_to_mutating_component() { let reentrancy: ComponentAddress = test.call_function("Reentrancy", "new", args![], vec![]); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(reentrancy, "reentrant_access", args![]) .build_and_seal(test.secret_key()), vec![], @@ -87,7 +87,7 @@ fn it_prevents_multiple_mutable_access_to_component() { let reentrancy: ComponentAddress = test.call_function("Reentrancy", "new", args![], vec![]); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(reentrancy, "reentrant_access_mut", args![]) .build_and_seal(test.secret_key()), vec![], diff --git a/crates/engine/tests/shenanigans.rs b/crates/engine/tests/shenanigans.rs index eb57358c94..9c0b6a64e1 100644 --- a/crates/engine/tests/shenanigans.rs +++ b/crates/engine/tests/shenanigans.rs @@ -7,7 +7,7 @@ use tari_engine_types::{ indexed_value::IndexedWellKnownTypes, resource_container::ResourceError, }; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::{ args::VaultAction, types::{Amount, ComponentAddress, ResourceType, constants::TARI_TOKEN}, @@ -23,7 +23,7 @@ fn it_rejects_dangling_vaults_in_constructor() { let template_addr = test.get_template_address(TEMPLATE_NAME); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "dangling_vault", args![]) .build_and_seal(test.secret_key()), vec![], @@ -42,7 +42,7 @@ fn it_rejects_dangling_vault_that_has_been_returned() { let template_addr = test.get_template_address(TEMPLATE_NAME); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "return_vault", args![]) .build_and_seal(test.secret_key()), vec![], @@ -58,7 +58,7 @@ fn it_rejects_dangling_vaults_in_component() { // Create with vault let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "with_vault", args![]) .build_and_seal(test.secret_key()), vec![], @@ -71,7 +71,7 @@ fn it_rejects_dangling_vaults_in_component() { let indexed = IndexedWellKnownTypes::from_value(component.state()).unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component_address, "drop_vault", args![]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -88,7 +88,7 @@ fn it_rejects_dangling_resources() { let template_addr = test.get_template_address(TEMPLATE_NAME); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "dangling_resource", args![]) .build_and_seal(test.secret_key()), vec![], @@ -103,7 +103,7 @@ fn it_rejects_unknown_substate_ids() { let template_addr = test.get_template_address(TEMPLATE_NAME); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "non_existent_id", args![]) .build_and_seal(test.secret_key()), vec![], @@ -126,7 +126,7 @@ fn it_rejects_references_to_buckets_that_arent_in_scope() { let (account, owner_token, owner_key) = test.create_funded_account(); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "with_vault", args![]) .build_and_seal(&owner_key), vec![owner_token.clone()], @@ -137,7 +137,7 @@ fn it_rejects_references_to_buckets_that_arent_in_scope() { .unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(account, "withdraw", args![TARI_TOKEN, 1000]) .put_last_instruction_output_on_workspace("bucket") .call_method(shenanigans, "take_bucket_zero", args![]) @@ -155,7 +155,7 @@ fn it_rejects_double_ownership_of_vault() { let template_addr = test.get_template_address(TEMPLATE_NAME); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "with_vault_copy", args![]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -177,7 +177,7 @@ fn it_prevents_access_to_vault_id_in_component_context() { }; let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "with_vault", args![]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -188,7 +188,7 @@ fn it_prevents_access_to_vault_id_in_component_context() { .unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(shenanigans, "take_from_a_vault", args![vault_id, 1000]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -208,7 +208,7 @@ fn it_prevents_access_to_out_of_scope_component() { let (account, _, _) = test.create_funded_account(); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "new", args![]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -219,7 +219,7 @@ fn it_prevents_access_to_out_of_scope_component() { .unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(shenanigans, "empty_state_on_component", args![account]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -246,7 +246,7 @@ fn it_disallows_calls_on_vaults_that_are_not_owned_by_current_component() { }; let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function( template_addr, "attempt_to_steal_funds_using_cross_template_call", @@ -276,7 +276,7 @@ fn it_disallows_vault_access_if_vault_is_not_owned() { }; let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "ref_stolen_vault", args![vault_id]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -294,7 +294,7 @@ fn it_disallows_minting_different_resource_type() { let (account, _, _) = test.create_empty_account(); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "new", args![]) .build_and_seal(test.secret_key()), vec![], @@ -305,7 +305,7 @@ fn it_disallows_minting_different_resource_type() { .unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "mint_different_resource_type", args![]) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) @@ -335,7 +335,7 @@ fn it_does_not_bring_non_owned_vault_id_into_scope() { }; let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "with_stolen_vault", args![vault_id]) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) @@ -364,7 +364,7 @@ fn it_disallows_withdraws_from_vaults_outside_of_component_context() { )]); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "take_from_hardcoded_vault", args![]) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) @@ -378,7 +378,7 @@ fn it_disallows_withdraws_from_vaults_outside_of_component_context() { }); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "take_from_vault_and_return_bucket", args![vault_id,]) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) @@ -410,7 +410,7 @@ fn it_disallows_withdraws_from_vaults_outside_of_owning_component() { )]); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "new", args![]) .build_and_seal(test.secret_key()), vec![], @@ -421,7 +421,7 @@ fn it_disallows_withdraws_from_vaults_outside_of_owning_component() { .unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "take_from_hardcoded_vault_in_component_context", args![]) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) diff --git a/crates/engine/tests/signature.rs b/crates/engine/tests/signature.rs index 956dc04b3b..23f6ac7d92 100644 --- a/crates/engine/tests/signature.rs +++ b/crates/engine/tests/signature.rs @@ -11,7 +11,7 @@ use tari_ootle_common_types::{ crypto::create_key_pair_from_seed, substate_type::SubstateType, }; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{ Amount, ComponentAddress, @@ -51,7 +51,7 @@ fn setup(allow_list: Vec) -> (TemplateTest, ComponentAddress) { let mut test = TemplateTest::new(CRATE_PATH, TEMPLATE_PATHS); let template_addr = test.get_template_address(TEMPLATE_NAME); - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "new", args![allow_list]) .build_and_seal(test.secret_key()); @@ -76,7 +76,7 @@ fn claim_with_valid_signature() { let transfer = stealth::generate_transfer_data(NO_INPUTS, 1_000_000_000_000u64, Some(1_000_000_000_000), 0); let signature = sign_it(&s1); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "claim_funds", args![p1, signature, transfer.statement]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -106,7 +106,7 @@ fn multi_claim() { let sig1 = sign_it(&s1); let sig2 = sign_it(&s2); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "claim_funds", args![p1, sig1, transfer1.statement]) .call_method(faucet, "claim_funds", args![p2, sig2, transfer2.statement]) .build_and_seal(test.secret_key()), @@ -123,7 +123,7 @@ fn bad_signature() { let transfer = stealth::generate_transfer_data(NO_INPUTS, 1000u64, Some(1000), 0); let sig1 = sign_it_with(&s1, b"A different message"); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "claim_funds", args![p1, sig1, transfer.statement]) .build_and_seal(test.secret_key()), vec![test.owner_proof()], @@ -144,7 +144,7 @@ fn check_signature_api() { let good_sig = sign_it(&s1); let bad_sig = sign_it_with(&s1, b"A different message"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "check_sig", args![p1, bad_sig]) .call_function(template_addr, "check_sig", args![p1, good_sig]) // Bad public key diff --git a/crates/engine/tests/spend_script.rs b/crates/engine/tests/spend_script.rs index 1074f4d18e..bf5926fbce 100644 --- a/crates/engine/tests/spend_script.rs +++ b/crates/engine/tests/spend_script.rs @@ -18,7 +18,7 @@ use tari_ootle_common_types::{ crypto::create_key_pair_from_seed, substate_type::SubstateType, }; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{ AccessRule, Amount, @@ -93,7 +93,7 @@ fn faucet_new_tx(test: &mut TemplateTest, mint: &StealthSecretTransferData) -> T test.enable_auto_add_proofs_from_signers(); let faucet_template = test.get_template_address(FAUCET_TEMPLATE); let initial_supply = mint.statement.inputs_statement.revealed_amount; - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(faucet_template, "new", args![ initial_supply, mint.statement.clone(), @@ -146,7 +146,7 @@ fn key_path_spend_authorised_by_signer_badge() { let transfer = spend_into(&mint, key_path(pk)); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -163,7 +163,7 @@ fn key_path_spend_rejected_without_signer_badge() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -185,7 +185,7 @@ fn script_path_access_rule_leaf_allows_spend() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -204,7 +204,7 @@ fn script_path_access_rule_leaf_denies_spend() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -248,7 +248,7 @@ fn multi_leaf_tree_spends_via_any_committed_leaf() { let transfer = stealth::generate_transfer_data([input], 0u64, [out(100, key_path(test.to_public_key_bytes()))], 0u64); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -287,7 +287,7 @@ fn revealing_a_leaf_not_in_the_tree_is_rejected() { let transfer = stealth::generate_transfer_data([input], 0u64, [out(100, key_path(test.to_public_key_bytes()))], 0u64); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -310,7 +310,7 @@ fn timelock_allows_spend_at_or_after_unlock_epoch() { // Output is key-path; the timelock is on the input being spent. let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -329,7 +329,7 @@ fn timelock_rejects_spend_before_unlock_epoch() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -351,7 +351,7 @@ fn covenant_allows_output_that_preserves_condition() { // The output carries the same covenant condition (so the same condition_root) -> the covenant is satisfied. let transfer = spend_into(&mint, covenant); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -369,7 +369,7 @@ fn covenant_rejects_output_that_changes_condition() { // The output changes the condition to a key path (different condition_root) -> the covenant rejects the spend. let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -389,7 +389,7 @@ fn covenant_rejects_output_with_added_key_path() { // would be key-spendable next block, escaping the covenant, so preserving the root alone must not satisfy it. let transfer = spend_into(&mint, key_and_conditions(test.to_public_key_bytes(), vec![covenant])); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -409,7 +409,7 @@ fn covenant_rejects_spend_with_no_stealth_outputs() { // preserves the condition, so the spend is rejected. let transfer = stealth::generate_transfer_data([mint.input_spec_for(0, 100)], 0u64, NO_OUTPUTS, 100u64); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -452,7 +452,7 @@ fn covenant_balance_allows_full_conservation() { // The full 100 units stay in the covenant -> conserved. let transfer = spend_with_covenant(&mint, &covenant, vec![out(100, covenant.clone())]); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -470,7 +470,7 @@ fn covenant_balance_rejects_value_leaving_partition() { // All 100 units go to a key-path output, leaving the covenant entirely -> rejected (allowance is zero). let transfer = spend_with_covenant(&mint, &covenant, vec![out(100, key_path(test.to_public_key_bytes()))]); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -494,7 +494,7 @@ fn covenant_balance_allows_withdrawal_within_allowance() { out(30, key_path(test.to_public_key_bytes())), ]); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -517,7 +517,7 @@ fn covenant_balance_rejects_withdrawal_over_allowance() { out(40, key_path(test.to_public_key_bytes())), ]); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -567,7 +567,7 @@ fn covenant_balance_verifies_each_partition_independently() { 0u64, ); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -594,7 +594,7 @@ fn covenant_balance_rejects_understated_withdrawal() { transfer.statement.covenant_claims[0].revealed_amount = Amount::from_u64(30); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -620,7 +620,7 @@ fn covenant_balance_allowance_vault_persists_across_spends() { let spend1 = spend_with_covenant(&mint, &vault, vec![out(70, vault.clone()), out(30, recipient.clone())]); let vault_70 = spend1.output_masks[0].clone(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, spend1.statement) .finish() .seal(test.secret_key()), @@ -642,7 +642,7 @@ fn covenant_balance_allowance_vault_persists_across_spends() { ); let vault_40 = spend2.output_masks[0].clone(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, spend2.statement) .finish() .seal(test.secret_key()), @@ -663,7 +663,7 @@ fn covenant_balance_allowance_vault_persists_across_spends() { 0u64, ); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, over.statement) .finish() .seal(test.secret_key()), @@ -682,7 +682,7 @@ fn always_reject_aborts_spend() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -701,7 +701,7 @@ fn read_only_sandbox_blocks_state_mutation() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -719,7 +719,7 @@ fn sandbox_denies_emit_event() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -743,7 +743,7 @@ fn sandbox_denies_cross_template_call() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -765,7 +765,7 @@ fn spend_script_exceeding_compute_budget_aborts() { // letting an expensive script stall execution. let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -809,7 +809,7 @@ fn signature_lock_allows_valid_signature() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -834,7 +834,7 @@ fn signature_lock_rejects_invalid_signature() { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -858,7 +858,7 @@ fn assert_spend_rejected(function: &str, args: Vec, expected: &str) { let transfer = spend_into(&mint, key_path(test.to_public_key_bytes())); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -930,11 +930,11 @@ fn script_path_witness_increases_transaction_weight() { ); let key_spend = stealth::generate_transfer_data([MaskAndValue { mask, value: 100 }], 0u64, NO_OUTPUTS, 100u64); - let script_tx = Transaction::builder_localnet() + let script_tx = Transaction::builder_localnet(Epoch(1)) .stealth_transfer(STEALTH_TARI_RESOURCE_ADDRESS, script_spend.statement) .finish() .seal(test.secret_key()); - let key_tx = Transaction::builder_localnet() + let key_tx = Transaction::builder_localnet(Epoch(1)) .stealth_transfer(STEALTH_TARI_RESOURCE_ADDRESS, key_spend.statement) .finish() .seal(test.secret_key()); @@ -963,7 +963,7 @@ fn all(conditions: Vec) -> SpendCondition { /// Submits a stealth transfer spending the minted UTXO and asserts success. fn submit_expect_success(test: &mut TemplateTest, resx: ResourceAddress, transfer: StealthSecretTransferData) { test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -979,7 +979,7 @@ fn submit_expect_rejected( expected: &str, ) { let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(resx, transfer.statement) .finish() .seal(test.secret_key()), diff --git a/crates/engine/tests/stealth.rs b/crates/engine/tests/stealth.rs index b26999a0cd..0682d049aa 100644 --- a/crates/engine/tests/stealth.rs +++ b/crates/engine/tests/stealth.rs @@ -16,7 +16,7 @@ use tari_engine_types::{ resource_container::ResourceError, }; use tari_ootle_common_types::{crypto::create_key_pair_from_seed, substate_type::SubstateType}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{ AccessRule, ComponentAddress, @@ -54,7 +54,7 @@ fn setup( let template_addr = test.get_template_address(TEMPLATE_NAME); let initial_supply = transfer_data.statement.inputs_statement.revealed_amount; - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "new", args![ initial_supply, transfer_data.statement, @@ -115,7 +115,7 @@ fn basic_transfer() { 0, ); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement) .finish() .add_signer(&test.to_public_key_bytes(), &mint.output_masks[0]) @@ -167,7 +167,7 @@ fn fee_intent_rejects_a_second_stealth_transfer() { let (first, second) = two_transfers(&mint); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder .stealth_transfer(faucet_resx, first.statement) @@ -195,7 +195,7 @@ fn fee_intent_counts_a_stealth_transfer_performed_from_wasm() { let (first, second) = two_transfers(&mint); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { builder.stealth_transfer(faucet_resx, first.statement).call_function( template_addr, @@ -222,7 +222,7 @@ fn main_intent_may_transfer_after_the_fee_intent_has() { let (first, second) = two_transfers(&mint); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| builder.stealth_transfer(faucet_resx, first.statement)) .stealth_transfer(faucet_resx, second.statement) .finish() @@ -255,7 +255,7 @@ fn programmatic_transfer() { 25, ); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "programmatic_transfer", args![transfer.statement]) .finish() .add_signer(&test.to_public_key_bytes(), &mint.output_masks[0]) @@ -292,7 +292,7 @@ fn transfer_with_revealed_outputs() { 700, ); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) @@ -341,7 +341,7 @@ fn transfer_revealed_between_accounts() { ); let transfer_from_alice_to_bob = stealth::generate_transfer_data(NO_INPUTS, 100u64, [25, 25, 25], 25); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer_from_faucet.statement) .put_last_instruction_output_on_workspace("withdrawn_funds_from_stealth_transfer") .call_method(alice, "deposit", args![Workspace( @@ -394,7 +394,7 @@ fn transfer_invalid_balance_in_statement() { 2, ); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer_from_faucet.statement) .put_last_instruction_output_on_workspace("bucket") .call_method(alice, "deposit", args![Workspace("bucket")]) @@ -424,7 +424,7 @@ fn transfer_fails_if_transaction_is_not_signed_by_utxo_owner() { let transfer_from_faucet = stealth::generate_transfer_data([input], 0u64, [100], 0); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer_from_faucet.statement) // Missing signer // .add_signer(&test.to_public_key_bytes(), &mint.output_masks[0]) @@ -467,7 +467,7 @@ fn transfer_invalid_range_proof_in_statement() { transfer_from_faucet.statement.outputs_statement.agg_range_proof = rp.try_into().unwrap(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer_from_faucet.statement) .put_last_instruction_output_on_workspace("bucket") .call_method(alice, "deposit", args![Workspace("bucket")]) @@ -517,7 +517,7 @@ fn many_outputs_in_one_transfer() { eprintln!("Generated transfer in {:.2?}", timer.elapsed()); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer_from_faucet.statement) .finish() .add_signer(&test.to_public_key_bytes(), &mint.output_masks[0]) @@ -571,7 +571,7 @@ fn mint_with_view_key() { &view_key, ); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, withdraw_proof.statement) .finish() .add_signer(&test.to_public_key_bytes(), &mint.output_masks[0]) @@ -631,7 +631,7 @@ fn freeze_then_attempt_spend() { .collect::>(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "freeze_utxos", args![utxos]) .build_and_seal(test.secret_key()), vec![owner.clone()], @@ -639,7 +639,7 @@ fn freeze_then_attempt_spend() { // Try and spend a frozen output let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement.clone()) .finish() .add_signer(&test.to_public_key_bytes(), &mint.output_masks[0]) @@ -651,7 +651,7 @@ fn freeze_then_attempt_spend() { assert_reject_reason(reason, ResourceError::InvalidSpend { details: String::new() }); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "unfreeze_utxos", args![utxos]) .build_and_seal(test.secret_key()), vec![owner], @@ -659,7 +659,7 @@ fn freeze_then_attempt_spend() { // Should be able to spend now let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement) .finish() .add_signer(&test.to_public_key_bytes(), &mint.output_masks[0]) @@ -713,7 +713,7 @@ fn burn_then_attempt_spend() { .collect::>(); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet, "burn_utxos", args![utxos_and_proofs.clone()]) .build_and_seal(test.secret_key()), vec![owner.clone()], @@ -721,7 +721,7 @@ fn burn_then_attempt_spend() { // Try and spend a burnt outputs let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement.clone()) .build_and_seal(test.secret_key()), vec![], @@ -769,7 +769,7 @@ fn burn_with_elgamal_value_proof_adjusts_supply() { let owner = test.owner_proof(); test.execute_expect_success( - Transaction::builder_localnet() + test.transaction() .call_method(faucet, "burn_utxos", args![vec![(utxo_id, proof)]]) .build_and_seal(test.secret_key()), vec![owner], @@ -814,7 +814,7 @@ fn burn_rejects_elgamal_value_proof_for_a_false_value() { let owner = test.owner_proof(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + test.transaction() .call_method(faucet, "burn_utxos", args![vec![(utxo_id, proof)]]) .build_and_seal(test.secret_key()), vec![owner], @@ -852,7 +852,7 @@ fn transfer_denied_by_resource_withdraw_rule() { let template_addr = test.get_template_address(TEMPLATE_NAME); let initial_supply = mint.statement.inputs_statement.revealed_amount; test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "new_withdraw_gated_by_signer", args![ initial_supply, mint.statement.clone() @@ -882,7 +882,7 @@ fn transfer_denied_by_resource_withdraw_rule() { // A non-issuer signer cannot authorise the transfer: the resource withdraw rule denies it. let (_attacker, _attacker_proof, attacker_sk) = test.create_empty_account(); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement.clone()) .finish() .seal(&attacker_sk), @@ -892,7 +892,7 @@ fn transfer_denied_by_resource_withdraw_rule() { // The issuer (the withdraw authority) can: the same transfer now succeeds. let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement) .finish() .seal(test.secret_key()), @@ -953,7 +953,7 @@ fn transfer_restricted_by_access_rules_n_of_m() { // First try to spend with only 2 of the required 3 signatures let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement.clone()) .finish() .add_signer(&test_pk, &sk2) @@ -965,7 +965,7 @@ fn transfer_restricted_by_access_rules_n_of_m() { assert_access_denied_for_action(reason, ActionIdent::Native(NativeAction::StealthUtxoSpend)); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, transfer.statement) .finish() .add_signer(&test_pk, &sk2) @@ -1011,7 +1011,7 @@ fn transfer_restricted_by_access_rules_component_scope() { // Create the new outputs with the component-bound spend condition test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .stealth_transfer(faucet_resx, initial_transfer.statement.clone()) .finish() .seal(test.secret_key()), @@ -1047,7 +1047,7 @@ fn transfer_restricted_by_access_rules_component_scope() { // First try to spend in a template context let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function( test.get_template_address(TEMPLATE_NAME), "static_programmatic_transfer", @@ -1062,7 +1062,7 @@ fn transfer_restricted_by_access_rules_component_scope() { // Then, spend in the component context, which succeeds let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(component, "programmatic_transfer", args![transfer.statement]) .finish() .seal(test.secret_key()), diff --git a/crates/engine/tests/tariswap.rs b/crates/engine/tests/tariswap.rs index 0c5d69af0d..5a422f76e3 100644 --- a/crates/engine/tests/tariswap.rs +++ b/crates/engine/tests/tariswap.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use tari_ootle_common_types::substate_type::SubstateType; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{Amount, ComponentAddress, NonFungibleAddress, ResourceAddress}; use tari_template_test_tooling::TemplateTest; @@ -101,7 +101,7 @@ fn fund_account( ) { template_test .build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet_component, "take_free_coins", args![]) .put_last_instruction_output_on_workspace("free_coins") .call_method(account_address, "deposit", args![Workspace("free_coins")]), @@ -114,7 +114,7 @@ fn fund_account( fn swap(test: &mut TariSwapTest, input_resource: &ResourceAddress, output_resource: &ResourceAddress, amount: Amount) { test.template_test .build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account_address, "withdraw", args![input_resource, amount]) .put_last_instruction_output_on_workspace("input_bucket") .call_method(test.tariswap, "swap", args![Workspace("input_bucket"), output_resource]) @@ -129,7 +129,7 @@ fn swap(test: &mut TariSwapTest, input_resource: &ResourceAddress, output_resour fn add_liquidity(test: &mut TariSwapTest, a_amount: Amount, b_amount: Amount) { test.template_test .build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account_address, "withdraw", args![test.a_resource, a_amount]) .put_last_instruction_output_on_workspace("a_bucket") .call_method(test.account_address, "withdraw", args![test.b_resource, b_amount]) @@ -149,7 +149,7 @@ fn add_liquidity(test: &mut TariSwapTest, a_amount: Amount, b_amount: Amount) { fn remove_liquidity(test: &mut TariSwapTest, lp_amount: Amount) { test.template_test .build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(test.account_address, "withdraw", args![test.lp_resource, lp_amount]) .put_last_instruction_output_on_workspace("lp_bucket") .call_method(test.tariswap, "remove_liquidity", args![Workspace("lp_bucket")]) diff --git a/crates/engine/tests/template_upgrade.rs b/crates/engine/tests/template_upgrade.rs index 55a704022d..3adc744b06 100644 --- a/crates/engine/tests/template_upgrade.rs +++ b/crates/engine/tests/template_upgrade.rs @@ -8,7 +8,7 @@ use tari_engine::{ }; use tari_engine_types::{commit_result::RejectReason, indexed_value::IndexedValue}; use tari_ootle_common_types::crypto::create_key_pair_from_seed; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::{ args::CallAction, types::{ComponentAddress, OwnerRule, VaultId}, @@ -36,7 +36,7 @@ fn create_component(test_mut: &mut TemplateTest) -> ComponentAddress { .collect::>(); test_mut.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(v1_template, "new", args![OwnerRule::OwnedBySigner, signers]) .finish() .seal(test_mut.secret_key()), @@ -69,7 +69,7 @@ fn it_migrates_to_a_new_template() { let v2_template = test.get_template_address("TemplateV2"); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .update_component_template_address_with_migrate(component, v2_template, "migrate_v1_to_v2", args![]) .call_method(component, "assert_correct", args![]) .finish() @@ -91,7 +91,7 @@ fn it_migrates_to_a_new_template_with_args() { let v2_template = test.get_template_address("TemplateV2"); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .update_component_template_address_with_migrate( component, v2_template, @@ -130,7 +130,7 @@ fn it_denies_migration_if_not_owner() { let (secret, _) = create_key_pair_from_seed(12); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .update_component_template_address_with_migrate( component, v2_template, @@ -162,7 +162,7 @@ fn it_fails_when_a_migration_drops_a_vault() { let v2_template = test.get_template_address("TemplateV2"); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .update_component_template_address_with_migrate( component, v2_template, @@ -196,7 +196,7 @@ fn it_fails_when_a_migration_panics() { let v2_template = test.get_template_address("TemplateV2"); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .update_component_template_address_with_migrate(component, v2_template, "faulty_migrate_panic", args![]) .finish() .seal(test.secret_key()), @@ -226,7 +226,7 @@ fn it_migrates_to_a_new_template_without_migration_call() { let v2_template = test.get_template_address("TemplateV2"); test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .update_component_template(component, v2_template) .call_method(component, "assert_correct", args![]) .finish() @@ -248,7 +248,7 @@ fn it_fails_when_a_migration_attempts_a_cross_template_call() { let v2_template = test.get_template_address("TemplateV2"); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .update_component_template_address_with_migrate( component, v2_template, @@ -276,7 +276,7 @@ fn it_disallows_calling_the_migration_function_directly() { let (secret, _) = create_key_pair_from_seed(12); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(v2_template, "migrate_v1_to_v2", args![]) .finish() .seal(&secret), diff --git a/crates/engine/tests/test.rs b/crates/engine/tests/test.rs index c6a39fd6de..87392e7f1f 100644 --- a/crates/engine/tests/test.rs +++ b/crates/engine/tests/test.rs @@ -32,7 +32,7 @@ use tari_engine_types::{ virtual_substate::{VirtualSubstate, VirtualSubstateId}, }; use tari_ootle_common_types::substate_type::SubstateType; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_builtin::{ACCOUNT_TEMPLATE_ADDRESS, NFT_FAUCET_TEMPLATE_ADDRESS, all_builtin_templates}; use tari_template_lib::{ models::NonFungible, @@ -293,7 +293,7 @@ fn test_engine_errors() { // check that public methods can still internally call private ones let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(test.get_template_address("Errors"), "invalid_engine_call", args![]) .build_and_seal(&Default::default()), vec![], @@ -381,7 +381,7 @@ fn test_random() { fn test_errors_on_infinite_loop() { let mut test = TemplateTest::new(CRATE_PATH, vec!["tests/templates/infinity_loop"]); let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(test.get_template_address("InfinityLoopTest"), "infinity_loop", args![]) .build_and_seal(test.secret_key()), vec![], @@ -526,7 +526,7 @@ mod consensus { template_test.remove_virtual_substate(VirtualSubstateId::CurrentEpochHash); let reason = template_test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function( template_test.get_template_address("TestConsensus"), "current_epoch_hash", @@ -568,7 +568,7 @@ mod fungible { let owner_proof = test.owner_proof(); let result = test.build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet_component, "burn_coins", args![500]) .call_method(faucet_component, "total_supply", args![]), vec![owner_proof.clone()], @@ -581,7 +581,7 @@ mod fungible { ); let result = test.build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet_component, "burn_coins", args![ initial_supply - Amount::from(500u64) ]) @@ -596,7 +596,7 @@ mod fungible { ); test.build_and_execute( - Transaction::builder_localnet().call_method(faucet_component, "burn_coins", args![1]), + Transaction::builder_localnet(Epoch(1)).call_method(faucet_component, "burn_coins", args![1]), vec![], ) .expect_failure(); @@ -1020,7 +1020,7 @@ mod emoji_id { price: Amount, owner_proof: NonFungibleAddress, ) -> Result { - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .call_method(account_address, "withdraw", args![faucet_resource, price]) .put_last_instruction_output_on_workspace("payment") .call_method(emoji_id_minter, "mint", args![emoji_id, Workspace("payment")]) @@ -1049,7 +1049,7 @@ mod emoji_id { let price = Amount::from(20u64); let result = test .build_and_execute( - Transaction::builder_localnet().call_function(emoji_id_template, "new", args![ + Transaction::builder_localnet(Epoch(1)).call_function(emoji_id_template, "new", args![ TARI_TOKEN, max_emoji_id_len, price @@ -1148,7 +1148,7 @@ mod tickets { let price = Amount::from(20u64); let event_description = "My music festival".to_string(); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(ticket_template, "new", args![initial_supply, price, event_description]) .build_and_seal(&secret), vec![owner_proof.clone()], @@ -1168,7 +1168,7 @@ mod tickets { // buy a ticket test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(account_address, "withdraw", args![TARI_TOKEN, 20]) .put_last_instruction_output_on_workspace("payment") .call_method(ticket_seller, "buy_ticket", args![Workspace("payment")]) diff --git a/crates/engine/tests/transaction_receipt.rs b/crates/engine/tests/transaction_receipt.rs index a8ad31788d..2bc5cc1803 100644 --- a/crates/engine/tests/transaction_receipt.rs +++ b/crates/engine/tests/transaction_receipt.rs @@ -1,7 +1,7 @@ // Copyright 2026 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_ootle_transaction::{PrunedTransaction, Transaction, TransactionIntent, args}; +use tari_ootle_transaction::{Epoch, PrunedTransaction, Transaction, TransactionIntent, args}; use tari_template_test_tooling::TemplateTest; const CRATE_PATH: &str = env!("CARGO_MANIFEST_DIR"); @@ -13,7 +13,7 @@ const TEMPLATE_PATHS: [&str; 1] = ["tests/templates/state"]; fn committed_receipt_commits_to_the_transaction_intent() { let mut test = TemplateTest::new(CRATE_PATH, TEMPLATE_PATHS); - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .call_function(test.get_template_address("State"), "new", args![]) .build_and_seal(test.secret_key()); @@ -37,7 +37,7 @@ fn fee_intent_receipt_commits_to_the_transaction_intent() { let (account, owner_token, private_key) = test.create_funded_account(); test.enable_fees(); - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 1000u64) .call_function(test.get_template_address("State"), "this_doesnt_exist", args![]) .build_and_seal(&private_key); @@ -56,10 +56,10 @@ fn receipt_does_not_match_a_different_intent() { let mut test = TemplateTest::new(CRATE_PATH, TEMPLATE_PATHS); let template = test.get_template_address("State"); - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .call_function(template, "new", args![]) .build_and_seal(test.secret_key()); - let other = Transaction::builder_localnet() + let other = Transaction::builder_localnet(Epoch(1)) .call_function(template, "new", args![]) .drop_all_proofs_in_workspace() .build_and_seal(test.secret_key()); diff --git a/crates/engine/tests/validator_fees.rs b/crates/engine/tests/validator_fees.rs index 92a73c8c91..e691670326 100644 --- a/crates/engine/tests/validator_fees.rs +++ b/crates/engine/tests/validator_fees.rs @@ -6,7 +6,7 @@ use tari_engine_types::{ ValidatorFeePool, substate::{Substate, SubstateId}, }; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::ValidatorFeePoolAddress; use tari_template_test_tooling::TemplateTest; @@ -31,7 +31,7 @@ fn test_claim_validator_fees_up_to() { // 1. Claim up to 60 TARI test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .claim_validator_fees_up_to(addr, 60u64) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) @@ -52,7 +52,7 @@ fn test_claim_validator_fees_up_to() { // 2. Claim all remaining (up to 1000) test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .claim_validator_fees_up_to(addr, 1000u64) .put_last_instruction_output_on_workspace("bucket") .call_method(account, "deposit", args![Workspace("bucket")]) diff --git a/crates/engine_types/src/commit_result.rs b/crates/engine_types/src/commit_result.rs index db29416063..ef368954d2 100644 --- a/crates/engine_types/src/commit_result.rs +++ b/crates/engine_types/src/commit_result.rs @@ -498,6 +498,10 @@ pub enum AbortReason { FeePaymentInMainIntent, #[n(8)] EpochExpired, + /// The transaction's `max_epoch` is further ahead of the epoch it was pinned to than the network + /// permits. Evaluated against the pinned epoch, so every shard group reaches the same verdict. + #[n(9)] + ValidityWindowTooLong, } impl Display for AbortReason { diff --git a/crates/ootle_sdk_core/fixtures/account_balances/multi_vault_u64.json b/crates/ootle_sdk_core/fixtures/account_balances/multi_vault_u64.json index 87ce3d6fca..eb620eb20b 100644 --- a/crates/ootle_sdk_core/fixtures/account_balances/multi_vault_u64.json +++ b/crates/ootle_sdk_core/fixtures/account_balances/multi_vault_u64.json @@ -2,8 +2,8 @@ "name": "account_balances/multi_vault_u64", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "account_balances", diff --git a/crates/ootle_sdk_core/fixtures/address_codec/identity_esmeralda.json b/crates/ootle_sdk_core/fixtures/address_codec/identity_esmeralda.json index 6cc486d334..816870312b 100644 --- a/crates/ootle_sdk_core/fixtures/address_codec/identity_esmeralda.json +++ b/crates/ootle_sdk_core/fixtures/address_codec/identity_esmeralda.json @@ -2,8 +2,8 @@ "name": "address_codec/identity_esmeralda", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "format_identity_address", diff --git a/crates/ootle_sdk_core/fixtures/address_codec/identity_localnet_with_pay_ref.json b/crates/ootle_sdk_core/fixtures/address_codec/identity_localnet_with_pay_ref.json index 63895cc69c..a6020e9348 100644 --- a/crates/ootle_sdk_core/fixtures/address_codec/identity_localnet_with_pay_ref.json +++ b/crates/ootle_sdk_core/fixtures/address_codec/identity_localnet_with_pay_ref.json @@ -2,8 +2,8 @@ "name": "address_codec/identity_localnet_with_pay_ref", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "format_identity_address", diff --git a/crates/ootle_sdk_core/fixtures/address_codec/identity_mainnet.json b/crates/ootle_sdk_core/fixtures/address_codec/identity_mainnet.json index 15fa37176f..4775950a4f 100644 --- a/crates/ootle_sdk_core/fixtures/address_codec/identity_mainnet.json +++ b/crates/ootle_sdk_core/fixtures/address_codec/identity_mainnet.json @@ -2,8 +2,8 @@ "name": "address_codec/identity_mainnet", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "format_identity_address", diff --git a/crates/ootle_sdk_core/fixtures/address_codec/parse_component.json b/crates/ootle_sdk_core/fixtures/address_codec/parse_component.json index d4c8e4125a..c6abe41f2a 100644 --- a/crates/ootle_sdk_core/fixtures/address_codec/parse_component.json +++ b/crates/ootle_sdk_core/fixtures/address_codec/parse_component.json @@ -2,8 +2,8 @@ "name": "address_codec/parse_component", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "parse_address", diff --git a/crates/ootle_sdk_core/fixtures/address_codec/parse_identity_esmeralda.json b/crates/ootle_sdk_core/fixtures/address_codec/parse_identity_esmeralda.json index 11edab412c..c84a642cca 100644 --- a/crates/ootle_sdk_core/fixtures/address_codec/parse_identity_esmeralda.json +++ b/crates/ootle_sdk_core/fixtures/address_codec/parse_identity_esmeralda.json @@ -2,8 +2,8 @@ "name": "address_codec/parse_identity_esmeralda", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "parse_address", diff --git a/crates/ootle_sdk_core/fixtures/address_codec/parse_resource.json b/crates/ootle_sdk_core/fixtures/address_codec/parse_resource.json index 19f1a63ea7..9054b55381 100644 --- a/crates/ootle_sdk_core/fixtures/address_codec/parse_resource.json +++ b/crates/ootle_sdk_core/fixtures/address_codec/parse_resource.json @@ -2,8 +2,8 @@ "name": "address_codec/parse_resource", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "parse_address", diff --git a/crates/ootle_sdk_core/fixtures/address_derive/from_curve_pk.json b/crates/ootle_sdk_core/fixtures/address_derive/from_curve_pk.json index dd11a4a2f1..71bbc2f047 100644 --- a/crates/ootle_sdk_core/fixtures/address_derive/from_curve_pk.json +++ b/crates/ootle_sdk_core/fixtures/address_derive/from_curve_pk.json @@ -2,8 +2,8 @@ "name": "address_derive/from_curve_pk", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "derive_account_address", diff --git a/crates/ootle_sdk_core/fixtures/address_derive/from_recipient_pk.json b/crates/ootle_sdk_core/fixtures/address_derive/from_recipient_pk.json index e507556598..b54d07d366 100644 --- a/crates/ootle_sdk_core/fixtures/address_derive/from_recipient_pk.json +++ b/crates/ootle_sdk_core/fixtures/address_derive/from_recipient_pk.json @@ -2,8 +2,8 @@ "name": "address_derive/from_recipient_pk", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "derive_account_address", diff --git a/crates/ootle_sdk_core/fixtures/address_derive/from_seed_account_pk.json b/crates/ootle_sdk_core/fixtures/address_derive/from_seed_account_pk.json index 8eb4062bb0..39e46de9d9 100644 --- a/crates/ootle_sdk_core/fixtures/address_derive/from_seed_account_pk.json +++ b/crates/ootle_sdk_core/fixtures/address_derive/from_seed_account_pk.json @@ -2,8 +2,8 @@ "name": "address_derive/from_seed_account_pk", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "derive_account_address", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_component.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_component.json index 5ee75c9e6b..2dad590269 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_component.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_component.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_component", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_non_fungible.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_non_fungible.json index cc38a93225..9123febd40 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_non_fungible.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_non_fungible.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_non_fungible", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_resource.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_resource.json index d05c47164b..3f94bb41e1 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_resource.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_resource.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_resource", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_template.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_template.json index 5ce524de23..c19088aa25 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_template.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_template.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_template", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_tombstone.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_tombstone.json index dc3dbbbcba..5e699d734d 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_tombstone.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_tombstone.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_tombstone", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_transaction_receipt.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_transaction_receipt.json index 1443200195..de566baf29 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_transaction_receipt.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_transaction_receipt.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_transaction_receipt", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_utxo.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_utxo.json index ec1d499de7..d779d91642 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_utxo.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_utxo.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_utxo", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_validator_fee_pool.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_validator_fee_pool.json index bc1311907d..13dc2a7c28 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_validator_fee_pool.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_validator_fee_pool.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_validator_fee_pool", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/address_vault.json b/crates/ootle_sdk_core/fixtures/arg_dsl/address_vault.json index 73c4040903..09db0aebf6 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/address_vault.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/address_vault.json @@ -2,8 +2,8 @@ "name": "arg_dsl/address_vault", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/amount.json b/crates/ootle_sdk_core/fixtures/arg_dsl/amount.json index e950866bcc..a5fd64a005 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/amount.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/amount.json @@ -2,8 +2,8 @@ "name": "arg_dsl/amount", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/amount_above_2_pow_33.json b/crates/ootle_sdk_core/fixtures/arg_dsl/amount_above_2_pow_33.json index edd8237634..feeda83fbc 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/amount_above_2_pow_33.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/amount_above_2_pow_33.json @@ -2,8 +2,8 @@ "name": "arg_dsl/amount_above_2_pow_33", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/bool_false.json b/crates/ootle_sdk_core/fixtures/arg_dsl/bool_false.json index f4fac29607..612aae42b8 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/bool_false.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/bool_false.json @@ -2,8 +2,8 @@ "name": "arg_dsl/bool_false", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/bool_true.json b/crates/ootle_sdk_core/fixtures/arg_dsl/bool_true.json index dabeb9af0e..18ab558d39 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/bool_true.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/bool_true.json @@ -2,8 +2,8 @@ "name": "arg_dsl/bool_true", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/bytes.json b/crates/ootle_sdk_core/fixtures/arg_dsl/bytes.json index e7c62b4edc..0235a1fe0a 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/bytes.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/bytes.json @@ -2,8 +2,8 @@ "name": "arg_dsl/bytes", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/i64_min.json b/crates/ootle_sdk_core/fixtures/arg_dsl/i64_min.json index 2bb5184c37..ac5dd3950a 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/i64_min.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/i64_min.json @@ -2,8 +2,8 @@ "name": "arg_dsl/i64_min", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/i64_negative.json b/crates/ootle_sdk_core/fixtures/arg_dsl/i64_negative.json index 54d0e46b70..0c378dc9c3 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/i64_negative.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/i64_negative.json @@ -2,8 +2,8 @@ "name": "arg_dsl/i64_negative", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/i64_positive.json b/crates/ootle_sdk_core/fixtures/arg_dsl/i64_positive.json index 75c27246ec..40cc6bd1b0 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/i64_positive.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/i64_positive.json @@ -2,8 +2,8 @@ "name": "arg_dsl/i64_positive", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/list_empty.json b/crates/ootle_sdk_core/fixtures/arg_dsl/list_empty.json index e3ce9d01e0..0ff51c1089 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/list_empty.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/list_empty.json @@ -2,8 +2,8 @@ "name": "arg_dsl/list_empty", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/list_nested.json b/crates/ootle_sdk_core/fixtures/arg_dsl/list_nested.json index 63e280c1b8..1116c6b8a4 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/list_nested.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/list_nested.json @@ -2,8 +2,8 @@ "name": "arg_dsl/list_nested", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/list_of_addresses.json b/crates/ootle_sdk_core/fixtures/arg_dsl/list_of_addresses.json index 8da50132df..24e2bffa0c 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/list_of_addresses.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/list_of_addresses.json @@ -2,8 +2,8 @@ "name": "arg_dsl/list_of_addresses", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/list_of_nfids.json b/crates/ootle_sdk_core/fixtures/arg_dsl/list_of_nfids.json index 026d82af1b..e27adf02d6 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/list_of_nfids.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/list_of_nfids.json @@ -2,8 +2,8 @@ "name": "arg_dsl/list_of_nfids", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/metadata.json b/crates/ootle_sdk_core/fixtures/arg_dsl/metadata.json index f083de859d..b560dea401 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/metadata.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/metadata.json @@ -2,8 +2,8 @@ "name": "arg_dsl/metadata", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_str.json b/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_str.json index 1a8e126f4d..9522044b3f 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_str.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_str.json @@ -2,8 +2,8 @@ "name": "arg_dsl/nfid_str", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_u32.json b/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_u32.json index 708cf73efa..8817566061 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_u32.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_u32.json @@ -2,8 +2,8 @@ "name": "arg_dsl/nfid_u32", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_u64.json b/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_u64.json index b220e12298..2bba343641 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_u64.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_u64.json @@ -2,8 +2,8 @@ "name": "arg_dsl/nfid_u64", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_uuid.json b/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_uuid.json index c3d3719270..389504e1ef 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_uuid.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/nfid_uuid.json @@ -2,8 +2,8 @@ "name": "arg_dsl/nfid_uuid", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/optional_address.json b/crates/ootle_sdk_core/fixtures/arg_dsl/optional_address.json index 273d62900f..aa78dc4b29 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/optional_address.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/optional_address.json @@ -2,8 +2,8 @@ "name": "arg_dsl/optional_address", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/optional_none.json b/crates/ootle_sdk_core/fixtures/arg_dsl/optional_none.json index abe1f11c24..1b2c7963d2 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/optional_none.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/optional_none.json @@ -2,8 +2,8 @@ "name": "arg_dsl/optional_none", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/optional_some.json b/crates/ootle_sdk_core/fixtures/arg_dsl/optional_some.json index 45585ebbdd..781a830831 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/optional_some.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/optional_some.json @@ -2,8 +2,8 @@ "name": "arg_dsl/optional_some", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/string.json b/crates/ootle_sdk_core/fixtures/arg_dsl/string.json index 89b2676982..a4f96f866d 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/string.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/string.json @@ -2,8 +2,8 @@ "name": "arg_dsl/string", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/u64.json b/crates/ootle_sdk_core/fixtures/arg_dsl/u64.json index cd05e5be29..59cd2282de 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/u64.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/u64.json @@ -2,8 +2,8 @@ "name": "arg_dsl/u64", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/arg_dsl/u64_above_2_pow_33.json b/crates/ootle_sdk_core/fixtures/arg_dsl/u64_above_2_pow_33.json index 3ebc758fd6..0963ebf528 100644 --- a/crates/ootle_sdk_core/fixtures/arg_dsl/u64_above_2_pow_33.json +++ b/crates/ootle_sdk_core/fixtures/arg_dsl/u64_above_2_pow_33.json @@ -2,8 +2,8 @@ "name": "arg_dsl/u64_above_2_pow_33", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "encode_arg", diff --git a/crates/ootle_sdk_core/fixtures/cosign/seal_with_auth.json b/crates/ootle_sdk_core/fixtures/cosign/seal_with_auth.json index 55d405eb64..8a581ae2d5 100644 --- a/crates/ootle_sdk_core/fixtures/cosign/seal_with_auth.json +++ b/crates/ootle_sdk_core/fixtures/cosign/seal_with_auth.json @@ -2,8 +2,8 @@ "name": "cosign/seal_with_auth", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "cosign_seal_with_auth", diff --git a/crates/ootle_sdk_core/fixtures/generic_build/call_function.json b/crates/ootle_sdk_core/fixtures/generic_build/call_function.json index 18d5ea4925..d40766896d 100644 --- a/crates/ootle_sdk_core/fixtures/generic_build/call_function.json +++ b/crates/ootle_sdk_core/fixtures/generic_build/call_function.json @@ -2,8 +2,8 @@ "name": "generic_build/call_function", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_instructions", @@ -38,7 +38,7 @@ ], "extra_inputs": [], "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false } }, @@ -86,7 +86,7 @@ } ], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/generic_build/call_method_transfer.json b/crates/ootle_sdk_core/fixtures/generic_build/call_method_transfer.json index 03fc61453b..02f342dcac 100644 --- a/crates/ootle_sdk_core/fixtures/generic_build/call_method_transfer.json +++ b/crates/ootle_sdk_core/fixtures/generic_build/call_method_transfer.json @@ -2,8 +2,8 @@ "name": "generic_build/call_method_transfer", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_instructions", diff --git a/crates/ootle_sdk_core/fixtures/generic_build/create_account.json b/crates/ootle_sdk_core/fixtures/generic_build/create_account.json index 57c88b87e6..77583e956e 100644 --- a/crates/ootle_sdk_core/fixtures/generic_build/create_account.json +++ b/crates/ootle_sdk_core/fixtures/generic_build/create_account.json @@ -2,8 +2,8 @@ "name": "generic_build/create_account", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_instructions", @@ -38,7 +38,7 @@ ], "extra_inputs": [], "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false } }, @@ -87,7 +87,7 @@ } ], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/generic_build/faucet_claim.json b/crates/ootle_sdk_core/fixtures/generic_build/faucet_claim.json index 2a3a5d7848..5e8b963f8e 100644 --- a/crates/ootle_sdk_core/fixtures/generic_build/faucet_claim.json +++ b/crates/ootle_sdk_core/fixtures/generic_build/faucet_claim.json @@ -2,8 +2,8 @@ "name": "generic_build/faucet_claim", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_faucet_claim", @@ -64,7 +64,7 @@ "recipient_public_key": "fea009ee8681783f3e9ed6152d3b7fc204a7ba78cde9808cdedae3dd221af013", "fee": 2000, "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false } }, @@ -141,7 +141,7 @@ ], "instructions": [], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/generic_build/publish_template.json b/crates/ootle_sdk_core/fixtures/generic_build/publish_template.json index bb866de2a9..2ed424ba25 100644 --- a/crates/ootle_sdk_core/fixtures/generic_build/publish_template.json +++ b/crates/ootle_sdk_core/fixtures/generic_build/publish_template.json @@ -2,8 +2,8 @@ "name": "generic_build/publish_template", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_instructions", @@ -41,7 +41,7 @@ ], "extra_inputs": [], "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false } }, @@ -90,7 +90,7 @@ } ], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/generic_build/self_funding_faucet.json b/crates/ootle_sdk_core/fixtures/generic_build/self_funding_faucet.json index c0d8f7d916..86e2df0289 100644 --- a/crates/ootle_sdk_core/fixtures/generic_build/self_funding_faucet.json +++ b/crates/ootle_sdk_core/fixtures/generic_build/self_funding_faucet.json @@ -2,8 +2,8 @@ "name": "generic_build/self_funding_faucet", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_instructions", @@ -58,7 +58,7 @@ ], "extra_inputs": [], "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false } }, @@ -127,7 +127,7 @@ ], "instructions": [], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/generic_build/workspace_pipe.json b/crates/ootle_sdk_core/fixtures/generic_build/workspace_pipe.json index e3e3e14b1a..1187f50090 100644 --- a/crates/ootle_sdk_core/fixtures/generic_build/workspace_pipe.json +++ b/crates/ootle_sdk_core/fixtures/generic_build/workspace_pipe.json @@ -2,8 +2,8 @@ "name": "generic_build/workspace_pipe", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_instructions", @@ -65,7 +65,7 @@ ], "extra_inputs": [], "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false } }, @@ -143,7 +143,7 @@ } ], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/keys/account_from_seed.json b/crates/ootle_sdk_core/fixtures/keys/account_from_seed.json index 884a8853df..f206cbf218 100644 --- a/crates/ootle_sdk_core/fixtures/keys/account_from_seed.json +++ b/crates/ootle_sdk_core/fixtures/keys/account_from_seed.json @@ -2,8 +2,8 @@ "name": "keys/account_from_seed", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "derive_account_key_from_seed", diff --git a/crates/ootle_sdk_core/fixtures/keys/view_from_seed.json b/crates/ootle_sdk_core/fixtures/keys/view_from_seed.json index 7eb2977564..f6d9718ed4 100644 --- a/crates/ootle_sdk_core/fixtures/keys/view_from_seed.json +++ b/crates/ootle_sdk_core/fixtures/keys/view_from_seed.json @@ -2,8 +2,8 @@ "name": "keys/view_from_seed", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "derive_view_key_from_seed", diff --git a/crates/ootle_sdk_core/fixtures/parse_finalized_result/accept.json b/crates/ootle_sdk_core/fixtures/parse_finalized_result/accept.json index 655b4d683d..1863af42c5 100644 --- a/crates/ootle_sdk_core/fixtures/parse_finalized_result/accept.json +++ b/crates/ootle_sdk_core/fixtures/parse_finalized_result/accept.json @@ -2,8 +2,8 @@ "name": "parse_finalized_result/accept", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "parse_finalized_result", diff --git a/crates/ootle_sdk_core/fixtures/parse_finalized_result/accept_fee_reject_rest.json b/crates/ootle_sdk_core/fixtures/parse_finalized_result/accept_fee_reject_rest.json index 0a8eb3e1a6..1b04b7ae7d 100644 --- a/crates/ootle_sdk_core/fixtures/parse_finalized_result/accept_fee_reject_rest.json +++ b/crates/ootle_sdk_core/fixtures/parse_finalized_result/accept_fee_reject_rest.json @@ -2,8 +2,8 @@ "name": "parse_finalized_result/accept_fee_reject_rest", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "parse_finalized_result", diff --git a/crates/ootle_sdk_core/fixtures/parse_finalized_result/dry_run.json b/crates/ootle_sdk_core/fixtures/parse_finalized_result/dry_run.json index 2cf0e7845d..f7dd1c4f9d 100644 --- a/crates/ootle_sdk_core/fixtures/parse_finalized_result/dry_run.json +++ b/crates/ootle_sdk_core/fixtures/parse_finalized_result/dry_run.json @@ -2,8 +2,8 @@ "name": "parse_finalized_result/dry_run", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "parse_finalized_result", diff --git a/crates/ootle_sdk_core/fixtures/parse_finalized_result/reject_epoch_expired.json b/crates/ootle_sdk_core/fixtures/parse_finalized_result/reject_epoch_expired.json index 29901a0ee0..6cc95087f6 100644 --- a/crates/ootle_sdk_core/fixtures/parse_finalized_result/reject_epoch_expired.json +++ b/crates/ootle_sdk_core/fixtures/parse_finalized_result/reject_epoch_expired.json @@ -2,8 +2,8 @@ "name": "parse_finalized_result/reject_epoch_expired", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "parse_finalized_result", diff --git a/crates/ootle_sdk_core/fixtures/public_transfer/large_amount.json b/crates/ootle_sdk_core/fixtures/public_transfer/large_amount.json index 52ebcc9575..d031a6af34 100644 --- a/crates/ootle_sdk_core/fixtures/public_transfer/large_amount.json +++ b/crates/ootle_sdk_core/fixtures/public_transfer/large_amount.json @@ -2,8 +2,8 @@ "name": "public_transfer/large_amount", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_public_transfer", diff --git a/crates/ootle_sdk_core/fixtures/public_transfer/sample_single_key_basic.json b/crates/ootle_sdk_core/fixtures/public_transfer/sample_single_key_basic.json index 739051c22a..7246f8533e 100644 --- a/crates/ootle_sdk_core/fixtures/public_transfer/sample_single_key_basic.json +++ b/crates/ootle_sdk_core/fixtures/public_transfer/sample_single_key_basic.json @@ -2,8 +2,8 @@ "name": "sample/public_transfer_single_key_basic", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_public_transfer", diff --git a/crates/ootle_sdk_core/fixtures/public_transfer/single_key_basic.json b/crates/ootle_sdk_core/fixtures/public_transfer/single_key_basic.json index 763ce738f5..db07031d1b 100644 --- a/crates/ootle_sdk_core/fixtures/public_transfer/single_key_basic.json +++ b/crates/ootle_sdk_core/fixtures/public_transfer/single_key_basic.json @@ -2,8 +2,8 @@ "name": "public_transfer/single_key_basic", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_public_transfer", diff --git a/crates/ootle_sdk_core/fixtures/resolve_public_transfer/large_amount.json b/crates/ootle_sdk_core/fixtures/resolve_public_transfer/large_amount.json index 11a9cfd9f2..c8415a69ad 100644 --- a/crates/ootle_sdk_core/fixtures/resolve_public_transfer/large_amount.json +++ b/crates/ootle_sdk_core/fixtures/resolve_public_transfer/large_amount.json @@ -2,8 +2,8 @@ "name": "resolve_public_transfer/large_amount", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "resolve_and_encode_public_transfer", diff --git a/crates/ootle_sdk_core/fixtures/resolve_public_transfer/single_key_basic.json b/crates/ootle_sdk_core/fixtures/resolve_public_transfer/single_key_basic.json index 9fffdec2c2..e55c9d4db9 100644 --- a/crates/ootle_sdk_core/fixtures/resolve_public_transfer/single_key_basic.json +++ b/crates/ootle_sdk_core/fixtures/resolve_public_transfer/single_key_basic.json @@ -2,8 +2,8 @@ "name": "resolve_public_transfer/single_key_basic", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "resolve_and_encode_public_transfer", diff --git a/crates/ootle_sdk_core/fixtures/stealth_outputs_statement/single_output_no_view_key.json b/crates/ootle_sdk_core/fixtures/stealth_outputs_statement/single_output_no_view_key.json index 146c07bb92..08f0a0bd95 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_outputs_statement/single_output_no_view_key.json +++ b/crates/ootle_sdk_core/fixtures/stealth_outputs_statement/single_output_no_view_key.json @@ -2,8 +2,8 @@ "name": "stealth_outputs_statement/single_output_no_view_key", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_stealth_outputs_statement", @@ -32,7 +32,7 @@ "revealed_input_amount": 0, "revealed_output_amount": 0, "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false, "pay_fee_from_revealed": false }, diff --git a/crates/ootle_sdk_core/fixtures/stealth_outputs_statement/single_output_with_view_key.json b/crates/ootle_sdk_core/fixtures/stealth_outputs_statement/single_output_with_view_key.json index dd0beb41ac..796cb97334 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_outputs_statement/single_output_with_view_key.json +++ b/crates/ootle_sdk_core/fixtures/stealth_outputs_statement/single_output_with_view_key.json @@ -2,8 +2,8 @@ "name": "stealth_outputs_statement/single_output_with_view_key", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_stealth_outputs_statement", @@ -32,7 +32,7 @@ "revealed_input_amount": 0, "revealed_output_amount": 0, "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false, "pay_fee_from_revealed": false }, diff --git a/crates/ootle_sdk_core/fixtures/stealth_scan/decode_utxo.json b/crates/ootle_sdk_core/fixtures/stealth_scan/decode_utxo.json index f0692c73fa..a7b80449af 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_scan/decode_utxo.json +++ b/crates/ootle_sdk_core/fixtures/stealth_scan/decode_utxo.json @@ -2,8 +2,8 @@ "name": "stealth_scan/decode_utxo", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "decode_stealth_utxo", diff --git a/crates/ootle_sdk_core/fixtures/stealth_scan/mine_basic.json b/crates/ootle_sdk_core/fixtures/stealth_scan/mine_basic.json index 3c3993cb9f..123d8987ca 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_scan/mine_basic.json +++ b/crates/ootle_sdk_core/fixtures/stealth_scan/mine_basic.json @@ -2,8 +2,8 @@ "name": "stealth_scan/mine_basic", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "scan_stealth_output", diff --git a/crates/ootle_sdk_core/fixtures/stealth_scan/not_mine.json b/crates/ootle_sdk_core/fixtures/stealth_scan/not_mine.json index 6cff80b1d5..12d51cd369 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_scan/not_mine.json +++ b/crates/ootle_sdk_core/fixtures/stealth_scan/not_mine.json @@ -2,8 +2,8 @@ "name": "stealth_scan/not_mine", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "scan_stealth_output", diff --git a/crates/ootle_sdk_core/fixtures/stealth_transfer/account_key_seal_with_revealed_input.json b/crates/ootle_sdk_core/fixtures/stealth_transfer/account_key_seal_with_revealed_input.json index e6ddacd93f..85d11e9fb8 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_transfer/account_key_seal_with_revealed_input.json +++ b/crates/ootle_sdk_core/fixtures/stealth_transfer/account_key_seal_with_revealed_input.json @@ -2,8 +2,8 @@ "name": "stealth_transfer/account_key_seal_with_revealed_input", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_stealth_transfer", @@ -32,7 +32,7 @@ "revealed_input_amount": 1000000, "revealed_output_amount": 0, "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false, "pay_fee_from_revealed": false }, @@ -132,7 +132,7 @@ } ], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/stealth_transfer/revealed_output_multi.json b/crates/ootle_sdk_core/fixtures/stealth_transfer/revealed_output_multi.json index d144230ccc..183031064e 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_transfer/revealed_output_multi.json +++ b/crates/ootle_sdk_core/fixtures/stealth_transfer/revealed_output_multi.json @@ -2,8 +2,8 @@ "name": "stealth_transfer/revealed_output_multi", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_stealth_transfer", @@ -44,7 +44,7 @@ "revealed_input_amount": 2500000, "revealed_output_amount": 1500000, "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false, "pay_fee_from_revealed": false }, @@ -204,7 +204,7 @@ } ], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/stealth_transfer/revealed_output_single.json b/crates/ootle_sdk_core/fixtures/stealth_transfer/revealed_output_single.json index a3c3500db0..5d09a60025 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_transfer/revealed_output_single.json +++ b/crates/ootle_sdk_core/fixtures/stealth_transfer/revealed_output_single.json @@ -2,8 +2,8 @@ "name": "stealth_transfer/revealed_output_single", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_stealth_transfer", @@ -32,7 +32,7 @@ "revealed_input_amount": 1500000, "revealed_output_amount": 500000, "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false, "pay_fee_from_revealed": false }, @@ -148,7 +148,7 @@ } ], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/stealth_transfer/stealth_seal_with_input.json b/crates/ootle_sdk_core/fixtures/stealth_transfer/stealth_seal_with_input.json index 89fc0276f8..6f6739e716 100644 --- a/crates/ootle_sdk_core/fixtures/stealth_transfer/stealth_seal_with_input.json +++ b/crates/ootle_sdk_core/fixtures/stealth_transfer/stealth_seal_with_input.json @@ -2,8 +2,8 @@ "name": "stealth_transfer/stealth_seal_with_input", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "build_and_encode_stealth_transfer", @@ -60,7 +60,7 @@ "revealed_input_amount": 0, "revealed_output_amount": 0, "min_epoch": null, - "max_epoch": null, + "max_epoch": 1, "dry_run": false, "pay_fee_from_revealed": false }, @@ -144,7 +144,7 @@ } ], "is_seal_signer_authorized": true, - "max_epoch": null, + "max_epoch": 1, "min_epoch": null, "network": 38, "nonce": 0 diff --git a/crates/ootle_sdk_core/fixtures/substate_decode/component.json b/crates/ootle_sdk_core/fixtures/substate_decode/component.json index 856f695dd5..034752f88d 100644 --- a/crates/ootle_sdk_core/fixtures/substate_decode/component.json +++ b/crates/ootle_sdk_core/fixtures/substate_decode/component.json @@ -2,8 +2,8 @@ "name": "substate_decode/component", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "decode_substate", diff --git a/crates/ootle_sdk_core/fixtures/substate_decode/fungible_vault.json b/crates/ootle_sdk_core/fixtures/substate_decode/fungible_vault.json index f92cbbf376..4021213b22 100644 --- a/crates/ootle_sdk_core/fixtures/substate_decode/fungible_vault.json +++ b/crates/ootle_sdk_core/fixtures/substate_decode/fungible_vault.json @@ -2,8 +2,8 @@ "name": "substate_decode/fungible_vault", "schema_version": 1, "provenance": { - "core_version": "0.37.0", - "git_rev": "be2591689b664cddf465a7540277d41b09ecfd0d", + "core_version": "0.39.0", + "git_rev": "9f4833febee5c633a6fb2f0bd61dd1ce6d33c481", "generated_by": "ootle_sdk_core golden-vector generator" }, "operation": "decode_substate", diff --git a/crates/ootle_sdk_core/src/builder.rs b/crates/ootle_sdk_core/src/builder.rs index e8d56b64eb..a2c215530f 100644 --- a/crates/ootle_sdk_core/src/builder.rs +++ b/crates/ootle_sdk_core/src/builder.rs @@ -87,8 +87,12 @@ pub(crate) fn resolve_transfer_recipe(intent: &PublicTransferIntent) -> Result TransactionBuilder { - let mut builder = TransactionBuilder::new(network.as_byte()); +pub(crate) fn build_transfer_recipe_builder( + network: Network, + recipe: &TransferRecipe, + max_epoch: Epoch, +) -> TransactionBuilder { + let mut builder = TransactionBuilder::new(network.as_byte(), max_epoch); // Fee first. builder = builder.pay_fee_from_component(recipe.from_component, recipe.fee); @@ -108,7 +112,6 @@ pub(crate) fn apply_epoch_and_dry_run( ) -> TransactionBuilder { builder .with_min_epoch(intent.min_epoch.map(Epoch)) - .with_max_epoch(intent.max_epoch.map(Epoch)) .with_dry_run(intent.dry_run) } @@ -122,7 +125,7 @@ pub fn build_public_transfer_unsigned( intent: &PublicTransferIntent, ) -> Result { let recipe = resolve_transfer_recipe(intent)?; - let mut builder = build_transfer_recipe_builder(network, &recipe); + let mut builder = build_transfer_recipe_builder(network, &recipe, Epoch(intent.max_epoch)); // Attach the caller-supplied explicit inputs. let inputs = intent.inputs_to_internal()?; @@ -162,7 +165,7 @@ mod tests { fee: BoundaryAmount::new(2000), inputs: vec![InputRef::versioned(component_str(), 0)], min_epoch: Some(5), - max_epoch: Some(99), + max_epoch: 99, dry_run: false, } } @@ -179,7 +182,7 @@ mod tests { // Epochs + dry-run threaded through. assert_eq!(unsigned.min_epoch(), Some(Epoch(5))); - assert_eq!(unsigned.max_epoch(), Some(Epoch(99))); + assert_eq!(unsigned.max_epoch(), Epoch(99)); // The explicit input was added. assert!(unsigned.inputs().iter().any(|i| i.version() == Some(0))); diff --git a/crates/ootle_sdk_core/src/cosign.rs b/crates/ootle_sdk_core/src/cosign.rs index 9892a894ed..1cc059f92f 100644 --- a/crates/ootle_sdk_core/src/cosign.rs +++ b/crates/ootle_sdk_core/src/cosign.rs @@ -289,7 +289,7 @@ mod tests { fee: BoundaryAmount::new(2000), inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, } } diff --git a/crates/ootle_sdk_core/src/faucet.rs b/crates/ootle_sdk_core/src/faucet.rs index 430ed27fab..5142242451 100644 --- a/crates/ootle_sdk_core/src/faucet.rs +++ b/crates/ootle_sdk_core/src/faucet.rs @@ -49,9 +49,9 @@ pub struct FaucetClaimIntent { /// Optional earliest epoch this transaction is valid in. #[serde(default)] pub min_epoch: Option, - /// Optional latest epoch this transaction is valid in. - #[serde(default)] - pub max_epoch: Option, + /// The last epoch this transaction is valid in. Mandatory: every transaction has a bounded + /// validity window, capped network-wide at `max_transaction_validity_epochs` past the current epoch. + pub max_epoch: u64, /// Whether this is a dry run (e.g. fee estimation). #[serde(default)] pub dry_run: bool, @@ -158,7 +158,7 @@ mod tests { recipient_public_key: recipient_pk(), fee: BoundaryAmount::new(2000), min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, } } diff --git a/crates/ootle_sdk_core/src/generic_builder.rs b/crates/ootle_sdk_core/src/generic_builder.rs index 037dae1090..c674909563 100644 --- a/crates/ootle_sdk_core/src/generic_builder.rs +++ b/crates/ootle_sdk_core/src/generic_builder.rs @@ -118,7 +118,8 @@ fn lower_intent(network: Network, intent: &GenericTransactionIntent) -> Result Result Result Result, OotleSdkError> { - let mut builder = TransactionBuilder::new(network.as_byte()); + let mut builder = TransactionBuilder::new(network.as_byte(), Epoch(intent.max_epoch)); let mut bound_labels: HashSet = HashSet::new(); for instr in &intent.fee_instructions { builder = lower_instruction(builder, intent, instr, &mut bound_labels)?; @@ -581,7 +581,7 @@ mod tests { inputs, extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, } } @@ -713,7 +713,7 @@ mod tests { inputs: vec![InputRef::versioned(from_component().to_string(), 0)], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let (partial, _w) = build_unsigned_instructions_with_wants(Network::Esmeralda, &intent).unwrap(); @@ -756,7 +756,7 @@ mod tests { inputs: vec![InputRef::versioned(from_component().to_string(), 0)], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let (partial, _w) = build_unsigned_instructions_with_wants(Network::Esmeralda, &intent).unwrap(); @@ -790,7 +790,7 @@ mod tests { inputs: vec![InputRef::versioned(from_component().to_string(), 0)], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let err = build_unsigned_instructions_with_wants(Network::Esmeralda, &intent).unwrap_err(); @@ -811,7 +811,7 @@ mod tests { inputs: vec![InputRef::versioned(from_component().to_string(), 0)], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let err = build_unsigned_instructions_with_wants(Network::Esmeralda, &intent).unwrap_err(); @@ -849,7 +849,7 @@ mod tests { // Mirror the generic path exactly: inputs are carried ONLY by `new_with_explicit_inputs` // (the generic `lower_intent` never calls `with_inputs`), so the builder emits just the // instructions and the explicit inputs are folded in at seal time. - let unsigned = TransactionBuilder::new(Network::Esmeralda.as_byte()) + let unsigned = TransactionBuilder::new(Network::Esmeralda.as_byte(), Epoch(1)) .pay_fee_from_component(from_component(), Amount::new(2000)) .call_method(from_component(), "withdraw", args![resource(), Amount::new(1_000_000)]) .put_last_instruction_output_on_workspace("bucket") @@ -950,7 +950,7 @@ mod tests { inputs: vec![InputRef::versioned(faucet_component().to_string(), 0)], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, } } @@ -1043,7 +1043,7 @@ mod tests { inputs: vec![InputRef::versioned(from_component().to_string(), 0)], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let wants = derive_wants(&intent).unwrap(); @@ -1094,7 +1094,7 @@ mod tests { inputs: vec![InputRef::versioned(from_component().to_string(), 0)], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let (partial, _w) = build_unsigned_instructions_with_wants(Network::Esmeralda, &intent).unwrap(); @@ -1130,7 +1130,7 @@ mod tests { inputs: vec![InputRef::versioned(from_component().to_string(), 0)], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let err = build_unsigned_instructions_with_wants(Network::Esmeralda, &intent).unwrap_err(); @@ -1159,7 +1159,7 @@ mod tests { // Auto-fill side: the two main instructions on the synchronous builder (no fee instruction, so // its inputs are exactly the component + the two literal-arg resources). - let auto_inputs: HashSet = TransactionBuilder::new(Network::Esmeralda.as_byte()) + let auto_inputs: HashSet = TransactionBuilder::new(Network::Esmeralda.as_byte(), Epoch(1)) .with_auto_fill_inputs() .call_method(foreign_component(), "do", args![arg_resource()]) .call_function(ACCOUNT_TEMPLATE_ADDRESS, "make", args![resource()]) @@ -1188,7 +1188,7 @@ mod tests { inputs: vec![], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let (partial, _w) = build_unsigned_instructions_with_wants(Network::Esmeralda, &intent).unwrap(); diff --git a/crates/ootle_sdk_core/src/inputs.rs b/crates/ootle_sdk_core/src/inputs.rs index 94a3f18ea4..ea0eaab7ad 100644 --- a/crates/ootle_sdk_core/src/inputs.rs +++ b/crates/ootle_sdk_core/src/inputs.rs @@ -451,7 +451,7 @@ pub fn build_public_transfer_unsigned_with_wants( intent: &PublicTransferIntent, ) -> Result<(PartialTransaction, WantList), OotleSdkError> { let recipe = resolve_transfer_recipe(intent)?; - let builder = build_transfer_recipe_builder(network, &recipe); + let builder = build_transfer_recipe_builder(network, &recipe, tari_ootle_common_types::Epoch(intent.max_epoch)); let builder = apply_epoch_and_dry_run(builder, intent); let unsigned = builder.build_unsigned(); @@ -960,7 +960,7 @@ mod tests { fee: BoundaryAmount::new(2000), inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, } } diff --git a/crates/ootle_sdk_core/src/public_transfer.rs b/crates/ootle_sdk_core/src/public_transfer.rs index 891b086616..33ef25a51e 100644 --- a/crates/ootle_sdk_core/src/public_transfer.rs +++ b/crates/ootle_sdk_core/src/public_transfer.rs @@ -100,7 +100,7 @@ mod tests { fee: BoundaryAmount::new(2000), inputs: vec![InputRef::versioned(component_str(), 0)], min_epoch: Some(5), - max_epoch: Some(99), + max_epoch: 99, dry_run: false, } } diff --git a/crates/ootle_sdk_core/src/resolved_transfer.rs b/crates/ootle_sdk_core/src/resolved_transfer.rs index c479cdeb5b..11fcd42605 100644 --- a/crates/ootle_sdk_core/src/resolved_transfer.rs +++ b/crates/ootle_sdk_core/src/resolved_transfer.rs @@ -187,7 +187,7 @@ mod tests { fee: BoundaryAmount::new(2000), inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, } } diff --git a/crates/ootle_sdk_core/src/stealth/assemble.rs b/crates/ootle_sdk_core/src/stealth/assemble.rs index b60a3645a6..042073f900 100644 --- a/crates/ootle_sdk_core/src/stealth/assemble.rs +++ b/crates/ootle_sdk_core/src/stealth/assemble.rs @@ -190,7 +190,7 @@ fn build_unsigned_transaction( let fee = intent.fee.to_internal(); let revealed_amount = intent.revealed_input_amount; - let mut builder = TransactionBuilder::new(network.as_byte()); + let mut builder = TransactionBuilder::new(network.as_byte(), tari_ootle_common_types::Epoch(intent.max_epoch)); builder = builder.pay_fee_from_component(from_component, fee); builder = if revealed_amount > 0 { @@ -239,7 +239,6 @@ fn build_unsigned_transaction( // Epoch window + dry-run flag. builder = builder .with_min_epoch(intent.min_epoch.map(tari_ootle_common_types::Epoch)) - .with_max_epoch(intent.max_epoch.map(tari_ootle_common_types::Epoch)) .with_dry_run(intent.dry_run); // Attach the resolved input substates (the from-account component + its vault, resolved via the @@ -386,7 +385,8 @@ fn seed_partial( // `build_unsigned_transaction` once the inputs/outputs statements exist (see // `assemble_resolved_stealth`). We only need the partial's resolver machinery + accumulated stealth // state + the stashed build context, so an empty unsigned suffices. - let unsigned = TransactionBuilder::new(network.as_byte()).build_unsigned(); + let unsigned = + TransactionBuilder::new(network.as_byte(), tari_ootle_common_types::Epoch(intent.max_epoch)).build_unsigned(); let ctx = StealthBuildCtx { intent: intent.clone(), entropy: entropy.clone(), @@ -668,7 +668,7 @@ mod tests { revealed_input_amount: revealed_input, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, } @@ -731,7 +731,7 @@ mod tests { revealed_input_amount: 500_000, revealed_output_amount: 500_000, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -817,7 +817,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -1017,7 +1017,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -1127,7 +1127,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: true, }; @@ -1217,7 +1217,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -1341,7 +1341,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -1427,7 +1427,7 @@ mod tests { revealed_input_amount: 1_500_000, revealed_output_amount: 500_000, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -1469,7 +1469,7 @@ mod tests { revealed_input_amount: 2_500_000, revealed_output_amount: 1_500_000, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -1518,7 +1518,7 @@ mod tests { revealed_input_amount: 1_500_000, revealed_output_amount: 500_000, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; diff --git a/crates/ootle_sdk_core/src/stealth/canonicalize.rs b/crates/ootle_sdk_core/src/stealth/canonicalize.rs index 0aa8ffa1a7..70ed3fc460 100644 --- a/crates/ootle_sdk_core/src/stealth/canonicalize.rs +++ b/crates/ootle_sdk_core/src/stealth/canonicalize.rs @@ -131,7 +131,7 @@ mod tests { fee: BoundaryAmount::new(2000), inputs: vec![InputRef::versioned(component, 0)], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; let keys = PublicTransferKeys::new(SecretKeyBytes::from_array(scalar_bytes(11))); diff --git a/crates/ootle_sdk_core/src/stealth/inputs.rs b/crates/ootle_sdk_core/src/stealth/inputs.rs index 1ea5009005..730abb9b50 100644 --- a/crates/ootle_sdk_core/src/stealth/inputs.rs +++ b/crates/ootle_sdk_core/src/stealth/inputs.rs @@ -722,7 +722,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -770,7 +770,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; diff --git a/crates/ootle_sdk_core/src/stealth/outputs.rs b/crates/ootle_sdk_core/src/stealth/outputs.rs index b286ca05fc..7bb1be11e5 100644 --- a/crates/ootle_sdk_core/src/stealth/outputs.rs +++ b/crates/ootle_sdk_core/src/stealth/outputs.rs @@ -394,7 +394,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: revealed_output, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, } diff --git a/crates/ootle_sdk_core/src/stealth/sign_seal.rs b/crates/ootle_sdk_core/src/stealth/sign_seal.rs index f201c7e663..399b787fa0 100644 --- a/crates/ootle_sdk_core/src/stealth/sign_seal.rs +++ b/crates/ootle_sdk_core/src/stealth/sign_seal.rs @@ -492,7 +492,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -515,7 +515,7 @@ mod tests { revealed_input_amount: 1_000_000, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; diff --git a/crates/ootle_sdk_core/src/types/generic_intent.rs b/crates/ootle_sdk_core/src/types/generic_intent.rs index dd2c38dc9b..0df8ca9066 100644 --- a/crates/ootle_sdk_core/src/types/generic_intent.rs +++ b/crates/ootle_sdk_core/src/types/generic_intent.rs @@ -455,8 +455,9 @@ pub struct GenericTransactionIntent { pub extra_inputs: Vec, /// Optional earliest epoch this transaction is valid in. pub min_epoch: Option, - /// Optional latest epoch this transaction is valid in. - pub max_epoch: Option, + /// The last epoch this transaction is valid in. Mandatory: every transaction has a bounded + /// validity window, capped network-wide at `max_transaction_validity_epochs` past the current epoch. + pub max_epoch: u64, /// Whether this is a dry run. pub dry_run: bool, } @@ -959,7 +960,7 @@ mod tests { inputs: vec![], extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; assert_eq!(intent.validate_blob_indices().unwrap_err().code(), "VALIDATION"); @@ -1018,7 +1019,7 @@ mod tests { inputs: vec![InputRef::versioned(component_str(), 0)], extra_inputs: vec![], min_epoch: Some(1), - max_epoch: Some(10), + max_epoch: 10, dry_run: true, }; let json = serde_json::to_string(&intent).unwrap(); diff --git a/crates/ootle_sdk_core/src/types/intent.rs b/crates/ootle_sdk_core/src/types/intent.rs index 8b11fd0134..c782a7e1c9 100644 --- a/crates/ootle_sdk_core/src/types/intent.rs +++ b/crates/ootle_sdk_core/src/types/intent.rs @@ -96,8 +96,9 @@ pub struct PublicTransferIntent { pub inputs: Vec, /// Optional earliest epoch this transaction is valid in. pub min_epoch: Option, - /// Optional latest epoch this transaction is valid in. - pub max_epoch: Option, + /// The last epoch this transaction is valid in. Mandatory: every transaction has a bounded + /// validity window, capped network-wide at `max_transaction_validity_epochs` past the current epoch. + pub max_epoch: u64, /// Whether this is a dry run. The core sets `is_seal_signer_authorized` itself — it is /// intentionally **not** part of the intent. pub dry_run: bool, @@ -159,7 +160,7 @@ mod tests { InputRef::unversioned(resource_str()), ], min_epoch: Some(10), - max_epoch: None, + max_epoch: 1, dry_run: false, }; let reqs = intent.inputs_to_internal().unwrap(); @@ -180,7 +181,7 @@ mod tests { fee: BoundaryAmount::new(2000), inputs: vec![InputRef::unversioned(resource_str())], min_epoch: None, - max_epoch: Some(99), + max_epoch: 99, dry_run: true, }; let json = serde_json::to_string(&intent).unwrap(); diff --git a/crates/ootle_sdk_core/src/types/result.rs b/crates/ootle_sdk_core/src/types/result.rs index 5aeccc145e..6b18233a3b 100644 --- a/crates/ootle_sdk_core/src/types/result.rs +++ b/crates/ootle_sdk_core/src/types/result.rs @@ -29,6 +29,7 @@ pub fn abort_code(reason: &AbortReason) -> &'static str { AbortReason::InsufficientFeesPaid => "INSUFFICIENT_FEES_PAID", AbortReason::FeePaymentInMainIntent => "FEE_PAYMENT_IN_MAIN_INTENT", AbortReason::EpochExpired => "EPOCH_EXPIRED", + AbortReason::ValidityWindowTooLong => "VALIDITY_WINDOW_TOO_LONG", } } @@ -255,6 +256,7 @@ mod tests { AbortReason::InsufficientFeesPaid, AbortReason::FeePaymentInMainIntent, AbortReason::EpochExpired, + AbortReason::ValidityWindowTooLong, ]; #[test] @@ -340,8 +342,7 @@ mod tests { } } - /// Each of the 9 canonical `AbortReason` variants (incl. `EpochExpired`) surfaces a non-empty, - /// unique stable code. + /// Each of the 10 canonical `AbortReason` variants surfaces a non-empty, unique stable code. #[test] fn abort_codes_are_non_empty_and_unique() { let mut seen = std::collections::HashSet::new(); @@ -350,8 +351,12 @@ mod tests { assert!(!code.is_empty(), "abort code for {reason:?} is empty"); assert!(seen.insert(code), "duplicate abort code {code} for {reason:?}"); } - assert_eq!(seen.len(), 9, "expected exactly 9 canonical abort codes"); + assert_eq!(seen.len(), 10, "expected exactly 10 canonical abort codes"); assert_eq!(abort_code(&AbortReason::EpochExpired), "EPOCH_EXPIRED"); + assert_eq!( + abort_code(&AbortReason::ValidityWindowTooLong), + "VALIDITY_WINDOW_TOO_LONG" + ); } /// The `Abort` arm surfaces the canonical abort sub-code while keeping its top-level `ABORT` code. diff --git a/crates/ootle_sdk_core/src/types/stealth.rs b/crates/ootle_sdk_core/src/types/stealth.rs index 5ee0db1675..ac3346150e 100644 --- a/crates/ootle_sdk_core/src/types/stealth.rs +++ b/crates/ootle_sdk_core/src/types/stealth.rs @@ -312,8 +312,9 @@ pub struct StealthTransferIntent { pub revealed_output_amount: u64, /// Optional earliest epoch. pub min_epoch: Option, - /// Optional latest epoch. - pub max_epoch: Option, + /// The last epoch this transaction is valid in. Mandatory: every transaction has a bounded + /// validity window, capped network-wide at `max_transaction_validity_epochs` past the current epoch. + pub max_epoch: u64, /// Dry-run flag. pub dry_run: bool, /// Pay the fee from the account's revealed (XTR) vault even when there is no revealed input. @@ -648,7 +649,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: Some(99), + max_epoch: 99, dry_run: false, pay_fee_from_revealed: false, }; @@ -753,7 +754,7 @@ mod tests { revealed_input_amount: 0, revealed_output_amount, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, } diff --git a/crates/ootle_sdk_core/tests/golden_vectors.rs b/crates/ootle_sdk_core/tests/golden_vectors.rs index 03378fd411..0e11b9e863 100644 --- a/crates/ootle_sdk_core/tests/golden_vectors.rs +++ b/crates/ootle_sdk_core/tests/golden_vectors.rs @@ -169,7 +169,7 @@ fn sample_input() -> VectorInput { fee: BoundaryAmount::new(2000), inputs: vec![InputRef::versioned(component, 0)], min_epoch: Some(5), - max_epoch: Some(99), + max_epoch: 99, dry_run: false, }; @@ -221,7 +221,7 @@ fn single_key_basic_input() -> VectorInput { fee: BoundaryAmount::new(2500), inputs: vec![InputRef::versioned(component, 0)], min_epoch: Some(1), - max_epoch: Some(10), + max_epoch: 10, dry_run: false, }; @@ -357,7 +357,7 @@ fn resolve_single_key_basic_input() -> VectorInput { // No explicit inputs ⇒ the resolution path runs. inputs: vec![], min_epoch: Some(1), - max_epoch: Some(10), + max_epoch: 10, dry_run: false, }; @@ -676,7 +676,7 @@ fn stealth_intent(amount: u64, with_view: bool) -> ootle_sdk_core::types::stealt revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, } @@ -885,7 +885,7 @@ fn stealth_send_stealth_seal_seed() -> Fixture { revealed_input_amount: 0, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -914,7 +914,7 @@ fn stealth_send_account_key_seed() -> Fixture { revealed_input_amount: 1_000_000, revealed_output_amount: 0, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -943,7 +943,7 @@ fn stealth_send_revealed_output_single_seed() -> Fixture { revealed_input_amount: 1_500_000, revealed_output_amount: 500_000, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -975,7 +975,7 @@ fn stealth_send_revealed_output_multi_seed() -> Fixture { revealed_input_amount: 2_500_000, revealed_output_amount: 1_500_000, min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, pay_fee_from_revealed: false, }; @@ -1941,7 +1941,7 @@ fn generic_transfer_intent() -> GenericTransactionIntent { inputs: generic_explicit_inputs(), extra_inputs: vec![], min_epoch: Some(1), - max_epoch: Some(10), + max_epoch: 10, dry_run: false, } } @@ -1964,7 +1964,7 @@ fn generic_create_account_seed() -> Fixture { inputs: generic_explicit_inputs(), extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; generic_build_fixture("generic_build/create_account", intent) @@ -1986,7 +1986,7 @@ fn generic_call_function_seed() -> Fixture { inputs: generic_explicit_inputs(), extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; generic_build_fixture("generic_build/call_function", intent) @@ -2007,7 +2007,7 @@ fn generic_publish_template_seed() -> Fixture { inputs: generic_explicit_inputs(), extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; generic_build_fixture("generic_build/publish_template", intent) @@ -2040,7 +2040,7 @@ fn generic_workspace_pipe_seed() -> Fixture { inputs: generic_explicit_inputs(), extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; generic_build_fixture("generic_build/workspace_pipe", intent) @@ -2078,7 +2078,7 @@ fn generic_self_funding_faucet_seed() -> Fixture { inputs: generic_explicit_inputs(), extra_inputs: vec![], min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; generic_build_fixture("generic_build/self_funding_faucet", intent) @@ -2111,7 +2111,7 @@ fn faucet_claim_seed() -> Fixture { recipient_public_key: generic_recipient_pk(), fee: BoundaryAmount::new(2000), min_epoch: None, - max_epoch: None, + max_epoch: 1, dry_run: false, }; @@ -2161,7 +2161,7 @@ fn generic_seals_identically_to_hand_built() { // carried ONLY by `new_with_explicit_inputs` (the generic path also never calls `with_inputs`), // so the comparison is like-for-like. let explicit_input = generic_explicit_inputs()[0].to_internal().unwrap(); - let unsigned = TransactionBuilder::new(Network::Esmeralda.as_byte()) + let unsigned = TransactionBuilder::new(Network::Esmeralda.as_byte(), tari_ootle_common_types::Epoch(10)) .pay_fee_from_component(generic_from_component(), Amount::new(2500)) .call_method(generic_from_component(), "withdraw", args![ generic_resource(), @@ -2170,7 +2170,6 @@ fn generic_seals_identically_to_hand_built() { .put_last_instruction_output_on_workspace("bucket") .create_account(generic_recipient_pk().to_internal()) .with_min_epoch(Some(tari_ootle_common_types::Epoch(1))) - .with_max_epoch(Some(tari_ootle_common_types::Epoch(10))) .build_unsigned(); let partial = PartialTransaction::new_with_explicit_inputs(unsigned, vec![explicit_input]); let hand = match apply_fetched_substates(partial, &[]).unwrap() { @@ -2284,7 +2283,7 @@ fn cosign_input() -> VectorInput { fee: BoundaryAmount::new(2500), inputs: vec![], min_epoch: Some(1), - max_epoch: Some(10), + max_epoch: 10, dry_run: false, }; VectorInput { diff --git a/crates/ootle_sdk_core/tests/ootle_rs_crosscheck.rs b/crates/ootle_sdk_core/tests/ootle_rs_crosscheck.rs index 541a323d0e..17c5513b35 100644 --- a/crates/ootle_sdk_core/tests/ootle_rs_crosscheck.rs +++ b/crates/ootle_sdk_core/tests/ootle_rs_crosscheck.rs @@ -82,7 +82,7 @@ fn build_unsigned_via_shared_builder(network: u8, intent: &PublicTransferIntent) }; // --- ootle-rs recipe, verbatim (account.rs). --- - let mut builder = TransactionBuilder::new(network); + let mut builder = TransactionBuilder::new(network, tari_ootle_common_types::Epoch(intent.max_epoch)); // `pay_fee` → pay_fee_from_component on the from-component. builder = builder.pay_fee_from_component(from_component, fee); @@ -102,7 +102,6 @@ fn build_unsigned_via_shared_builder(network: u8, intent: &PublicTransferIntent) builder = builder .with_min_epoch(intent.min_epoch.map(tari_ootle_common_types::Epoch)) - .with_max_epoch(intent.max_epoch.map(tari_ootle_common_types::Epoch)) .with_dry_run(intent.dry_run); builder.build_unsigned() diff --git a/crates/ootle_wasm/core/src/hash.rs b/crates/ootle_wasm/core/src/hash.rs index 5a70ddb840..f008d87f69 100644 --- a/crates/ootle_wasm/core/src/hash.rs +++ b/crates/ootle_wasm/core/src/hash.rs @@ -47,6 +47,7 @@ mod tests { ristretto::{RistrettoPublicKey, RistrettoSecretKey}, tari_utilities::ByteArray, }; + use tari_engine_types::Epoch; use tari_ootle_transaction::UnsignedTransactionV1; use super::*; @@ -58,7 +59,7 @@ mod tests { let public_key_bytes = public_key.as_bytes().to_vec(); let seal_signer_bytes: RistrettoPublicKeyBytes = public_key.to_byte_type(); - let tx = UnsignedTransactionV1::new(0u8, vec![], vec![], Default::default(), None, None, false); + let tx = UnsignedTransactionV1::new(0u8, vec![], vec![], Default::default(), None, Epoch(1), false); // Hash via our function let our_hash = hash_unsigned_transaction(&tx, &public_key_bytes).unwrap(); @@ -75,7 +76,7 @@ mod tests { let public_key = RistrettoPublicKey::from_secret_key(&secret); let public_key_bytes = public_key.as_bytes().to_vec(); - let tx = UnsignedTransactionV1::new(0u8, vec![], vec![], Default::default(), None, None, false); + let tx = UnsignedTransactionV1::new(0u8, vec![], vec![], Default::default(), None, Epoch(1), false); let json = serde_json::to_string(&tx).unwrap(); let hash_from_struct = hash_unsigned_transaction(&tx, &public_key_bytes).unwrap(); diff --git a/crates/ootle_wasm/core/src/transaction.rs b/crates/ootle_wasm/core/src/transaction.rs index f877b09bc3..9b8e681151 100644 --- a/crates/ootle_wasm/core/src/transaction.rs +++ b/crates/ootle_wasm/core/src/transaction.rs @@ -60,12 +60,13 @@ mod tests { ristretto::{RistrettoPublicKey, RistrettoSecretKey}, tari_utilities::ByteArray, }; + use tari_engine_types::Epoch; use tari_ootle_transaction::{Transaction, UnsignedTransactionV1}; use super::*; fn make_unsigned_tx() -> UnsignedTransactionV1 { - UnsignedTransactionV1::new(0u8, vec![], vec![], Default::default(), None, None, false) + UnsignedTransactionV1::new(0u8, vec![], vec![], Default::default(), None, Epoch(1), false) } #[test] diff --git a/crates/p2p/proto/consensus.proto b/crates/p2p/proto/consensus.proto index 3d3488a3bd..55f114a052 100644 --- a/crates/p2p/proto/consensus.proto +++ b/crates/p2p/proto/consensus.proto @@ -160,6 +160,7 @@ enum AbortReason { INSUFFICIENT_FEES_PAID = 7; FEE_PAYMENT_IN_MAIN_INTENT = 8; EPOCH_EXPIRED = 9; + VALIDITY_WINDOW_TOO_LONG = 10; } message Evidence { diff --git a/crates/p2p/src/conversions/consensus.rs b/crates/p2p/src/conversions/consensus.rs index 0631178028..c5cb02046b 100644 --- a/crates/p2p/src/conversions/consensus.rs +++ b/crates/p2p/src/conversions/consensus.rs @@ -837,6 +837,7 @@ impl From for proto::consensus::AbortReason { AbortReason::InsufficientFeesPaid => Self::InsufficientFeesPaid, AbortReason::FeePaymentInMainIntent => Self::FeePaymentInMainIntent, AbortReason::EpochExpired => Self::EpochExpired, + AbortReason::ValidityWindowTooLong => Self::ValidityWindowTooLong, } } } @@ -856,6 +857,7 @@ impl TryFrom for AbortReason { proto::consensus::AbortReason::InsufficientFeesPaid => Ok(Self::InsufficientFeesPaid), proto::consensus::AbortReason::FeePaymentInMainIntent => Ok(Self::FeePaymentInMainIntent), proto::consensus::AbortReason::EpochExpired => Ok(Self::EpochExpired), + proto::consensus::AbortReason::ValidityWindowTooLong => Ok(Self::ValidityWindowTooLong), } } } diff --git a/crates/state_store_rocksdb/src/writer.rs b/crates/state_store_rocksdb/src/writer.rs index 08141e53af..7ce1d10d75 100644 --- a/crates/state_store_rocksdb/src/writer.rs +++ b/crates/state_store_rocksdb/src/writer.rs @@ -859,7 +859,7 @@ impl<'tx, TAddr: NodeAddressable + 'tx> StateStoreWriteTransaction for RocksDbSt initial_evidence: &Evidence, is_ready: bool, is_global: bool, - max_epoch: Option, + max_epoch: Epoch, transaction_weight: u64, ) -> Result<(), StorageError> { let value = TransactionPoolRecord::load( diff --git a/crates/state_store_rocksdb/tests/transactions.rs b/crates/state_store_rocksdb/tests/transactions.rs index 4aa02ff9f1..cdf0cf9d53 100644 --- a/crates/state_store_rocksdb/tests/transactions.rs +++ b/crates/state_store_rocksdb/tests/transactions.rs @@ -86,11 +86,11 @@ mod confirm_all_transitions { block1.as_locked().set(&mut tx).unwrap(); block1.as_leaf().set(&mut tx).unwrap(); - tx.transaction_pool_insert_new(atom1.id, atom1.decision, &Evidence::empty(), true, false, None, 0) + tx.transaction_pool_insert_new(atom1.id, atom1.decision, &Evidence::empty(), true, false, Epoch(1), 0) .unwrap(); - tx.transaction_pool_insert_new(atom2.id, atom2.decision, &Evidence::empty(), true, false, None, 0) + tx.transaction_pool_insert_new(atom2.id, atom2.decision, &Evidence::empty(), true, false, Epoch(1), 0) .unwrap(); - tx.transaction_pool_insert_new(atom3.id, atom3.decision, &Evidence::empty(), true, false, None, 0) + tx.transaction_pool_insert_new(atom3.id, atom3.decision, &Evidence::empty(), true, false, Epoch(1), 0) .unwrap(); let block_id = *block1.id(); let transactions = tx.transaction_pool_get_all(1000).unwrap(); @@ -191,7 +191,7 @@ mod confirm_all_transitions { block1.as_locked().set(&mut tx).unwrap(); block1.as_leaf().set(&mut tx).unwrap(); - tx.transaction_pool_insert_new(atom1.id, atom1.decision, &Evidence::empty(), true, false, None, 0) + tx.transaction_pool_insert_new(atom1.id, atom1.decision, &Evidence::empty(), true, false, Epoch(1), 0) .unwrap(); // Base record has no pending update yet. @@ -230,21 +230,21 @@ mod transaction_operations { // transactions_insert let tx1 = TransactionRecord::new( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .add_instruction(Instruction::DropAllProofsInWorkspace) .add_input(SubstateRequirement::new(create_random_substate_id(), Some(0))) .build_and_seal(&PrivateKey::default()), ); tx.transactions_insert(&tx1).unwrap(); let tx2 = TransactionRecord::new( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .add_instruction(Instruction::DropAllProofsInWorkspace) .add_input(SubstateRequirement::new(create_random_substate_id(), Some(1))) .build_and_seal(&PrivateKey::default()), ); tx.transactions_insert(&tx2).unwrap(); let unexisting_tx = TransactionRecord::new( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .add_instruction(Instruction::DropAllProofsInWorkspace) .add_input(SubstateRequirement::new(create_random_substate_id(), Some(2))) .build_and_seal(&PrivateKey::default()), @@ -267,7 +267,7 @@ mod transaction_operations { // transactions_update let updated_tx = TransactionRecord::new( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .add_instruction(Instruction::DropAllProofsInWorkspace) .add_input(SubstateRequirement::new(create_random_substate_id(), Some(3))) .build_and_seal(&PrivateKey::default()), @@ -308,14 +308,14 @@ mod transaction_execution_operations { // insert some transactions let tx1 = TransactionRecord::new( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .add_instruction(Instruction::DropAllProofsInWorkspace) .add_input(SubstateRequirement::new(create_random_substate_id(), Some(0))) .build_and_seal(&PrivateKey::default()), ); tx.transactions_insert(&tx1).unwrap(); let tx2 = TransactionRecord::new( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .add_instruction(Instruction::DropAllProofsInWorkspace) .add_input(SubstateRequirement::new(create_random_substate_id(), Some(1))) .build_and_seal(&PrivateKey::default()), @@ -408,8 +408,16 @@ mod transaction_execution_operations { assert!(all.is_empty()); // transactions_finalize_all - tx.transaction_pool_insert_new(*tx1.id(), Decision::Commit, &Evidence::empty(), true, false, None, 0) - .unwrap(); + tx.transaction_pool_insert_new( + *tx1.id(), + Decision::Commit, + &Evidence::empty(), + true, + false, + Epoch(1), + 0, + ) + .unwrap(); let transactions = tx.transaction_pool_get_all(1000).unwrap(); assert_eq!(transactions.len(), 1); tx.transactions_finalize_all(Epoch(1), transactions.iter()).unwrap(); @@ -446,7 +454,7 @@ mod transaction_execution_operations { let mut tx = db.create_write_tx().unwrap(); let tx1 = TransactionRecord::new( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .add_instruction(Instruction::DropAllProofsInWorkspace) .add_input(SubstateRequirement::new(create_random_substate_id(), Some(0))) .build_and_seal(&PrivateKey::default()), @@ -505,7 +513,7 @@ mod finalized_transaction_gc { fn insert_transaction(tx: &mut impl StateStoreWriteTransaction) -> TransactionRecord { let rec = TransactionRecord::new( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .add_instruction(Instruction::DropAllProofsInWorkspace) .add_input(SubstateRequirement::new(create_random_substate_id(), Some(0))) .build_and_seal(&PrivateKey::default()), @@ -527,8 +535,16 @@ mod finalized_transaction_gc { commit_chain(&mut tx, &chain); let tx1 = insert_transaction(&mut tx); - tx.transaction_pool_insert_new(*tx1.id(), Decision::Commit, &Evidence::empty(), true, false, None, 0) - .unwrap(); + tx.transaction_pool_insert_new( + *tx1.id(), + Decision::Commit, + &Evidence::empty(), + true, + false, + Epoch(1), + 0, + ) + .unwrap(); let pool = tx.transaction_pool_get_all(1000).unwrap(); tx.transactions_finalize_all(Epoch(1), pool.iter()).unwrap(); assert!(tx.transactions_exists(tx1.id()).unwrap()); @@ -561,8 +577,16 @@ mod finalized_transaction_gc { commit_chain(&mut tx, &chain); let tx1 = insert_transaction(&mut tx); - tx.transaction_pool_insert_new(*tx1.id(), Decision::Commit, &Evidence::empty(), true, false, None, 0) - .unwrap(); + tx.transaction_pool_insert_new( + *tx1.id(), + Decision::Commit, + &Evidence::empty(), + true, + false, + Epoch(1), + 0, + ) + .unwrap(); let pool = tx.transaction_pool_get_all(1000).unwrap(); tx.transactions_finalize_all(Epoch(1), pool.iter()).unwrap(); @@ -591,8 +615,16 @@ mod finalized_transaction_gc { commit_chain(&mut tx, &chain); let tx1 = insert_transaction(&mut tx); - tx.transaction_pool_insert_new(*tx1.id(), Decision::Commit, &Evidence::empty(), true, false, None, 0) - .unwrap(); + tx.transaction_pool_insert_new( + *tx1.id(), + Decision::Commit, + &Evidence::empty(), + true, + false, + Epoch(1), + 0, + ) + .unwrap(); let pool = tx.transaction_pool_get_all(1000).unwrap(); // Finalized in epoch 1, then finalized again in epoch 5: the index entry moves. @@ -670,8 +702,16 @@ mod get_many_ready_weight_budget { block1.as_leaf().set(&mut tx).unwrap(); for (atom, weight) in atoms.iter().zip(weights) { - tx.transaction_pool_insert_new(atom.id, atom.decision, &Evidence::empty(), true, false, None, *weight) - .unwrap(); + tx.transaction_pool_insert_new( + atom.id, + atom.decision, + &Evidence::empty(), + true, + false, + Epoch(1), + *weight, + ) + .unwrap(); } let block_id = *block1.id(); tx.commit().unwrap(); diff --git a/crates/storage/src/consensus_models/transaction_pool.rs b/crates/storage/src/consensus_models/transaction_pool.rs index b304498ced..987b28ee0e 100644 --- a/crates/storage/src/consensus_models/transaction_pool.rs +++ b/crates/storage/src/consensus_models/transaction_pool.rs @@ -73,7 +73,7 @@ impl TransactionPool { initial_evidence: &Evidence, is_ready: bool, is_global: bool, - max_epoch: Option, + max_epoch: Epoch, transaction_weight: u64, ) -> Result<(), TransactionPoolError> { tx.transaction_pool_insert_new( @@ -414,9 +414,8 @@ pub struct TransactionPoolRecord { #[n(10)] is_ready: bool, /// The maximum epoch for which this transaction is valid. - #[serde(default)] #[n(11)] - max_epoch: Option, + max_epoch: Epoch, /// Epoch to use when executing the transaction. This updates as foreign proposals are received /// until the transaction is executed. #[n(12)] @@ -478,7 +477,7 @@ impl TransactionPoolRecord { local_decision: Option, remote_decision: Option, is_ready: bool, - max_epoch: Option, + max_epoch: Epoch, locked_epoch: Option, last_updated: time::OffsetDateTime, last_updated_in_block: Option, @@ -551,7 +550,7 @@ impl TransactionPoolRecord { self.remote_decision } - pub fn max_epoch(&self) -> Option { + pub fn max_epoch(&self) -> Epoch { self.max_epoch } @@ -1063,7 +1062,7 @@ mod tests { local_decision: None, remote_decision: None, is_ready: true, - max_epoch: None, + max_epoch: Epoch(1), locked_epoch: None, last_updated: time::OffsetDateTime::now_utc(), last_updated_in_block: None, @@ -1128,7 +1127,7 @@ mod tests { local_decision: None, remote_decision: None, is_ready: false, - max_epoch: None, + max_epoch: Epoch(1), locked_epoch: None, last_updated: time::OffsetDateTime::now_utc(), last_updated_in_block: None, diff --git a/crates/storage/src/state_store/mod.rs b/crates/storage/src/state_store/mod.rs index ecd6c5474a..3304092b9c 100644 --- a/crates/storage/src/state_store/mod.rs +++ b/crates/storage/src/state_store/mod.rs @@ -477,7 +477,7 @@ pub trait StateStoreWriteTransaction { initial_evidence: &Evidence, is_ready: bool, is_global: bool, - max_epoch: Option, + max_epoch: Epoch, transaction_weight: u64, ) -> Result<(), StorageError>; fn transaction_pool_add_pending_update( diff --git a/crates/template_builtin/tests/liquidity_pool.rs b/crates/template_builtin/tests/liquidity_pool.rs index 03c6979452..4334e930eb 100644 --- a/crates/template_builtin/tests/liquidity_pool.rs +++ b/crates/template_builtin/tests/liquidity_pool.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use tari_engine_types::indexed_value::IndexedWellKnownTypes; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{AccessRule, OwnerRule, constants::TARI_TOKEN, metadata}; use tari_template_lib_types::{Amount, ComponentAddress, ResourceAddress}; use tari_template_test_tooling::TemplateTest; @@ -26,7 +26,7 @@ fn initial_contribution_and_redeem() { // ACT 1: Create liquidity pool and contribute liquidity test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("pool") .call_function(template_address, "create", args![ OwnerRule::OwnedBySigner, @@ -80,7 +80,7 @@ fn initial_contribution_and_redeem() { // ACT 2: Redeem liquidity test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(user1, "withdraw", args![pool_unit_resx, pool_units.balance()]) .put_last_instruction_output_on_workspace("redeem_pool_units") .call_method(pool_addr, "redeem", args![Workspace("redeem_pool_units")]) @@ -119,7 +119,7 @@ fn second_contribution_and_partial_redeem() { // ACT 1: create the pool with an initial 1000 TARI : 4000 stablecoin contribution (ratio 1:4, LP minted = 2000). test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("pool") .call_function(template_address, "create", args![ OwnerRule::OwnedBySigner, @@ -159,7 +159,7 @@ fn second_contribution_and_partial_redeem() { // ACT 2: a deliberately non-proportional second contribution: 500 TARI but 4000 stablecoin. At the 1:4 reserve // ratio only 2000 stablecoin is needed to match the 500 TARI, so 2000 stablecoin must be returned as change. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet_component, "take_free_coins_custom", args![4000]) .put_last_instruction_output_on_workspace("faucet_coins") .call_method(user1, "withdraw", args![TARI_TOKEN, 500]) @@ -200,7 +200,7 @@ fn second_contribution_and_partial_redeem() { // ACT 3: redeem HALF the LP (1500 of 3000). Proportional payout = 750 TARI and 3000 stablecoin. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(user1, "withdraw", args![lp_resx, 1500]) .put_last_instruction_output_on_workspace("redeem_lp") .call_method(pool_addr, "redeem", args![Workspace("redeem_lp")]) @@ -242,7 +242,7 @@ fn bootstrap_contribution_after_seeding_reserve() { // ACT 1: create the pool owned by user1 (so user1 can call the owner-gated protected_* methods). With // `OwnerRule::OwnedBySigner` the owner is the seal signer, i.e. user1. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("pool") .call_function(template_address, "create", args![ OwnerRule::OwnedBySigner, @@ -265,7 +265,7 @@ fn bootstrap_contribution_after_seeding_reserve() { // ACT 2: owner seeds 1000 TARI into reserve A via protected_add_liquidity (no LP minted). test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(user1, "withdraw", args![TARI_TOKEN, 1000]) .put_last_instruction_output_on_workspace("seed_a") .call_method(pool_addr, "protected_add_liquidity", args![Workspace("seed_a")]) @@ -276,7 +276,7 @@ fn bootstrap_contribution_after_seeding_reserve() { // ACT 3: bootstrap with a full contribution of 500 TARI + 2000 stablecoin. Total reserves become // (1000 + 500, 0 + 2000) = (1500, 2000) and LP minted = floor(sqrt(1500 * 2000)) = 1732. test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(faucet_component, "take_free_coins_custom", args![2000]) .put_last_instruction_output_on_workspace("faucet_coins") .call_method(user1, "withdraw", args![TARI_TOKEN, 500]) @@ -328,7 +328,7 @@ fn basic_constant_product_swap() { // ACT 1: Create liquidity pool and contribute liquidity test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("pool") .call_function(template_address, "create", args![ OwnerRule::OwnedBySigner, @@ -374,7 +374,7 @@ fn basic_constant_product_swap() { // ACT 2: Swap to pay fees test.enable_fees(); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .with_fee_instructions_builder(|builder| { // User2 would like to swap stablecoin for XTR to pay fees builder @@ -425,7 +425,7 @@ pub fn create_test_faucet_component>( ) -> (ComponentAddress, ResourceAddress) { let template_addr = test.get_template_address("TestFaucet"); let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(template_addr, "mint", args![initial_supply.into()]) .build_and_seal(test.secret_key()), vec![], diff --git a/crates/template_builtin/tests/nft_faucet.rs b/crates/template_builtin/tests/nft_faucet.rs index a22cff2983..9e31f14e95 100644 --- a/crates/template_builtin/tests/nft_faucet.rs +++ b/crates/template_builtin/tests/nft_faucet.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use tari_engine_types::commit_result::ExecuteResult; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::{ ComponentAddress, Metadata, @@ -72,7 +72,7 @@ fn mint_faucet_nft( metadata: Metadata, ) -> ExecuteResult { test.build_and_execute( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(NFT_FAUCET_COMPONENT_ADDRESS, "mint", args![1, metadata]) .put_last_instruction_output_on_workspace("my_nft") .call_method(account, "deposit", args![Workspace("my_nft")]), diff --git a/crates/template_builtin/tests/xtr_faucet.rs b/crates/template_builtin/tests/xtr_faucet.rs index 68e3ead712..39a25f1743 100644 --- a/crates/template_builtin/tests/xtr_faucet.rs +++ b/crates/template_builtin/tests/xtr_faucet.rs @@ -1,7 +1,7 @@ // Copyright 2025 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_lib::types::constants::{STEALTH_TARI_RESOURCE_ADDRESS, XTR_FAUCET_COMPONENT_ADDRESS}; use tari_template_test_tooling::{TemplateTest, support::assert_error::assert_reject_reason}; @@ -39,7 +39,7 @@ fn second_claim_by_same_signer_is_rejected() { // Second claim: same signing key → same claim-receipt NFT ID → DuplicateNonFungibleId let reject_reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_method(XTR_FAUCET_COMPONENT_ADDRESS, "take", args![account]) .build_and_seal(&secret_key), vec![], diff --git a/crates/template_test_tooling/src/template_test.rs b/crates/template_test_tooling/src/template_test.rs index 7a91c3266f..b9bc28fa0c 100644 --- a/crates/template_test_tooling/src/template_test.rs +++ b/crates/template_test_tooling/src/template_test.rs @@ -796,7 +796,7 @@ impl TemplateTest { pub fn transaction(&self) -> TransactionBuilder { let seq = self.transaction_seq.get(); self.transaction_seq.set(seq + 1); - Transaction::builder(Network::LocalNet).with_max_epoch(Some(Epoch(seq))) + Transaction::builder(Network::LocalNet, Epoch(seq)) } /// Executes a transaction. Panics if the transaction is not finalized (fee transaction fails). Does not panic if diff --git a/crates/transaction/src/builder/mod.rs b/crates/transaction/src/builder/mod.rs index 61f7aeac9e..753b8532c0 100644 --- a/crates/transaction/src/builder/mod.rs +++ b/crates/transaction/src/builder/mod.rs @@ -79,22 +79,34 @@ pub struct TransactionBuilder { } impl TransactionBuilder { - pub fn new>(network: N) -> Self { + /// `max_epoch` is required: every transaction has a bounded validity window, so the last epoch + /// in which it may be sequenced must be chosen when it is built. + pub fn new>(network: N, max_epoch: Epoch) -> Self { let network = network.into(); Self { - unsigned_transaction: UnsignedTransaction::new(network), + unsigned_transaction: UnsignedTransaction::new(network, max_epoch), workspace_ids: WorkspaceIds::new(), blob_ids: BlobIds::new(), - fee_instruction_builder: Some(Box::new(Self::new_fee_builder(network))), + fee_instruction_builder: Some(Box::new(Self::new_fee_builder(network, max_epoch))), _discriminator: std::marker::PhantomData, fill_inputs: false, } } pub fn with_unsigned_transaction>(self, unsigned_transaction: T) -> Self { + Self::from_unsigned(unsigned_transaction) + } + + /// Starts from a complete unsigned transaction, taking its network and `max_epoch` from the + /// transaction itself. Nothing about the caller's transaction is chosen here, so this needs no + /// prior builder and — unlike [`Transaction::builder`] — no `max_epoch` decided up front. + pub fn from_unsigned>(unsigned_transaction: T) -> Self { let unsigned_transaction = unsigned_transaction.into(); Self { - fee_instruction_builder: Some(Box::new(Self::new_fee_builder(unsigned_transaction.network()))), + fee_instruction_builder: Some(Box::new(Self::new_fee_builder( + unsigned_transaction.network(), + unsigned_transaction.max_epoch(), + ))), unsigned_transaction, workspace_ids: WorkspaceIds::new(), blob_ids: BlobIds::new(), @@ -103,9 +115,9 @@ impl TransactionBuilder { } } - fn new_fee_builder>(network: N) -> TransactionBuilder { + fn new_fee_builder>(network: N, max_epoch: Epoch) -> TransactionBuilder { TransactionBuilder { - unsigned_transaction: UnsignedTransaction::new(network), + unsigned_transaction: UnsignedTransaction::new(network, max_epoch), workspace_ids: WorkspaceIds::new(), blob_ids: BlobIds::new(), fee_instruction_builder: None, @@ -194,7 +206,7 @@ impl TransactionBuilder { self } - pub fn with_max_epoch(mut self, max_epoch: Option) -> Self { + pub fn with_max_epoch(mut self, max_epoch: Epoch) -> Self { self.unsigned_transaction.set_max_epoch(max_epoch); self } diff --git a/crates/transaction/src/builder/tests.rs b/crates/transaction/src/builder/tests.rs index 42411aadb2..ebf7e06108 100644 --- a/crates/transaction/src/builder/tests.rs +++ b/crates/transaction/src/builder/tests.rs @@ -1,6 +1,7 @@ // Copyright 2025 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use tari_ootle_common_types::Epoch; use tari_template_lib_types::TemplateAddress; use crate::{ @@ -15,7 +16,7 @@ use crate::{ #[test] fn it_converts_workspace_names_to_ids() { - let transaction = Transaction::builder_localnet() + let transaction = Transaction::builder_localnet(Epoch(1)) .put_last_instruction_output_on_workspace("thing1") .allocate_resource_address("thing2") .allocate_component_address("thing3") @@ -65,12 +66,12 @@ fn merge_remaps_blob_ids_and_appends_blobs() { let address = TemplateAddress::from_array([7; 32]); // First builder owns one blob `a` referenced by an arg. - let a = Transaction::builder_localnet() + let a = Transaction::builder_localnet(Epoch(1)) .add_blob("a", vec![1u8, 2, 3]) .call_function(address, "f", args![Blob("a")]); // Second builder owns its own blob `b`. - let b = Transaction::builder_localnet() + let b = Transaction::builder_localnet(Epoch(1)) .add_blob("b", vec![4u8, 5]) .call_function(address, "g", args![Blob("b")]); @@ -99,8 +100,8 @@ fn merge_remaps_blob_ids_and_appends_blobs() { #[test] #[should_panic(expected = "blob name 'a' collides during merge")] fn merge_rejects_colliding_blob_names() { - let a = Transaction::builder_localnet().add_blob("a", vec![1u8]); - let b = Transaction::builder_localnet().add_blob("a", vec![2u8]); + let a = Transaction::builder_localnet(Epoch(1)).add_blob("a", vec![1u8]); + let b = Transaction::builder_localnet(Epoch(1)).add_blob("a", vec![2u8]); let _unused = a.merge(b); } @@ -108,8 +109,8 @@ fn merge_rejects_colliding_blob_names() { fn merge_remaps_publish_template_blob_index() { // `self` already has a blob, so the merged builder's auto-added template blob index 0 // becomes index 1 after merge. - let a = Transaction::builder_localnet().add_blob("filler", vec![0u8; 4]); - let b = Transaction::builder_localnet().publish_template(vec![9u8, 9, 9]); + let a = Transaction::builder_localnet(Epoch(1)).add_blob("filler", vec![0u8; 4]); + let b = Transaction::builder_localnet(Epoch(1)).publish_template(vec![9u8, 9, 9]); let merged = a.merge(b).build_unsigned(); diff --git a/crates/transaction/src/lib.rs b/crates/transaction/src/lib.rs index eb73ece9b2..6cb1eff4f5 100644 --- a/crates/transaction/src/lib.rs +++ b/crates/transaction/src/lib.rs @@ -38,6 +38,9 @@ pub use builder::TransactionBuilder; pub use envelope::*; pub use ootle_network::Network; pub use signable::*; +// Re-exported because `max_epoch` is a mandatory public field of the unsigned transaction, so any +// caller constructing one needs the type. +pub use tari_ootle_common_types::Epoch; pub use transaction::*; pub use transaction_id::*; pub use unsealed::*; diff --git a/crates/transaction/src/transaction.rs b/crates/transaction/src/transaction.rs index 5c5f14788f..227635d041 100644 --- a/crates/transaction/src/transaction.rs +++ b/crates/transaction/src/transaction.rs @@ -54,12 +54,12 @@ impl Transaction { /// Creates a new transaction builder. /// NOTE: The network is set to LocalNet. Be sure to set the correct network using for_network. /// NOTE: this method will likely be deprecated in the future - pub fn builder_localnet() -> TransactionBuilder { - Self::builder(Network::LocalNet) + pub fn builder_localnet(max_epoch: Epoch) -> TransactionBuilder { + Self::builder(Network::LocalNet, max_epoch) } - pub fn builder>(network: N) -> TransactionBuilder { - TransactionBuilder::new(network) + pub fn builder>(network: N, max_epoch: Epoch) -> TransactionBuilder { + TransactionBuilder::new(network, max_epoch) } pub fn new(transaction: UnsealedTransaction, seal_signature: TransactionSealSignature) -> Self { @@ -290,7 +290,7 @@ impl Transaction { } } - pub fn max_epoch(&self) -> Option { + pub fn max_epoch(&self) -> Epoch { match self { Self::V1(tx) => tx.max_epoch(), } @@ -434,7 +434,7 @@ impl PrunedTransaction { } } - pub fn max_epoch(&self) -> Option { + pub fn max_epoch(&self) -> Epoch { match self { Self::V1(tx) => tx.max_epoch(), } @@ -483,7 +483,7 @@ mod tests { use crate::{args, call_args}; fn create_transaction() -> TransactionBuilder { - Transaction::builder(123u8) + Transaction::builder(123u8, Epoch(1)) .create_account(Default::default()) .call_method(ComponentAddress::from_array([1; 32]), "method", args![ 1, diff --git a/crates/transaction/src/unsigned_transaction.rs b/crates/transaction/src/unsigned_transaction.rs index 5353520a83..4cc87e1a2d 100644 --- a/crates/transaction/src/unsigned_transaction.rs +++ b/crates/transaction/src/unsigned_transaction.rs @@ -31,8 +31,8 @@ pub enum UnsignedTransaction { } impl UnsignedTransaction { - pub fn new>(network: N) -> Self { - Self::V1(UnsignedTransactionV1::new_default(network)) + pub fn new>(network: N, max_epoch: Epoch) -> Self { + Self::V1(UnsignedTransactionV1::new_default(network, max_epoch)) } pub fn then Self>(self, f: F) -> Self { @@ -128,7 +128,7 @@ impl UnsignedTransaction { } } - pub fn max_epoch(&self) -> Option { + pub fn max_epoch(&self) -> Epoch { match self { Self::V1(tx) => tx.max_epoch(), } @@ -158,7 +158,7 @@ impl UnsignedTransaction { self } - pub fn set_max_epoch(&mut self, max_epoch: Option) -> &mut Self { + pub fn set_max_epoch(&mut self, max_epoch: Epoch) -> &mut Self { match self { Self::V1(tx) => tx.max_epoch = max_epoch, } diff --git a/crates/transaction/src/v1/intent.rs b/crates/transaction/src/v1/intent.rs index 2f784c557a..4190cd2db7 100644 --- a/crates/transaction/src/v1/intent.rs +++ b/crates/transaction/src/v1/intent.rs @@ -126,7 +126,7 @@ mod tests { ], inputs, min_epoch: Some(Epoch(100)), - max_epoch: Some(Epoch(200)), + max_epoch: Epoch(200), is_seal_signer_authorized: false, dry_run: true, blobs: Blobs::from_vec(vec![Blob::from(vec![1, 2, 3])]), @@ -235,7 +235,7 @@ mod tests { .collect(); assert_ne!(commitment_of(u), base_commitment, "inputs (version changed)"); - // min_epoch / max_epoch: value change and Some <-> None + // min_epoch: value change and Some <-> None; max_epoch: value change let mut u = base.clone(); u.min_epoch = Some(Epoch(101)); assert_ne!(commitment_of(u), base_commitment, "min_epoch (value)"); @@ -243,11 +243,8 @@ mod tests { u.min_epoch = None; assert_ne!(commitment_of(u), base_commitment, "min_epoch (None)"); let mut u = base.clone(); - u.max_epoch = Some(Epoch(999)); + u.max_epoch = Epoch(999); assert_ne!(commitment_of(u), base_commitment, "max_epoch (value)"); - let mut u = base.clone(); - u.max_epoch = None; - assert_ne!(commitment_of(u), base_commitment, "max_epoch (None)"); // is_seal_signer_authorized let mut u = base.clone(); diff --git a/crates/transaction/src/v1/pruned.rs b/crates/transaction/src/v1/pruned.rs index 16e42150a5..a426bd3ce4 100644 --- a/crates/transaction/src/v1/pruned.rs +++ b/crates/transaction/src/v1/pruned.rs @@ -52,7 +52,7 @@ pub struct PrunedUnsignedTransactionV1 { #[n(4)] pub min_epoch: Option, #[n(5)] - pub max_epoch: Option, + pub max_epoch: Epoch, #[n(6)] pub is_seal_signer_authorized: bool, #[n(7)] @@ -263,7 +263,7 @@ impl PrunedTransactionV1 { self.body.transaction.min_epoch } - pub fn max_epoch(&self) -> Option { + pub fn max_epoch(&self) -> Epoch { self.body.transaction.max_epoch } @@ -383,7 +383,7 @@ mod tests { instructions: vec![Instruction::DropAllProofsInWorkspace], inputs, min_epoch: Some(Epoch(100)), - max_epoch: Some(Epoch(200)), + max_epoch: Epoch(200), is_seal_signer_authorized: false, dry_run: true, blobs, diff --git a/crates/transaction/src/v1/signature.rs b/crates/transaction/src/v1/signature.rs index 3eccf1793c..f78e1423b7 100644 --- a/crates/transaction/src/v1/signature.rs +++ b/crates/transaction/src/v1/signature.rs @@ -411,7 +411,7 @@ pub(crate) struct TransactionSignatureFields<'a> { instructions: &'a [Instruction], inputs: &'a IndexSet, min_epoch: Option, - max_epoch: Option, + max_epoch: Epoch, is_seal_signer_authorized: bool, dry_run: bool, nonce: u64, @@ -483,7 +483,7 @@ mod tests { ], inputs, min_epoch: Some(Epoch(100)), - max_epoch: Some(Epoch(200)), + max_epoch: Epoch(200), is_seal_signer_authorized: false, dry_run: true, blobs: crate::Blobs::empty(), @@ -576,13 +576,10 @@ mod tests { tx.min_epoch = None; assert_ne!(sig_msg(&signer, &tx), base_msg, "min_epoch (None)"); - // max_epoch: value change / Some <-> None + // max_epoch let mut tx = base.clone(); - tx.max_epoch = Some(Epoch(999)); + tx.max_epoch = Epoch(999); assert_ne!(sig_msg(&signer, &tx), base_msg, "max_epoch (value)"); - let mut tx = base.clone(); - tx.max_epoch = None; - assert_ne!(sig_msg(&signer, &tx), base_msg, "max_epoch (None)"); // is_seal_signer_authorized let mut tx = base.clone(); @@ -707,11 +704,8 @@ mod tests { // max_epoch let mut u = base_unsigned.clone(); - u.max_epoch = Some(Epoch(999)); + u.max_epoch = Epoch(999); assert_ne!(seal_msg(&with_body(u)), base_msg, "max_epoch (value)"); - let mut u = base_unsigned.clone(); - u.max_epoch = None; - assert_ne!(seal_msg(&with_body(u)), base_msg, "max_epoch (None)"); // is_seal_signer_authorized let mut u = base_unsigned.clone(); diff --git a/crates/transaction/src/v1/transaction.rs b/crates/transaction/src/v1/transaction.rs index 6122461774..3b5d46fae6 100644 --- a/crates/transaction/src/v1/transaction.rs +++ b/crates/transaction/src/v1/transaction.rs @@ -220,7 +220,7 @@ impl TransactionV1 { self.body.min_epoch() } - pub fn max_epoch(&self) -> Option { + pub fn max_epoch(&self) -> Epoch { self.body.max_epoch() } @@ -458,7 +458,7 @@ mod blob_validation_tests { instructions, inputs: indexmap::IndexSet::new(), min_epoch: None, - max_epoch: None, + max_epoch: Epoch(1), is_seal_signer_authorized: true, dry_run: false, blobs, @@ -616,7 +616,7 @@ mod transaction_id_tests { instructions: vec![Instruction::PutLastInstructionOutputOnWorkspace { key: 3 }], inputs: indexmap::IndexSet::new(), min_epoch: None, - max_epoch: Some(Epoch(10)), + max_epoch: Epoch(10), is_seal_signer_authorized: true, dry_run: false, blobs: Blobs::empty(), diff --git a/crates/transaction/src/v1/unsealed.rs b/crates/transaction/src/v1/unsealed.rs index 09e0fa735b..33f8c4a040 100644 --- a/crates/transaction/src/v1/unsealed.rs +++ b/crates/transaction/src/v1/unsealed.rs @@ -142,7 +142,7 @@ impl UnsealedTransactionV1 { self.transaction.min_epoch } - pub fn max_epoch(&self) -> Option { + pub fn max_epoch(&self) -> Epoch { self.transaction.max_epoch } diff --git a/crates/transaction/src/v1/unsigned.rs b/crates/transaction/src/v1/unsigned.rs index 5256d0fac5..b22ca367ab 100644 --- a/crates/transaction/src/v1/unsigned.rs +++ b/crates/transaction/src/v1/unsigned.rs @@ -30,8 +30,12 @@ pub struct UnsignedTransactionV1 { pub inputs: IndexSet, #[n(4)] pub min_epoch: Option, + /// The last epoch in which this transaction may be sequenced. Mandatory: every transaction has a + /// bounded lifetime, capped at `ConsensusConstants::max_transaction_validity_epochs` past the + /// current epoch, so a transaction's death is deterministic and an aborted attempt cannot be + /// retried indefinitely. #[n(5)] - pub max_epoch: Option, + pub max_epoch: Epoch, #[n(6)] pub is_seal_signer_authorized: bool, #[n(7)] @@ -61,14 +65,14 @@ pub struct UnsignedTransactionV1 { } impl UnsignedTransactionV1 { - pub(crate) fn new_default>(network: N) -> Self { + pub(crate) fn new_default>(network: N, max_epoch: Epoch) -> Self { Self { network: network.into(), fee_instructions: vec![], instructions: vec![], inputs: IndexSet::new(), min_epoch: None, - max_epoch: None, + max_epoch, is_seal_signer_authorized: true, dry_run: false, blobs: Blobs::empty(), @@ -82,7 +86,7 @@ impl UnsignedTransactionV1 { instructions: Vec, inputs: IndexSet, min_epoch: Option, - max_epoch: Option, + max_epoch: Epoch, dry_run: bool, ) -> Self { Self { @@ -173,7 +177,7 @@ impl UnsignedTransactionV1 { self.min_epoch } - pub fn max_epoch(&self) -> Option { + pub fn max_epoch(&self) -> Epoch { self.max_epoch } diff --git a/crates/transaction_validation/src/blob_references.rs b/crates/transaction_validation/src/blob_references.rs index bd8eee163b..2a48123d84 100644 --- a/crates/transaction_validation/src/blob_references.rs +++ b/crates/transaction_validation/src/blob_references.rs @@ -45,6 +45,7 @@ impl Validator for BlobReferenceValidator { #[cfg(test)] mod tests { use indexmap::IndexSet; + use tari_engine_types::Epoch; use tari_ootle_transaction::{ Blob, Blobs, @@ -86,7 +87,7 @@ mod tests { instructions, IndexSet::new(), None, - None, + Epoch(100), false, ); unsigned.blobs = Blobs::from_vec(blobs); diff --git a/crates/transaction_validation/src/dry_run.rs b/crates/transaction_validation/src/dry_run.rs index 6349906506..587538f962 100644 --- a/crates/transaction_validation/src/dry_run.rs +++ b/crates/transaction_validation/src/dry_run.rs @@ -32,6 +32,7 @@ impl Validator for TransactionDryRunValidator { #[cfg(test)] mod tests { use indexmap::IndexSet; + use tari_ootle_common_types::Epoch; use tari_ootle_transaction::{ Network, Transaction, @@ -53,7 +54,7 @@ mod tests { vec![], IndexSet::new(), None, - None, + Epoch(1), dry_run, ), vec![TransactionSignature::new( diff --git a/crates/transaction_validation/src/epoch_range.rs b/crates/transaction_validation/src/epoch_range.rs index b1d240f676..b854af7eb1 100644 --- a/crates/transaction_validation/src/epoch_range.rs +++ b/crates/transaction_validation/src/epoch_range.rs @@ -9,6 +9,14 @@ use crate::{TransactionValidationError, Validator}; const LOG_TARGET: &str = "tari::ootle::mempool::validators::epoch_range"; +/// Checks a transaction against the epoch window it declares: not before `min_epoch`, not after +/// `max_epoch`. +/// +/// Safe to run wherever a transaction is admitted, including the consensus sequencing path. Both +/// rules fail in the permissive direction for a node whose epoch view lags: a lagging node admits +/// what a node ahead of it would refuse, so it never discards a transaction its committee has +/// already sequenced. The complementary ceiling on how far ahead `max_epoch` may sit is deliberately +/// **not** here — see [`TransactionValidityWindowValidator`]. #[derive(Debug, Default)] pub struct EpochRangeValidator; @@ -33,9 +41,8 @@ impl Validator for EpochRangeValidator { }); } - if let Some(max_epoch) = transaction.max_epoch() && - current_epoch > max_epoch - { + let max_epoch = transaction.max_epoch(); + if current_epoch > max_epoch { warn!(target: LOG_TARGET, "EpochRangeValidator - FAIL: Current epoch {current_epoch} greater than maximum epoch {max_epoch}."); return Err(TransactionValidationError::CurrentEpochGreaterThanMaximum { current_epoch, @@ -46,3 +53,151 @@ impl Validator for EpochRangeValidator { Ok(()) } } + +/// Caps how far ahead of the current epoch a transaction's `max_epoch` may sit, bounding every +/// transaction's lifetime. +/// +/// **Admission only.** Unlike [`EpochRangeValidator`] this rule fails in the *strict* direction for +/// a lagging node: a node an epoch behind computes a lower ceiling and refuses a window a node ahead +/// of it accepts. Running it where a transaction can be silently discarded after being sequenced — +/// the consensus new-transaction gate — would let a lagging shard group refuse to admit a +/// transaction another group had already sequenced, stalling it until that group catches up. The +/// binding enforcement therefore happens at execution against the pinned, cross-group-agreed epoch, +/// where every node reaches the same verdict and an out-of-window transaction is sequenced as an +/// abort rather than dropped. +#[derive(Debug)] +pub struct TransactionValidityWindowValidator { + max_validity_epochs: u64, +} + +impl TransactionValidityWindowValidator { + pub fn new(max_validity_epochs: u64) -> Self { + Self { max_validity_epochs } + } +} + +impl Validator for TransactionValidityWindowValidator { + type Context = Epoch; + type Error = TransactionValidationError; + + fn validate(&self, ¤t_epoch: &Epoch, transaction: &Transaction) -> Result<(), TransactionValidationError> { + let max_epoch = transaction.max_epoch(); + // Saturating: an overflowing ceiling admits every representable max_epoch, which is the + // correct reading of "no epoch is further ahead than the limit allows". + let latest_permitted = Epoch(current_epoch.as_u64().saturating_add(self.max_validity_epochs)); + if max_epoch > latest_permitted { + warn!( + target: LOG_TARGET, + "TransactionValidityWindowValidator - FAIL: Maximum epoch {max_epoch} is more than {} epochs beyond \ + current epoch {current_epoch}.", + self.max_validity_epochs + ); + return Err(TransactionValidationError::MaxEpochTooFarAhead { + current_epoch, + max_epoch, + max_validity_epochs: self.max_validity_epochs, + }); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use indexmap::IndexSet; + use tari_ootle_common_types::Epoch; + use tari_ootle_transaction::{ + Network, + Transaction, + TransactionSealSignature, + UnsealedTransactionV1, + UnsignedTransactionV1, + }; + use tari_template_lib::types::crypto::{RistrettoPublicKeyBytes, SchnorrSignatureBytes}; + + use super::*; + + const MAX_VALIDITY_EPOCHS: u64 = 10; + + fn transaction(min_epoch: Option, max_epoch: Epoch) -> Transaction { + Transaction::new( + UnsealedTransactionV1::new( + UnsignedTransactionV1::new( + Network::LocalNet.as_byte(), + vec![], + vec![], + IndexSet::new(), + min_epoch, + max_epoch, + false, + ), + vec![], + ) + .into(), + TransactionSealSignature::new(RistrettoPublicKeyBytes::zero(), SchnorrSignatureBytes::zero()), + ) + } + + /// The pair as composed at mempool ingress: window rules plus the admission ceiling. + fn validate(current_epoch: Epoch, transaction: &Transaction) -> Result<(), TransactionValidationError> { + EpochRangeValidator::new().validate(¤t_epoch, transaction)?; + TransactionValidityWindowValidator::new(MAX_VALIDITY_EPOCHS).validate(¤t_epoch, transaction) + } + + #[test] + fn it_accepts_a_transaction_inside_its_window() { + let tx = transaction(Some(Epoch(5)), Epoch(12)); + validate(Epoch(5), &tx).unwrap(); + validate(Epoch(12), &tx).unwrap(); + } + + #[test] + fn it_rejects_a_transaction_before_its_min_epoch() { + let tx = transaction(Some(Epoch(5)), Epoch(12)); + assert!(matches!( + validate(Epoch(4), &tx), + Err(TransactionValidationError::CurrentEpochLessThanMinimum { .. }) + )); + } + + #[test] + fn it_rejects_an_expired_transaction() { + let tx = transaction(None, Epoch(12)); + assert!(matches!( + validate(Epoch(13), &tx), + Err(TransactionValidationError::CurrentEpochGreaterThanMaximum { .. }) + )); + } + + #[test] + fn it_rejects_a_window_beyond_the_ceiling() { + let tx = transaction(None, Epoch(11)); + assert!(matches!( + validate(Epoch(0), &tx), + Err(TransactionValidationError::MaxEpochTooFarAhead { .. }) + )); + } + + /// The consensus sequencing path must never refuse a window for being too far ahead: a lagging + /// node would otherwise discard a transaction another shard group has already sequenced. + #[test] + fn the_sequencing_rules_ignore_the_ceiling() { + let tx = transaction(None, Epoch(u64::MAX)); + EpochRangeValidator::new().validate(&Epoch(1), &tx).unwrap(); + } + + #[test] + fn the_ceiling_is_inclusive() { + let tx = transaction(None, Epoch(MAX_VALIDITY_EPOCHS)); + validate(Epoch(0), &tx).unwrap(); + } + + #[test] + fn a_ceiling_that_overflows_admits_any_max_epoch() { + let tx = transaction(None, Epoch(u64::MAX)); + TransactionValidityWindowValidator::new(u64::MAX) + .validate(&Epoch(1), &tx) + .unwrap(); + } +} diff --git a/crates/transaction_validation/src/error.rs b/crates/transaction_validation/src/error.rs index cb78e6d611..d8f54e488e 100644 --- a/crates/transaction_validation/src/error.rs +++ b/crates/transaction_validation/src/error.rs @@ -32,6 +32,15 @@ pub enum TransactionValidationError { CurrentEpochLessThanMinimum { current_epoch: Epoch, min_epoch: Epoch }, #[error("Current epoch ({current_epoch}) is greater than maximum epoch ({max_epoch}) required for transaction")] CurrentEpochGreaterThanMaximum { current_epoch: Epoch, max_epoch: Epoch }, + #[error( + "Maximum epoch ({max_epoch}) is more than {max_validity_epochs} epochs beyond the current epoch \ + ({current_epoch})" + )] + MaxEpochTooFarAhead { + current_epoch: Epoch, + max_epoch: Epoch, + max_validity_epochs: u64, + }, #[error("Invalid transaction signature")] InvalidSignature, #[error("Transaction {transaction_id} has no main signer")] @@ -115,6 +124,23 @@ impl TransactionValidationError { Self::CurrentEpochLessThanMinimum { .. } | Self::CurrentEpochGreaterThanMaximum { .. } => false, + // A window near the ceiling is a disagreement about the current epoch, not misbehaviour: + // a node whose view lags computes a lower ceiling and would otherwise graylist an honest + // peer whose transaction every node ahead of it accepts. Beyond twice the ceiling no + // epoch view reconciles the value — the sender is at fault on any honest reading, and + // saying so is what stops a flood of unbounded windows costing every node full + // structural and signature validation for free. + Self::MaxEpochTooFarAhead { + current_epoch, + max_epoch, + max_validity_epochs, + } => { + let beyond_any_lag = current_epoch + .as_u64() + .saturating_add(max_validity_epochs.saturating_mul(2)); + max_epoch.as_u64() > beyond_any_lag + }, + // Properties of the transaction itself, on which every node agrees. Self::NoFeeInstructions { .. } | Self::InvalidSignature | @@ -163,12 +189,49 @@ mod tests { current_epoch: Epoch(3), max_epoch: Epoch(2), }, + // Just past the ceiling: reconciled by our own view lagging a few epochs. + TransactionValidationError::MaxEpochTooFarAhead { + current_epoch: Epoch(1), + max_epoch: Epoch(12), + max_validity_epochs: 10, + }, ]; for err in node_local { assert!(!err.is_sender_fault(), "must not penalise the sender for: {err}"); } } + /// A window no epoch view reconciles is misbehaviour, and must cost the sender: otherwise a + /// flood of unbounded windows extracts full structural and signature validation from every node + /// for free. + #[test] + fn a_window_beyond_any_plausible_lag_is_blamed_on_the_sender() { + let err = TransactionValidationError::MaxEpochTooFarAhead { + current_epoch: Epoch(1), + max_epoch: Epoch(u64::MAX), + max_validity_epochs: 10, + }; + assert!(err.is_sender_fault(), "must penalise the sender for: {err}"); + } + + /// The boundary between the two: twice the ceiling is still forgiven, one past it is not. + #[test] + fn the_sender_fault_boundary_is_twice_the_ceiling() { + let at_boundary = TransactionValidationError::MaxEpochTooFarAhead { + current_epoch: Epoch(1), + max_epoch: Epoch(21), + max_validity_epochs: 10, + }; + assert!(!at_boundary.is_sender_fault()); + + let past_boundary = TransactionValidationError::MaxEpochTooFarAhead { + current_epoch: Epoch(1), + max_epoch: Epoch(22), + max_validity_epochs: 10, + }; + assert!(past_boundary.is_sender_fault()); + } + /// Properties of the transaction itself: every honest node reaches the same verdict, so the /// peer that sent it is answerable for it. #[test] diff --git a/crates/transaction_validation/src/lib.rs b/crates/transaction_validation/src/lib.rs index ccd49e05e4..d9331de4e4 100644 --- a/crates/transaction_validation/src/lib.rs +++ b/crates/transaction_validation/src/lib.rs @@ -35,6 +35,10 @@ mod template_exists; pub use template_exists::*; mod weight; pub use weight::*; + +mod noop; +pub use noop::*; + mod with_context; pub use with_context::*; diff --git a/crates/transaction_validation/src/network.rs b/crates/transaction_validation/src/network.rs index 380bea7c39..0539ee6838 100644 --- a/crates/transaction_validation/src/network.rs +++ b/crates/transaction_validation/src/network.rs @@ -45,6 +45,7 @@ impl Validator for TransactionNetworkValidator { #[cfg(test)] mod tests { use indexmap::IndexSet; + use tari_ootle_common_types::Epoch; use tari_ootle_transaction::{ Network, Transaction, @@ -60,7 +61,7 @@ mod tests { fn tx(network_byte: u8) -> Transaction { Transaction::new( UnsealedTransactionV1::new( - UnsignedTransactionV1::new(network_byte, vec![], vec![], IndexSet::new(), None, None, false), + UnsignedTransactionV1::new(network_byte, vec![], vec![], IndexSet::new(), None, Epoch(1), false), vec![TransactionSignature::new( RistrettoPublicKeyBytes::zero(), SchnorrSignatureBytes::zero(), diff --git a/crates/transaction_validation/src/noop.rs b/crates/transaction_validation/src/noop.rs new file mode 100644 index 0000000000..54a02e8f54 --- /dev/null +++ b/crates/transaction_validation/src/noop.rs @@ -0,0 +1,39 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::marker::PhantomData; + +use tari_ootle_transaction::Transaction; + +use crate::Validator; + +/// No Op validator - does nothing. Generic on any context or error +#[derive(Debug)] +pub struct NoopValidator(PhantomData<(Ctx, Err)>); + +impl NoopValidator { + pub const fn new() -> Self { + Self(PhantomData) + } +} + +impl Validator for NoopValidator { + type Context = Ctx; + type Error = Err; + + fn validate(&self, _context: &Ctx, _transaction: &Transaction) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl Clone for NoopValidator { + fn clone(&self) -> Self { + NoopValidator(PhantomData) + } +} + +impl Default for NoopValidator { + fn default() -> Self { + NoopValidator::new() + } +} diff --git a/crates/transaction_validation/src/publish_template_limits.rs b/crates/transaction_validation/src/publish_template_limits.rs index 762916b4e0..e459ec5611 100644 --- a/crates/transaction_validation/src/publish_template_limits.rs +++ b/crates/transaction_validation/src/publish_template_limits.rs @@ -57,6 +57,7 @@ impl Validator for PublishTemplateLimitValidator { #[cfg(test)] mod tests { use indexmap::IndexSet; + use tari_ootle_common_types::Epoch; use tari_ootle_transaction::{ Network, TransactionSealSignature, @@ -84,7 +85,7 @@ mod tests { instructions, IndexSet::new(), None, - None, + Epoch(1), false, ), vec![TransactionSignature::new( diff --git a/crates/transaction_validation/src/signature_limits.rs b/crates/transaction_validation/src/signature_limits.rs index dddec709d1..7631a3ea75 100644 --- a/crates/transaction_validation/src/signature_limits.rs +++ b/crates/transaction_validation/src/signature_limits.rs @@ -49,6 +49,7 @@ impl Validator for SignatureLimitValidator { #[cfg(test)] mod tests { use indexmap::IndexSet; + use tari_ootle_common_types::Epoch; use tari_ootle_transaction::{ Network, TransactionSealSignature, @@ -69,7 +70,7 @@ mod tests { vec![], IndexSet::new(), None, - None, + Epoch(1), false, ), (0..num_signatures) diff --git a/crates/transaction_validation/src/stealth_limits.rs b/crates/transaction_validation/src/stealth_limits.rs index 4602254bac..27f277731b 100644 --- a/crates/transaction_validation/src/stealth_limits.rs +++ b/crates/transaction_validation/src/stealth_limits.rs @@ -128,6 +128,7 @@ impl StealthTransactionLimitsValidator { #[cfg(test)] mod tests { use indexmap::IndexSet; + use tari_ootle_common_types::Epoch; use tari_ootle_transaction::{ Network, ResourceAddressRef, @@ -203,7 +204,7 @@ mod tests { transfer_instructions(main_statements), IndexSet::new(), None, - None, + Epoch(1), false, ), vec![TransactionSignature::new( diff --git a/crates/transaction_validation/src/weight.rs b/crates/transaction_validation/src/weight.rs index 004c8b7c43..19e65a5f33 100644 --- a/crates/transaction_validation/src/weight.rs +++ b/crates/transaction_validation/src/weight.rs @@ -53,6 +53,7 @@ impl Validator for TransactionWeightValidator { #[cfg(test)] mod tests { use indexmap::IndexSet; + use tari_ootle_common_types::Epoch; use tari_ootle_transaction::{ Instruction, Network, @@ -85,7 +86,7 @@ mod tests { instructions, IndexSet::new(), None, - None, + Epoch(1), false, ), vec![TransactionSignature::new( diff --git a/crates/wallet/ledger/app/src/handlers/sign_transaction.rs b/crates/wallet/ledger/app/src/handlers/sign_transaction.rs index e20a9f2d0c..ef47fe4998 100644 --- a/crates/wallet/ledger/app/src/handlers/sign_transaction.rs +++ b/crates/wallet/ledger/app/src/handlers/sign_transaction.rs @@ -193,7 +193,8 @@ fn capture_display(display: &mut TxDisplay, field: SigningField, data: &[u8]) -> SigningField::Inputs => display.input_count = read_u32(data)?, // Option borsh: 1-byte tag, then a u64 little-endian if present. SigningField::MinEpoch => display.min_epoch = read_option_u64(data)?, - SigningField::MaxEpoch => display.max_epoch = read_option_u64(data)?, + // Epoch borsh: a bare u64 little-endian — max_epoch is mandatory, so there is no tag. + SigningField::MaxEpoch => display.max_epoch = read_u64(data)?, _ => {}, } Ok(()) @@ -208,6 +209,15 @@ fn read_u32(data: &[u8]) -> Result { Ok(u32::from_le_bytes(bytes)) } +fn read_u64(data: &[u8]) -> Result { + let bytes: [u8; 8] = data + .get(..8) + .ok_or_else(bad_request)? + .try_into() + .map_err(|_| bad_request())?; + Ok(u64::from_le_bytes(bytes)) +} + fn read_option_u64(data: &[u8]) -> Result, AppStatus> { match data.first() { Some(0) => Ok(None), @@ -267,9 +277,7 @@ pub fn review_fields(review: &SignReview) -> Vec<(String, String)> { if let Some(epoch) = review.display.min_epoch { fields.push(("Min epoch".to_string(), epoch.to_string())); } - if let Some(epoch) = review.display.max_epoch { - fields.push(("Max epoch".to_string(), epoch.to_string())); - } + fields.push(("Max epoch".to_string(), review.display.max_epoch.to_string())); fields.push(("Tx digest".to_string(), to_hex(&review.message))); fields } diff --git a/crates/wallet/ledger/app/src/state.rs b/crates/wallet/ledger/app/src/state.rs index 4d05cc8192..e58c0afeff 100644 --- a/crates/wallet/ledger/app/src/state.rs +++ b/crates/wallet/ledger/app/src/state.rs @@ -47,5 +47,5 @@ pub struct TxDisplay { pub instruction_count: u32, pub input_count: u32, pub min_epoch: Option, - pub max_epoch: Option, + pub max_epoch: u64, } diff --git a/crates/wallet/ledger/client/src/client.rs b/crates/wallet/ledger/client/src/client.rs index 9c78c91bf9..60a023e3ea 100644 --- a/crates/wallet/ledger/client/src/client.rs +++ b/crates/wallet/ledger/client/src/client.rs @@ -235,6 +235,7 @@ mod tests { use tari_ootle_transaction::{ Blob, Blobs, + Epoch, Instruction, PreimageSegment, TransactionSealSignature, @@ -256,7 +257,7 @@ mod tests { ], inputs: IndexSet::new(), min_epoch: None, - max_epoch: None, + max_epoch: Epoch(1), is_seal_signer_authorized: false, dry_run: false, nonce: 0, diff --git a/crates/wallet/ledger/client/tests/signing_recipe.rs b/crates/wallet/ledger/client/tests/signing_recipe.rs index 2c9df135e4..5713c8415f 100644 --- a/crates/wallet/ledger/client/tests/signing_recipe.rs +++ b/crates/wallet/ledger/client/tests/signing_recipe.rs @@ -33,6 +33,7 @@ use rand::Rng; use tari_ootle_transaction::{ Blob, Blobs, + Epoch, Instruction, PreimageSegment, TransactionSealSignature, @@ -178,7 +179,7 @@ fn sample_unsigned() -> UnsignedTransactionV1 { ], inputs: IndexSet::new(), min_epoch: None, - max_epoch: None, + max_epoch: Epoch(1), is_seal_signer_authorized: false, dry_run: true, blobs, diff --git a/crates/wallet/ootle-rs/examples/adaptor_claim.rs b/crates/wallet/ootle-rs/examples/adaptor_claim.rs index 23030cd2a7..3d7a122ab7 100644 --- a/crates/wallet/ootle-rs/examples/adaptor_claim.rs +++ b/crates/wallet/ootle-rs/examples/adaptor_claim.rs @@ -52,7 +52,7 @@ use tari_crypto::{ ristretto::{RistrettoPublicKey, RistrettoSecretKey}, }; use tari_ootle_common_types::engine_types::transaction_receipt::TransactionReceipt; -use tari_ootle_transaction::Transaction; +use tari_ootle_transaction::{Epoch, Transaction}; /// 1 TARI expressed in microTARI. const ONE_TARI: u64 = 1_000_000; @@ -75,6 +75,10 @@ async fn main() { .unwrap(); let current_epoch = provider.get_epoch().await.unwrap().as_u64(); + // Every transaction declares the last epoch it may be sequenced in; past it the transaction can + // never land. Ten epochs is a comfortable window for an example — the network caps how far + // ahead this may be set. + let max_epoch = Epoch(provider.get_epoch().await.unwrap().as_u64() + 10); println!("Current epoch: {current_epoch}"); let mut rng = rand::rng(); @@ -130,7 +134,7 @@ async fn main() { let swap_commitment = *lock_transfer.stealth_outputs()[0].commitment(); - let lock_tx = IFaucet::new(&provider) + let lock_tx = IFaucet::new(&provider, max_epoch) .take_faucet_funds() .into_stealth_transfer(lock_transfer) .and_pay_fee_from_revealed_output() @@ -168,7 +172,7 @@ async fn main() { .await .unwrap(); - let claim_tx = Transaction::builder(provider.network()) + let claim_tx = Transaction::builder(provider.network(), max_epoch) .with_fee_instructions_builder(|builder| { builder .stealth_transfer(tari_token, claim_transfer) diff --git a/crates/wallet/ootle-rs/examples/claim_burn.rs b/crates/wallet/ootle-rs/examples/claim_burn.rs index 7fd3ecdf88..8218bd61b3 100644 --- a/crates/wallet/ootle-rs/examples/claim_burn.rs +++ b/crates/wallet/ootle-rs/examples/claim_burn.rs @@ -45,6 +45,7 @@ use tari_crypto::{ ristretto::{RistrettoPublicKey, RistrettoSecretKey}, tari_utilities::hex::Hex, }; +use tari_ootle_transaction::Epoch; /// MicroTARI revealed from the claimed funds to pay the transaction fee. const MAX_FEE: u64 = 2000; @@ -98,10 +99,15 @@ async fn main() { .connect(default_indexer_url(network)) .await .expect("failed to connect to indexer"); + + // Every transaction declares the last epoch it may be sequenced in; past it the transaction can + // never land. Ten epochs is a comfortable window for an example — the network caps how far + // ahead this may be set. + let max_epoch = Epoch(provider.get_epoch().await.unwrap().as_u64() + 10); assert_eq!(provider.network(), network); // Build the claim transaction and the sealer that signs it with the derived stealth claim key. - let (unsigned_tx, sealer) = ClaimBurn::new(&provider, claim_proof, encrypted_data) + let (unsigned_tx, sealer) = ClaimBurn::new(&provider, claim_proof, encrypted_data, max_epoch) .with_max_fee(MAX_FEE) .with_memo_message("claimed via ootle-rs") .prepare() diff --git a/crates/wallet/ootle-rs/examples/fungible_transfer.rs b/crates/wallet/ootle-rs/examples/fungible_transfer.rs index 3def6ed384..fd15c67617 100644 --- a/crates/wallet/ootle-rs/examples/fungible_transfer.rs +++ b/crates/wallet/ootle-rs/examples/fungible_transfer.rs @@ -15,6 +15,7 @@ use ootle_rs::{ transaction::TransactionSigner, wallet::OotleWallet, }; +use tari_ootle_transaction::Epoch; #[tokio::main] async fn main() { @@ -58,9 +59,13 @@ async fn main() { // Get the latest block number. let latest_epoch = provider.get_epoch().await.unwrap(); println!("Latest epoch: {latest_epoch}"); + // Every transaction declares the last epoch it may be sequenced in; past it the transaction can + // never land. Ten epochs is a comfortable window for an example — the network caps how far + // ahead this may be set. + let max_epoch = Epoch(provider.get_epoch().await.unwrap().as_u64() + 10); // First let's transfer some faucet TARI to our account to have funds for fees and transfers. - let unsigned_tx = IFaucet::new(&provider) + let unsigned_tx = IFaucet::new(&provider, max_epoch) .take_faucet_funds() // NOTE that pay fee must be called after the faucet funds are taken because fees are paid from the faucet funds .pay_fee(500u64) @@ -87,7 +92,7 @@ async fn main() { // Send some TARI to another address. You can replace TARI_TOKEN with any other fungible token resource address. let tari_token = TARI_TOKEN; // resource_address!("resource_deadbeaf"); - let unsigned_tx = IAccount::new(&provider) + let unsigned_tx = IAccount::new(&provider, max_epoch) .pay_fee(1000u64) // Multiple transfers in a single transaction .public_transfer(&recipient1, tari_token, 2 * TARI) diff --git a/crates/wallet/ootle-rs/examples/stealth_spend_conditions.rs b/crates/wallet/ootle-rs/examples/stealth_spend_conditions.rs index a319c9123c..b61e6773bd 100644 --- a/crates/wallet/ootle-rs/examples/stealth_spend_conditions.rs +++ b/crates/wallet/ootle-rs/examples/stealth_spend_conditions.rs @@ -46,7 +46,7 @@ use ootle_rs::{ wallet::OotleWallet, }; use tari_ootle_common_types::engine_types::transaction_receipt::TransactionReceipt; -use tari_ootle_transaction::Transaction; +use tari_ootle_transaction::{Epoch, Transaction}; /// 1 TARI expressed in microTARI. const ONE_TARI: u64 = 1_000_000; @@ -68,6 +68,10 @@ async fn main() { .unwrap(); let current_epoch = provider.get_epoch().await.unwrap().as_u64(); + // Every transaction declares the last epoch it may be sequenced in; past it the transaction can + // never land. Ten epochs is a comfortable window for an example — the network caps how far + // ahead this may be set. + let max_epoch = Epoch(provider.get_epoch().await.unwrap().as_u64() + 10); println!("Current epoch: {current_epoch}"); // ------------------------------------------------------------------------------------------------- @@ -126,7 +130,7 @@ async fn main() { // The commitment of the HTLC output we are about to create — the input we will claim in TX 2. let htlc_commitment = *lock_transfer.stealth_outputs()[0].commitment(); - let lock_tx = IFaucet::new(&provider) + let lock_tx = IFaucet::new(&provider, max_epoch) .take_faucet_funds() .into_stealth_transfer(lock_transfer) .and_pay_fee_from_revealed_output() @@ -165,7 +169,7 @@ async fn main() { .await .unwrap(); - let claim_tx = Transaction::builder(provider.network()) + let claim_tx = Transaction::builder(provider.network(), max_epoch) .with_fee_instructions_builder(|builder| { builder .stealth_transfer(tari_token, claim_transfer) diff --git a/crates/wallet/ootle-rs/examples/stealth_transfer.rs b/crates/wallet/ootle-rs/examples/stealth_transfer.rs index 524c21cea7..cbd8e34d4b 100644 --- a/crates/wallet/ootle-rs/examples/stealth_transfer.rs +++ b/crates/wallet/ootle-rs/examples/stealth_transfer.rs @@ -20,7 +20,7 @@ use ootle_rs::{ wallet::OotleWallet, }; use tari_ootle_common_types::engine_types::transaction_receipt::TransactionReceipt; -use tari_ootle_transaction::Transaction; +use tari_ootle_transaction::{Epoch, Transaction}; #[tokio::main] async fn main() { @@ -59,6 +59,10 @@ async fn main() { assert_eq!(network, provider.network()); // Get the latest block number. let latest_epoch = provider.get_epoch().await.unwrap(); + // Every transaction declares the last epoch it may be sequenced in; past it the transaction can + // never land. Ten epochs is a comfortable window for an example — the network caps how far + // ahead this may be set. + let max_epoch = Epoch(provider.get_epoch().await.unwrap().as_u64() + 10); println!("Latest epoch: {latest_epoch}"); // Send some TARI to another address. You can replace TARI with any other fungible token resource address. @@ -97,7 +101,7 @@ async fn main() { let inputs_to_spend = faucet_transfer.stealth_outputs().to_vec(); // First let's transfer some faucet TARI to our account to have funds for fees and transfers. - let unsigned_tx = IFaucet::new(&provider) + let unsigned_tx = IFaucet::new(&provider, max_epoch) .take_faucet_funds() .into_stealth_transfer(faucet_transfer) .and_pay_fee_from_revealed_output() @@ -141,7 +145,7 @@ async fn main() { // We'll generate an unsigned transaction directly using the Transaction builder. In future, we may make this // easier. - let unsigned_tx = Transaction::builder(provider.network()) + let unsigned_tx = Transaction::builder(provider.network(), max_epoch) .with_fee_instructions_builder(|builder| { builder .stealth_transfer(tari_token, transfer) diff --git a/crates/wallet/ootle-rs/examples/template_invoke.rs b/crates/wallet/ootle-rs/examples/template_invoke.rs index 2182c89be4..7ab31fec79 100644 --- a/crates/wallet/ootle-rs/examples/template_invoke.rs +++ b/crates/wallet/ootle-rs/examples/template_invoke.rs @@ -31,13 +31,14 @@ use ootle_rs::{ wallet::OotleWallet, }; use tari_ootle_common_types::engine_types::published_template::PublishedTemplateAddress; +use tari_ootle_transaction::Epoch; // --------------------------------------------------------------------------- // Step 1: Define the template interface // // The macro generates a single generic struct `StableCoin<'a, P, I>` parameterized // by an interface marker. Use the constructors to select the interface: -// - `StableCoin::for_component(addr, &provider)` — component methods (&self / &mut self) -// - `StableCoin::for_template(addr, &provider)` — template functions (no self, e.g. constructors) +// - `StableCoin::for_component(addr, &provider, max_epoch)` — component methods (&self / &mut self) +// - `StableCoin::for_template(addr, &provider, max_epoch)` — template functions (no self, e.g. constructors) // // The method signatures should match the template's public API. Argument types // must implement `serde::Serialize` for CBOR encoding. @@ -109,8 +110,13 @@ async fn main() { .await .expect("Failed to connect to indexer"); + // Every transaction declares the last epoch it may be sequenced in; past it the transaction can + // never land. Ten epochs is a comfortable window for an example — the network caps how far + // ahead this may be set. + let max_epoch = Epoch(provider.get_epoch().await.unwrap().as_u64() + 10); + // Fund the account from faucet - let unsigned_tx = IFaucet::new(&provider) + let unsigned_tx = IFaucet::new(&provider, max_epoch) .take_faucet_funds() .pay_fee(500u64) .prepare() @@ -133,7 +139,7 @@ async fn main() { // ----------------------------------------------------------------------- let view_key = ootle_rs::template_types::crypto::RistrettoPublicKeyBytes::default(); - let tpl = StableCoin::for_template(stable_coin_template.as_template_address(), &provider); + let tpl = StableCoin::for_template(stable_coin_template.as_template_address(), &provider, max_epoch); println!("Stable coin template: {}", tpl.template_address()); let unsigned_tx = tpl @@ -159,7 +165,7 @@ async fn main() { // exposes methods with &self / &mut self. // ----------------------------------------------------------------------- - let coin = StableCoin::for_component(stable_coin_component, &provider); + let coin = StableCoin::for_component(stable_coin_component, &provider, max_epoch); println!("Stable coin component: {}", coin.component_address()); // -- Example: Increase supply -- @@ -182,7 +188,7 @@ async fn main() { wait_for_tx(&pending_tx).await; // -- Example: Set fee configuration -- - let coin = StableCoin::for_component(stable_coin_component, &provider); + let coin = StableCoin::for_component(stable_coin_component, &provider, max_epoch); let unsigned_tx = coin .set_config_transfer_fee_percentage(2u8) .pay_fee(1000u64) @@ -201,7 +207,7 @@ async fn main() { // -- Example: Using the generic IComponent builder directly -- // For one-off calls where defining a full interface isn't worth it, // you can use IComponent directly with string method names. - let unsigned_tx = IComponent::new(&provider) + let unsigned_tx = IComponent::new(&provider, max_epoch) .call_method(stable_coin_component, "decrease_supply", tari_ootle_transaction::args![ Amount::new(500_000) ]) @@ -220,7 +226,7 @@ async fn main() { // -- Example: Chaining with workspace piping -- // Withdraw returns a Bucket that can be piped to another method. - let coin = StableCoin::for_component(stable_coin_component, &provider); + let coin = StableCoin::for_component(stable_coin_component, &provider, max_epoch); let unsigned_tx = coin .withdraw(Amount::new(100_000)) .put_last_instruction_output_on_workspace("bucket") diff --git a/crates/wallet/ootle-rs/src/builtin_templates/account.rs b/crates/wallet/ootle-rs/src/builtin_templates/account.rs index a490ffee46..45f1e2cb0f 100644 --- a/crates/wallet/ootle-rs/src/builtin_templates/account.rs +++ b/crates/wallet/ootle-rs/src/builtin_templates/account.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; -use tari_ootle_common_types::SubstateRequirement; +use tari_ootle_common_types::{Epoch, SubstateRequirement}; use tari_ootle_template_metadata::MetadataHash; use tari_ootle_transaction::{Blob, TransactionBuilder, UnsignedTransaction, args}; use tari_template_lib_types::{Amount, ResourceAddress, constants::TARI_TOKEN}; @@ -59,10 +59,12 @@ impl<'a, P: Provider> UnsignedTransactionBuilder for AccountInvokeBuilder<'a, P> } impl<'a, P: Provider> AccountInvokeBuilder<'a, P> { - pub fn new(provider: &'a P) -> Self { + /// `max_epoch` is the last epoch the built transaction may be sequenced in. It is required: + /// every transaction carries a bounded validity window. + pub fn new(provider: &'a P, max_epoch: Epoch) -> Self { let network = provider.network(); Self { - builder: TransactionBuilder::new(network).with_auto_fill_inputs(), + builder: TransactionBuilder::new(network, max_epoch).with_auto_fill_inputs(), provider, want_list: HashSet::new(), } diff --git a/crates/wallet/ootle-rs/src/builtin_templates/component.rs b/crates/wallet/ootle-rs/src/builtin_templates/component.rs index 97deacaa13..61a1e2c264 100644 --- a/crates/wallet/ootle-rs/src/builtin_templates/component.rs +++ b/crates/wallet/ootle-rs/src/builtin_templates/component.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; -use tari_ootle_common_types::{SubstateRequirement, engine_types::substate::SubstateId}; +use tari_ootle_common_types::{Epoch, SubstateRequirement, engine_types::substate::SubstateId}; use tari_ootle_transaction::{TransactionBuilder, UnsignedTransaction, builder::named_args::NamedArg}; use tari_template_lib_types::{ Amount, @@ -181,10 +181,12 @@ impl<'a, P: Provider> TransactionBuildable for ComponentInvokeBuilder<'a, P> { } impl<'a, P: Provider> ComponentInvokeBuilder<'a, P> { - pub fn new(provider: &'a P) -> Self { + /// `max_epoch` is the last epoch the built transaction may be sequenced in. It is required: + /// every transaction carries a bounded validity window. + pub fn new(provider: &'a P, max_epoch: Epoch) -> Self { let network = provider.network(); Self { - builder: TransactionBuilder::new(network).with_auto_fill_inputs(), + builder: TransactionBuilder::new(network, max_epoch).with_auto_fill_inputs(), provider, want_list: HashSet::new(), } diff --git a/crates/wallet/ootle-rs/src/builtin_templates/faucet.rs b/crates/wallet/ootle-rs/src/builtin_templates/faucet.rs index f6e31bf532..fbdd029a7d 100644 --- a/crates/wallet/ootle-rs/src/builtin_templates/faucet.rs +++ b/crates/wallet/ootle-rs/src/builtin_templates/faucet.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; -use tari_ootle_common_types::SubstateRequirement; +use tari_ootle_common_types::{Epoch, SubstateRequirement}; use tari_ootle_transaction::{TransactionBuilder, UnsignedTransaction, args}; use tari_template_lib_types::{ Amount, @@ -73,10 +73,12 @@ impl<'a, P: Provider> UnsignedTransactionBuilder for FaucetInvokeBuilder<'a, P> } impl<'a, P: Provider> FaucetInvokeBuilder<'a, P> { - pub fn new(provider: &'a P) -> Self { + /// `max_epoch` is the last epoch the built transaction may be sequenced in. It is required: + /// every transaction carries a bounded validity window. + pub fn new(provider: &'a P, max_epoch: Epoch) -> Self { let network = provider.network(); Self { - builder: TransactionBuilder::new(network).with_auto_fill_inputs(), + builder: TransactionBuilder::new(network, max_epoch).with_auto_fill_inputs(), provider, want_list: HashSet::new(), account_workspace_name: None, diff --git a/crates/wallet/ootle-rs/src/claim_burn/mod.rs b/crates/wallet/ootle-rs/src/claim_burn/mod.rs index 8800d9854b..082e7d9bd9 100644 --- a/crates/wallet/ootle-rs/src/claim_burn/mod.rs +++ b/crates/wallet/ootle-rs/src/claim_burn/mod.rs @@ -46,7 +46,7 @@ use tari_crypto::{ // Re-export the burn proof types so callers don't need to reach into `tari_ootle_common_types`. pub use tari_ootle_common_types::engine_types::confidential::{ClaimBurnOutputData, MinotariBurnClaimProof}; use tari_ootle_common_types::engine_types::stealth::validate_transfer; -use tari_ootle_transaction::{Transaction, UnsealedTransaction, UnsignedTransaction}; +use tari_ootle_transaction::{Epoch, Transaction, UnsealedTransaction, UnsignedTransaction}; use tari_ootle_wallet_crypto::{StealthCryptoApi, memo::Memo}; use tari_template_lib_types::{Amount, EncryptedData, constants::TARI_TOKEN}; @@ -70,6 +70,7 @@ pub struct ClaimBurn<'a, P> { claim_proof: MinotariBurnClaimProof, encrypted_data: EncryptedData, max_fee: Amount, + max_epoch: Epoch, recipient: Option
, memo: Option, } @@ -79,12 +80,20 @@ impl<'a, P: Provider> ClaimBurn<'a, P> { /// /// `claim_proof` and `encrypted_data` are produced by the L1 (minotari) wallet for the burn /// output being claimed (the wallet daemon bundles them as `ClaimBurnProofContents`). - pub fn new(provider: &'a P, claim_proof: MinotariBurnClaimProof, encrypted_data: EncryptedData) -> Self { + /// `max_epoch` is the last epoch the claim transaction may be sequenced in. It is required: + /// every transaction carries a bounded validity window. + pub fn new( + provider: &'a P, + claim_proof: MinotariBurnClaimProof, + encrypted_data: EncryptedData, + max_epoch: Epoch, + ) -> Self { Self { provider, claim_proof, encrypted_data, max_fee: Amount::zero(), + max_epoch, recipient: None, memo: None, } @@ -133,6 +142,7 @@ impl<'a, P: WalletProvider> ClaimBurn<'a, P> { claim_proof, encrypted_data, max_fee, + max_epoch, recipient, memo, } = self; @@ -214,7 +224,7 @@ impl<'a, P: WalletProvider> ClaimBurn<'a, P> { // encrypted data here, so this is not strictly required, but it keeps the values consistent. let output_data = ClaimBurnOutputData { encrypted_data }; - let unsigned_tx = Transaction::builder(network) + let unsigned_tx = Transaction::builder(network, max_epoch) .with_fee_instructions_builder(|builder| { builder // Mint the burned funds as a confidential UTXO. diff --git a/crates/wallet/ootle-rs/src/key_provider/local/generic_impls.rs b/crates/wallet/ootle-rs/src/key_provider/local/generic_impls.rs index 48d22026f4..d247a8a1e9 100644 --- a/crates/wallet/ootle-rs/src/key_provider/local/generic_impls.rs +++ b/crates/wallet/ootle-rs/src/key_provider/local/generic_impls.rs @@ -577,7 +577,9 @@ mod tests { async fn the_derived_stealth_public_key_is_the_one_that_signs() { let provider = PrivateKeyProvider::random(Network::LocalNet); let (_, public_nonce) = RistrettoPublicKey::random_keypair(&mut rand::rng()); - let unsigned = tari_ootle_transaction::Transaction::builder(Network::LocalNet).build_unsigned(); + let unsigned = + tari_ootle_transaction::Transaction::builder(Network::LocalNet, tari_ootle_transaction::Epoch(1)) + .build_unsigned(); let derived = provider .stealth_public_key(&public_nonce) diff --git a/crates/wallet/ootle-rs/src/lib.rs b/crates/wallet/ootle-rs/src/lib.rs index 2ffc0f4bf9..7c50ecb63a 100644 --- a/crates/wallet/ootle-rs/src/lib.rs +++ b/crates/wallet/ootle-rs/src/lib.rs @@ -22,7 +22,7 @@ //! key_provider::PrivateKeyProvider, //! provider::ProviderBuilder, //! wallet::OotleWallet, -//! Network, TransactionRequest, +//! Epoch, Network, TransactionRequest, //! }; //! use tari_template_lib_types::constants::TARI_TOKEN; //! @@ -39,8 +39,12 @@ //! .connect("http://127.0.0.1:12500") //! .await?; //! +//! // Every transaction declares the last epoch it may be sequenced in; past it the +//! // transaction can never land. The network caps how far ahead this may be set. +//! let max_epoch = Epoch(provider.get_epoch().await?.as_u64() + 10); +//! //! // 3. Fund our account from the faucet -//! let faucet_tx = IFaucet::new(&provider) +//! let faucet_tx = IFaucet::new(&provider, max_epoch) //! .pay_fee(1000u64) //! .take_free_coins(500_000_000u64) //! .prepare() @@ -55,7 +59,7 @@ //! // 4. Transfer tokens to a recipient //! let recipient = address!("otl_loc_10mc0v2lyy43kldl0ft4c2x5pe7j0ckduv8zej6jgr2z2g9m07fz7gl96ar5wwgu0qu0atmr5tl53ye7n38xr5u7ytlmudq0ruxcau0gge7rxk"); //! -//! let unsigned_tx = IAccount::new(&provider) +//! let unsigned_tx = IAccount::new(&provider, max_epoch) //! .pay_fee(1000u64) //! .public_transfer(&recipient, TARI_TOKEN, 1_000_000u64) //! .prepare() @@ -157,7 +161,8 @@ mod types; // Re-export the address macro from the ootle_address crate pub use helpers::*; pub use tari_ootle_address::{Network, address}; -pub use tari_ootle_common_types::displayable; +// Re-exported because every transaction builder takes a `max_epoch`, so callers need the type. +pub use tari_ootle_common_types::{Epoch, displayable}; pub use tari_ootle_wallet_crypto as crypto; pub use tari_template_lib_types as template_types; pub use types::*; diff --git a/crates/wallet/ootle-rs/src/macros.rs b/crates/wallet/ootle-rs/src/macros.rs index 4cbaec4e77..f86a69e81b 100644 --- a/crates/wallet/ootle-rs/src/macros.rs +++ b/crates/wallet/ootle-rs/src/macros.rs @@ -9,7 +9,7 @@ macro_rules! resource_address { } pub mod _macro_exports { - pub use tari_ootle_common_types::{SubstateRequirement, engine_types::substate::SubstateId}; + pub use tari_ootle_common_types::{Epoch, SubstateRequirement, engine_types::substate::SubstateId}; pub use tari_ootle_transaction::{ self as transaction, TransactionBuilder, @@ -159,10 +159,11 @@ macro_rules! __ootle_template_inner { pub fn for_component( component: $crate::macros::_macro_exports::ComponentAddress, provider: &'a P, + max_epoch: $crate::macros::_macro_exports::Epoch, ) -> Self { Self { interface: $crate::macros::_macro_exports::ComponentInterface { component }, - builder: $crate::macros::_macro_exports::ComponentInvokeBuilder::new(provider), + builder: $crate::macros::_macro_exports::ComponentInvokeBuilder::new(provider, max_epoch), } } @@ -222,10 +223,11 @@ macro_rules! __ootle_template_inner { pub fn for_template( template: $crate::macros::_macro_exports::TemplateAddress, provider: &'a P, + max_epoch: $crate::macros::_macro_exports::Epoch, ) -> Self { Self { interface: $crate::macros::_macro_exports::TemplateInterface { template }, - builder: $crate::macros::_macro_exports::ComponentInvokeBuilder::new(provider), + builder: $crate::macros::_macro_exports::ComponentInvokeBuilder::new(provider, max_epoch), } } @@ -410,6 +412,7 @@ macro_rules! const_nonzero_u64 { #[cfg(test)] mod tests { + use tari_ootle_common_types::Epoch; use tari_template_lib_types::Amount; use crate::{Network, builtin_templates::component::TransactionBuildable}; @@ -496,13 +499,13 @@ mod tests { }; let component = tari_template_lib_types::ComponentAddress::new([0u8; 32].into()); - let coin = TestStableCoin::for_component(component, &provider); + let coin = TestStableCoin::for_component(component, &provider, Epoch(1)); // Verify component_address accessor assert_eq!(coin.component_address(), component); // Verify typed methods return Self and can be chained - let coin = TestStableCoin::for_component(component, &provider); + let coin = TestStableCoin::for_component(component, &provider, Epoch(1)); let coin = coin.increase_supply(Amount::new(1000)); // Can chain another typed method — this is the key improvement let coin = coin.decrease_supply(Amount::new(500)); @@ -531,11 +534,11 @@ mod tests { // Chain: StableCoin.withdraw -> put on workspace -> Account.deposit (via then, since // workspace refs don't cross chain boundaries) -> chain an independent Account.withdraw - let coin = TestStableCoin::for_component(component_a, &provider); + let coin = TestStableCoin::for_component(component_a, &provider, Epoch(1)); let _coin = coin .withdraw(Amount::new(1000)) .put_last_instruction_output_on_workspace("bucket") - .chain(TestAccount::for_component(component_b, &provider).withdraw(Amount::new(500))) + .chain(TestAccount::for_component(component_b, &provider, Epoch(1)).withdraw(Amount::new(500))) .pay_fee(1000u64); } @@ -549,13 +552,13 @@ mod tests { }; let template = tari_template_lib_types::TemplateAddress::from_array([1u8; 32]); - let tpl = TestStableCoin::for_template(template, &provider); + let tpl = TestStableCoin::for_template(template, &provider, Epoch(1)); // Verify template_address accessor assert_eq!(tpl.template_address(), template); // Verify template function returns Self and can chain shared methods - let tpl = TestStableCoin::for_template(template, &provider); + let tpl = TestStableCoin::for_template(template, &provider, Epoch(1)); let _tpl = tpl.instantiate(Amount::new(1_000_000)).pay_fee(1000u64); } } diff --git a/crates/wallet/ootle-rs/src/signer/ledger.rs b/crates/wallet/ootle-rs/src/signer/ledger.rs index 5c7b787d74..6b6559c6cd 100644 --- a/crates/wallet/ootle-rs/src/signer/ledger.rs +++ b/crates/wallet/ootle-rs/src/signer/ledger.rs @@ -264,7 +264,7 @@ mod tests { keys::{PublicKey, SecretKey}, ristretto::RistrettoSecretKey, }; - use tari_ootle_transaction::{Transaction as TransactionBuilderEntry, UnsealedTransactionV1}; + use tari_ootle_transaction::{Epoch, Transaction as TransactionBuilderEntry, UnsealedTransactionV1}; use super::*; @@ -302,7 +302,7 @@ mod tests { #[tokio::test] async fn signing_a_transaction_from_another_network_is_rejected() { let signer = signer_on(Network::Igor); - let tx = TransactionBuilderEntry::builder(Network::LocalNet).build_unsigned(); + let tx = TransactionBuilderEntry::builder(Network::LocalNet, Epoch(1)).build_unsigned(); let nonce = RistrettoPublicKey::from_secret_key(&RistrettoSecretKey::random(&mut rand::rng())); let err = signer diff --git a/crates/wallet/ootle-rs/src/wallet/stealth.rs b/crates/wallet/ootle-rs/src/wallet/stealth.rs index 3947fb24ae..c6def8d887 100644 --- a/crates/wallet/ootle-rs/src/wallet/stealth.rs +++ b/crates/wallet/ootle-rs/src/wallet/stealth.rs @@ -139,6 +139,7 @@ mod tests { keys::{PublicKey, SecretKey}, ristretto::{RistrettoPublicKey, RistrettoSecretKey}, }; + use tari_ootle_common_types::Epoch; use super::*; use crate::{ @@ -168,7 +169,7 @@ mod tests { } fn unsigned() -> UnsignedTransaction { - Transaction::builder(Network::LocalNet).build_unsigned() + Transaction::builder(Network::LocalNet, Epoch(1)).build_unsigned() } /// Several stealth inputs owned by the same address: one seals with its one-time key and the rest authorize diff --git a/crates/wallet/sdk/src/apis/confidential_transfer.rs b/crates/wallet/sdk/src/apis/confidential_transfer.rs index 2271a2b518..76e71ec744 100644 --- a/crates/wallet/sdk/src/apis/confidential_transfer.rs +++ b/crates/wallet/sdk/src/apis/confidential_transfer.rs @@ -9,6 +9,7 @@ use tari_bor::{Deserialize, Serialize}; use tari_crypto::{keys::PublicKey, ristretto::RistrettoPublicKey}; use tari_ootle_address::OotleAddress; use tari_ootle_common_types::{ + Epoch, SubstateRequirement, optional::{IsNotFoundError, Optional}, }; @@ -429,7 +430,7 @@ where TSpec: WalletSdkSpec )?; let network = self.config_api.get_network()?; - let transaction = Transaction::builder(network.as_byte()) + let transaction = Transaction::builder(network.as_byte(), params.max_epoch) .with_dry_run(params.is_dry_run) // TODO: we assume that from_account has TARI .pay_fee_from_component(*from_account.component_address(), max_fee) @@ -537,6 +538,10 @@ pub struct ConfidentialTransferParams { /// A memo to include in the output, if any. This memo is encrypted in the output and can only be decrypted by the /// recipient pub memo: Option, + /// The last epoch the built transaction may be sequenced in. Mandatory: every transaction + /// carries a bounded validity window, so the caller decides how long this one stays + /// submittable. + pub max_epoch: Epoch, /// Run as a dry run, no funds will be transferred if true pub is_dry_run: bool, } diff --git a/crates/wallet/sdk/src/apis/stealth_transfer/api.rs b/crates/wallet/sdk/src/apis/stealth_transfer/api.rs index 129c4a0005..b76dcceb85 100644 --- a/crates/wallet/sdk/src/apis/stealth_transfer/api.rs +++ b/crates/wallet/sdk/src/apis/stealth_transfer/api.rs @@ -913,7 +913,7 @@ impl<'a, TSpec: WalletSdkSpec> StealthTransferApi<'a, TSpec> { let revealed_input_amount = transfer_statement.inputs_statement.revealed_amount; let revealed_output_amount = transfer_statement.outputs_statement.revealed_output_amount; - let transaction = Transaction::builder(network.as_byte()) + let transaction = Transaction::builder(network.as_byte(), params.max_epoch) .with_dry_run(params.is_dry_run) .with_fee_instructions_builder(|builder| { let fee_resource = params.fee_params.pay_fee_with_swap.as_ref().map(|swap| swap.input_resource).unwrap_or(TARI_TOKEN); @@ -1069,7 +1069,7 @@ impl<'a, TSpec: WalletSdkSpec> StealthTransferApi<'a, TSpec> { let revealed_to_recipients = params.total_revealed_output_amount(); let account_address = *owner_account.component_address(); - let transaction = Transaction::builder(network.as_byte()) + let transaction = Transaction::builder(network.as_byte(), params.max_epoch) .with_dry_run(params.is_dry_run) .with_fee_instructions_builder(|builder| { builder diff --git a/crates/wallet/sdk/src/apis/stealth_transfer/params.rs b/crates/wallet/sdk/src/apis/stealth_transfer/params.rs index 4996caf5d0..27b73c38b0 100644 --- a/crates/wallet/sdk/src/apis/stealth_transfer/params.rs +++ b/crates/wallet/sdk/src/apis/stealth_transfer/params.rs @@ -6,6 +6,7 @@ use ootle_network::Network; use tari_bor::{Deserialize, Serialize}; use tari_engine_types::crypto::MAX_LAZY_BP_AGG_FACTORS; use tari_ootle_address::OotleAddress; +use tari_ootle_common_types::Epoch; use tari_ootle_wallet_crypto::{memo::Memo, pay_to::PayTo, stealth::validated_condition_root}; use tari_template_lib::types::{Amount, ComponentAddress, NonFungibleAddress, ResourceAddress}; @@ -27,6 +28,10 @@ pub struct StealthTransferParams { pub resource_address: ResourceAddress, /// Fee to lock for the transaction pub max_fee: u64, + /// The last epoch the built transaction may be sequenced in. Mandatory: every transaction + /// carries a bounded validity window, so the caller decides how long this one stays + /// submittable. + pub max_epoch: Epoch, /// Run as a dry run, no funds will be transferred if true pub is_dry_run: bool, } @@ -257,6 +262,7 @@ mod tests { /// A single output of `blinded_amount` blinded plus `revealed_amount` revealed, paying to `pay_to`. fn params_paying_to(blinded_amount: u64, revealed_amount: u64, pay_to: PayTo) -> StealthTransferParams { StealthTransferParams { + max_epoch: Epoch(1), fee_params: TransferFeeParams::new(UtxoInputSelection::PreferConfidential), input_selection: UtxoInputSelection::PreferConfidential, outputs: vec![TransferOutput { diff --git a/crates/wallet/sdk/src/models/transaction_request.rs b/crates/wallet/sdk/src/models/transaction_request.rs index b159db180c..96f47fdbfb 100644 --- a/crates/wallet/sdk/src/models/transaction_request.rs +++ b/crates/wallet/sdk/src/models/transaction_request.rs @@ -155,7 +155,7 @@ mod tests { fn request(status: TransactionRequestStatus) -> TransactionRequestModel { TransactionRequestModel { id: 1, - unsigned_transaction: UnsignedTransaction::new(0u8), + unsigned_transaction: UnsignedTransaction::new(0u8, tari_ootle_common_types::Epoch(1)), seal_signer: KeyId::Derived { key_branch: KeyBranch::Account, index: 0u64, diff --git a/crates/wallet/sdk_tests/tests/balance_changes.rs b/crates/wallet/sdk_tests/tests/balance_changes.rs index c2f0a59482..50f3668d21 100644 --- a/crates/wallet/sdk_tests/tests/balance_changes.rs +++ b/crates/wallet/sdk_tests/tests/balance_changes.rs @@ -27,7 +27,7 @@ use tari_template_lib::types::{ use crate::support::Test; fn build_transaction() -> Transaction { - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("component") .put_last_instruction_output_on_workspace("bucket") .call_method("component", "new", args!["bucket"]) diff --git a/crates/wallet/sdk_tests/tests/transaction_api.rs b/crates/wallet/sdk_tests/tests/transaction_api.rs index 88784fda48..b48aa0bde9 100644 --- a/crates/wallet/sdk_tests/tests/transaction_api.rs +++ b/crates/wallet/sdk_tests/tests/transaction_api.rs @@ -8,6 +8,7 @@ use std::time::Duration; use tari_consensus_types::Decision; use tari_crypto::ristretto::RistrettoSecretKey; use tari_engine_types::{ + Epoch, commit_result::{AbortReason, ExecuteResult, FinalizeResult, RejectReason, TransactionResult}, fees::{FeeBreakdown, FeeReceipt, FeeSource}, substate::SubstateDiff, @@ -28,7 +29,7 @@ fn now() -> PrimitiveDateTime { } fn build_transaction() -> Transaction { - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(100)) .allocate_component_address("component") .put_last_instruction_output_on_workspace("bucket") .call_method("component", "new", args!["bucket"]) diff --git a/crates/wallet/storage_sqlite/tests/balance_changes.rs b/crates/wallet/storage_sqlite/tests/balance_changes.rs index 0b82470a75..9f48dd52c1 100644 --- a/crates/wallet/storage_sqlite/tests/balance_changes.rs +++ b/crates/wallet/storage_sqlite/tests/balance_changes.rs @@ -54,7 +54,7 @@ use tari_template_lib_types::{ }; fn build_transaction(seed: u64) -> Transaction { - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("component") .put_last_instruction_output_on_workspace("bucket") .call_method("component", "new", args!["bucket"]) diff --git a/crates/wallet/storage_sqlite/tests/transaction.rs b/crates/wallet/storage_sqlite/tests/transaction.rs index f3301c0367..d0337cc6ab 100644 --- a/crates/wallet/storage_sqlite/tests/transaction.rs +++ b/crates/wallet/storage_sqlite/tests/transaction.rs @@ -19,7 +19,7 @@ use tari_template_lib_types::ComponentAddress; fn build_transaction() -> Transaction { let key = RistrettoSecretKey::from(123); - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("component") .put_last_instruction_output_on_workspace("bucket") .call_method("component", "new", args!["bucket"]) @@ -31,7 +31,7 @@ fn build_transaction() -> Transaction { /// transaction id, so multiple transactions can be inserted in one test. fn build_linked_transaction(seed: u64) -> Transaction { let key = RistrettoSecretKey::from(seed); - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .allocate_component_address("component") .put_last_instruction_output_on_workspace("bucket") .call_method("component", "new", args!["bucket"]) diff --git a/crates/wallet/storage_sqlite/tests/transaction_requests.rs b/crates/wallet/storage_sqlite/tests/transaction_requests.rs index 7f6bf6c825..b670f9f0cc 100644 --- a/crates/wallet/storage_sqlite/tests/transaction_requests.rs +++ b/crates/wallet/storage_sqlite/tests/transaction_requests.rs @@ -14,7 +14,7 @@ use std::time::Duration; -use tari_ootle_common_types::optional::IsNotFoundError; +use tari_ootle_common_types::{Epoch, optional::IsNotFoundError}; use tari_ootle_transaction::{TransactionId, UnsignedTransaction}; use tari_ootle_wallet_sdk::{ models::{KeyBranch, KeyId, TransactionRequestId, TransactionRequestStatus}, @@ -44,7 +44,7 @@ fn insert_request(db: &SqliteWalletStore) -> TransactionRequestId { let mut tx = db.create_write_tx().unwrap(); let model = tx .transaction_request_insert( - &UnsignedTransaction::new(0u8), + &UnsignedTransaction::new(0u8, Epoch(1)), seal_signer(), &[], &[], @@ -347,7 +347,7 @@ fn a_ttl_past_the_representable_expiry_is_an_error() { let err = tx .transaction_request_insert( - &UnsignedTransaction::new(0u8), + &UnsignedTransaction::new(0u8, Epoch(1)), seal_signer(), &[], &[], diff --git a/integration_tests/src/util.rs b/integration_tests/src/util.rs index 2217eef794..e067751eba 100644 --- a/integration_tests/src/util.rs +++ b/integration_tests/src/util.rs @@ -2,16 +2,22 @@ // SPDX-License-Identifier: BSD-3-Clause use tari_ootle_address::Network; -use tari_ootle_transaction::TransactionBuilder; +use tari_ootle_transaction::{Epoch, TransactionBuilder}; /// The builder is stamped with a random nonce so that repeated identical calls (concurrent or /// sequential steps invoking the same method with unversioned inputs) build distinct /// transactions — the transaction id excludes the seal signature, so identical bodies sealed by /// the same key would otherwise be a single transaction. pub fn transaction_builder() -> TransactionBuilder { - TransactionBuilder::new(Network::LocalNet).with_nonce(rand::random()) + TransactionBuilder::new(Network::LocalNet, DEFAULT_TEST_MAX_EPOCH).with_nonce(rand::random()) } +/// Validity window for transactions built by the test harness. A cucumber network starts at a low +/// epoch and a scenario is short, so this outlives every run while staying well inside the +/// network's `max_transaction_validity_epochs` ceiling. Scenarios that pin a specific window +/// override it with `with_max_epoch`. +pub const DEFAULT_TEST_MAX_EPOCH: Epoch = Epoch(100); + #[macro_export] macro_rules! cucumber_log { ($($msg:tt)*) => {{ diff --git a/integration_tests/src/validator_node_client.rs b/integration_tests/src/validator_node_client.rs index 4310bde89d..cdcd326262 100644 --- a/integration_tests/src/validator_node_client.rs +++ b/integration_tests/src/validator_node_client.rs @@ -25,7 +25,13 @@ use tari_validator_node_client::{ }; use tokio::time::{MissedTickBehavior, interval}; -use crate::{TariWorld, cucumber_log, helpers::get_component_from_namespace, template::RegisteredTemplate}; +use crate::{ + TariWorld, + cucumber_log, + helpers::get_component_from_namespace, + template::RegisteredTemplate, + util::DEFAULT_TEST_MAX_EPOCH, +}; /// Creates a component by calling a template function pub async fn create_component( @@ -51,7 +57,7 @@ pub async fn create_component( let secret_key = get_signing_key(world).await; // Build and sign the transaction (Network::LocalNet == 0u8) - let transaction = TransactionBuilder::new(Network::LocalNet) + let transaction = TransactionBuilder::new(Network::LocalNet, DEFAULT_TEST_MAX_EPOCH) .call_function(template_address, function_call, parsed_args) .with_inputs(vec![]) .build_and_seal(&secret_key); @@ -291,7 +297,7 @@ async fn call_method_inner( })?; // Build transaction - let transaction = TransactionBuilder::new(Network::LocalNet) + let transaction = TransactionBuilder::new(Network::LocalNet, DEFAULT_TEST_MAX_EPOCH) .call_method(component_address, method_call, vec![]) .with_inputs(vec![component]) .build_and_seal(&secret_key); diff --git a/integration_tests/src/wallet_daemon_client.rs b/integration_tests/src/wallet_daemon_client.rs index 6f7b49737c..cd10bd1f59 100644 --- a/integration_tests/src/wallet_daemon_client.rs +++ b/integration_tests/src/wallet_daemon_client.rs @@ -449,7 +449,10 @@ pub async fn submit_manifest_with_signing_keys( .pay_fee_from_component(account.component_address, 5000u64) .with_instructions(instructions.instructions) .with_min_epoch(min_epoch) - .with_max_epoch(max_epoch) + .then(|b| match max_epoch { + Some(max_epoch) => b.with_max_epoch(max_epoch), + None => b, + }) .with_inputs(inputs.into_iter().map(|i| i.into_unversioned())) .build_unsigned(); @@ -538,7 +541,10 @@ pub async fn submit_manifest( .pay_fee_from_component(account.component_address, 5000u64) .with_instructions(instructions.instructions) .with_min_epoch(min_epoch) - .with_max_epoch(max_epoch) + .then(|b| match max_epoch { + Some(max_epoch) => b.with_max_epoch(max_epoch), + None => b, + }) .with_inputs(inputs) .build_unsigned(); @@ -655,7 +661,10 @@ pub async fn create_component( .pay_fee_from_component(account.component_address, 5000u64) .call_function(template_address, function_call, args) .with_min_epoch(min_epoch) - .with_max_epoch(max_epoch) + .then(|b| match max_epoch { + Some(max_epoch) => b.with_max_epoch(max_epoch), + None => b, + }) .build_unsigned(); let transaction_submit_req = TransactionSubmitRequest { diff --git a/utilities/tariswap_test_bench/src/runner.rs b/utilities/tariswap_test_bench/src/runner.rs index 9a654b4fd8..1842d86bd4 100644 --- a/utilities/tariswap_test_bench/src/runner.rs +++ b/utilities/tariswap_test_bench/src/runner.rs @@ -6,6 +6,7 @@ use std::{path::Path, str::FromStr, time::Duration}; use log::info; use tari_crypto::tari_utilities::SafePassword; use tari_engine_types::commit_result::FinalizeResult; +use tari_ootle_common_types::Epoch; use tari_ootle_transaction::{Network, Transaction, TransactionBuilder, TransactionId}; use tari_ootle_wallet_sdk::{ WalletSdk as Sdk, @@ -13,6 +14,7 @@ use tari_ootle_wallet_sdk::{ cipher_seed::CipherSeedRestore, local_key_store::LocalKeyStore, models::EpochBirthday, + network::WalletNetworkInterface, }; use tari_ootle_wallet_sdk_services::indexer_rest_api::IndexerRestApiNetworkInterface; use tari_ootle_wallet_storage_sqlite::SqliteWalletStore; @@ -37,18 +39,27 @@ pub struct Runner { pub(crate) faucet_template: TemplateAddress, pub(crate) tariswap_template: TemplateAddress, pub(crate) stats: Stats, + /// Validity window shared by every transaction the bench builds, resolved once at startup. A + /// bench run is short relative to this, so a single window covers the whole run. + pub(crate) max_epoch: Epoch, } +/// Epochs past the epoch at startup that bench transactions stay valid for. +const BENCH_TRANSACTION_VALIDITY_EPOCHS: u64 = 10; + impl Runner { pub async fn init(cli: CommonArgs) -> anyhow::Result { let sdk = initialize_wallet_sdk(&cli.db_path, cli.indexer_url.clone())?; let (faucet_template, tariswap_template) = get_templates(&cli).await?; + let current_epoch = sdk.get_network_interface().get_current_epoch().await?; + let max_epoch = Epoch(current_epoch.as_u64() + BENCH_TRANSACTION_VALIDITY_EPOCHS); Ok(Self { sdk, cli, faucet_template, tariswap_template, stats: Stats::default(), + max_epoch, }) } @@ -117,7 +128,7 @@ impl Runner { } pub fn new_transaction_builder(&self) -> TransactionBuilder { - Transaction::builder(self.cli.network) + Transaction::builder(self.cli.network, self.max_epoch) } } diff --git a/utilities/traffic-sim/src/sim.rs b/utilities/traffic-sim/src/sim.rs index 31143a821c..d4ea461360 100644 --- a/utilities/traffic-sim/src/sim.rs +++ b/utilities/traffic-sim/src/sim.rs @@ -21,7 +21,7 @@ use tari_ootle_common_types::{ engine_types::published_template::PublishedTemplateAddress, optional::Optional, }; -use tari_ootle_transaction::{Network, Transaction, args}; +use tari_ootle_transaction::{Epoch, Network, Transaction, args}; use tari_ootle_wallet_sdk::{ apis::{ confidential_transfer::UtxoInputSelection, @@ -68,7 +68,20 @@ pub struct Wallet { pub network: Network, } +/// Validity window stamped on simulated transactions. Well within any network's ceiling, and long +/// enough that a slow simulation run never has a transaction expire mid-flight. +const SIM_TRANSACTION_VALIDITY_EPOCHS: u64 = 10; + impl Wallet { + /// A validity window ending [`SIM_TRANSACTION_VALIDITY_EPOCHS`] past the daemon's current epoch. + pub async fn max_epoch(&self) -> anyhow::Result { + let settings = self.client.clone().get_settings().await?; + let current_epoch = settings + .current_epoch + .ok_or_else(|| anyhow::anyhow!("Wallet daemon could not reach its indexer, so the epoch is unknown"))?; + Ok(Epoch(current_epoch.as_u64() + SIM_TRANSACTION_VALIDITY_EPOCHS)) + } + pub async fn connect(name: String, address: &str) -> anyhow::Result { let mut client = WalletDaemonClient::connect(address, None)?; let resp = client @@ -304,7 +317,8 @@ impl TrafficSim { }, }; - let transaction = Transaction::builder(exchange_wallet.network.as_byte()) + let max_epoch = exchange_wallet.max_epoch().await?; + let transaction = Transaction::builder(exchange_wallet.network.as_byte(), max_epoch) .pay_fee_from_component(*account.component_address(), 3000u64) .allocate_component_address("sc") .call_function(stablecoin_template, "instantiate", args![ @@ -413,6 +427,10 @@ impl TrafficSim { for (wallet, account) in self.wallet_and_account_iter() { let mut client = wallet.client.clone(); + // Resolved per wallet, not once for the run: each iteration makes several daemon + // round-trips, so a window taken at the start can lapse before the later wallets are + // funded. + let max_epoch = wallet.max_epoch().await?; let fund_amount = 100_000_000_000u64; // 1000 coins @@ -482,7 +500,7 @@ impl TrafficSim { }) .await?; - let transaction = Transaction::builder(wallet.network.as_byte()) + let transaction = Transaction::builder(wallet.network.as_byte(), max_epoch) .pay_fee_from_component(*exchange_account.component_address(), 2000u64) .call_method(*exchange_account.component_address(), "create_proof_by_amount", args![ admin_resource_address, diff --git a/utilities/transaction_generator/src/cli.rs b/utilities/transaction_generator/src/cli.rs index 59f4ba6d8a..6d180078b6 100644 --- a/utilities/transaction_generator/src/cli.rs +++ b/utilities/transaction_generator/src/cli.rs @@ -67,6 +67,12 @@ pub struct WriteArgs { pub signer_secret_key: Option, #[clap(long, short = 't')] pub network: Option, + /// The last epoch the generated transactions are valid in. Every transaction carries a mandatory + /// validity window, so this must be set to an epoch the target network will still accept when the + /// generated file is submitted: no earlier than the current epoch and no more than + /// `max_transaction_validity_epochs` beyond it. + #[clap(long)] + pub max_epoch: u64, } #[derive(Args, Debug)] pub struct ReadArgs { diff --git a/utilities/transaction_generator/src/main.rs b/utilities/transaction_generator/src/main.rs index f951d4e63c..97d23254ce 100644 --- a/utilities/transaction_generator/src/main.rs +++ b/utilities/transaction_generator/src/main.rs @@ -14,7 +14,7 @@ use std::{ use anyhow::anyhow; use cli::Cli; use tari_crypto::{keys::SecretKey, ristretto::RistrettoSecretKey, tari_utilities::hex::Hex}; -use tari_ootle_common_types::SubstateRequirement; +use tari_ootle_common_types::{Epoch, SubstateRequirement}; use tari_ootle_transaction::{Blob, Network}; use tari_template_lib_types::TemplateAddress; use tari_transaction_manifest::ManifestValue; @@ -121,9 +121,10 @@ fn get_transaction_builder(args: &WriteArgs) -> anyhow::Result Ok(Box::new(free_coins::builder(network))), + None => Ok(Box::new(free_coins::builder(network, Epoch(args.max_epoch)))), } } diff --git a/utilities/transaction_generator/src/transaction_builders/free_coins.rs b/utilities/transaction_generator/src/transaction_builders/free_coins.rs index dbfacb83f0..f05a83add3 100644 --- a/utilities/transaction_generator/src/transaction_builders/free_coins.rs +++ b/utilities/transaction_generator/src/transaction_builders/free_coins.rs @@ -3,7 +3,7 @@ use ootle_byte_type::ToByteType; use tari_crypto::{keys::PublicKey, ristretto::RistrettoPublicKey}; -use tari_ootle_common_types::SubstateRequirement; +use tari_ootle_common_types::{Epoch, SubstateRequirement}; use tari_ootle_transaction::{Network, Transaction, args}; use tari_template_lib_types::constants::{ TARI_TOKEN, @@ -12,12 +12,12 @@ use tari_template_lib_types::constants::{ XTR_FAUCET_VAULT_ADDRESS, }; -pub fn builder(network: Network) -> impl Fn(u64) -> Transaction { +pub fn builder(network: Network, max_epoch: Epoch) -> impl Fn(u64) -> Transaction { move |_: u64| -> Transaction { let (signer_secret_key, signer_public_key) = RistrettoPublicKey::random_keypair(&mut rand::rng()); let signer_public_key = signer_public_key.to_byte_type(); - Transaction::builder(network.as_byte()) + Transaction::builder(network.as_byte(), max_epoch) .with_fee_instructions_builder(|builder| { builder .create_account(signer_public_key) diff --git a/utilities/transaction_generator/src/transaction_builders/manifest.rs b/utilities/transaction_generator/src/transaction_builders/manifest.rs index 845b651f2c..d9a3b12134 100644 --- a/utilities/transaction_generator/src/transaction_builders/manifest.rs +++ b/utilities/transaction_generator/src/transaction_builders/manifest.rs @@ -8,7 +8,7 @@ use tari_crypto::{ keys::PublicKey, ristretto::{RistrettoPublicKey, RistrettoSecretKey}, }; -use tari_ootle_common_types::SubstateRequirement; +use tari_ootle_common_types::{Epoch, SubstateRequirement}; use tari_ootle_transaction::{Blob, Network, Transaction}; use tari_template_lib_types::TemplateAddress; use tari_transaction_manifest::ManifestValue; @@ -25,6 +25,7 @@ pub fn builder>( extra_inputs: Vec, blob_inputs: HashMap, random_signer: bool, + max_epoch: Epoch, ) -> anyhow::Result { let contents = fs::read_to_string(manifest)?; // Every substate referenced by the transaction must be declared as an input, otherwise the @@ -47,7 +48,7 @@ pub fn builder>( let blobs = instructions.blobs; Ok(Box::new(move |_| { - let mut builder = Transaction::builder(network.as_byte()) + let mut builder = Transaction::builder(network.as_byte(), max_epoch) .with_fee_instructions(fee_instructions.clone()) .with_instructions(main_instructions.clone()); for (i, blob) in blobs.iter().enumerate() { diff --git a/utilities/transaction_generator/tests/manifest_inputs.rs b/utilities/transaction_generator/tests/manifest_inputs.rs index 5964a9cf54..3f88d055dd 100644 --- a/utilities/transaction_generator/tests/manifest_inputs.rs +++ b/utilities/transaction_generator/tests/manifest_inputs.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use tari_crypto::ristretto::RistrettoSecretKey; use tari_ootle_common_types::SubstateRequirement; -use tari_ootle_transaction::Network; +use tari_ootle_transaction::{Epoch, Network}; use tari_template_lib_types::TemplateAddress; use tari_transaction_manifest::ManifestValue; use transaction_generator::transaction_builders::manifest; @@ -36,6 +36,7 @@ fn declares_account_arg_and_explicit_input_as_transaction_inputs() { extra_inputs, HashMap::new(), false, + Epoch(1), ) .unwrap(); diff --git a/utilities/transaction_generator/tests/max_compute.rs b/utilities/transaction_generator/tests/max_compute.rs index 31642586d5..05e83acd1e 100644 --- a/utilities/transaction_generator/tests/max_compute.rs +++ b/utilities/transaction_generator/tests/max_compute.rs @@ -7,7 +7,7 @@ //! retuned if the cost drifts. use tari_engine_types::{commit_result::RejectReason, fees::FeeSource}; -use tari_ootle_transaction::{Transaction, args}; +use tari_ootle_transaction::{Epoch, Transaction, args}; use tari_template_test_tooling::TemplateTest; const CRATE_PATH: &str = env!("CARGO_MANIFEST_DIR"); @@ -34,7 +34,7 @@ fn busy_max_stays_under_the_metering_budget() { // Succeeds => the single busy_max() call did not exhaust its metering budget. let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 900_000_000u64) .call_function(addr, "busy_max", args![]) .build_and_seal(&key), @@ -72,7 +72,7 @@ fn busy_cost_is_linear_in_rounds() { let mut busy = |rounds: u64| { let result = test.execute_expect_success( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .pay_fee_from_component(account, 900_000_000u64) .call_function(addr, "busy", args![rounds]) .build_and_seal(&key), @@ -112,7 +112,7 @@ fn stacked_busy_max_is_rejected_by_per_transaction_budget() { // Fees are disabled here on purpose: the per-transaction budget is enforced independently of fee // charging, so the rejection must be the out-of-gas execution failure, not insufficient fees. let reason = test.execute_expect_failure( - Transaction::builder_localnet() + Transaction::builder_localnet(Epoch(1)) .call_function(addr, "busy_max", args![]) .call_function(addr, "busy_max", args![]) .build_and_seal(test.secret_key()), diff --git a/utilities/transaction_generator/tests/publish_template.rs b/utilities/transaction_generator/tests/publish_template.rs index 2b10c171ca..46e9233f55 100644 --- a/utilities/transaction_generator/tests/publish_template.rs +++ b/utilities/transaction_generator/tests/publish_template.rs @@ -11,7 +11,7 @@ use tari_crypto::{ keys::{PublicKey, SecretKey}, ristretto::{RistrettoPublicKey, RistrettoSecretKey}, }; -use tari_ootle_transaction::{Blob, Instruction, Network}; +use tari_ootle_transaction::{Blob, Epoch, Instruction, Network}; use tari_transaction_manifest::ManifestValue; use transaction_generator::transaction_builders::manifest; @@ -46,6 +46,7 @@ fn random_signer_makes_each_publish_address_distinct() { Vec::new(), blob_inputs(), true, + Epoch(1), ) .unwrap(); @@ -113,6 +114,7 @@ fn without_random_signer_a_single_signer_seals() { Vec::new(), blob_inputs(), false, + Epoch(1), ) .unwrap(); diff --git a/utilities/transaction_generator/tests/publish_template_engine.rs b/utilities/transaction_generator/tests/publish_template_engine.rs index 9cb6f0b46b..f25dfaf5bf 100644 --- a/utilities/transaction_generator/tests/publish_template_engine.rs +++ b/utilities/transaction_generator/tests/publish_template_engine.rs @@ -17,7 +17,7 @@ use tari_engine_types::{ commit_result::ExecuteResult, substate::{SubstateId, SubstateValue}, }; -use tari_ootle_transaction::{Blob, Network}; +use tari_ootle_transaction::{Blob, Epoch, Network}; use tari_template_test_tooling::{TemplateTest, compile::compile_template}; use tari_transaction_manifest::ManifestValue; use transaction_generator::transaction_builders::manifest; @@ -55,6 +55,7 @@ fn random_signer_publishes_same_binary_twice_without_duplicating() { Vec::new(), blob_inputs, true, + Epoch(1), ) .unwrap();