Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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([]);

Expand Down Expand Up @@ -205,6 +196,18 @@ function Result({ transaction_id }: IndexerGetTransactionResultRequest) {
<DataTableCell>{data.result.Finalized.abort_details}</DataTableCell>
</TableRow>
)}
{transaction?.min_epoch != null && (
<TableRow>
<TableCell>Min Epoch</TableCell>
<DataTableCell>{transaction.min_epoch.toString()}</DataTableCell>
</TableRow>
)}
{transaction?.max_epoch != null && (
<TableRow>
<TableCell>Max Epoch</TableCell>
<DataTableCell>{transaction.max_epoch.toString()}</DataTableCell>
</TableRow>
)}
<TableRow>
<TableCell>Download</TableCell>
<DataTableCell>
Expand Down Expand Up @@ -278,15 +281,10 @@ function Result({ transaction_id }: IndexerGetTransactionResultRequest) {
{/* Blobs */}
<Accordion expanded={expandedPanels.includes("p10")} onChange={handleChange("p10")}>
<AccordionSummary>
<Typography variant="h5">
Blobs ({transaction?.blob_hashes?.length ?? 0})
</Typography>
<Typography variant="h5">Blobs ({transaction?.blob_hashes?.length ?? 0})</Typography>
</AccordionSummary>
<AccordionDetails>
<BlobsContent
hashes={transaction?.blob_hashes || []}
sizes={transaction?.blob_sizes || []}
/>
<BlobsContent hashes={transaction?.blob_hashes || []} sizes={transaction?.blob_sizes || []} />
</AccordionDetails>
</Accordion>

Expand Down
33 changes: 27 additions & 6 deletions applications/tari_validator_node/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -81,6 +82,7 @@ use tari_ootle_transaction_validation::{
BasicValidations,
BlobReferenceValidator,
EpochRangeValidator,
NoopValidator,
PublishTemplateLimitValidator,
SignatureLimitValidator,
StealthTransactionLimitsValidator,
Expand All @@ -89,6 +91,7 @@ use tari_ootle_transaction_validation::{
TransactionNetworkValidator,
TransactionSignatureValidator,
TransactionValidationError,
TransactionValidityWindowValidator,
TransactionWeightValidator,
Validator,
};
Expand Down Expand Up @@ -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(),
);

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -515,19 +520,19 @@ async fn spawn_p2p_rpc<TStateStore: StateStore + Clone + Send + Sync + 'static>(
Ok(handle)
}

pub fn create_mempool_transaction_validator<TProvider: TemplateProvider>(
pub fn create_structural_transaction_validator<TProvider: TemplateProvider>(
network: Network,
template_manager: TProvider,
) -> impl Validator<Transaction, Context = (), Error = TransactionValidationError> {
let max_transaction_weight = ConsensusConstants::from(network).max_transaction_weight;
constants: &ConsensusConstants,
) -> impl Validator<Transaction, Context = (), Error = TransactionValidationError> + use<TProvider> {
TransactionNetworkValidator::new(network)
.and_then(TransactionDryRunValidator)
.and_then(BasicValidations::new())
// Blob payloads must be exactly what the instructions reference: bad indices would only
// 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())
Expand All @@ -538,6 +543,22 @@ pub fn create_mempool_transaction_validator<TProvider: TemplateProvider>(
.and_then(TemplateExistsValidator::new(template_manager))
}

pub fn create_mempool_transaction_validator<TProvider: TemplateProvider>(
network: Network,
template_manager: TProvider,
constants: &ConsensusConstants,
) -> impl Validator<Transaction, Context = Epoch, Error = TransactionValidationError> + use<TProvider> {
NoopValidator::<Epoch, TransactionValidationError>::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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -48,7 +49,7 @@ pub fn spawn<TValidator, TStateStore>(
#[cfg(feature = "metrics")] metrics_registry: &mut prometheus_client::registry::Registry,
) -> (MempoolHandle, JoinHandle<anyhow::Result<()>>)
where
TValidator: Validator<Transaction, Context = (), Error = TransactionValidationError> + Send + Sync + 'static,
TValidator: Validator<Transaction, Context = Epoch, Error = TransactionValidationError> + Send + Sync + 'static,
TStateStore: StateStore<Addr = PeerAddress> + Send + Sync + 'static,
{
// This channel only needs to be size 1, because each mempool request must wait for a reply and the mempool is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -67,7 +67,7 @@ pub struct MempoolService<TValidator, TStateStore> {

impl<TValidator, TStateStore> MempoolService<TValidator, TStateStore>
where
TValidator: Validator<Transaction, Context = (), Error = TransactionValidationError>,
TValidator: Validator<Transaction, Context = Epoch, Error = TransactionValidationError>,
TStateStore: StateStore,
{
pub(super) fn new(
Expand Down Expand Up @@ -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(&current_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
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,16 @@ export default function TransactionDetails() {
<TableCell>Total Fees</TableCell>
<DataTableCell>{fee?.toString()}</DataTableCell>
</TableRow>
{transaction?.min_epoch != null && (
<TableRow>
<TableCell>Min Epoch</TableCell>
<DataTableCell>{transaction.min_epoch.toString()}</DataTableCell>
</TableRow>
)}
<TableRow>
<TableCell>Max Epoch</TableCell>
<DataTableCell>{transaction?.max_epoch?.toString()}</DataTableCell>
</TableRow>
{final_decision && (
<TableRow>
<TableCell>Status</TableCell>
Expand Down
46 changes: 38 additions & 8 deletions applications/tari_wallet_cli/src/command/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,30 @@ pub struct CommonSubmitArgs {
pub fee_account: Option<ComponentAddressOrName>,
#[clap(long)]
pub min_epoch: Option<u64>,
/// 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<u64>,
}

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<Epoch>, default_validity_epochs: u64) -> anyhow::Result<Epoch> {
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,
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions applications/tari_walletd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)])
Expand Down
Loading
Loading