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
1 change: 1 addition & 0 deletions Cargo.lock

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

16 changes: 16 additions & 0 deletions packages/rs-platform-wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ image = { version = "0.25", default-features = false, features = ["png", "jpeg",
# Security
zeroize = "1"

# Sync primitives used by the optional `lock-stats` feature for the
# per-tag breakdown map. Plain `parking_lot::Mutex` (sync, fast) is the
# right shape here — the per-tag map is touched on the lock acquire /
# release path, never across an `.await`.
parking_lot = { version = "0.12", optional = true }

# Shielded pool (optional, behind `shielded` feature)
grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "8f25b20d04bfc0e8bdfb3870676d647a0d74918b", optional = true }
zip32 = { version = "0.2.0", default-features = false, optional = true }
Expand All @@ -65,6 +71,16 @@ default = ["bls", "eddsa"]
bls = ["key-wallet/bls", "key-wallet-manager/bls"]
eddsa = ["key-wallet/eddsa", "key-wallet-manager/eddsa"]
shielded = ["dep:grovedb-commitment-tree", "dep:zip32", "dash-sdk/shielded", "dpp/shielded-client"]

# Off by default. When enabled, the per-wallet `wallet_manager` RwLock is
# wrapped with an `InstrumentedRwLock` that records acquisition counts,
# wait time, and hold time per call site (using `read_at("tag")` /
# `write_at("tag")`). With the feature off the wrapper is a transparent
# type alias for `tokio::sync::RwLock` and there is zero runtime cost.
Comment thread
QuantumExplorer marked this conversation as resolved.
# See `crate::diagnostics::instrumented_lock` for the API and
# `LockStats` for the snapshot shape.
lock-stats = ["dep:parking_lot"]

# Forward to the upstream `key-wallet` / `key-wallet-manager`
# `keep-finalized-transactions` feature. With it OFF (the default),
# chainlocked transactions are evicted from the in-memory
Expand Down
28 changes: 22 additions & 6 deletions packages/rs-platform-wallet/src/changeset/core_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,17 @@ use key_wallet::transaction_checking::TransactionContext;
use key_wallet::Utxo;
use key_wallet_manager::{WalletEvent, WalletId, WalletManager};
use tokio::sync::broadcast::error::RecvError;
use tokio::sync::RwLock;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;

use crate::changeset::changeset::{CoreChangeSet, PlatformWalletChangeSet};
use crate::changeset::traits::PlatformWalletPersistence;
// `InstrumentedRwLockExt` is unused when `lock-stats` is on — the
// wrapper has the same methods inherent and method resolution prefers
// inherent over trait. Suppress the warning so the call sites can
// import the trait unconditionally.
#[allow(unused_imports)]
use crate::diagnostics::{InstrumentedRwLock, InstrumentedRwLockExt};
use crate::wallet::platform_wallet::PlatformWalletInfo;

/// Spawn the wallet-event subscriber task.
Expand All @@ -54,7 +59,7 @@ use crate::wallet::platform_wallet::PlatformWalletInfo;
/// `Arc<P>` (not the `Arc<dyn PlatformWalletPersistence>`
/// coercion) to actually realize the static-dispatch win.
pub fn spawn_wallet_event_adapter<P>(
wallet_manager: Arc<RwLock<WalletManager<PlatformWalletInfo>>>,
wallet_manager: Arc<InstrumentedRwLock<WalletManager<PlatformWalletInfo>>>,
persister: Arc<P>,
cancel: CancellationToken,
) -> JoinHandle<()>
Expand All @@ -63,7 +68,11 @@ where
{
tokio::spawn(async move {
let mut receiver = {
let guard = wallet_manager.read().await;
// Subscribe-time read; happens once at task start. Tagged
// so the `lock-stats` feature can confirm the one-shot
// nature of this acquisition vs. the per-event probe in
// `is_chain_locked`.
let guard = wallet_manager.read_at("event_adapter::subscribe").await;
guard.subscribe_events()
};
tracing::debug!("wallet-event adapter task started");
Expand Down Expand Up @@ -120,7 +129,7 @@ where
/// Project an upstream [`WalletEvent`] into a [`CoreChangeSet`] suitable
/// for atomic persistence.
async fn build_core_changeset(
wallet_manager: &Arc<RwLock<WalletManager<PlatformWalletInfo>>>,
wallet_manager: &Arc<InstrumentedRwLock<WalletManager<PlatformWalletInfo>>>,
event: &WalletEvent,
) -> CoreChangeSet {
match event {
Expand Down Expand Up @@ -201,11 +210,18 @@ async fn build_core_changeset(
/// Returns `true` when the wallet's stored record for `txid` is in a
/// chain-locked block. Used to gate IS-lock projection.
async fn is_chain_locked(
wallet_manager: &Arc<RwLock<WalletManager<PlatformWalletInfo>>>,
wallet_manager: &Arc<InstrumentedRwLock<WalletManager<PlatformWalletInfo>>>,
wallet_id: &WalletId,
txid: &dashcore::Txid,
) -> bool {
let guard = wallet_manager.read().await;
// Tagged so the `lock-stats` feature can attribute this site's
// contribution to wallet-manager contention. The event adapter
// touches this lock once per `TransactionInstantLocked` event;
// tagging lets a perf audit distinguish the IS-lock finality
// probe from generic identity / token / address-sync reads.
let guard = wallet_manager
.read_at("event_adapter::is_chain_locked")
.await;
let Some(info) = guard.get_wallet_info(wallet_id) else {
return false;
};
Expand Down
Loading
Loading