Skip to content

Commit

Permalink
chore: pagination for get transaction history (#1343)
Browse files Browse the repository at this point in the history
Fixes:
* #1346
* #1317
* partially #883

Description
--
Since tari-project/tari#6733 is merged, we can
now implement pagination for infinite scrolling. Wallets with extensive
transaction history can now open `latest wins` tab without performance
issues.

Changes
--
* Infinite scrolling in the "latest wins" tab
* Filter only `COINBASE_UNCONFIRMED` and `COINBASE_CONFIRMED`
transactions(blocks wins). Rename methods from `get_transaction_history`
to `get_coinbase_transactions` to reflect changes.
* `get_coinbase_transactions` handles pagination - we define
`continuation` and `limit`(can be ommitted to fetch all at once)
* We fetch already ordered transactions, we don't need to sort it again
* Refresh the data everytime when either we open "recent wins" tab or
new block is mined
* Split `Wallet` component into `WalletMarkup` and `History` to improve
performance(reduce rerenders)

Testing
--
Just play with the "recent wins" tab

---------

Co-authored-by: Brian Pearce <[email protected]>
  • Loading branch information
mmrrnn and brianp authored Jan 21, 2025
1 parent 95fa430 commit ff19146
Show file tree
Hide file tree
Showing 16 changed files with 286 additions and 183 deletions.
20 changes: 20 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"react-hook-form": "^7.54.2",
"react-i18next": "^15.4.0",
"react-icons": "^5.4.0",
"react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^9.0.3",
"socket.io-client": "^4.8.1",
"styled-components": "^6.1.14",
Expand Down
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

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

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ hex = "0.4.3"
openssl = { version = "0.10", features = ["vendored"] }
ring-compat = "0.8.0"
der = "0.7.9"
tonic = "0.12.3"

[target.'cfg(windows)'.dependencies]
winreg = "0.52.0"
Expand Down
8 changes: 5 additions & 3 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -691,8 +691,10 @@ pub async fn get_tor_entry_guards(
}

#[tauri::command]
pub async fn get_transaction_history(
pub async fn get_coinbase_transactions(
state: tauri::State<'_, UniverseAppState>,
continuation: bool,
limit: Option<u32>,
) -> Result<Vec<TransactionInfo>, String> {
let timer = Instant::now();
if state.is_getting_transaction_history.load(Ordering::SeqCst) {
Expand All @@ -704,7 +706,7 @@ pub async fn get_transaction_history(
.store(true, Ordering::SeqCst);
let transactions = state
.wallet_manager
.get_transaction_history()
.get_coinbase_transactions(continuation, limit)
.await
.unwrap_or_else(|e| {
if !matches!(e, WalletManagerError::WalletNotStarted) {
Expand All @@ -714,7 +716,7 @@ pub async fn get_transaction_history(
});

if timer.elapsed() > MAX_ACCEPTABLE_COMMAND_TIME {
warn!(target: LOG_TARGET, "get_transaction_history took too long: {:?}", timer.elapsed());
warn!(target: LOG_TARGET, "get_coinbase_transactions took too long: {:?}", timer.elapsed());
}

state
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1119,7 +1119,7 @@ fn main() {
commands::emit_tari_wallet_details,
commands::get_tor_config,
commands::get_tor_entry_guards,
commands::get_transaction_history,
commands::get_coinbase_transactions,
commands::import_seed_words,
commands::log_web_message,
commands::open_log_dir,
Expand Down
70 changes: 51 additions & 19 deletions src-tauri/src/wallet_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ use anyhow::Error;
use async_trait::async_trait;
use log::{info, warn};
use minotari_node_grpc_client::grpc::wallet_client::WalletClient;
use minotari_node_grpc_client::grpc::{GetBalanceRequest, GetCompletedTransactionsRequest};
use minotari_node_grpc_client::grpc::{
GetBalanceRequest, GetCompletedTransactionsRequest, GetCompletedTransactionsResponse,
};
use serde::Serialize;
use std::path::PathBuf;
use tari_common::configuration::Network;
Expand All @@ -39,7 +41,8 @@ use tari_core::transactions::tari_amount::MicroMinotari;
use tari_crypto::ristretto::RistrettoPublicKey;
use tari_shutdown::Shutdown;
use tari_utilities::hex::Hex;
use tokio::sync::watch;
use tokio::sync::{watch, Mutex};
use tonic::Streaming;

#[cfg(target_os = "windows")]
use crate::utils::windows_setup_utils::add_firewall_rule;
Expand Down Expand Up @@ -192,6 +195,7 @@ impl ProcessAdapter for WalletAdapter {
WalletStatusMonitor {
grpc_port: self.grpc_port,
latest_balance_broadcast: self.balance_broadcast.clone(),
completed_transactions_stream: Mutex::new(None),
},
))
}
Expand All @@ -215,10 +219,20 @@ pub enum WalletStatusMonitorError {
UnknownError(#[from] anyhow::Error),
}

#[derive(Clone)]
pub struct WalletStatusMonitor {
grpc_port: u16,
latest_balance_broadcast: watch::Sender<Option<WalletBalance>>,
completed_transactions_stream: Mutex<Option<Streaming<GetCompletedTransactionsResponse>>>,
}

impl Clone for WalletStatusMonitor {
fn clone(&self) -> Self {
Self {
grpc_port: self.grpc_port,
latest_balance_broadcast: self.latest_balance_broadcast.clone(),
completed_transactions_stream: Mutex::new(None),
}
}
}

#[async_trait]
Expand Down Expand Up @@ -251,11 +265,8 @@ pub struct TransactionInfo {
pub source_address: String,
pub dest_address: String,
pub status: i32,
pub direction: i32,
pub amount: MicroMinotari,
pub fee: u64,
pub is_cancelled: bool,
pub excess_sig: String,
pub timestamp: u64,
pub payment_id: String,
pub mined_in_block_height: u64,
Expand Down Expand Up @@ -284,17 +295,28 @@ impl WalletStatusMonitor {
})
}

pub async fn get_transaction_history(
pub async fn get_coinbase_transactions(
&self,
continuation: bool,
limit: Option<u32>,
) -> Result<Vec<TransactionInfo>, WalletStatusMonitorError> {
let mut client = WalletClient::connect(self.wallet_grpc_address())
.await
.map_err(|_e| WalletStatusMonitorError::WalletNotStarted)?;
let res = client
.get_completed_transactions(GetCompletedTransactionsRequest {})
.await
.map_err(|e| WalletStatusMonitorError::UnknownError(e.into()))?;
let mut stream = res.into_inner();
let mut stream =
if continuation && self.completed_transactions_stream.lock().await.is_some() {
self.completed_transactions_stream
.lock()
.await
.take()
.expect("completed_transactions_stream not found")
} else {
let mut client = WalletClient::connect(self.wallet_grpc_address())
.await
.map_err(|_e| WalletStatusMonitorError::WalletNotStarted)?;
let res = client
.get_completed_transactions(GetCompletedTransactionsRequest {})
.await
.map_err(|e| WalletStatusMonitorError::UnknownError(e.into()))?;
res.into_inner()
};

let mut transactions: Vec<TransactionInfo> = Vec::new();

Expand All @@ -304,22 +326,32 @@ impl WalletStatusMonitor {
.map_err(|e| WalletStatusMonitorError::UnknownError(e.into()))?
{
let tx = message.transaction.expect("Transaction not found");

if tx.status != 12 && tx.status != 13 {
// Consider only COINBASE_UNCONFIRMED and COINBASE_UNCONFIRMED
continue;
}
transactions.push(TransactionInfo {
tx_id: tx.tx_id,
source_address: tx.source_address.to_hex(),
dest_address: tx.dest_address.to_hex(),
status: tx.status,
direction: tx.direction,
amount: MicroMinotari(tx.amount),
fee: tx.fee,
is_cancelled: tx.is_cancelled,
excess_sig: tx.excess_sig.to_hex(),
timestamp: tx.timestamp,
payment_id: tx.payment_id.to_hex(),
mined_in_block_height: tx.mined_in_block_height,
});
if let Some(limit) = limit {
if transactions.len() >= limit as usize {
break;
}
}
}

self.completed_transactions_stream
.lock()
.await
.replace(stream);
Ok(transactions)
}

Expand Down
6 changes: 4 additions & 2 deletions src-tauri/src/wallet_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,17 @@ impl WalletManager {
process_watcher.adapter.spend_key = spend_key;
}

pub async fn get_transaction_history(
pub async fn get_coinbase_transactions(
&self,
continuation: bool,
limit: Option<u32>,
) -> Result<Vec<TransactionInfo>, WalletManagerError> {
let process_watcher = self.watcher.read().await;
process_watcher
.status_monitor
.as_ref()
.ok_or_else(|| WalletManagerError::WalletNotStarted)?
.get_transaction_history()
.get_coinbase_transactions(continuation, limit)
.await
.map_err(|e| match e {
WalletStatusMonitorError::WalletNotStarted => WalletManagerError::WalletNotStarted,
Expand Down
37 changes: 27 additions & 10 deletions src/containers/main/SideBar/components/Wallet/History.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useTranslation } from 'react-i18next';
import { useWalletStore } from '@app/store/useWalletStore';
import { CircularProgress } from '@app/components/elements/CircularProgress';
import InfiniteScroll from 'react-infinite-scroll-component';

import { ListLabel } from './HistoryItem.styles';
import { HistoryContainer, HistoryPadding } from './Wallet.styles';
import HistoryItem from './HistoryItem';
import { memo, useCallback } from 'react';

const container = {
hidden: { opacity: 0, height: 0 },
Expand All @@ -14,21 +16,36 @@ const container = {
},
};

export default function History() {
const History = () => {
const { t } = useTranslation('sidebar', { useSuspense: false });
const isTransactionLoading = useWalletStore((s) => s.isTransactionLoading);
const transactions = useWalletStore((s) => s.transactions);
const is_reward_history_loading = useWalletStore((s) => s.is_reward_history_loading);
const transactions = useWalletStore((s) => s.coinbase_transactions);
const fetchCoinbaseTransactions = useWalletStore((s) => s.fetchCoinbaseTransactions);
const hasMore = useWalletStore((s) => s.has_more_coinbase_transactions);

const handleNext = useCallback(() => {
fetchCoinbaseTransactions(true, 20);
}, [fetchCoinbaseTransactions]);

return (
<HistoryContainer initial="hidden" animate="visible" exit="hidden" variants={container}>
<HistoryPadding>
<HistoryPadding id="history-padding">
<ListLabel>{t('recent-wins')}</ListLabel>
{isTransactionLoading && !transactions?.length ? (
<CircularProgress />
) : (
transactions.map((tx) => <HistoryItem key={tx.tx_id} item={tx} />)
)}
{is_reward_history_loading && !transactions?.length && <CircularProgress />}
<InfiniteScroll
dataLength={transactions?.length || 0}
next={handleNext}
hasMore={hasMore}
loader={<CircularProgress />}
scrollableTarget="history-padding"
>
{transactions.map((tx) => (
<HistoryItem key={tx.tx_id} item={tx} />
))}
</InfiniteScroll>
</HistoryPadding>
</HistoryContainer>
);
}
};

export default memo(History);
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,8 @@ export const HistoryContainer = styled(m.div)`
`;

export const HistoryPadding = styled('div')`
display: flex;
flex-direction: column;
gap: 6px;
height: 310;
overflow: auto;
width: 100%;
padding: 0 5px 60px 5px;
`;
Expand Down
Loading

0 comments on commit ff19146

Please sign in to comment.