Skip to content
Merged
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
186 changes: 128 additions & 58 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NOTE: When editing this version, also edit the versions in template_built_in/templates/account and account_nft
[workspace.package]
version = "0.14.2"
version = "0.15.0"
edition = "2021"
authors = ["The Tari Development Community"]
repository = "https://github.com/tari-project/tari-ootle"
Expand Down Expand Up @@ -64,7 +64,7 @@ members = [
"utilities/transaction_submitter",
"utilities/transaction_submitter",
"utilities/generate_ristretto_value_lookup",
"utilities/db_inspector",
"utilities/db_inspector", "utilities/traffic-sim",
]
resolver = "2"

Expand Down
34 changes: 30 additions & 4 deletions applications/tari_indexer/src/rest_api/handlers/utxos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@
use std::collections::HashSet;

use axum::{
extract::Query,
http::header::HeaderMap,
response::{IntoResponse, Response},
Extension,
Json,
};
use log::*;
use tari_indexer_client::types::{GetUnspentUtxosRequest, GetUnspentUtxosResponse, GetUtxoUpdatesRequest};
use tari_indexer_client::types::{
GetUtxoUpdatesRequest,
GetUtxosRequest,
GetUtxosResponse,
ListUtxosRequest,
ListUtxosResponse,
};
use tari_ootle_common_types::NumPreshards;

use crate::rest_api::{
Expand Down Expand Up @@ -92,8 +99,8 @@ pub async fn stream_utxo_updates(
)]
pub async fn fetch_utxos(
Extension(context): Extension<HandlerContext>,
Json(req): Json<GetUnspentUtxosRequest>,
) -> HandlerResult<Json<GetUnspentUtxosResponse>> {
Json(req): Json<GetUtxosRequest>,
) -> HandlerResult<Json<GetUtxosResponse>> {
if req.tag_and_nonce_pairs.len() > 1000 {
return Err(ErrorResponse::bad_request("cannot query more than 1000 UTXOs"));
}
Expand All @@ -102,5 +109,24 @@ pub async fn fetch_utxos(
.get_unspent_utxos(&req.resource_address, &req.tag_and_nonce_pairs)
.map_err(ErrorResponse::anyhow)?;

Ok(Json(GetUnspentUtxosResponse { utxos }))
Ok(Json(GetUtxosResponse { utxos }))
}

#[utoipa::path(get, path = "/utxos", description = "List full UTXO data")]
pub async fn list_utxos(
Extension(context): Extension<HandlerContext>,
Query(req): Query<ListUtxosRequest>,
) -> HandlerResult<Json<ListUtxosResponse>> {
if req.limit == 0 {
return Err(ErrorResponse::bad_request("limit must be greater than 0"));
}
if req.limit > 1000 {
return Err(ErrorResponse::bad_request("cannot query more than 1000 UTXOs"));
}
let utxos = context
.substate_manager()
.list_utxos(&req.resource_address, req.from_id, req.limit)
.map_err(ErrorResponse::anyhow)?;

Ok(Json(ListUtxosResponse { utxos }))
}
2 changes: 2 additions & 0 deletions applications/tari_indexer/src/rest_api/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const REQUEST_BODY_LIMIT: usize = 4 * 1024 * 1024; // 4 MB
handlers::templates::get_template_definition,
handlers::templates::list_templates,
handlers::utxos::fetch_utxos,
handlers::utxos::list_utxos,
handlers::utxos::stream_utxo_updates,
handlers::transaction_receipts::list_transaction_receipts,
handlers::transaction_receipts::get_transaction_receipt
Expand Down Expand Up @@ -92,6 +93,7 @@ impl Server {
)
.route("/non-fungibles", get(handlers::nfts::get_non_fungibles)) // Placeholder for future non-fungible endpoints
.nest("/utxos", Router::new()
.route("/", get(handlers::utxos::list_utxos))
.route("/fetch", post(handlers::utxos::fetch_utxos))
.route("/stream", post(handlers::utxos::stream_utxo_updates))
)
Expand Down
48 changes: 47 additions & 1 deletion applications/tari_indexer/src/storage_sqlite/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ impl IndexerStoreReadTransaction for SqliteStoreReadTransaction<'_> {
let tr = alias!(transaction_receipts as tr);
let subquery = tr
.select(tr.field(transaction_receipts::id))
.filter(transaction_receipts::address.eq(last_id.to_string()))
.filter(tr.field(transaction_receipts::address).eq(last_id.to_string()))
.limit(1)
.single_value()
.assume_not_null();
Expand Down Expand Up @@ -560,6 +560,52 @@ impl IndexerStoreReadTransaction for SqliteStoreReadTransaction<'_> {
Ok((max_state_version, updates))
}

