Skip to content

Commit

Permalink
Support for tracking standalone addresses (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
shesek committed Dec 4, 2020
1 parent 497407a commit 6e2ef7d
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 30 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Unreleased

- Support for tracking standalone addresses (#14)

Breaking CLI changes:

- The `-a` CLI option was changed to mean `--address` instead of `--bitcoind-auth`
(which is now available as `-T`).

## 0.2.0 - 2020-11-24

- Descriptor based tracking! ✨🎉 (#1)
Expand Down
1 change: 1 addition & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl App {
&config.descriptors[..],
&config.xpubs[..],
&config.bare_xpubs[..],
config.addresses()?,
config.rescan_since,
config.network,
config.gap_limit,
Expand Down
73 changes: 59 additions & 14 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::{net, path, time};
use std::{fs, io, net, path, time};

use bitcoin::Network;
use bitcoin::{Address, Network};
use bitcoincore_rpc::Auth as RpcAuth;

use crate::error::{OptionExt, Result};
use crate::error::{Context, OptionExt, Result};
use crate::query::QueryConfig;
use crate::types::RescanSince;
use crate::util::descriptor::ExtendedDescriptor;
use crate::util::xpub::XyzPubKey;
use crate::util::BoolThen;

#[cfg(feature = "pretty_env_logger")]
use {log::Level, pretty_env_logger::env_logger::Builder as LogBuilder};
Expand Down Expand Up @@ -100,7 +101,7 @@ pub struct Config {
#[cfg_attr(
feature = "cli",
structopt(
short = "a",
short = "T",
long,
help = "Credentials for accessing the bitcoind RPC server (as <username>:<password>, used instead of the cookie file)",
alias = "bitcoind-cred",
Expand Down Expand Up @@ -161,12 +162,43 @@ pub struct Config {
env,
hide_env_values(true),
use_delimiter(true),
value_delimiter(";"),
display_order(22)
)
)]
#[serde(default)]
pub bare_xpubs: Vec<XyzPubKey>,

#[cfg_attr(
feature = "cli",
structopt(
short = "a",
long,
help = "Addresses to track",
env,
hide_env_values(true),
use_delimiter(true),
value_delimiter(";"),
display_order(23)
)
)]
#[serde(default)]
pub addresses: Vec<Address>,

#[cfg_attr(
feature = "cli",
structopt(
short = "A",
long,
help = "File with addresses to track",
env,
hide_env_values(true),
display_order(24)
)
)]
#[serde(default)]
pub addresses_file: Option<path::PathBuf>,

