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
37 changes: 37 additions & 0 deletions zebra-crosslink/wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,33 @@ pub static FAUCET_REQUEST: Mutex<Option<FaucetRequestClosure>> = Mutex::new(None
pub static USER_UFVK_STRING: Mutex<Option<String>> = Mutex::new(None);
pub static GUI_ENABLE_MINE: Mutex<bool> = Mutex::new(true);

/// Cheap, point-in-time snapshot of the headless wallet's own note-scan progress and
/// balances, updated as a handful of plain field assignments alongside the existing
/// `WalletState` update (see the write site in `wallet_main`) -- no new per-block cost.
/// Mirrors `zebra_state::crosslink::WalletSyncStatusSnapshot`; kept as a separate,
/// dependency-free struct here so this crate doesn't need to depend on zebra-state just to
/// hold this value. The RPC layer (which already depends on both crates) converts between
/// the two at the point where it builds the RPC response.
#[derive(Clone, Copy, Debug, Default)]
pub struct WalletSyncStatus {
pub sync_height: u32,
pub tip_height: u32,
pub user_shielded_spendable_zats: u64,
pub user_shielded_pending_zats: u64,
pub user_unshielded_zats: u64,
pub staked_zats: u64,
pub withdrawable_zats: u64,
}
pub static WALLET_SYNC_STATUS: Mutex<WalletSyncStatus> = Mutex::new(WalletSyncStatus {
sync_height: 0,
tip_height: 0,
user_shielded_spendable_zats: 0,
user_shielded_pending_zats: 0,
user_unshielded_zats: 0,
staked_zats: 0,
withdrawable_zats: 0,
});

pub static STAKING_STAGE: Mutex<Option<(StakingActionRequest, tokio::sync::oneshot::Sender<Result<String, String>>)>> = Mutex::new(None);

#[derive(Clone)]
Expand Down Expand Up @@ -4357,6 +4384,16 @@ pub async fn wallet_main(wallet_state: Arc<Mutex<WalletState>>) {

lock.staked_balance = user_staked_funds;
lock.withdrawable_balance = user_withdrawable_funds;

*WALLET_SYNC_STATUS.lock().unwrap() = WalletSyncStatus {
sync_height: wallets_sync_h.0,
tip_height: network_tip_h.0,
user_shielded_spendable_zats: user_shielded_spendable_funds,
user_shielded_pending_zats: user_shielded_pending_funds,
user_unshielded_zats: user_unshielded_funds,
staked_zats: user_staked_funds,
withdrawable_zats: user_withdrawable_funds,
};
}


Expand Down
13 changes: 13 additions & 0 deletions zebra-crosslink/zebra-crosslink/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1840,6 +1840,19 @@ async fn tfl_service_incoming_request(
TFLServiceRequest::StakingCmd(String) => Err(TFLServiceError::NotImplemented),

TFLServiceRequest::WalletUfvk => Ok(TFLServiceResponse::WalletUfvk(wallet::USER_UFVK_STRING.lock().unwrap().clone())),

TFLServiceRequest::WalletSyncStatus => {
let s = *wallet::WALLET_SYNC_STATUS.lock().unwrap();
Ok(TFLServiceResponse::WalletSyncStatus(WalletSyncStatusSnapshot {
sync_height: s.sync_height,
tip_height: s.tip_height,
user_shielded_spendable_zats: s.user_shielded_spendable_zats,
user_shielded_pending_zats: s.user_shielded_pending_zats,
user_unshielded_zats: s.user_unshielded_zats,
staked_zats: s.staked_zats,
withdrawable_zats: s.withdrawable_zats,
}))
}
}
}

