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
9 changes: 9 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fn read_secret_file(path: &std::path::Path) -> anyhow::Result<String> {
pub struct AppConfig {
pub network: ZNetwork,
pub datadir: PathBuf,
pub walletnotify: Option<String>,
pub default_wallet: String,
pub wallets: BTreeMap<String, WalletEntry>,
pub backend: BackendConfig,
Expand Down Expand Up @@ -501,6 +502,7 @@ impl SpendConfig {
struct ConfigFile {
network: Option<String>,
datadir: Option<PathBuf>,
walletnotify: Option<String>,
default_wallet: Option<String>,
#[serde(default)]
wallets: BTreeMap<String, WalletFile>,
Expand Down Expand Up @@ -664,6 +666,10 @@ pub struct Cli {
#[arg(long)]
pub testnet: bool,

/// Command to execute when a wallet transaction comes in.
#[arg(long, value_name = "CMD")]
pub walletnotify: Option<String>,

/// Use regtest - a local zebra regtest chain (overrides config `network`).
#[arg(long)]
pub regtest: bool,
Expand Down Expand Up @@ -831,6 +837,8 @@ impl AppConfig {
.clone()
.unwrap_or_else(|| "default".to_string());

let walletnotify = cli.walletnotify.clone().or(file.walletnotify);

// keys.toml location override (so the encrypted seed can be a mounted Secret, separate
// from the disposable datadir). The global `[keys] keys_file` / `ZECD_KEYS_FILE` /
// `--keys-file` applies to the default wallet; a per-wallet `[wallets.<name>] keys_file`
Expand Down Expand Up @@ -1094,6 +1102,7 @@ impl AppConfig {
Ok(AppConfig {
network,
datadir,
walletnotify,
default_wallet,
wallets,
backend,
Expand Down
1 change: 1 addition & 0 deletions src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ pub async fn run(config: AppConfig) -> anyhow::Result<()> {
transparent_allow_beyond_recovery_window: entry
.transparent_allow_beyond_recovery_window,
transparent_gap_warn_threshold: entry.transparent_gap_warn_threshold,
walletnotify: config.walletnotify.clone(),
shutdown: shutdown_tx.subscribe(),
};
match actor::spawn(actor_cfg).await {
Expand Down
21 changes: 21 additions & 0 deletions src/wallet/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ pub struct ActorConfig {
/// Warn when fewer than this many in-window transparent address slots remain. Only used when
/// `transparent_enabled`.
pub transparent_gap_warn_threshold: u32,
pub walletnotify: Option<String>,
/// Flips to `true` on Ctrl-C/`stop`; the actor exits its loop (between sync batches)
/// so the `WalletDb` is dropped cleanly before the process ends.
pub shutdown: watch::Receiver<bool>,
Expand Down Expand Up @@ -358,6 +359,7 @@ struct WalletActor {
/// Warn when fewer than this many in-window transparent address slots remain before generation
/// would hit the gap limit.
transparent_gap_warn_threshold: u32,
walletnotify: Option<String>,
/// The wallet's exposed transparent receiving + change addresses, as a membership set for the
/// block-scan / mempool receive matcher. Transient (rebuilt from the DB, respects the stateless
/// invariant). librustzcash never asks us to scan our *receiving* transparent addresses for
Expand Down Expand Up @@ -695,6 +697,7 @@ pub async fn spawn(
transparent_initial_scan: cfg.transparent_initial_scan,
transparent_allow_beyond_recovery_window: cfg.transparent_allow_beyond_recovery_window,
transparent_gap_warn_threshold: cfg.transparent_gap_warn_threshold,
walletnotify: cfg.walletnotify.clone(),
transparent_scripts: None,
transparent_set_dirty: true,
transparent_preexposed: false,
Expand Down Expand Up @@ -1533,6 +1536,7 @@ impl WalletActor {
&tx,
mined,
)?;
self.trigger_walletnotify(&tx.txid().to_string());
}
}
}
Expand Down Expand Up @@ -1575,6 +1579,7 @@ impl WalletActor {
&tx,
mined,
)?;
self.trigger_walletnotify(&tx.txid().to_string());
}
}
}
Expand Down Expand Up @@ -1869,6 +1874,9 @@ impl WalletActor {
// decrypt stored it or we just recorded a transparent receive from it.
let txid_hex = txid.to_string();
let ours = t_recorded > 0 || super::read::tx_exists(&self.wallet_dir, &txid_hex);
if ours {
self.trigger_walletnotify(&txid_hex);
}
tracing::debug!(
"[{}] processed mempool tx {txid} (ours={ours}, transparent_receives={t_recorded})",
self.name
Expand Down Expand Up @@ -2042,6 +2050,19 @@ impl WalletActor {
Ok(outcome.worked)
}

fn trigger_walletnotify(&self, txid_hex: &str) {
if let Some(cmd) = &self.walletnotify {
let replaced = cmd.replace("%s", txid_hex);
tracing::info!("[{}] walletnotify: executing {}", self.name, replaced);
std::thread::spawn(move || {
let _ = std::process::Command::new("sh")
.arg("-c")
.arg(&replaced)
.spawn();
});
}
}

/// Rebuild [`Self::transparent_scripts`] from the account's exposed transparent receivers
/// (external + internal/change). Cheap relative to a sync batch and only run when the set may
/// have changed (`transparent_set_dirty`), so an exchange with ~100k addresses pays the query
Expand Down