#[cfg_attr(
feature = "cli",
structopt(
Expand Down Expand Up @@ -213,14 +245,6 @@ pub struct Config {
#[serde(default = "default_initial_import_size")]
pub initial_import_size: u32,

//// TODO
//#[structopt(
//short,
//long,
//help = "addresses to track (address:yyyy-mm-dd)",
//parse(try_from_str = "parse_address")
//)]
//addresses: Vec<(String, RescanSince)>,
#[cfg(feature = "electrum")]
#[cfg_attr(
feature = "cli",
Expand Down Expand Up @@ -384,6 +408,27 @@ impl Config {
.or_err("no valid authentication found for bitcoind rpc, specify user/pass or a cookie file")?)
}

pub fn addresses(&self) -> Result<Vec<Address>> {
let mut addresses = self.addresses.clone();

if let Some(addresses_file) = &self.addresses_file {
let file = fs::File::open(addresses_file).context("failed opening addresses file")?;
let reader = io::BufReader::new(file);

addresses.append(
&mut io::BufRead::lines(reader)
.filter_map(|l| {
let l = l.ok()?;
let l = l.trim();
(!l.is_empty()).do_then(|| l.parse())
})
.collect::<std::result::Result<Vec<_>, _>>()?,
);
}

Ok(addresses)
}

#[cfg(feature = "electrum")]
pub fn electrum_addr(&self) -> Option<net::SocketAddr> {
self.electrum_addr.clone().or_else(|| {
Expand Down Expand Up @@ -483,7 +528,6 @@ fn parse_desc(s: &str) -> Result<ExtendedDescriptor> {

#[cfg(feature = "cli")]
fn parse_rescan(s: &str) -> Result<RescanSince> {
use crate::error::Context;
Ok(match s {
"all" | "genesis" => RescanSince::Timestamp(0),
"now" | "none" => RescanSince::Now,
Expand Down Expand Up @@ -568,7 +612,8 @@ impl From<&Config> for QueryConfig {
// Create a Default implementation
defaultable!(Config,
@default(
verbose, timestamp, descriptors, xpubs, bare_xpubs, broadcast_cmd, startup_banner,
verbose, timestamp, broadcast_cmd, startup_banner,
descriptors, xpubs, bare_xpubs, addresses, addresses_file,
bitcoind_wallet, bitcoind_dir, bitcoind_url, bitcoind_auth, bitcoind_cookie,
#[cfg(feature = "electrum")] electrum_addr,
#[cfg(feature = "electrum")] electrum_skip_merkle,
Expand Down
8 changes: 6 additions & 2 deletions src/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use std::{fmt, thread, time};

use serde::Serialize;

use bitcoin::{BlockHash, OutPoint, Txid};
use bitcoin::{Address, BlockHash, OutPoint, Txid};
use bitcoincore_rpc::json::{
GetTransactionResultDetailCategory as TxCategory, ListTransactionResult,
};
use bitcoincore_rpc::{Client as RpcClient, RpcApi};

use crate::error::Result;
use crate::store::{FundingInfo, MemoryStore, SpendingInfo, TxEntry};
use crate::types::{BlockId, InPoint, ScriptHash, TxStatus};
use crate::types::{BlockId, InPoint, RescanSince, ScriptHash, TxStatus};
use crate::util::bitcoincore_ext::{Progress, RpcApiExt};
use crate::wallet::{KeyOrigin, WalletWatcher};

Expand Down Expand Up @@ -320,6 +320,10 @@ impl Indexer {
.chain(self.tip.clone().map(IndexChange::ChainTip).into_iter())
.collect()
}

pub fn track_address(&mut self, address: Address, rescan_since: RescanSince) -> Result<()> {
self.watcher.track_address(address, rescan_since)
}
}

#[derive(Clone, Serialize, Debug)]
Expand Down
24 changes: 23 additions & 1 deletion src/util/bitcoincore_ext.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
use serde::{de, Serialize};
use std::collections::HashMap;
use std::fmt::{self, Formatter};
use std::{sync::mpsc, thread, time};

use bitcoin::Address;
use bitcoincore_rpc::json::{self, ImportMultiRescanSince, ScanningDetails};
use bitcoincore_rpc::{Client, Result as RpcResult, RpcApi};
use bitcoincore_rpc::{self as rpc, Client, Result as RpcResult, RpcApi};

const WAIT_INTERVAL: time::Duration = time::Duration::from_secs(7);

// Extensions for rust-bitcoincore-rpc

pub trait RpcApiExt: RpcApi {
fn list_labels(&self) -> RpcResult<Vec<String>> {
self.call("listlabels", &[])
}

fn get_addresses_by_label(&self, label: &str) -> RpcResult<HashMap<Address, AddressEntry>> {
match self.call("getaddressesbylabel", &[json!(label)]) {
Ok(x) => Ok(x),
// "No addresses with label ..."
Err(rpc::Error::JsonRpc(rpc::jsonrpc::Error::Rpc(e))) if e.code == -11 => {
Ok(HashMap::new())
}
Err(e) => Err(e),
}
}

// Only supports the fields we're interested in (so not currently upstremable)
fn get_block_stats(&self, blockhash: &bitcoin::BlockHash) -> RpcResult<GetBlockStatsResult> {
let fields = (
Expand Down Expand Up @@ -109,6 +126,11 @@ pub enum Progress {
Scan { progress_n: f32, eta: u64 },
}

#[derive(Debug, Deserialize)]
pub struct AddressEntry {
pub purpose: json::GetAddressInfoResultLabelPurpose,
}

#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct GetBlockStatsResult {
pub height: u64,
Expand Down
Loading

0 comments on commit 6e2ef7d

Please sign in to comment.