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. 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