fn utxos_list(
&mut self,
resource_address: &ResourceAddress,
from_id: Option<UtxoId>,
limit: u32,
) -> Result<Vec<(UtxoId, Utxo)>, StorageError> {
const OPERATION: &str = "utxos_list";
use crate::storage_sqlite::schema::utxos;

let mut query = utxos::table
.filter(utxos::resource_address.eq(resource_address.to_string()))
.filter(utxos::is_spent.eq(false))
.filter(utxos::is_burnt.eq(false))
.into_boxed();

if let Some(from_id) = from_id {
let uxo = alias!(utxos as uxo);
let subquery = uxo
.select(uxo.field(utxos::id))
.filter(uxo.field(utxos::commitment).eq(from_id.to_commitment_hex_string()))
.limit(1)
.single_value()
.assume_not_null();
query = query.filter(utxos::id.gt(subquery));
}

let rows = query
.limit(i64::from(limit))
.order_by(utxos::id.asc())
.load_iter::<models::UtxoRecord, _>(self.connection())
.map_err(|e| StorageError::QueryError {
reason: format!("{OPERATION}: {}", e),
})?;

rows.map(|res| {
res.map_err(|e| StorageError::QueryError {
reason: format!("{OPERATION}: {}", e),
})
.and_then(|row| {
let (address, utxo) = row.try_convert_to_utxo()?;
Ok((*address.id(), utxo))
})
})
.collect()
}
Comment thread
sdbondi marked this conversation as resolved.

