Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detailed tx summaries #1271

Merged
merged 9 commits into from
Jul 21, 2024
5 changes: 4 additions & 1 deletion darkside-tests/tests/network_interruption_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ use zingo_testutils::{
use zingoconfig::RegtestNetwork;
use zingolib::{
lightclient::PoolBalances,
wallet::transaction_record::{SendType, TransactionKind},
wallet::{
data::summaries::TransactionSummaryInterface as _,
transaction_record::{SendType, TransactionKind},
},
};

#[ignore]
Expand Down
1 change: 1 addition & 0 deletions libtonode-tests/tests/concrete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use zingo_testutils::{
};
use zingolib::lightclient::propose::ProposeSendError;
use zingolib::utils::conversion::address_from_str;
use zingolib::wallet::data::summaries::TransactionSummaryInterface;

use zingo_testvectors::{
block_rewards,
Expand Down
3 changes: 2 additions & 1 deletion zingo-testutils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use std::time::Duration;
use tokio::task::JoinHandle;
use zcash_address::unified::{Fvk, Ufvk};
use zingolib::wallet::data::summaries::{
OrchardNoteSummary, SaplingNoteSummary, SpendStatus, TransactionSummary, TransparentCoinSummary,
OrchardNoteSummary, SaplingNoteSummary, SpendStatus, TransactionSummary,
TransactionSummaryInterface as _, TransparentCoinSummary,
};
use zingolib::wallet::keys::unified::WalletCapability;
use zingolib::wallet::WalletBase;
Expand Down
2 changes: 2 additions & 0 deletions zingo-testutils/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ macro_rules! get_base_address_macro {
#[macro_export]
macro_rules! check_client_balances {
($client:ident, o: $orchard:tt s: $sapling:tt t: $transparent:tt) => {
use zingolib::wallet::data::summaries::TransactionSummaryInterface as _;

let balance = $client.do_balance().await;
assert_eq!(
balance.orchard_balance.unwrap(),
Expand Down
30 changes: 30 additions & 0 deletions zingolib/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,32 @@ impl Command for TransactionsCommand {
}
}

struct DetailedTransactionsCommand {}
impl Command for DetailedTransactionsCommand {
fn help(&self) -> &'static str {
indoc! {r#"
Provides a detailed list of transaction summaries related to this wallet in order of blockheight.

Usage:
detailed_transactions
"#}
}

fn short_help(&self) -> &'static str {
"Provides a detailed list of transaction summaries related to this wallet in order of blockheight."
}

fn exec(&self, args: &[&str], lightclient: &LightClient) -> String {
if !args.is_empty() {
return "Error: invalid arguments\nTry 'help detailed_transactions' for correct usage and examples"
.to_string();
}
RT.block_on(
async move { format!("{}", lightclient.detailed_transaction_summaries().await) },
)
}
}

struct MemoBytesToAddressCommand {}
impl Command for MemoBytesToAddressCommand {
fn help(&self) -> &'static str {
Expand Down Expand Up @@ -1697,6 +1723,10 @@ pub fn get_commands() -> HashMap<&'static str, Box<dyn Command>> {
("setoption", Box::new(SetOptionCommand {})),
("valuetransfers", Box::new(ValueTransfersCommand {})),
("transactions", Box::new(TransactionsCommand {})),
(
"detailed_transactions",
Box::new(DetailedTransactionsCommand {}),
),
("value_to_address", Box::new(ValueToAddressCommand {})),
("sends_to_address", Box::new(SendsToAddressCommand {})),
(
Expand Down
141 changes: 60 additions & 81 deletions zingolib/src/lightclient/describe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ use crate::{
data::{
finsight,
summaries::{
OrchardNoteSummary, SaplingNoteSummary, SpendStatus, TransactionSummaries,
TransactionSummaryBuilder, TransparentCoinSummary, ValueTransfer,
ValueTransferBuilder, ValueTransferKind, ValueTransfers,
basic_transaction_summary_parts, DetailedTransactionSummaries,
DetailedTransactionSummaryBuilder, TransactionSummaries, TransactionSummaryBuilder,
TransactionSummaryInterface as _, ValueTransfer, ValueTransferBuilder,
ValueTransferKind, ValueTransfers,
},
OutgoingTxData,
},
Expand Down Expand Up @@ -577,87 +578,63 @@ impl LightClient {
let mut transaction_summaries = transaction_records
.values()
.map(|tx| {
let kind = transaction_records.transaction_kind(tx);
let value = match kind {
TransactionKind::Received
| TransactionKind::Sent(SendType::Shield)
| TransactionKind::Sent(SendType::SendToSelf) => tx.total_value_received(),
TransactionKind::Sent(SendType::Send) => tx.value_outgoing(),
};
let fee = transaction_records.calculate_transaction_fee(tx).ok();
let orchard_notes = tx
.orchard_notes
.iter()
.map(|output| {
let spend_status = if let Some((txid, _)) = output.spent() {
SpendStatus::Spent(*txid)
} else if let Some((txid, _)) = output.pending_spent() {
SpendStatus::PendingSpent(*txid)
} else {
SpendStatus::Unspent
};
let (kind, value, fee, orchard_notes, sapling_notes, transparent_coins) =
basic_transaction_summary_parts(tx, transaction_records);

let memo = if let Some(Memo::Text(memo_text)) = &output.memo {
Some(memo_text.to_string())
} else {
None
};
TransactionSummaryBuilder::new()
.txid(tx.txid)
.datetime(tx.datetime)
.blockheight(tx.status.get_height())
.kind(kind)
.value(value)
.fee(fee)
.status(tx.status)
.zec_price(tx.price)
.orchard_notes(orchard_notes)
.sapling_notes(sapling_notes)
.transparent_coins(transparent_coins)
.outgoing_tx_data(tx.outgoing_tx_data.clone())
.build()
.expect("all fields should be populated")
})
.collect::<Vec<_>>();
transaction_summaries.sort_by_key(|tx| tx.blockheight());

OrchardNoteSummary::from_parts(
output.value(),
spend_status,
output.output_index,
memo,
)
})
.collect::<Vec<_>>();
let sapling_notes = tx
.sapling_notes
.iter()
.map(|output| {
let spend_status = if let Some((txid, _)) = output.spent() {
SpendStatus::Spent(*txid)
} else if let Some((txid, _)) = output.pending_spent() {
SpendStatus::PendingSpent(*txid)
} else {
SpendStatus::Unspent
};
TransactionSummaries::new(transaction_summaries)
}

let memo = if let Some(Memo::Text(memo_text)) = &output.memo {
Some(memo_text.to_string())
} else {
None
};
/// TODO: doc comment
pub async fn transaction_summaries_json_string(&self) -> String {
json::JsonValue::from(self.transaction_summaries().await).pretty(2)
}

SaplingNoteSummary::from_parts(
output.value(),
spend_status,
output.output_index,
memo,
)
})
.collect::<Vec<_>>();
let transparent_coins = tx
.transparent_outputs
.iter()
.map(|output| {
let spend_status = if let Some((txid, _)) = output.spent() {
SpendStatus::Spent(*txid)
} else if let Some((txid, _)) = output.pending_spent() {
SpendStatus::PendingSpent(*txid)
} else {
SpendStatus::Unspent
};
/// Provides a detailed list of transaction summaries related to this wallet in order of blockheight
pub async fn detailed_transaction_summaries(&self) -> DetailedTransactionSummaries {
let transaction_map = self
.wallet
.transaction_context
.transaction_metadata_set
.read()
.await;
let transaction_records = &transaction_map.transaction_records_by_id;

TransparentCoinSummary::from_parts(
output.value(),
spend_status,
output.output_index,
)
})
.collect::<Vec<_>>();
let mut transaction_summaries = transaction_records
.values()
.map(|tx| {
let (kind, value, fee, orchard_notes, sapling_notes, transparent_coins) =
basic_transaction_summary_parts(tx, transaction_records);
let orchard_nullifiers: Vec<String> = tx
.spent_orchard_nullifiers
.iter()
.map(|nullifier| hex::encode(nullifier.to_bytes()))
.collect();
let sapling_nullifiers: Vec<String> = tx
.spent_sapling_nullifiers
.iter()
.map(hex::encode)
.collect();

TransactionSummaryBuilder::new()
DetailedTransactionSummaryBuilder::new()
.txid(tx.txid)
.datetime(tx.datetime)
.blockheight(tx.status.get_height())
Expand All @@ -670,18 +647,20 @@ impl LightClient {
.sapling_notes(sapling_notes)
.transparent_coins(transparent_coins)
.outgoing_tx_data(tx.outgoing_tx_data.clone())
.orchard_nullifiers(orchard_nullifiers)
.sapling_nullifiers(sapling_nullifiers)
.build()
.expect("all fields should be populated")
})
.collect::<Vec<_>>();
transaction_summaries.sort_by_key(|tx| tx.blockheight());

TransactionSummaries::new(transaction_summaries)
DetailedTransactionSummaries::new(transaction_summaries)
}

/// TODO: doc comment
pub async fn transaction_summaries_json_string(&self) -> String {
json::JsonValue::from(self.transaction_summaries().await).pretty(2)
pub async fn detailed_transaction_summaries_json_string(&self) -> String {
json::JsonValue::from(self.detailed_transaction_summaries().await).pretty(2)
}

/// TODO: Add Doc Comment Here!
Expand Down
Loading
Loading