Expand Down
34 changes: 33 additions & 1 deletion zebra-crosslink/zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ use zebra_chain::{
use zebra_consensus::{funding_stream_address, ParameterCheckpoint, RouterError};
use zebra_network::{address_book_peers::AddressBookPeers, PeerSocketAddr};
use zebra_node_services::mempool;
use zebra_state::crosslink::{TFLBlockFinality, TFLServiceRequest, TFLServiceResponse};
use zebra_state::crosslink::{TFLBlockFinality, TFLServiceRequest, TFLServiceResponse, WalletSyncStatusSnapshot};
use zebra_state::{HashOrHeight, OutputLocation, ReadRequest, ReadResponse, TransactionLocation};

use crate::{
Expand Down Expand Up @@ -540,6 +540,23 @@ pub trait Rpc {
#[method(name = "get_wallet_ufvk")]
async fn get_wallet_ufvk(&self) -> Option<String>;

/// Get the headless wallet's own note-scan progress and balances.
///
/// `sync_height` is how far the wallet's own (in-memory, restart-resets-to-zero) note-scan
/// has reached -- distinct from the node's raw chain-sync height (see `getblockchaininfo`),
/// which can be fully caught up while this still lags behind, particularly right after a
/// restart. Balances (and anything derived from wallet-tracked notes, e.g.
/// `wallet_staking_action`) are only accurate once `sync_height` is close to `tip_height`.
///
/// ## Example Usage
/// ```bash
/// curl -X POST -H "Content-Type: application/json" -d \
/// '{ "jsonrpc": "2.0", "method": "get_wallet_sync_status", "params": [], "id": 1 }' \
/// http://127.0.0.1:8232
/// ```
#[method(name = "get_wallet_sync_status")]
async fn get_wallet_sync_status(&self) -> Result<WalletSyncStatusSnapshot>;

/// send a staking action from the given wallet
#[method(name = "wallet_staking_action")]
async fn wallet_staking_action(&self, staking_action: StakingActionRequest) -> Result<String>;
Expand Down Expand Up @@ -2363,6 +2380,21 @@ where
}
}

async fn get_wallet_sync_status(&self) -> Result<WalletSyncStatusSnapshot> {
let res = self
.tfl_service
.clone()
.ready()
.await
.unwrap()
.call(TFLServiceRequest::WalletSyncStatus)
.await;
match res {
Ok(TFLServiceResponse::WalletSyncStatus(status)) => Ok(status),
_ => Ok(WalletSyncStatusSnapshot::default()),
}
}

async fn wallet_staking_action(&self, staking_action: StakingActionRequest) -> Result<String> {
let res = self
.tfl_service
Expand Down
31 changes: 31 additions & 0 deletions zebra-crosslink/zebra-state/src/crosslink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@ use serde_with::serde_as;
pub use zcash_primitives::bft::{FinalizerRecencyStatus, TFLRecencyStatus, ScanInfo};
use zcash_primitives::transaction::StakingActionRequest;

/// A cheap, point-in-time snapshot of the headless wallet's own note-scan progress and
/// balances. Updated as a handful of plain field assignments once per wallet loop iteration
/// (see `wallet::WALLET_SYNC_STATUS`), so reading it via RPC costs nothing beyond what the
/// wallet already computes for its own internal use -- unlike deriving this information from
/// the `DUMP_NOTES` debug log, which reprints the *entire* notes list from scratch every
/// iteration and does not scale as that list grows.
#[derive(Debug, Default, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
pub struct WalletSyncStatusSnapshot {
/// The highest block height the headless wallet's own note-scan has processed so far.
/// Distinct from the raw node's chain-sync height (see `getblockchaininfo`) -- this can
/// lag behind it, particularly right after a restart, since wallet note-scanning is
/// in-memory only and always restarts from genesis.
pub sync_height: u32,
/// The chain tip height the wallet is scanning towards, as of the same snapshot.
pub tip_height: u32,
/// The user wallet's confirmed, spendable (not staked, not pending) balance, in zatoshis.
pub user_shielded_spendable_zats: u64,
/// The user wallet's unconfirmed/pending shielded balance, in zatoshis.
pub user_shielded_pending_zats: u64,
/// The user wallet's transparent balance, in zatoshis.
pub user_unshielded_zats: u64,
/// Total currently staked (bonded) balance, in zatoshis.
pub staked_zats: u64,
/// Balance available to withdraw from completed unbonding, in zatoshis.
pub withdrawable_zats: u64,
}

/// The finality status of a block
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
pub enum TFLBlockFinality {
Expand Down Expand Up @@ -63,6 +90,8 @@ pub enum TFLServiceRequest {
WalletUfvk,
/// Send staking action from wallet
WalletStakingAction(StakingActionRequest),
/// Get the headless wallet's own note-scan progress and balances
WalletSyncStatus,
}

/// Types of responses that can be returned by the TFLService.
Expand Down Expand Up @@ -98,6 +127,8 @@ pub enum TFLServiceResponse {
WalletUfvk(Option<String>),
/// Send staking action from wallet
WalletStakingAction(Result<String, String>),
/// The headless wallet's own note-scan progress and balances
WalletSyncStatus(WalletSyncStatusSnapshot),
}

/// Errors that can occur when interacting with the TFLService.
Expand Down