fn utxos_get_unspent_by_public_nonce_and_tag(
&mut self,
resource_address: &ResourceAddress,
Expand Down
7 changes: 7 additions & 0 deletions applications/tari_indexer/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ pub trait IndexerStoreReadTransaction {
limit: u32,
) -> Result<(StateVersion, Vec<WalletUtxoUpdate>), StorageError>;

fn utxos_list(
&mut self,
resource_address: &ResourceAddress,
from_id: Option<UtxoId>,
limit: u32,
) -> Result<Vec<(UtxoId, Utxo)>, StorageError>;

fn utxos_get_unspent_by_public_nonce_and_tag(
&mut self,
resource_address: &ResourceAddress,
Expand Down
12 changes: 12 additions & 0 deletions applications/tari_indexer/src/substate_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,18 @@ impl SubstateManager {
Ok(utxos)
}

pub fn list_utxos(
&self,
resource_address: &ResourceAddress,
from_id: Option<UtxoId>,
limit: u32,
) -> Result<Vec<(UtxoId, Utxo)>, anyhow::Error> {
let utxos = self
.substate_store
.with_read_tx(|tx| tx.utxos_list(resource_address, from_id, limit))?;
Ok(utxos)
}

pub async fn get_substate(
&self,
substate_id: &SubstateId,
Expand Down
19 changes: 16 additions & 3 deletions applications/tari_validator_node/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ use std::{net::SocketAddr, path::PathBuf};

use clap::Parser;
use minotari_app_utilities::common_cli_args::CommonCliArgs;
use tari_common::configuration::{ConfigOverrideProvider, Network as L1Network};
use tari_ootle_app_utilities::{configuration::convert_l1_network_to_network, p2p_config::ReachabilityMode};
use tari_common::{
configuration::{ConfigOverrideProvider, Network as L1Network},
ConfigPath,
};
use tari_ootle_app_utilities::{
configuration::convert_l1_network_to_network,
p2p_config::{PeerSeedsConfig, ReachabilityMode},
};
Comment on lines +27 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify if ConfigPath import is necessary.

The ConfigPath import on line 29 doesn't appear to be used anywhere in this file. If it's not needed, consider removing it to keep imports clean.

Run the following script to verify if ConfigPath is used in this file:


🏁 Script executed:

#!/bin/bash
# Description: Check if ConfigPath is used in the cli.rs file

# Search for ConfigPath usage excluding the import line
rg -n 'ConfigPath' applications/tari_validator_node/src/cli.rs | grep -v 'use.*ConfigPath'

Length of output: 18


Remove unused ConfigPath import from line 29.

The verification confirms that ConfigPath is imported on line 29 but is never used anywhere in this file. Remove it to keep imports clean.

🤖 Prompt for AI Agents
In applications/tari_validator_node/src/cli.rs around lines 27 to 34, the import
list includes an unused ConfigPath (line 29); remove ConfigPath from the
tari_common import group so the file no longer imports that unused symbol and
keep imports tidy.

use tari_ootle_common_types::Network;
use url::Url;

Expand Down Expand Up @@ -100,7 +106,14 @@ impl ConfigOverrideProvider for Cli {
));
}
if !self.peer_seeds.is_empty() {
overrides.push(("p2p.seeds.peer_seeds".to_string(), self.peer_seeds.join(",")));
overrides.push((
format!("{}.peer_seeds", PeerSeedsConfig::main_key_prefix()),
self.peer_seeds.join(","),
));
overrides.push((
format!("{}.{}.peer_seeds", network, PeerSeedsConfig::main_key_prefix()),
self.peer_seeds.join(","),
));
}
if let Some(listener_port) = self.listener_port {
overrides.push((
Expand Down
36 changes: 26 additions & 10 deletions applications/tari_wallet_cli/src/command/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ use tari_engine_types::{
};
use tari_ootle_address::OotleAddress;
use tari_ootle_common_types::{Epoch, SubstateAddress, SubstateRequirement};
use tari_ootle_wallet_sdk::{apis::confidential_transfer::ConfidentialTransferInputSelection, crypto::memo::Memo};
use tari_ootle_wallet_sdk::{
apis::confidential_transfer::UtxoInputSelection,
crypto::memo::Memo,
models::BranchAndKeyId,
};
use tari_template_lib::{
constants::STEALTH_TARI_RESOURCE_ADDRESS,
models::{BucketId, NonFungibleAddress, NonFungibleId},
Expand Down Expand Up @@ -246,6 +250,10 @@ pub async fn handle_submit(args: SubmitArgs, client: &mut WalletDaemonClient) ->
fee_account = client.accounts_get_default().await?.account;
}

let owner_key_id = fee_account
.owner_key_id
.ok_or_else(|| anyhow!("Fee account does not have an owner key ID"))?;

let SettingsGetResponse { network, .. } = client.get_settings().await?;

let mut builder = Transaction::builder()
Expand Down Expand Up @@ -275,20 +283,22 @@ pub async fn handle_submit(args: SubmitArgs, client: &mut WalletDaemonClient) ->
let resp = client
.submit_transaction_dry_run(TransactionSubmitDryRunRequest {
transaction,
signing_key_id: None,
seal_signer: BranchAndKeyId::for_account(owner_key_id),
other_signers: vec![],
detect_inputs: common.detect_inputs.unwrap_or(true),
detect_inputs_use_unversioned: true,
proof_ids: vec![],
lock_ids: vec![],
})
.await?;
wait_transaction_result(resp.transaction_id, client).await?;
} else {
let request = TransactionSubmitRequest {
transaction,
signing_key_id: None,
seal_signer: BranchAndKeyId::for_account(owner_key_id),
other_signers: vec![],
detect_inputs: common.detect_inputs.unwrap_or(true),
detect_inputs_use_unversioned: true,
proof_ids: vec![],
lock_ids: vec![],
};
let resp = client.submit_transaction(&request).await?;
wait_transaction_result(resp.transaction_id, client).await?;
Expand All @@ -312,6 +322,10 @@ async fn handle_submit_manifest(
fee_account = client.accounts_get_default().await?.account;
}

let owner_key_id = fee_account
.owner_key_id
.ok_or_else(|| anyhow!("Fee account does not have an owner key ID"))?;

let SettingsGetResponse { network, .. } = client.get_settings().await?;

let builder = Transaction::builder()
Expand All @@ -338,20 +352,22 @@ async fn handle_submit_manifest(
let resp = client
.submit_transaction_dry_run(TransactionSubmitDryRunRequest {
transaction,
signing_key_id: fee_account.owner_key_id,
seal_signer: BranchAndKeyId::for_account(owner_key_id),
other_signers: vec![],
detect_inputs: common.detect_inputs.unwrap_or(true),
detect_inputs_use_unversioned: true,
proof_ids: vec![],
lock_ids: vec![],
})
.await?;
summarize(&resp.result.finalize, timer.elapsed());
} else {
let request = TransactionSubmitRequest {
transaction,
signing_key_id: fee_account.owner_key_id,
seal_signer: BranchAndKeyId::for_account(owner_key_id),
other_signers: vec![],
detect_inputs: common.detect_inputs.unwrap_or(true),
detect_inputs_use_unversioned: true,
proof_ids: vec![],
lock_ids: vec![],
};

let resp = client.submit_transaction(&request).await?;
Expand Down Expand Up @@ -426,7 +442,7 @@ pub async fn handle_confidential_transfer(
let resp = client
.accounts_confidential_transfer(ConfidentialTransferRequest {
account: source_account,
input_selection: ConfidentialTransferInputSelection::PreferConfidential,
input_selection: UtxoInputSelection::PreferConfidential,
amount: amount.into(),
resource_address: resource_address.unwrap_or(STEALTH_TARI_RESOURCE_ADDRESS),
destination_address,
Expand Down
Loading
Loading