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

chore: pagination for get transaction history #1343

Merged
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
Loading